Repository: IgorKowalczyk/majobot Branch: master Commit: 7a451fb2b950 Files: 492 Total size: 2.4 MB Directory structure: gitextract_ftazrc3x/ ├── .dockerignore ├── .github/ │ ├── codeowners │ ├── renovate.json │ └── workflows/ │ ├── codeql-analysis.yml │ ├── format-check.yml │ └── lint.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── CONTRIBUTING.md ├── README.md ├── apps/ │ ├── bot/ │ │ ├── .env.example │ │ ├── .prettierignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── commands/ │ │ │ ├── Fun/ │ │ │ │ ├── 8ball.ts │ │ │ │ ├── advice.ts │ │ │ │ ├── anime.ts │ │ │ │ ├── cat.ts │ │ │ │ ├── comic.ts │ │ │ │ ├── dice.ts │ │ │ │ ├── flip.ts │ │ │ │ ├── iq.ts │ │ │ │ ├── joke.ts │ │ │ │ ├── letmegpt.ts │ │ │ │ ├── lmgtfy.ts │ │ │ │ ├── meme.ts │ │ │ │ ├── rate.ts │ │ │ │ ├── ship.ts │ │ │ │ └── why.ts │ │ │ ├── General/ │ │ │ │ ├── about.ts │ │ │ │ ├── contact.ts │ │ │ │ ├── dashboard.ts │ │ │ │ ├── donate.ts │ │ │ │ ├── help.ts │ │ │ │ ├── invite.ts │ │ │ │ ├── opensource.ts │ │ │ │ ├── permissions.ts │ │ │ │ ├── ping.ts │ │ │ │ ├── servers.ts │ │ │ │ ├── suggestion.ts │ │ │ │ └── uptime.ts │ │ │ ├── Giveaway/ │ │ │ │ └── giveaway.ts │ │ │ ├── Image/ │ │ │ │ ├── achievement.ts │ │ │ │ ├── ai.ts │ │ │ │ ├── bangladesh.ts │ │ │ │ ├── change-my-mind.ts │ │ │ │ ├── dog-say.ts │ │ │ │ ├── fbi.ts │ │ │ │ ├── flag.ts │ │ │ │ ├── galaxy-note.ts │ │ │ │ ├── image.ts │ │ │ │ ├── iq-graph.ts │ │ │ │ ├── jail.ts │ │ │ │ ├── note.ts │ │ │ │ ├── ph-comment.ts │ │ │ │ ├── sonic-says.ts │ │ │ │ ├── trash.ts │ │ │ │ ├── trigger.ts │ │ │ │ ├── type-c.ts │ │ │ │ └── yt-comment.ts │ │ │ ├── Levels/ │ │ │ │ ├── leaderboard.ts │ │ │ │ ├── rank.ts │ │ │ │ ├── reset-xp.ts │ │ │ │ └── xp-settings.ts │ │ │ ├── Moderation/ │ │ │ │ ├── automod.ts │ │ │ │ ├── emoji.ts │ │ │ │ ├── leave.ts │ │ │ │ ├── logs.ts │ │ │ │ ├── member.ts │ │ │ │ ├── role.ts │ │ │ │ ├── slowmode.ts │ │ │ │ ├── statistics.ts │ │ │ │ ├── warn.ts │ │ │ │ └── welcome.ts │ │ │ ├── Reputation/ │ │ │ │ └── rep.ts │ │ │ └── Utility/ │ │ │ ├── ascii.ts │ │ │ ├── base64.ts │ │ │ ├── color.ts │ │ │ ├── minecraft.ts │ │ │ ├── qrcode.ts │ │ │ └── translate.ts │ │ ├── events/ │ │ │ ├── client/ │ │ │ │ ├── GuildCreate.ts │ │ │ │ ├── GuildDelete.ts │ │ │ │ └── clientReady.ts │ │ │ └── guild/ │ │ │ ├── channelCreate.ts │ │ │ ├── channelDelete.ts │ │ │ ├── channelPinsUpdate.ts │ │ │ ├── channelUpdate.ts │ │ │ ├── emojiCreate.ts │ │ │ ├── emojiDelete.ts │ │ │ ├── emojiUpdate.ts │ │ │ ├── guildBanAdd.ts │ │ │ ├── guildBanRemove.ts │ │ │ ├── guildMemberAdd.ts │ │ │ ├── guildMemberRemove.ts │ │ │ ├── guildUpdate.ts │ │ │ ├── interactionCreate.ts │ │ │ ├── inviteCreate.ts │ │ │ ├── inviteDelete.ts │ │ │ ├── messageBulkDelete.ts │ │ │ ├── messageCreate.ts │ │ │ ├── messageDelete.ts │ │ │ ├── messageUpdate.ts │ │ │ ├── roleCreate.ts │ │ │ ├── roleDelete.ts │ │ │ ├── roleUpdate.ts │ │ │ ├── stickerCreate.ts │ │ │ ├── stickerDelete.ts │ │ │ └── stickerUpdate.ts │ │ ├── index.ts │ │ ├── modals/ │ │ │ └── suggestion.ts │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── util/ │ │ ├── giveaway/ │ │ │ ├── core.ts │ │ │ ├── endGiveaway.ts │ │ │ ├── findGiveaways.ts │ │ │ ├── pauseGiveaway.ts │ │ │ ├── rerollGiveaway.ts │ │ │ ├── resumeGiveaway.ts │ │ │ └── startGiveaway.ts │ │ ├── images/ │ │ │ ├── createUserGuildCard.ts │ │ │ └── createXPCard.ts │ │ ├── loaders/ │ │ │ ├── loadCommands.ts │ │ │ ├── loadEmojis.ts │ │ │ ├── loadEvents.ts │ │ │ ├── loadFonts.ts │ │ │ └── loadModals.ts │ │ ├── moderation/ │ │ │ ├── automod/ │ │ │ │ ├── antiBadWords/ │ │ │ │ │ ├── disable.ts │ │ │ │ │ └── enable.ts │ │ │ │ ├── antiInvite/ │ │ │ │ │ ├── disable.ts │ │ │ │ │ └── enable.ts │ │ │ │ ├── antiLinks/ │ │ │ │ │ ├── disable.ts │ │ │ │ │ └── enable.ts │ │ │ │ ├── antiMention/ │ │ │ │ │ ├── disable.ts │ │ │ │ │ └── enable.ts │ │ │ │ ├── antiSpam/ │ │ │ │ │ ├── disable.ts │ │ │ │ │ └── enable.ts │ │ │ │ ├── index.ts │ │ │ │ └── settings/ │ │ │ │ └── index.ts │ │ │ ├── ban.ts │ │ │ ├── changeMemberNickname.ts │ │ │ ├── getMemberImages.ts │ │ │ ├── getMemberInfo.ts │ │ │ ├── kick.ts │ │ │ └── unban.ts │ │ └── types/ │ │ └── Command.ts │ └── dashboard/ │ ├── .env.example │ ├── .gitignore │ ├── .prettierignore │ ├── README.md │ ├── app/ │ │ ├── _components/ │ │ │ ├── AddReaction.tsx │ │ │ ├── BotReplacement.tsx │ │ │ ├── ExampleChart.tsx │ │ │ ├── LevelUp.tsx │ │ │ └── Notifications.tsx │ │ ├── api/ │ │ │ ├── auth/ │ │ │ │ └── [...nextauth]/ │ │ │ │ └── route.ts │ │ │ ├── cron/ │ │ │ │ └── cleanup/ │ │ │ │ └── route.ts │ │ │ ├── invite/ │ │ │ │ ├── [invite]/ │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ ├── logs/ │ │ │ │ └── [serverId]/ │ │ │ │ └── route.ts │ │ │ ├── settings/ │ │ │ │ ├── automod/ │ │ │ │ │ ├── anti-invite/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── anti-link/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── anti-mention/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── anti-spam/ │ │ │ │ │ └── route.ts │ │ │ │ ├── categories/ │ │ │ │ │ └── route.ts │ │ │ │ ├── change-reputation/ │ │ │ │ │ └── route.ts │ │ │ │ ├── commands/ │ │ │ │ │ └── route.ts │ │ │ │ ├── delete-data/ │ │ │ │ │ └── route.ts │ │ │ │ ├── download/ │ │ │ │ │ └── [id]/ │ │ │ │ │ └── route.ts │ │ │ │ ├── embed-color/ │ │ │ │ │ └── route.ts │ │ │ │ ├── messages/ │ │ │ │ │ ├── leave/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── welcome/ │ │ │ │ │ └── route.ts │ │ │ │ ├── public-dashboard/ │ │ │ │ │ └── route.ts │ │ │ │ ├── public-vanity/ │ │ │ │ │ └── route.ts │ │ │ │ ├── reset-user-xp/ │ │ │ │ │ └── route.ts │ │ │ │ └── update-logs/ │ │ │ │ └── route.ts │ │ │ ├── user/ │ │ │ │ ├── avatar/ │ │ │ │ │ └── [id]/ │ │ │ │ │ └── route.ts │ │ │ │ ├── avatar-decoration/ │ │ │ │ │ └── [id]/ │ │ │ │ │ └── route.ts │ │ │ │ ├── banner/ │ │ │ │ │ └── [id]/ │ │ │ │ │ └── route.ts │ │ │ │ ├── delete/ │ │ │ │ │ └── route.ts │ │ │ │ └── download/ │ │ │ │ └── route.ts │ │ │ └── warns/ │ │ │ └── [serverId]/ │ │ │ └── [warnId]/ │ │ │ └── route.ts │ │ ├── auth/ │ │ │ ├── error/ │ │ │ │ └── page.tsx │ │ │ └── login/ │ │ │ └── page.tsx │ │ ├── commands/ │ │ │ └── page.tsx │ │ ├── dashboard/ │ │ │ ├── [server]/ │ │ │ │ ├── automod/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ ├── AntiInvite.tsx │ │ │ │ │ │ ├── AntiLink.tsx │ │ │ │ │ │ ├── AntiMention.tsx │ │ │ │ │ │ ├── AntiSpam.tsx │ │ │ │ │ │ ├── DeleteMessage.tsx │ │ │ │ │ │ ├── Limit.tsx │ │ │ │ │ │ ├── LogChannel.tsx │ │ │ │ │ │ └── TimeoutMember.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── dashboard-logs/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── Logs.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── giveaways/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── Giveaways.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── leaderboard/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── Leaderboard.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── loading.tsx │ │ │ │ ├── logs/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ ├── UpdateLogs.tsx │ │ │ │ │ │ └── handleLogText.ts │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── messages/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── ChangeCustomMessages.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── modules/ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── settings/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ ├── ChangeEmbedColor.tsx │ │ │ │ │ │ ├── DeleteServerData.tsx │ │ │ │ │ │ └── PublicDashboard.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── statistics/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── StatisticCharts.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── user/ │ │ │ │ │ ├── [id]/ │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── components/ │ │ │ │ │ ├── ChangeUserReputation.tsx │ │ │ │ │ └── ResetUserXP.tsx │ │ │ │ └── warns/ │ │ │ │ ├── components/ │ │ │ │ │ └── Warns.tsx │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── error.tsx │ │ ├── layout.tsx │ │ ├── legal/ │ │ │ ├── layout.tsx │ │ │ ├── privacy-policy/ │ │ │ │ └── page.mdx │ │ │ └── terms-of-service/ │ │ │ └── page.mdx │ │ ├── not-found.tsx │ │ ├── opengraph-image.tsx │ │ ├── page.tsx │ │ ├── robots.ts │ │ ├── server/ │ │ │ └── [server]/ │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── sitemap.ts │ │ └── user/ │ │ ├── components/ │ │ │ └── DeleteUserData.tsx │ │ └── profile/ │ │ └── page.tsx │ ├── components/ │ │ ├── CategoryBar.tsx │ │ ├── DiscordEmojis.tsx │ │ ├── DiscordLogin.tsx │ │ ├── Footer.tsx │ │ ├── Hotjar.tsx │ │ ├── LoginButton.tsx │ │ ├── Session.tsx │ │ ├── TailwindIndicator.tsx │ │ ├── client/ │ │ │ ├── charts/ │ │ │ │ └── ServerStatsChart.tsx │ │ │ └── commands/ │ │ │ ├── DiscordCommands.tsx │ │ │ ├── UpdateCategories.tsx │ │ │ └── UpdateCommands.tsx │ │ ├── nav/ │ │ │ ├── GuildMenuDropdown.tsx │ │ │ ├── ProgressBar.tsx │ │ │ ├── SideMenuControl.tsx │ │ │ ├── SideNavigation.tsx │ │ │ ├── TopNavigation.tsx │ │ │ ├── UserMenuDropdown.tsx │ │ │ └── VisibilityContext.tsx │ │ └── ui/ │ │ ├── Accordion.tsx │ │ ├── Badge.tsx │ │ ├── Block.tsx │ │ ├── Buttons.tsx │ │ ├── ChannelsSelect.tsx │ │ ├── Chart.tsx │ │ ├── Command.tsx │ │ ├── Dialog.tsx │ │ ├── Disclosure.tsx │ │ ├── DropdownMenu.tsx │ │ ├── Embed.tsx │ │ ├── GraphCard.tsx │ │ ├── Headers.tsx │ │ ├── Icons.tsx │ │ ├── Image.tsx │ │ ├── Input.tsx │ │ ├── Loaders.tsx │ │ ├── Popover.tsx │ │ ├── RolesSelect.tsx │ │ ├── Select.tsx │ │ ├── Skeletons.tsx │ │ ├── SparkChart.tsx │ │ ├── Switch.tsx │ │ ├── Table.tsx │ │ ├── TimeSelect.tsx │ │ ├── Tooltip.tsx │ │ ├── ViewSelect.tsx │ │ └── effects/ │ │ ├── AnimatedBeam.tsx │ │ ├── AnimatedList.tsx │ │ ├── AnimatedShinyText.tsx │ │ ├── BorderBeam.tsx │ │ ├── FadeText.tsx │ │ ├── FlickeringGrid.tsx │ │ ├── Globe.tsx │ │ ├── Meteors.tsx │ │ ├── NumberTicker.tsx │ │ ├── Particles.tsx │ │ ├── Ripple.tsx │ │ └── WordPullUp.tsx │ ├── env.ts │ ├── eslint.config.ts │ ├── jsconfig.json │ ├── lib/ │ │ ├── authOptions.ts │ │ ├── session.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── mdx-components.tsx │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── styles/ │ │ └── globals.css │ ├── tsconfig.json │ └── vercel.json ├── docker-compose.yaml ├── eslint.config.ts ├── license.md ├── package.json ├── packages/ │ ├── config/ │ │ ├── .prettierignore │ │ ├── configs/ │ │ │ ├── bot.ts │ │ │ ├── dashboard.ts │ │ │ ├── debugger.ts │ │ │ ├── global.ts │ │ │ └── permissions.ts │ │ ├── index.ts │ │ ├── package.json │ │ └── tsconfig.json │ ├── database/ │ │ ├── .env.example │ │ ├── .prettierignore │ │ ├── README.md │ │ ├── package.json │ │ ├── prisma/ │ │ │ ├── migrations/ │ │ │ │ ├── 20230516174916_/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230516200625_add_auth/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230516201206_add_discord_schema/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230516202208_save_only_required/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230516202300_bigint_for_flags/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230516202517_make_email_unique/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230516202902_fix_unique/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230516210849_add_maps/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230516211309_add_verification_token/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230516212035_fix_unique_values/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230516212254_switch_datatype_nitro/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230516212512_add_email_verified/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230521135936_add_suggestions/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230521142039_add_guild_id/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230521143031_add_guild_model/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230521184416_add_guild_logs/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230521185323_fix_guild_logs/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230521221630_add_avatar/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230522210912_link_user_to_logs/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230528212858_add_embed_color/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230529160641_add_ratelimit_for_server_change/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230604182342_add_guild_xp/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230609200921_migrate_to_discord_handles/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230610181051_update_account/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230610195213_update_rows_names/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230716165214_add_guild_joins/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230807093042_add_reputation/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230807100800_fix_user_fields/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230809192933_add_giveaways/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230809193154_change_giveaways_name/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230810131104_add_channel_id/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230810131904_remove_channel_id/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230813164922_add_messages_count/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230829161606_add_xp_settings/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230901110432_add_vanity_urls/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231008115944_add_guild_controlled_commands/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231008120512_fix_commands/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231008120900_add_default_options/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231008143223_update_default_member_permissions/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231008163839_fix_fields/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231008165157_remove_perms_and_created_at/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231008170130_update_command_categories/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231008171003_fix_schema/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231015105007_remove_email_adresses/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231017181219_fix_user_emails/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231018085005_add_automod_rules/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231030220258_add_warning_system/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231030222116_fix_ids/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231030230714_add_created_by_id_to_warnings/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231031083245_add_warn_id/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231117165927_add_last_login_field/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231119203658_remove_enabled_disable_state_from_automod/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231216135714_add_welcome_and_leave_messages/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231216145835_remove_unused_fields/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231218000910_add_titles_and_descriptions_for_custom_messages/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231228221226_add_embed_color_and_enabled_state/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20241209125259_add_better_logs/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20241209132307_add_vanity_update_event/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20241209140053_add_giveaway_events/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20241220180643_add_guild_settings/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20241220180800_change_name_of_guild_logs_settings/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20241220181109_add_type_as_unique/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20241221205356_remove_not_used_log_types/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20241228112527_allow_channel_id_to_be_null/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20241228131412_add_log_update_event/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20241231150633_remove_message_polls/ │ │ │ │ │ └── migration.sql │ │ │ │ └── migration_lock.toml │ │ │ └── schema.prisma │ │ ├── prisma.config.ts │ │ ├── src/ │ │ │ ├── client.ts │ │ │ ├── logger.ts │ │ │ ├── redis/ │ │ │ │ ├── cache.ts │ │ │ │ └── client.ts │ │ │ ├── seed.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── typescript-config/ │ │ ├── base.json │ │ ├── index.ts │ │ └── package.json │ └── utils/ │ ├── .prettierignore │ ├── content/ │ │ ├── advices.json │ │ └── why.json │ ├── database/ │ │ ├── index.ts │ │ ├── logs/ │ │ │ ├── createLog.ts │ │ │ ├── events.ts │ │ │ ├── fetchLogs.ts │ │ │ └── getGuildLogSettings.ts │ │ ├── moderation/ │ │ │ ├── automod/ │ │ │ │ ├── createDatabaseAutoModRule.ts │ │ │ │ ├── deleteDatabaseAutoModRule.ts │ │ │ │ ├── fetchDatabaseAutoModRules.ts │ │ │ │ ├── index.ts │ │ │ │ ├── syncDatabaseAutoModRule.ts │ │ │ │ └── updateDatabaseAutoModRule.ts │ │ │ └── warn/ │ │ │ ├── clearWarns.ts │ │ │ ├── listWarns.ts │ │ │ ├── removeWarn.ts │ │ │ └── warnUser.ts │ │ ├── reputation/ │ │ │ ├── checkReputation.ts │ │ │ ├── giveReputation.ts │ │ │ ├── setReputation.ts │ │ │ └── takeReputation.ts │ │ ├── settings/ │ │ │ └── XPSettings.ts │ │ ├── user/ │ │ │ └── createUser.ts │ │ └── xp/ │ │ ├── checkXP.ts │ │ └── resetXP.ts │ ├── embeds/ │ │ ├── createErrorEmbed.ts │ │ └── index.ts │ ├── functions/ │ │ ├── automod/ │ │ │ ├── createDiscordAutoModRule.ts │ │ │ ├── deleteDiscordAutoModRule.ts │ │ │ ├── fetchDiscordAutoModRules.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── validateAutoModIgnores.ts │ │ │ └── validateAutoModRuleActions.ts │ │ ├── files/ │ │ │ ├── index.ts │ │ │ └── readDir.ts │ │ ├── guild/ │ │ │ ├── getGuild.ts │ │ │ ├── getGuildChannels.ts │ │ │ ├── getGuildFromMemberGuilds.ts │ │ │ ├── getGuildPreview.ts │ │ │ ├── getGuildRoles.ts │ │ │ ├── getMemberGuilds.ts │ │ │ ├── index.ts │ │ │ └── isBotInServer.ts │ │ ├── user/ │ │ │ ├── getDiscordUser.ts │ │ │ ├── getFlags.ts │ │ │ ├── getPermissionNames.ts │ │ │ ├── index.ts │ │ │ └── isApiUser.ts │ │ └── util/ │ │ ├── adjustColor.ts │ │ ├── capitalize.ts │ │ ├── fillMissingDates.ts │ │ ├── flipText.ts │ │ ├── formatDate.ts │ │ ├── formatDuration.ts │ │ ├── formatNumber.ts │ │ ├── generateDates.ts │ │ ├── index.ts │ │ ├── isNumeric.ts │ │ ├── logger.ts │ │ ├── percentageBar.ts │ │ ├── randomness.ts │ │ ├── shortenText.ts │ │ ├── splitCamelCase.ts │ │ └── sumArray.ts │ ├── images/ │ │ ├── index.ts │ │ ├── invertColor.ts │ │ └── linesHelper.ts │ ├── package.json │ └── tsconfig.json ├── pnpm-workspace.yaml ├── prettier.config.js ├── tsconfig.json └── turbo.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ **/.classpath **/.dockerignore **/.env **/.git **/.gitignore **/.project **/.settings **/.toolstarget **/.vs **/.vscode **/.next **/.cache **/*.*proj.user **/*.dbmdl **/*.jfm **/charts **/docker-compose* **/compose* **/Dockerfile* **/node_modules **/npm-debug.log **/obj **/secrets.dev.yaml **/values.dev.yaml **/build **/dist LICENSE README.md ================================================ FILE: .github/codeowners ================================================ license.md @igorkowalczyk ================================================ FILE: .github/renovate.json ================================================ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["github>igorkowalczyk/shared-configs//packages/renovate-config/index.json"], "packageRules": [ { "matchFiles": ["/packages/dashboard/**"], "labels": ["dependencies", "dashboard"] }, { "matchFiles": ["/packages/bot/**"], "labels": ["dependencies"] }, { "matchPaths": ["+(package.json)"], "labels": ["dependencies"] } ] } ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ name: "CodeQL Checks" on: [push, pull_request] jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: ["javascript-typescript"] steps: - name: 🧱 Checkout repository uses: actions/checkout@v6 - name: 🚀 Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - name: 🚀 Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 with: category: "/language:${{matrix.language}}" ================================================ FILE: .github/workflows/format-check.yml ================================================ name: Prettier check on: [push, pull_request] jobs: check: timeout-minutes: 15 name: Format check runs-on: ubuntu-latest env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} strategy: fail-fast: false matrix: node: ["lts/*"] steps: - name: 🧱 Checkout repository uses: actions/checkout@v6 - name: 🔩 Setup PNPM uses: pnpm/action-setup@v4 with: standalone: true - name: 🔩 Setup Node ${{ matrix.node }} uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} cache: "pnpm" - name: 🚀 Install dependencies run: pnpm install - name: 🚀 Check formatting run: "pnpm run format:check" ================================================ FILE: .github/workflows/lint.yml ================================================ name: Lint on: [push, pull_request] jobs: lint: timeout-minutes: 15 name: ESLint runs-on: ubuntu-latest env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} strategy: fail-fast: false matrix: node: ["lts/*"] steps: - name: 🧱 Checkout repository uses: actions/checkout@v6 - name: 🔩 Setup PNPM uses: pnpm/action-setup@v4 with: standalone: true - name: 🔩 Setup Node ${{ matrix.node }} uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} cache: "pnpm" - name: 🚀 Install dependencies run: pnpm install - name: 🚀 Run ESLint run: pnpm lint ================================================ FILE: .gitignore ================================================ # .gitignore # Logs npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Coverage directory used by tools like istanbul coverage *.lcov # Dependency directories and files node_modules/ jspm_packages/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz *.zip # dotenv environment variables file .env* !.env.example # Stores VSCode versions used for testing VSCode extensions .vscode-test .vscode # Next.js build output .next # Certificates *.key *.cert # Turborepo & Vercel .turbo .vercel # Prisma packages/database/prisma/client ================================================ FILE: .npmrc ================================================ public-hoist-pattern[]=*prisma* ================================================ FILE: .prettierignore ================================================ package.json pnpm-lock.yaml */*/package.json */*/pnpm-lock.yaml ================================================ FILE: CONTRIBUTING.md ================================================ ![Header](https://github.com/IgorKowalczyk/majo.exe/assets/49127376/4df57b45-2900-40e0-bf34-707170fb83bc)

Discord Discord.js CodeQL Checks GitHub License

> [!NOTE] > If you are reading this, **you are probably interested in contributing to Majo.exe**. **We appreciate your interest** in improving the project and welcome your contributions. To ensure a smooth and productive collaboration, **please review the following guidelines.** ## 🆕 Submitting Contributions We know that contributing to a project can be intimidating, especially if you are new to the community. However, we want to assure you that **contributing to Majo.exe is easy and fun**. We have created a welcoming community that encourages collaboration and supports new contributors. **We are excited to have you on board!** 🙌 ### 1. Choose existing issues or create your own 📝 Start by **reviewing the existing issues** to identify areas where your contributions **can make a significant impact**. If you **have new features, enhancements, or bug fixes in mind**, you can create a **new issue** to propose your contributions. **Please provide comprehensive details to clearly explain your ideas.** This will help the project maintainers and fellow contributors to understand your suggestions and provide valuable feedback. ### 2. Fork the repository 🍴 To initiate your contribution, **fork the primary repository by clicking the "Fork" button.** This creates a copy of the repository in your personal GitHub account. You will use this copy to make and propose changes to the project. ![Forking igorkowalczyk/majo.exe repository on Github](https://github.com/IgorKowalczyk/majo.exe/assets/49127376/dfaedc7f-5a59-4332-b19b-5305d1c78935) After forking the repository, **clone your forked repository to your local development environment** using the following command: ```bash git clone https://github.com/your-username/majo.exe.git cd majo.exe ``` This creates a local copy of your forked repository on your computer in directory `majo.exe`. > [!NOTE] > Replace `your-username` with your GitHub username ### 3. Set up the upstream remote ☝️ To keep your forked repository in sync with the primary repository, **set up the upstream remote** using the following command: ```bash git remote add upstream https://github.com/IgorKowalczyk/majo.exe git remote -v ``` ### 4. Create a new branch 🔗 Before you can start working on your contribution, **create a new branch dedicated to your specific task**: ```bash git checkout -b my-contribution ``` > [!NOTE] > Replace `my-contribution` with a descriptive name that clearly identifies the purpose of your branch. Please use lowercase letters and hyphens instead of spaces. ### 5. Make Changes 🔥 Now you can start working on your contribution! Implement your awesome ideas, fix bugs, or add new features. **Be creative and have fun!** Ensure your contributions are well-structured, documented, and aligned with the project's objectives. **Quick Tip:** To keep your forked repository in sync with the primary repository, **pull the latest changes** from the primary repository before making your contributions: ```bash git pull upstream main ``` > [!IMPORTANT] > Remember to test your changes before submitting them! **You can read the [README.md](https://github.com/igorkowalczyk/majo.exe/blob/master/README.md) file for instructions on how to set up the project locally.** > [!NOTE] > - **Please ensure your code is well-formatted and linted** before submitting your changes. > - To **format your code**, use the following command: `pnpm run format` > - To **lint your code**, use the following command: `pnpm run lint:fix` > - To **build the project**, use the following command: `pnpm run build` ### 6. Commit Your Changes 📝 Commit your awesome contributions with informative commit messages that clearly explain the purpose of your changes: ```bash git commit -m "Add a descriptive message here" ``` > [!WARNING] > Please ensure your commit messages are descriptive and informative. This will help the project maintainers and fellow contributors to better understand your changes. > > **Please do not use vague commit messages like `Update README.md` or `Fix bugs`.** Instead, use messages like `Update README.md with instructions on how to set up the project locally` or `Fix bugs related to incorrect variable names`. ### 7. Push Your Changes 🚀 Push your commits to your forked repository on GitHub: ```bash git push origin my-contribution ``` > [!NOTE] > Replace `my-contribution` with the name of your branch. ### 8. Create a Pull Request 📩 **Visit your forked repository on GitHub and click the "Compare & pull request"** button to create a pull request from your branch to the primary repository. **Please provide comprehensive details to clearly explain your changes.** This will help the project maintainers and fellow contributors to understand your contributions and provide valuable feedback. > [!NOTE] > **Please ensure your pull request is well-formatted and linted** before submitting it. This will help the project maintainers and fellow contributors to work with your code more efficiently, and it will increase the likelihood of your pull request being accepted. ### 9. Await review 👀 Wait for the project maintainers to review your pull request. **Be prepared to address any suggested changes or refinements.** Reviewers may ask you to make changes to your pull request before it can be accepted. **Please be patient and respond to any questions or requests in a timely manner.** We know that waiting for your pull request to be accepted can be stressful. **Please be assured that we will do our best to review your pull request as soon as possible.** We appreciate your patience and understanding ❤️‍🔥 --- ## 📖 Additional Resources ### Join the Community 🤝 Want to contribute to Majo.exe but don't know where to start? **Join our community!** We have created a welcoming community that encourages collaboration and supports new contributors. **We are excited to have you on board!** 🙌 [We created a Discord server for the Majo.exe community.](https://majoexe.com/discord) **Join the server to connect with fellow contributors and get support from the project maintainers.** We look forward to collaborating with you! 🚀👾🙌 [![Join the Discord server](https://invidget.switchblade.xyz/sgt4QEyDxK)](https://majoexe.com/discord) ### Share Your Feedback 📣 We want to hear from you! **Your feedback is vital for the project's development and success.** If you have any questions, suggestions, or concerns, please feel free to contact the project maintainers. We are always happy to help! 🙌 You can contact the project maintainers by [joining the Discord server](https://majoexe.com/discord) or [opening an issue](https://github.com/igorkowalczyk/majo.exe/issues/new/choose) on GitHub. [![Create a new issue on GitHub](https://github.com/IgorKowalczyk/majo.exe/assets/49127376/15422f9b-c7cc-48ef-a047-6a81c0e6c6e9)](https://github.com/igorkowalczyk/majo.exe/issues/new/choose) ### Spread the Word 📢 **Help us spread the word about Majo.exe!** We would love to see more people join our community and contribute to the project. **Please share the project with your friends and colleagues.** We appreciate your support! 🙌 **Thank you for your valuable contributions, and we look forward to collaborating with you on the Majo.exe project! 🚀👾🙌** ================================================ FILE: README.md ================================================ ![Header](https://github.com/IgorKowalczyk/majo.exe/assets/49127376/8fd53e0b-1902-460c-9d6c-7d42ea84f8bb)

Discord Discord.js CodeQL Checks GitHub License

## ✨ Features - ⚙️ **Fully Customizable:** Tailor Majo.exe to your preferences with comprehensive customization options. - 🌆 **Built-in Dashboard:** Manage your Majo.exe settings effortlessly through our intuitive dashboard. - 📝 **Easy Configuration:** Streamlined configuration process that makes setting up a breeze. - 💯 **150+ Commands:** Access over 150 versatile commands to enrich your server's experience. - 📚 **Easy Hosting:** Effortlessly host Majo.exe on your servers and keep it online 24/7. ## 🔗 Invite Go to [this link](https://discord.com/oauth2/authorize/?permissions=4294967287&scope=bot%20applications.commands&client_id=949342410150924319) and add the bot (this requires `MANAGE_GUILD` permission) to your server. [Or to make it easier, visit our website](https://majoexe.com/) ## 🖥️ Hosting We are hosting Majo.exe on our own servers. Majo.exe will be online 24/7. [Invite Majo here!](https://majoexe.com/api/invite) However, if you want to host Majo.exe yourself, you can do it. [Check out our tutorials](#-tutorials) to learn how to do it. > [!IMPORTANT] > **This project is not for beginners.** If you are not familiar with Node.js, Prisma, Discord.js or any other technology used in this project, you should not host Majo.exe yourself! ### 📝 Tutorials - **[🤖 Bot setup tutorial](/apps/bot/README.md)** - **[🔩 Dashboard setup tutorial](/apps/dashboard/README.md)** - **[📝 Database setup tutorial](/packages/database/README.md)** ## ⚙️ System Requirements Ensure your setup meets these prerequisites before setting up Majo.exe: - `PostgreSQL 14x` or higher - `Node.js 18x` or higher - `(Any)` Linux x64¹ - `~512MB` of RAM (minimum) - `~3GB` of hard drive space (minimum) > [!NOTE] > 1. Debian based distros are recommended, bot can also run on Windows and MacOS but it's not recommended. ## 🔒 `.env` files | Variable | Description | Required (Bot) | Required (Dashboard) | | --------------------------- | -------------------------------------------------------- | -------------- | -------------------- | | `TOKEN` | Discord bot token | `✅ Yes` | `✅ Yes` | | `CLIENT_ID` | Discord client ID | `✅ Yes` | `✅ Yes` | | `CLIENT_SECRET` | Discord client secret | `❌ No` | `✅ Yes` | | `DATABASE_URL` | Main database connection string | `✅ Yes` | `✅ Yes` | | `DATABASE_URL_UNPOOLED` | Non-pooling database connection string | `❌ No` | `❌ No` | | `REDIS_URL` | Redis Cache connection string | `✅ Yes` | `✅ Yes` | | `SECRET` | Secret string (minimum 32 characters) | `❌ No` | `✅ Yes` | | `NEXTAUTH_URL` | NextAuth.js URL (e.g., http://localhost:3000) | `❌ No` | `✅ Yes` | | `NEXT_PUBLIC_URL` | Next.js public URL (e.g., http://localhost:3000) | `❌ No`¹ | `✅ Yes` | | `HOTJAR_ID` | [Hotjar](https://hotjar.com) ID | `❌ No` | `❌ No` | | `DISCORD_SUPPORT_SERVER_ID` | Discord support server ID | `❌ No` | `❌ No`² | | `TOPGG_API_KEY` | [top.gg](https://top.gg) API key | `❌ No` | `❌ No`³ | | `DISCORD_BOT_LIST_API_KEY` | [discordbotlist.com](https://discordbotlist.com) API key | `❌ No` | `❌ No`⁴ | > [!NOTE] > 1. `NEXT_PUBLIC_URL` is required only if you want to also host the dashboard. > 2. `DISCORD_SUPPORT_SERVER_ID` is required only if you want to automatically add users to your own Discord server when they log in to the dashboard. Please note that the bot needs `Manage Server` permission in the server!\ > 3. `TOPGG_API_KEY` is required only if you want to automatically post server count to [top.gg](https://top.gg). > 4. `DISCORD_BOT_LIST_API_KEY` is required only if you want to automatically post server count, stats and more to [discordbotlist.com](https://discordbotlist.com). ## 📝 Contributors - [**@binary-blazer**](https://github.com/binary-blazer) - Hosting support - [**@TsukiyoDevs**](https://github.com/TsukiyoDevs) - Bug fixes, New features, Testing - [**@r-kjha**](https://github.com/r-kjha) - Emoji config support, Bug fixes, New features, Testing - [**@Joao-Victor-Liporini**](https://github.com/Joao-Victor-Liporini) - Bug fixes, Command handler improvements, Testing, New features - [**@evandev**](https://github.com/xefew) - Bug fixes, Testing - [**iWeedy\_**](https://github.com/i-weedy) - Testing - [**@krzesl0**](https://github.com/krzesl0) - New Features, Bug fixes, Testing - [**@\_index1337**](https://github.com/index1337) - Readme tutorials - [**@Wafelowski**](https://github.com/HeavyWolfPL) - Translation improvements - [**@Sakshyam6966**](https://github.com/Sakshyam6966) - New Features, Bug fixes, Testing ## ⁉️ Issues If you have any issues with the page please create [new issue here](https://github.com/igorkowalczyk/majo.exe/issues). When creating new issue please provide as much information as possible. If you can, please provide logs from console. We will review your pull request as soon as possible. We might suggest some changes or improvements. ## 📥 Pull Requests When submitting a pull request: - Clone the repository (`git clone https://github.com/igorkowalczyk/majo.exe`) - Create a branch off of `master` and give it a meaningful name (e.g. `my-awesome-new-feature`). - Open a [pull request](https://github.com/igorkowalczyk/majo.exe/pulls) on [GitHub](https://github.com) and describe the feature or fix. ## 📋 License This project is licensed under the MIT. See the [LICENSE](https://github.com/igorkowalczyk/majo.exe/blob/master/license.md) file for details
The cake is a lie 🍰 Github repository views
================================================ FILE: apps/bot/.env.example ================================================ # Bot TOKEN= CLIENT_ID= CLIENT_SECRET= DISCORD_SUPPORT_SERVER_ID= # Top.gg (Optional) TOPGG_API_KEY= # discordbotlist.com (Optional) DISCORD_BOT_LIST_API_KEY= ================================================ FILE: apps/bot/.prettierignore ================================================ package.json ================================================ FILE: apps/bot/Dockerfile ================================================ ARG APP_PKG_NAME=@majoexe/bot FROM node:24-alpine RUN apk add curl WORKDIR /app ENV NODE_ENV=production ENV TURBO_TELEMETRY_DISABLED=1 RUN corepack enable RUN corepack prepare pnpm@latest --activate RUN curl -sfS https://dotenvx.sh/install.sh | sh COPY . . RUN pnpm install --recursive --prod CMD ["dotenvx", "run", "--", "pnpm", "run", "deploy", "--filter", "!@majoexe/dashboard"] ================================================ FILE: apps/bot/README.md ================================================ ![Header - Bot](https://github.com/IgorKowalczyk/majo.exe/assets/49127376/9a34b389-e710-435e-9514-1f4c5f733d74)

Discord Discord.js CodeQL Checks GitHub License

## 🤖 Self-Hosting 1. Clone [this repository](https://github.com/igorkowalczyk/majo.exe) `git clone https://github.com/IgorKowalczyk/majo.exe.git` 2. Go to `/packages/database/` directory and follow [Database Setup](/packages/database/README.md) tutorial. 3. Grab a Discord Bot token and client secret on [Discord's developer portal](https://discord.com/developers/applications) [Tutorial](#-discord-credentials). 4. Create new file or edit existing `.env` file in `/apps/bot/` directory. 5. In `.env` file set this values: - `TOKEN` - Discord bot token [[Tutorial](#-discord-token)] - `SECRET` - Random string (min. length = 32 chars) - `CLIENT_SECRET` - Discord bot secret [[Tutorial](#-discord-secret)] - `DISCORD_SUPPORT_SERVER_ID` - Your Discord support server ID - `TOPGG_API_KEY` - Your Top.gg API key (optional) - `DISCORD_BOT_LIST_API_KEY` - Your discordbotlist.com API key (optional) 6. Run `pnpm i` to install all dependencies 7. Go to `/packages/config/` directory and change values in `/configs/bot.js` to your values. 8. Go back to main directory and run `pnpm run dev --filter="@majoexe/bot"` or `pnpm run deploy --filter="@majoexe/bot"` to start bot 9. That's it! You can now invite your bot to your server and use it! ## 🔒 Example `.env` file ``` # Bot TOKEN= CLIENT_ID= CLIENT_SECRET= DISCORD_SUPPORT_SERVER_ID= # Top.gg (Optional) TOPGG_API_KEY= # discordbotlist.com (Optional) DISCORD_BOT_LIST_API_KEY= ``` > [!WARNING] > Make sure to not share your `.env` file with anyone, it contains sensitive information like your bot token and secrets. --- ## ⚙️ System Requirements Ensure your setup meets these prerequisites before setting up Majo.exe: - `PostgreSQL 14x` or higher - `Node.js 18x` or higher - `(Any)` Linux x64¹ - `~512MB` of RAM (minimum) - `~3GB` of hard drive space (minimum) > [!NOTE] > 1: Debian based distros are recommended, Dashboard can also run on Windows and MacOS but it's not recommended. ## 🔓 Tokens tutorials ### 🔏 Discord Token 1. Go to Discord Developer Portal 2. At the top right of the screen, click "New application" and assign it a name. Next in the left part of the screen on the navigation bar, find "Bot" then click it and find button named "Add Bot" 3. After confirming the bot creation, click the "Copy token" button 4. Paste your token in `.env` file - `TOKEN=BOT_TOKEN` > Tutorial written by: \_index1337 ### 🔓 Discord Bot Secret 1. Go to Discord Developer Portal 2. In the left part of the screen on the bar, find "OAuth2" then click it 3. Find section named "Client Secret", under the bot secret click "Copy" button 4. Paste client secret to `.env` - `CLIENT_SECRET=CLIENT_SECRET` > Tutorial written by: \_index1337 ## 📝 Contributors - [**@binary-blazer**](https://github.com/binary-blazer) - Hosting support - [**@TsukiyoDevs**](https://github.com/TsukiyoDevs) - Bug fixes, New features, Testing - [**@r-kjha**](https://github.com/r-kjha) - Emoji config support, Bug fixes, New features, Testing - [**@Joao-Victor-Liporini**](https://github.com/Joao-Victor-Liporini) - Bug fixes, Command handler improvements, Testing, New features - [**@evandev**](https://github.com/xefew) - Bug fixes, Testing - [**iWeedy\_**](https://github.com/i-weedy) - Testing - [**@krzesl0**](https://github.com/krzesl0) - New Features, Bug fixes, Testing - [**@\_index1337**](https://github.com/index1337) - Readme tutorials - [**@Wafelowski**](https://github.com/HeavyWolfPL) - Translation improvements - [**@Sakshyam6966**](https://github.com/Sakshyam6966) - New Features, Bug fixes, Testing ## ⁉️ Issues If you have any issues with the bot please create [new issue here](https://github.com/igorkowalczyk/majo.exe/issues). When creating new issue please provide as much information as possible. If you can, please provide logs from console. ## 📥 Pull Requests When submitting a pull request: - Clone the repo. - Create a branch off of `master` and give it a meaningful name (e.g. `my-awesome-new-feature`). - Open a [pull request](https://github.com/igorkowalczyk/majo.exe/pulls) on [GitHub](https://github.com) and describe the feature or fix. We will review your pull request as soon as possible. We might suggest some changes or improvements. ## 📋 License This project is licensed under the MIT. See the [LICENSE](https://github.com/igorkowalczyk/majo.exe/blob/master/license.md) file for details ================================================ FILE: apps/bot/commands/Fun/8ball.ts ================================================ import { ApplicationCommandType, ApplicationCommandOptionType, EmbedBuilder, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "8ball", description: "🎱 Ask the 8ball a question", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/8ball ", options: [ { name: "question", description: "Question to ask 8ball", required: true, type: ApplicationCommandOptionType.String, max_length: 100, }, ], run: async (client, interaction, guildSettings) => { try { const args = interaction.options.getString("question"); if (!args) return client.errorMessages.createSlashError(interaction, "❌ Please provide a valid question."); const images = [ ["Yes.", "https://c.tenor.com/TFhmPga4xEwAAAAC/magic8ball-yes.gif"], ["It is certain", "https://c.tenor.com/eyI116E3kWYAAAAC/yoda-8ball.gif"], ["Without a doubt", "https://c.tenor.com/-0tatbxLQVQAAAAC/yoda-8ball.gif"], ["Yes definelty", "https://c.tenor.com/fc7fywg2oQQAAAAC/yoda-8ball.gif"], ["You may rely on it", "https://c.tenor.com/8J1uZFp8xMUAAAAC/yoda-8ball.gif"], ["As I see it, yes", "https://c.tenor.com/EIAYng3CUf0AAAAC/yoda-8ball.gif"], ["Most likely", "https://c.tenor.com/EIAYng3CUf0AAAAC/yoda-8ball.gif"], ["Outlook not so good", "https://c.tenor.com/Ji3GcuKvu1cAAAAC/magic8ball-simpsons.gif"], ["Signs point to yes", "https://c.tenor.com/mrN4WoxyRE8AAAAC/shaking8ball-stranger-things4.gif"], ["followUp hazy, try again", "https://c.tenor.com/BokmYoZhr1AAAAAC/yoda-8ball.gif"], ["Ask again later", "https://c.tenor.com/Voqiq18wUFIAAAAC/yoda-8ball.gif"], ["Better not tell you now...", "https://c.tenor.com/Voqiq18wUFIAAAAC/yoda-8ball.gif"], ["Cannot predict now", "https://c.tenor.com/fs_hXVg58LkAAAAC/yoda-8ball.gif"], ["Concentrate and ask again", "https://c.tenor.com/Voqiq18wUFIAAAAC/yoda-8ball.gif"], ["Don't count on it", "https://c.tenor.com/cw2aa9cnQ6QAAAAC/magic-eight.gif"], ["My followUp is no", "https://c.tenor.com/rJ1ioW_FkhUAAAAC/yoda-8ball.gif"], ]; const parsed = images.map((x) => [x[0], x[1]]); const random = Math.floor(Math.random() * parsed.length); if (!parsed[random] || !parsed[random][0] || !parsed[random][1]) return client.errorMessages.createSlashError(interaction, "❌ No answer available."); const embed = new EmbedBuilder() .setDescription(`>>> **Q:** ${args} \n**A:** ${parsed[random][0] || "No answer available"}`) .setImage(parsed[random][1]) .setTimestamp() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); return interaction.followUp({ embeds: [embed] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/advice.ts ================================================ import advices from "@majoexe/util/content/advices.json"; import { ApplicationCommandType, ApplicationIntegrationType, EmbedBuilder, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "advice", description: "🤌 Get a random helpful advice", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/advice", run: async (client, interaction, guildSettings) => { try { const parsed = advices[Math.floor(Math.random() * advices.length)]; if (!parsed) return client.errorMessages.createSlashError(interaction, "❌ Failed to fetch advice. Please try again later."); const embed = new EmbedBuilder() .setTitle("🤌 My advice is:") .setDescription(`>>> **${parsed.advice}**`) .setTimestamp() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setThumbnail(interaction.user.displayAvatarURL({ size: 256 })) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); return interaction.followUp({ embeds: [embed] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/anime.ts ================================================ import { ApplicationCommandType, ApplicationCommandOptionType, EmbedBuilder, codeBlock, ActionRowBuilder, ButtonBuilder, ButtonStyle, ApplicationIntegrationType, InteractionContextType, MessageFlags, } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "anime", description: "💮 Search for information about Anime by given name", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/anime ", autocomplete: async (client, interaction) => { const focusedOption = interaction.options.getFocused(true); if (focusedOption.name === "query") { const query = focusedOption.value; const request = await fetch(`https://kitsu.app/api/edge/anime?filter[text]=${query}&page%5Boffset%5D=0&page%5Blimit%5D=5`, { method: "GET", headers: { "Content-Type": "application/vnd.api+json", Accept: "application/vnd.api+json", }, }); if (!request || !request.ok) { return; } const json = await request.json(); if (!json || !json.data || json.data.length < 1) { return; } /* eslint-disable typescript/no-explicit-any */ const results = json.data.map((result: any) => { const data = result.attributes; return { name: data.canonicalTitle, value: data.canonicalTitle, }; }); await interaction.respond(results); } }, options: [ { name: "query", description: "Anime name", required: true, autocomplete: true, type: ApplicationCommandOptionType.String, max_length: 256, }, ], run: async (client, interaction, guildSettings) => { try { const query = interaction.options.getString("query"); if (!query) return client.errorMessages.createSlashError(interaction, "❌ Please provide a valid anime name."); const request = await fetch(`https://kitsu.app/api/edge/anime?filter[text]=${query}&page%5Boffset%5D=0&page%5Blimit%5D=1`, { method: "GET", headers: { "Content-Type": "application/vnd.api+json", Accept: "application/vnd.api+json", }, }); if (!request || !request.ok) { return client.errorMessages.createSlashError(interaction, "❌ No results found."); } const json = await request.json(); if (!json || !json.data || json.data.length < 1) { return client.errorMessages.createSlashError(interaction, "❌ No results found."); } const data = json.data[0].attributes; if (!data) { const embed = new EmbedBuilder() .setColor("#EF4444") .setTimestamp() .setTitle("❌ Error") .setDescription("> No results found.") .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }); return interaction.followUp({ flags: [MessageFlags.Ephemeral], embeds: [embed] }); } if (data.synopsis.length > 1024) { data.synopsis = data.synopsis.slice(0, 1021) + "..."; } const embed = new EmbedBuilder() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setTitle(data.canonicalTitle || query.slice(0, 20)) .setURL(`https://kitsu.io/anime/${data.slug}`) .setDescription(data.synopsis ? (data.synopsis.length > 1024 ? data.synopsis.slice(0, 1021) + "..." : data.synopsis) : "No description!") .addFields([ { name: `${client.config.emojis.flag_gb} English Title`, value: codeBlock(data.titles?.en || "None!"), inline: false, }, { name: `${client.config.emojis.flag_jp} Japanese Title`, value: codeBlock(data.titles?.ja_jp || "None!"), inline: false, }, { name: `${client.config.emojis.book} Type`, value: codeBlock(data.showType || "N/A!"), inline: true, }, { name: `${client.config.emojis.star} Score`, value: codeBlock(`${data.averageRating || "N/A!"} (${data.favoritesCount} favorites)`), inline: true, }, { name: `${client.config.emojis.counting} Episodes`, value: codeBlock(`${data.episodeCount || "N/A!"} episodes (${data.episodeLength || "N/A!"} minutes each)`), inline: false, }, { name: `${client.config.emojis.star2} Rating`, value: codeBlock(`${data.ageRating || "N/A!"} ${"- " + data.ageRatingGuide || ""}`), inline: false, }, { name: `${client.config.emojis.calendar_spillar} Aired`, value: codeBlock(`${data.startDate || "N/A!"} - ${data.endDate || "N/A!"}`), inline: false, }, { name: `${client.config.emojis.barchart} Popularity`, value: codeBlock(`#${data.popularityRank || "N/A!"} (Most Popular Anime)\n#${data.ratingRank || "N/A!"} (Highest Rated Anime)`), inline: false, }, ]) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }); if (data.posterImage?.original) { embed.setThumbnail(data.posterImage.original); } const actionRow = new ActionRowBuilder() // prettier .addComponents([ new ButtonBuilder() // prettier .setStyle(ButtonStyle.Link) .setLabel("View on Kitsu") .setURL(`https://kitsu.io/anime/${data.slug}`), ]); return interaction.followUp({ embeds: [embed], components: [actionRow] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/cat.ts ================================================ import { ActionRowBuilder, ApplicationCommandType, ApplicationIntegrationType, ButtonBuilder, ButtonStyle, EmbedBuilder, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "cat", description: "🐱 Get a random cat image", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/cat", run: async (client, interaction, guildSettings) => { try { const data = await fetch("https://api.thecatapi.com/v1/images/search"); const json = await data.json(); if (!json || !json[0]) { return client.errorMessages.createSlashError(interaction, "❌ No results found."); } const embed = new EmbedBuilder() .setTitle("🐱 Meow!") .setImage(json[0].url) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); const actionRow = new ActionRowBuilder() // prettier .addComponents( new ButtonBuilder() // prettier .setStyle(ButtonStyle.Link) .setLabel("View image") .setURL(json[0].url) ); return interaction.followUp({ embeds: [embed], components: [actionRow] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/comic.ts ================================================ import { load } from "cheerio"; import { ApplicationCommandType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ApplicationCommandOptionType, ButtonStyle, ApplicationIntegrationType, InteractionContextType, } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "comic", description: "📚 Get a comic from xkcd, phd or garfield", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/comic ", options: [ { name: "xkcd", description: "💬 Check out the latest xkcd comic", type: ApplicationCommandOptionType.Subcommand, options: [ { type: ApplicationCommandOptionType.Integer, name: "issue", description: "Issue number to see the comic.", min_value: 1, }, ], }, { name: "phd", description: "💬 Check out the latest phd comic", type: ApplicationCommandOptionType.Subcommand, options: [ { type: ApplicationCommandOptionType.Integer, name: "issue", description: "Issue number to see the comic.", min_value: 1, }, ], }, { name: "garfield", description: "💬 Check out the latest garfield comic", type: ApplicationCommandOptionType.Subcommand, }, ], run: async (client, interaction, guildSettings) => { try { const command = interaction.options.getSubcommand(); if (command === "xkcd") { const issue = interaction.options.getInteger("issue"); const data = await fetch(`https://xkcd.com${issue ? `/${issue}` : ""}/info.0.json`); const json = await data.json(); if (!json) { return client.errorMessages.createSlashError(interaction, "❌ No results found."); } const embed = new EmbedBuilder() .setTitle(`📚 xkcd ${json.num} - ${json.title}`) .setDescription(json.alt) .setImage(json.img) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); const actionRow = new ActionRowBuilder() // prettier .addComponents([ new ButtonBuilder() // prettier .setStyle(ButtonStyle.Link) .setLabel("View on xkcd") .setURL(`https://xkcd.com/${json.num}`), ]); return interaction.followUp({ embeds: [embed], components: [actionRow] }); } if (command === "phd") { const issue = interaction.options.getInteger("issue"); const data = await fetch(`https://phdcomics.com${issue ? "/comics/archive.php?comicid=" + issue : ""}`); const text = await data.text(); if (!text) { return client.errorMessages.createSlashError(interaction, "❌ No results found."); } const $ = load(text); const images: string[] = []; $("img[name='comic2']").each((_, element) => { const imageUrl = $(element).attr("src"); if (imageUrl) images.push(imageUrl); }); if (!images[0]) { return client.errorMessages.createSlashError(interaction, "❌ No results found."); } const embed = new EmbedBuilder() .setTitle(`📚 PHD Comics ${issue ? `#${issue}` : ""}`) .setImage(images[0]) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); const actionRow = new ActionRowBuilder() // prettier .addComponents([ new ButtonBuilder() // prettier .setStyle(ButtonStyle.Link) .setLabel("View on PHD Comics") .setURL(`https://phdcomics.com${issue ? "/comics/archive.php?comicid=" + issue : ""}`), ]); return interaction.followUp({ embeds: [embed], components: [actionRow] }); } if (command === "garfield") { const date = new Date(); const year = date.getUTCFullYear(); const month = date.getUTCMonth() + 1; const day = date.getUTCDate(); const request = await fetch(`https://www.gocomics.com/garfield/${year}/${month}/${day}`); const text = await request.text(); if (!text) { return client.errorMessages.createSlashError(interaction, "❌ No results found."); } const $ = load(text); const image = $(".item-comic-image img").attr("src"); if (!image) { return client.errorMessages.createSlashError(interaction, "❌ No results found."); } const embed = new EmbedBuilder() .setTitle(`📚 Garfield by Jim Davis (${year}/${month}/${day})`) .setImage(image) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); const actionRow = new ActionRowBuilder() // .addComponents([ new ButtonBuilder() // prettier .setStyle(ButtonStyle.Link) .setLabel("View on GoComics") .setURL(`https://www.gocomics.com/garfield/${year}/${month}/${day}`), ]); return interaction.followUp({ embeds: [embed], components: [actionRow] }); } } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/dice.ts ================================================ import { ApplicationCommandType, ApplicationIntegrationType, EmbedBuilder, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "dice", description: "🎲 Roll a dice", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/dice", run: async (client, interaction, guildSettings) => { try { const dice = Math.floor(Math.random() * 6) + 1; const embed = new EmbedBuilder() .setTitle("🎲 Dice") .setDescription(`>>> **You rolled a ${dice}!**`) .setTimestamp() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setThumbnail(interaction.user.displayAvatarURL({ size: 256 })) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); return interaction.followUp({ embeds: [embed] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/flip.ts ================================================ import { flipText } from "@majoexe/util/functions/util"; import { ApplicationCommandType, ApplicationCommandOptionType, EmbedBuilder, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "flip", description: "🔁 Flip text (upside down) or 🪙 Flip coin (heads or tails)", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/flip ", options: [ { name: "coin", description: "🪙 Flip coin (heads or tails)", type: ApplicationCommandOptionType.Subcommand, usage: "/flip coin", }, { name: "text", description: "🔁 Flip text (upside down)", type: ApplicationCommandOptionType.Subcommand, usage: "/flip text", options: [ { name: "text", description: "Text to flip", required: true, type: ApplicationCommandOptionType.String, max_length: 100, }, ], }, ], run: async (client, interaction, guildSettings) => { try { if (interaction.options.getSubcommand() == "coin") { const coin = Math.floor(Math.random() * 2) + 1; const embed = new EmbedBuilder() .setTitle("🪙 Coin Flip") .setDescription(`>>> **You flipped a ${coin == 1 ? "heads" : "tails"}!**`) .setTimestamp() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setThumbnail(interaction.user.displayAvatarURL({ size: 256 })) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); return interaction.followUp({ embeds: [embed] }); } else if (interaction.options.getSubcommand() == "text") { const text = interaction.options.getString("text"); if (!text) return client.errorMessages.createSlashError(interaction, "❌ Please provide a valid text to flip."); const embed = new EmbedBuilder() .setTitle("🔁 Flipped Text") .setDescription(`>>> **${flipText(text)}**`) .setTimestamp() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setThumbnail(interaction.user.displayAvatarURL({ size: 256 })) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); return interaction.followUp({ embeds: [embed] }); } } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/iq.ts ================================================ import { ApplicationCommandType, ApplicationCommandOptionType, EmbedBuilder, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "iq", description: "🧠 Get a random IQ score", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/iq (user)", options: [ { name: "user", description: "User to get IQ score", type: ApplicationCommandOptionType.User, required: false, }, ], run: async (client, interaction, guildSettings) => { try { const user = interaction.options.getUser("user") || interaction.user; const iq = Math.floor(Math.random() * 200) + 1; const embed = new EmbedBuilder() .setTitle("🧠 IQ") .setDescription(`>>> **${user} has an IQ of ${iq}!**`) .setTimestamp() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setThumbnail(user.displayAvatarURL({ size: 256 })) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); return interaction.followUp({ embeds: [embed] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/joke.ts ================================================ import { ApplicationCommandType, ApplicationIntegrationType, EmbedBuilder, InteractionContextType } from "discord.js"; import fetch from "node-fetch"; import type { SlashCommand } from "@/util/types/Command"; interface Joke { type: string; setup: string; punchline: string; id: number; } export default { name: "joke", description: "😂 Get a random joke", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/joke", run: async (client, interaction, guildSettings) => { try { const joke = await fetch("https://official-joke-api.appspot.com/random_joke"); if (!joke.ok) return client.errorMessages.createSlashError(interaction, "❌ No jokes found! Please try again later."); const json = (await joke.json()) as Joke; if (!json || !json.setup || !json.punchline) { return client.errorMessages.createSlashError(interaction, "❌ No jokes found! Please try again later."); } const embed = new EmbedBuilder() .setTitle("😂 Joke") .setDescription(`>>> **${json.setup}**\n\n${json.punchline}`) .setTimestamp() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setThumbnail(interaction.user.displayAvatarURL({ size: 256 })) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); return interaction.followUp({ embeds: [embed] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/letmegpt.ts ================================================ import { ApplicationCommandType, ApplicationCommandOptionType, EmbedBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, ApplicationIntegrationType, InteractionContextType, } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "letmegpt", description: "🔍 Let me GPT that for you", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/lmgtfy ", options: [ { name: "query", description: "Search query", required: true, type: ApplicationCommandOptionType.String, max_length: 256, }, ], run: async (client, interaction, guildSettings) => { try { const query = interaction.options.getString("query"); if (!query) return client.errorMessages.createSlashError(interaction, "❌ Please provide a valid search query."); const embed = new EmbedBuilder() .setTitle("🔍 Let me GPT that for you") .setDescription(`>>> https://letmegpt.com/search?q=${encodeURIComponent(query)}`) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); const row = new ActionRowBuilder() // prettier .addComponents( new ButtonBuilder() // prettier .setLabel("Search") .setEmoji("🔍") .setStyle(ButtonStyle.Link) .setURL(`https://letmegpt.com/search?q=${encodeURIComponent(query)}`) ); return interaction.followUp({ embeds: [embed], components: [row] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/lmgtfy.ts ================================================ import { ApplicationCommandType, ApplicationCommandOptionType, EmbedBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, ApplicationIntegrationType, InteractionContextType, } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "lmgtfy", description: "🔍 Let me google that for you", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/lmgtfy ", options: [ { name: "query", description: "Search query", required: true, type: ApplicationCommandOptionType.String, max_length: 256, }, ], run: async (client, interaction, guildSettings) => { try { const query = interaction.options.getString("query"); if (!query) return client.errorMessages.createSlashError(interaction, "❌ Please provide a valid search query."); const embed = new EmbedBuilder() .setTitle("🔍 Let me google that for you") .setDescription(`>>> https://letmegooglethat.com/?q=${encodeURIComponent(query)}`) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); const row = new ActionRowBuilder() // prettier .addComponents( new ButtonBuilder() // prettier .setLabel("Search") .setEmoji("🔍") .setStyle(ButtonStyle.Link) .setURL(`https://letmegooglethat.com/?q=${encodeURIComponent(query)}`) ); return interaction.followUp({ embeds: [embed], components: [row] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/meme.ts ================================================ import { ActionRowBuilder, ApplicationCommandType, ApplicationIntegrationType, ButtonBuilder, ButtonStyle, EmbedBuilder, InteractionContextType } from "discord.js"; import fetch from "node-fetch"; import type { SlashCommand } from "@/util/types/Command"; interface RedditPost { title: string; url: string; permalink: string; } interface RedditChildren { data: RedditPost; } interface RedditData { children: RedditChildren[]; } interface RedditResponse { kind: string; data: RedditData; } export default { name: "meme", description: "😂 Get a random meme", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/meme", run: async (client, interaction, guildSettings) => { try { const meme = await fetch("https://reddit.com/r/dankmemes/random/.json"); if (!meme.ok) return client.errorMessages.createSlashError(interaction, "❌ No memes found! Please try again later."); const json = (await meme.json()) as RedditResponse[]; if ( !json || !json[0] || !json[0].data || !json[0].data.children || !json[0].data.children[0] || !json[0].data.children[0].data || !json[0].data.children[0].data.title || !json[0].data.children[0].data.url ) { return client.errorMessages.createSlashError(interaction, "❌ No results found."); } const embed = new EmbedBuilder() .setTitle(json[0].data.children[0].data.title) .setImage(json[0].data.children[0].data.url) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); const actionRow = new ActionRowBuilder() // prettier .addComponents( new ButtonBuilder() // prettier .setStyle(ButtonStyle.Link) .setLabel("View on Reddit") .setURL(`https://reddit.com${json[0].data.children[0].data.permalink}`) ); return interaction.followUp({ embeds: [embed], components: [actionRow] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/rate.ts ================================================ import { ApplicationCommandType, ApplicationCommandOptionType, EmbedBuilder, type ColorResolvable, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "rate", description: "📈 Rate something", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/rate (thing)", options: [ { name: "thing", description: "Thing to rate", type: ApplicationCommandOptionType.String, max_length: 256, required: true, }, ], run: async (client, interaction) => { try { const thing = interaction.options.getString("thing"); const rate = Math.floor(Math.random() * 100) + 1; let color; if (rate <= 100 && rate >= 90) { color = "#57F287"; } else if (rate >= 50 && rate <= 89) { color = "#FFFF00"; } else if (rate >= 0 && rate <= 49) { color = "#ED4245"; } const embed = new EmbedBuilder() .setTitle("📈 Rating") .setDescription(`>>> **I rate ${thing} a ${rate}/100!**`) .setTimestamp() .setColor(color as ColorResolvable) .setThumbnail(interaction.user.displayAvatarURL({ size: 256 })) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); return interaction.followUp({ embeds: [embed] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/ship.ts ================================================ import { percentageBar } from "@majoexe/util/functions/util"; import { ApplicationCommandType, ApplicationCommandOptionType, EmbedBuilder, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "ship", description: "❤️ Ship users together", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/ship ", options: [ { name: "first", description: "The first user to ship", type: ApplicationCommandOptionType.User, required: true, }, { name: "second", description: "The second user to ship", type: ApplicationCommandOptionType.User, required: true, }, ], run: async (client, interaction, guildSettings) => { try { const first = interaction.options.getUser("first"); const second = interaction.options.getUser("second"); if (!first || !second) { return client.errorMessages.createSlashError(interaction, "❌ You need to specify two users to ship"); } if (first.id === second.id) { return client.errorMessages.createSlashError(interaction, "❌ You can't ship the same user with themselves"); } const ship = Math.floor(Math.random() * 100) + 1; const embed = new EmbedBuilder() .setTitle(`❤️ Shipping ${first.globalName || first.username} and ${second.globalName || second.username}`) .setDescription(` **${ship > 50 ? "🔥 They are born for each other!" : "❄️ This isn't a match"}**\n\n${percentageBar(100, ship, 20)}`) .setTimestamp() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }); if (ship > 50) { embed.setThumbnail("https://cdn.discordapp.com/emojis/797365365595439104.gif?v=1"); } else { embed.setThumbnail("https://cdn.discordapp.com/emojis/853644938867769454.gif?v=1"); } interaction.followUp({ embeds: [embed] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Fun/why.ts ================================================ import why from "@majoexe/util/content/why.json"; import { ApplicationCommandType, ApplicationIntegrationType, EmbedBuilder, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "why", description: "🤔 Get a random why question", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/why", run: async (client, interaction, guildSettings) => { try { const parsed = why[Math.floor(Math.random() * why.length)]; const embed = new EmbedBuilder() .setTitle("🤔 Why?") .setDescription(`> **${parsed}**\n\n*Some questions can be outdated or not make sense!\n Don't take them seriously!*`) .setTimestamp() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setThumbnail(interaction.user.displayAvatarURL({ size: 256 })) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256 }), }); return interaction.followUp({ embeds: [embed] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/General/about.ts ================================================ import { ActionRowBuilder, ApplicationCommandType, ButtonStyle, ButtonBuilder, EmbedBuilder, InteractionContextType, ApplicationIntegrationType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "about", description: "🏷️ Learn more about Majo.exe", type: ApplicationCommandType.ChatInput, cooldown: 3000, usage: "/about", contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], run: async (client, interaction, guildSettings) => { try { if (!client.user) return client.errorMessages.createSlashError(interaction, "❌ Bot is not ready yet. Please try again later."); const embed = new EmbedBuilder() // Prettier .setTitle(`🤖 About ${client.user.username}`) .setDescription( `Majo.exe is a Discord bot made for **Memes, Image editing, Giveaways, Moderation, Anime and even more!** 🎉 It is made by the awesome [Majo.exe Team & Contributors](https://github.com/IgorKowalczyk/majo.exe#-contributors) and is **completly open source and free**. **You can find the source code [on Github](https://github.com/igorkowalczyk/majo.exe).** If you want to help us with our journey and you know how to code, you can contribute to the project by forking the repository and making a pull request. **We really appreciate it!** ❤️‍🔥 ${client.config.url ? `**If you want to invite Majo.exe to your server, you can do so by clicking [here](${client.config.url})**` : ""} ` ) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp(); if (client.config.url) { const action = new ActionRowBuilder() // prettier .addComponents( new ButtonBuilder() // prettier .setLabel("Dashboard") .setStyle(ButtonStyle.Link) .setURL(client.config.url), new ButtonBuilder() // prettier .setLabel("Invite") .setStyle(ButtonStyle.Link) .setURL(`${client.config.url}/invite`) ); return interaction.followUp({ embeds: [embed], components: [action] }); } else { return interaction.followUp({ embeds: [embed] }); } } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/General/contact.ts ================================================ import { ApplicationCommandType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "contact", description: "📝 Contact the Majo.exe team", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/contact", run: async (client, interaction, guildSettings) => { try { if (!client.config.url) { const embed = new EmbedBuilder() .setTitle("😢 We are sorry!") .setDescription( "Apologies for the inconvenience, but our dashboard and contact page are currently experiencing technical difficulties. We kindly ask you to try again later." ) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setTitle("📝 Contact"); return interaction.followUp({ embeds: [embed] }); } const action = new ActionRowBuilder() // prettier .addComponents( new ButtonBuilder() // prettier .setLabel("Contact") .setStyle(ButtonStyle.Link) .setURL(`${client.config.url}/contact`), new ButtonBuilder() // prettier .setLabel("Commands") .setStyle(ButtonStyle.Link) .setURL(`${client.config.url}/commands`), new ButtonBuilder() // prettier .setLabel("Support") .setStyle(ButtonStyle.Link) .setURL(`${client.config.url}/support`) ); const embed = new EmbedBuilder() .setDescription( `Click the button below or [click here](${client.config.url}/contact) to contact the Majo.exe team.\n\n>>> **Useful links:**\n- [View all Majo.exe commands](${client.config.url}/commands)\n- [Majo.exe support server](${client.config.url}/support)` ) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setTitle("📝 Contact"); return interaction.followUp({ embeds: [embed], components: [action] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/General/dashboard.ts ================================================ import { ApplicationCommandType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "dashboard", description: "💻 Visit our dashboard", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/dashboard", run: async (client, interaction, guildSettings) => { try { if (!client.config.url) { const embed = new EmbedBuilder() .setDescription("Our dashboard is not working at the moment, please try again later!") .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setTitle("💻 Dashboard"); return interaction.followUp({ embeds: [embed] }); } const action = new ActionRowBuilder() // prettier .addComponents( new ButtonBuilder() // prettier .setLabel("Dashboard") .setStyle(ButtonStyle.Link) .setURL(client.config.url) ); const embed = new EmbedBuilder() .setDescription( `Click the button below or [click here](${client.config.url}) to visit our dashboard.\n\n>>> **Useful links:**\n- [View all Majo.exe commands](${client.config.url}/commands)\n- [Majo.exe support server](${client.config.url}/support)` ) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setTitle("💻 Dashboard"); return interaction.followUp({ embeds: [embed], components: [action] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/General/donate.ts ================================================ import { ApplicationCommandType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; interface DonateLink { icon: string; name: string; url: string; } export default { name: "donate", description: "🪙 Help us develop Majo.exe by donating", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/donate", run: async (client, interaction, guildSettings) => { try { if (!client.config.donate.enabled || !client.config.donate.links) { const embed = new EmbedBuilder() .setDescription("Currently, we do not accept any donation methods! Try again later") .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setTitle("🪙 Donate to Majo.exe"); return interaction.followUp({ embeds: [embed] }); } const embed = new EmbedBuilder() .setDescription( "> **You can donate to Majo.exe by using the following methods:**\n" + client.config.donate.links.map((link: DonateLink) => `- [${link.icon} ${link.name}](${link.url})`).join("\n") ) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setTitle("🪙 Donate to Majo.exe"); const action = new ActionRowBuilder() // prettier .addComponents( client.config.donate.links.map((link: DonateLink) => { return new ButtonBuilder().setLabel(link.name).setStyle(ButtonStyle.Link).setURL(link.url).setEmoji(link.icon); }) ); return interaction.followUp({ embeds: [embed], components: [action] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/General/help.ts ================================================ import { formatDuration } from "@majoexe/util/functions/util"; import { ApplicationCommandType, ApplicationCommandOptionType, EmbedBuilder, codeBlock, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, ComponentType, ApplicationIntegrationType, InteractionContextType, } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; interface Category { name: string; emoji: string; } export default { name: "help", description: "❔ Display a list of all available commands", type: ApplicationCommandType.ChatInput, cooldown: 5000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/help [command]", options: [ { name: "query", description: "The full name of command or category", autocomplete: true, type: ApplicationCommandOptionType.String, max_length: 256, required: false, }, ], autocomplete: async (client, interaction) => { const focusedOption = interaction.options.getFocused(true); if (focusedOption.name === "query") { const commands = focusedOption.value // prettier ? Array.from(client.slashCommands.filter((cmd) => cmd.name.toLowerCase().includes(focusedOption.value.toLowerCase())).values()) : Array.from(client.slashCommands.values()); await interaction.respond( // prettier commands.slice(0, 25).map((choice) => ({ name: `/${choice.name} - ${choice.description}`, value: choice.name, })) ); } }, run: async (client, interaction, guildSettings) => { try { if (!client.user) return client.errorMessages.createSlashError(interaction, "❌ Bot is not ready yet. Please try again later."); const globalActionRow: ActionRowBuilder[] = []; const inviteLink = `https://discord.com/oauth2/authorize/?permissions=${client.config.permissions}&scope=${client.config.scopes}&client_id=${client.user.id}`; if (client.config.url) { const buttonRow = new ActionRowBuilder() // prettier .addComponents( new ButtonBuilder() // prettier .setStyle(ButtonStyle.Link) .setLabel("Dashboard") .setURL(client.config.url), new ButtonBuilder() // prettier .setStyle(ButtonStyle.Link) .setLabel("Invite") .setURL(inviteLink), new ButtonBuilder() // prettier .setStyle(ButtonStyle.Link) .setLabel("All Commands") .setURL(`${client.config.url}/commands`) ); globalActionRow.push(buttonRow); } const query = interaction.options.getString("query") || ""; const isCategory = client.slashCommands.map((cmd) => cmd.category?.toLowerCase()).includes(query?.toLowerCase()); // #region Command if (query && !isCategory) { // If the query is a command, display the command's help menu. const command = client.slashCommands.get(query.toLowerCase()); if (!command) { return client.errorMessages.createSlashError(interaction, `❌ The command \`${query}\` does not exist. Please check your spelling and try again.`); } const embed = new EmbedBuilder() .setTitle(`❔ Help for /${command.name}`) .addFields([ { name: "Name", value: codeBlock(command.name), inline: true, }, { name: "Usage", value: codeBlock(command.usage), inline: true, }, { name: "Description", value: codeBlock(command.description), }, { name: "Cooldown", value: codeBlock(formatDuration(command.cooldown || 0)), inline: true, }, { name: "Category", value: codeBlock(command.category || "General"), inline: true, }, ]) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }); return interaction.followUp({ embeds: [embed], components: globalActionRow }); // #endregion // #region Category } else if (query && isCategory) { // If the query is a category, display all commands in that category. const commands = client.slashCommands.filter((cmd) => cmd.category?.toLowerCase() === query.toLowerCase()); const embed = new EmbedBuilder() .setTitle(`${client.config.emojis.categories.find((cat: Category) => cat.name === query.toLowerCase()).emoji} Available \`${query}\` commands \`(${commands.size})\``) .setDescription(`> ${commands.map((cmd) => `\`/${cmd.name}\``).join(", ")}`) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }); return interaction.followUp({ embeds: [embed], components: globalActionRow }); // #endregion // #region Main menu } else { // If there is no query, display the main help menu. const categories = [...new Set(client.slashCommands.map((cmd) => cmd.category))]; const embed = new EmbedBuilder() .setTitle("❔ Help") .setDescription(`> Use the menu, or use [\`/help [category]\`](${inviteLink}) to view commands based on their category!`) .addFields( categories .map((category) => ({ name: `${client.config.emojis.categories.find((cat: Category) => cat.name === category?.toLowerCase()).emoji} ${category}`, value: codeBlock(`/help ${category?.toLowerCase()}`), inline: `/help ${category?.toLowerCase()}`.length < 15, })) .sort((a, b) => (a.inline === b.inline ? a.name.length - b.name.length : a.inline ? -1 : 1)) ) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setThumbnail(client.user.displayAvatarURL({ size: 256 })) .setAuthor({ name: `${client.user.username} Help`, iconURL: client.user.displayAvatarURL({ size: 256 }), }) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }); const selectRow = new ActionRowBuilder() // prettier .addComponents( new StringSelectMenuBuilder() // prettier .setCustomId("help_select") .setPlaceholder("Select a category") .addOptions( categories.map((category) => ({ label: `${client.config.emojis.categories.find((cat: Category) => cat.name === category?.toLowerCase()).emoji} ${category}`, description: `View all ${client.slashCommands.filter((cmd) => cmd.category?.toLowerCase() === category?.toLowerCase()).size} commands`, value: category?.toLowerCase() ?? "", })) ) ); const actionRow = [selectRow, ...globalActionRow]; const response = await interaction.followUp({ embeds: [embed], components: actionRow }); const collector = response.createMessageComponentCollector({ componentType: ComponentType.StringSelect, filter: (i) => i.user.id === interaction.user.id, time: 3 * 60 * 1000, // 30 seconds }); collector.on("collect", async (i) => { /* eslint-disable-next-line prefer-destructuring */ const category = i.values[0]; const commands = client.slashCommands.filter((cmd) => cmd.category?.toLowerCase() === (category ?? "").toLowerCase()); const embed = new EmbedBuilder() .setTitle( `${client.config.emojis.categories.find((cat: Category) => cat.name === (category ?? "").toLowerCase()).emoji} Available \`${category ?? ""}\` commands \`(${commands.size})\`` ) .setDescription(`> ${commands.map((cmd) => `\`/${cmd.name}\``).join(", ")}`) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }); try { await i.update({ embeds: [embed], components: actionRow }); } catch (_err) { return; } }); collector.on("end", async () => { try { await interaction.editReply({ embeds: [embed], components: globalActionRow }); } catch (_err) { return; } }); return; } // #endregion } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/General/invite.ts ================================================ import { ApplicationCommandType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "invite", description: "🎉 Invite Majo.exe to your server!", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/invite", run: async (client, interaction, guildSettings) => { try { if (!client.user) return client.errorMessages.createSlashError(interaction, "❌ Bot is not ready yet. Please try again later."); const inviteLink = `https://discord.com/oauth2/authorize/?permissions=${client.config.permissions}&scope=${client.config.scopes}&client_id=${client.user.id}`; const inviteLinkRoot = `https://discord.com/oauth2/authorize/?permissions=8&scope=${client.config.scopes}&client_id=${client.user.id}`; const embed = new EmbedBuilder() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setTitle(`🎉 Invite ${client.user.username} to your server!`) .setDescription( `> **[Click this link to invite me!](${inviteLink})** **__[Recomended!]__**\n\n *Or [click this link to invite me as administrator](${inviteLinkRoot}) [Not recomended!]*` ) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }); const row = new ActionRowBuilder() // Prettier .addComponents( new ButtonBuilder() // Prettier .setURL(inviteLink) .setLabel("Invite me!") .setStyle(ButtonStyle.Link) ); return interaction.followUp({ embeds: [embed], components: [row] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/General/opensource.ts ================================================ import { ApplicationCommandType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, time, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import fetch from "node-fetch"; import type { SlashCommand } from "@/util/types/Command"; interface GithubResponse { sha: string; html_url: string; commit: { committer: { date: string; }; }; } export default { name: "opensource", description: "📚 Check out Majo.exe source code", type: ApplicationCommandType.ChatInput, cooldown: 3000, usage: "/opensource", contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], run: async (client, interaction, guildSettings) => { try { if (!client.user) return client.errorMessages.createSlashError(interaction, "❌ Bot is not ready yet. Please try again later."); const request = await fetch("https://api.github.com/repos/igorkowalczyk/majo.exe/commits?per_page=1"); if (!request.ok) return client.errorMessages.createSlashError(interaction, "❌ No results found. Please try again later."); const response = (await request.json()) as GithubResponse[]; if (!response[0]) return client.errorMessages.createSlashError(interaction, "❌ No results found. Please try again later."); const lastTimestamp = Math.floor(new Date(response[0].commit.committer.date).getTime() / 1000); const embed = new EmbedBuilder() // Prettier .setTitle(`🐙 ${client.user.username} Github Repository`) .setDescription("**This project is open source: [@igorkowalczyk/majo.exe](https://github.com/igorkowalczyk/majo.exe)**") .addFields([ { name: `📚 Latest commit ${time(lastTimestamp)} (${time(lastTimestamp, "R")})`, value: `🖇️ SHA: [\`${response[0].sha}\`](${response[0].html_url})`, }, ]) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp(); const row = new ActionRowBuilder() .addComponents( new ButtonBuilder() // Prettier .setURL("https://github.com/igorkowalczyk/majo.exe") .setLabel("Source code") .setStyle(ButtonStyle.Link) ) .addComponents( new ButtonBuilder() // Prettier .setURL(response[0].html_url) .setLabel("Latest commit") .setStyle(ButtonStyle.Link) ); return interaction.followUp({ embeds: [embed], components: [row] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/General/permissions.ts ================================================ import { ApplicationCommandType, ApplicationIntegrationType, EmbedBuilder, InteractionContextType, PermissionsBitField, codeBlock } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; function convertCamelCaseToWords(text: string) { return text.replace(/([A-Z])/g, " $1").replace(/^./, (str) => { return str.toUpperCase().trim(); }); } export default { name: "permissions", description: "🎛️ Check Majo.exe's permissions in your server", type: ApplicationCommandType.ChatInput, cooldown: 5000, contexts: [InteractionContextType.Guild], integrationTypes: [ApplicationIntegrationType.GuildInstall], usage: "/permissions", run: async (client, interaction, guildSettings) => { try { if (!client.user) return client.errorMessages.createSlashError(interaction, "❌ Bot is not ready yet. Please try again later."); if (!interaction.guild) return client.errorMessages.createSlashError(interaction, "❌ This command can only be used in a server."); if (!guildSettings) return client.errorMessages.createSlashError(interaction, "❌ Guild settings not found. Please try again later."); const clientMember = interaction.guild.members.cache.get(client.user.id); if (!clientMember) return client.errorMessages.createSlashError(interaction, "❌ Bot is not in the server."); const requiredPermissions = new PermissionsBitField(client.config.permissions); const permissionsText = requiredPermissions.toArray().map((permission) => { const hasPermission = clientMember.permissions.has(permission); const permissionName = convertCamelCaseToWords(permission.replace(/_/g, " ")); return `${hasPermission ? "✅" : "❌"} ${permissionName}`; }); const embed = new EmbedBuilder() .setColor(guildSettings.embedColor || client.config.defaultColor) .setTimestamp() .setTitle(`🎛️ Permissions in ${interaction.guild.name}`) .setDescription(`> To work properly, ${client.user} needs **all** of the following permissions:\n${codeBlock(permissionsText.join("\n"))}`) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }); return interaction.followUp({ embeds: [embed] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/General/ping.ts ================================================ import prismaClient from "@majoexe/database"; import { ApplicationCommandType, EmbedBuilder, codeBlock, Status, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "ping", description: "🏓 Check the Majo.exe ping", type: ApplicationCommandType.ChatInput, cooldown: 3000, usage: "/ping", contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], run: async (client, interaction, guildSettings) => { try { const dbTime = performance.now(); await prismaClient.user.findUnique({ where: { id: "1" } }); const dbTiming = performance.now() - dbTime; const waitEmbed = new EmbedBuilder() // prettier .setColor(guildSettings?.embedColor || client.config.defaultColor) .setDescription("🏓 Pong!..."); const message = await interaction.followUp({ embeds: [waitEmbed] }); const pingMessage = new EmbedBuilder() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setTitle("🏓 Pong!") .addFields([ { name: "Host Latency", value: codeBlock("yaml", client.ws.ping > 0 ? `${Math.floor(client.ws.ping)}ms` : "Calculating..."), inline: true, }, { name: "Client Latency", value: codeBlock("yaml", `${Math.floor(message.createdTimestamp - interaction.createdTimestamp)}ms`), inline: true, }, { name: "Database Latency", value: codeBlock("yaml", `${Math.floor(dbTiming)}ms`), inline: true, }, ]) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }); if (interaction.guild) { const thisServerShard = client.ws.shards.get(interaction.guild.shardId); if (!thisServerShard) return interaction.editReply({ embeds: [pingMessage] }); pingMessage.addFields([ { name: "Websocket", value: codeBlock("yaml", `${Status[thisServerShard.status]}`), inline: true, }, { name: "Shard", value: codeBlock("yaml", `${thisServerShard.id}/${client.ws.shards.size} (${thisServerShard.ping > 0 ? `${Math.floor(thisServerShard.ping)}ms` : "Calculating..."})`), inline: true, }, ]); } await interaction.editReply({ embeds: [pingMessage] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/General/servers.ts ================================================ import { formatNumber } from "@majoexe/util/functions/util"; import { ApplicationCommandType, EmbedBuilder, ButtonBuilder, ActionRowBuilder, ButtonStyle, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "servers", description: "🧭 Display the number of servers the Majo.exe is in", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/servers", run: async (client, interaction, guildSettings) => { try { if (!client.user) return client.errorMessages.createSlashError(interaction, "❌ Bot is not ready yet. Please try again later."); const allGuilds = client.guilds.cache; const allChannels = client.channels.cache; const allUsers = client.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0); const inviteLink = `https://discord.com/oauth2/authorize/?permissions=${client.config.permissions}&scope=${client.config.scopes}&client_id=${client.user.id}`; const embed = new EmbedBuilder() // Prettier .setTitle(`🧭 ${client.user.username} is in ${allGuilds.size} servers!`) .setDescription( `**...thats a lot of servers!** To be exact, <@${client.user.id}> is serving commands to \`${formatNumber(allUsers) || "0"}\` users in \`${formatNumber(allChannels.size)}\` channels across \`${formatNumber(allGuilds.size)}\` servers!\n\n**If you want to invite Majo.exe to your server, you can do so by clicking [here](${inviteLink}).**` ) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }) .setColor(guildSettings?.embedColor || client.config.defaultColor) .setTimestamp() .setThumbnail(client.user.displayAvatarURL({ size: 256 })); const inviteButton = new ButtonBuilder() // prettier .setLabel("Invite") .setStyle(ButtonStyle.Link) .setURL(inviteLink); if (client.config.url) { const contactButton = new ButtonBuilder() // prettier .setLabel("Dashboard") .setStyle(ButtonStyle.Link) .setURL(client.config.url); const action = new ActionRowBuilder() // prettier .addComponents( // prettier inviteButton, contactButton ); return interaction.followUp({ embeds: [embed], components: [action] }); } const action = new ActionRowBuilder() // prettier .addComponents( // prettier inviteButton ); return interaction.followUp({ embeds: [embed], components: [action] }); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/General/suggestion.ts ================================================ import { ApplicationCommandType, ActionRowBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "suggestion", description: "📝 Send a suggestion to the Majo.exe team", type: ApplicationCommandType.ChatInput, cooldown: 10000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], defer: false, usage: "/suggestion", run: async (client, interaction) => { try { const modal = new ModalBuilder() // prettier .setCustomId("suggestion") .setTitle("📝 Suggestion"); const action = new ActionRowBuilder() // prettier .addComponents( // prettier new TextInputBuilder() // prettier .setCustomId("suggestion") .setPlaceholder("Enter your suggestion here...") .setMinLength(5) .setMaxLength(500) .setRequired(true) .setStyle(TextInputStyle.Paragraph) .setLabel("Suggestion") ); modal.addComponents(action); await interaction.showModal(modal); } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/General/uptime.ts ================================================ import { EmbedBuilder, time, ButtonBuilder, ActionRowBuilder, ApplicationCommandType, ButtonStyle, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "uptime", description: "⌛ View Majo.exe bot uptime and past status", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel], integrationTypes: [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall], usage: "/uptime", run: async (client, interaction, guildSettings) => { try { if (!client.user) return client.errorMessages.createSlashError(interaction, "❌ Bot is not ready yet. Please try again later."); const embed = new EmbedBuilder() .setTitle("📈 Majo.exe uptime") .setDescription( `**🚀 Date launched**: ${client.readyAt ? time(client.readyAt) : "Unknown"} **⏱️ Started:** ${client.readyAt ? time(client.readyAt, "R") : "Unknown"} **✨ Did you know?** From the time Majo.exe was launched it served \`${client.commandsRan}\` commands! ` ) .setTimestamp() .setColor(guildSettings?.embedColor || client.config.defaultColor) .setFooter({ text: `Requested by ${interaction.user.globalName || interaction.user.username}`, iconURL: interaction.user.displayAvatarURL({ size: 256, }), }); if (client.config.url) { const action = new ActionRowBuilder() // prettier .addComponents( new ButtonBuilder() // prettier .setLabel("Status page") .setStyle(ButtonStyle.Link) .setURL(`${client.config.url}/status`) ); return interaction.followUp({ embeds: [embed], components: [action] }); } else { return interaction.followUp({ embeds: [embed] }); } } catch (err) { client.errorMessages.internalError(interaction, err); } }, } satisfies SlashCommand; ================================================ FILE: apps/bot/commands/Giveaway/giveaway.ts ================================================ import { ApplicationCommandType, ApplicationCommandOptionType, ChannelType, PermissionFlagsBits, ApplicationIntegrationType, InteractionContextType } from "discord.js"; import { EndGiveaway } from "@/util/giveaway/endGiveaway"; import { FindGiveaways } from "@/util/giveaway/findGiveaways"; import { PauseGiveaway } from "@/util/giveaway/pauseGiveaway"; import { RerollGiveaway } from "@/util/giveaway/rerollGiveaway"; import { ResumeGiveaway } from "@/util/giveaway/resumeGiveaway"; import { StartDropGiveaway, StartGiveaway } from "@/util/giveaway/startGiveaway"; import type { SlashCommand } from "@/util/types/Command"; export default { name: "giveaway", description: "🎉 Manage giveaway's", type: ApplicationCommandType.ChatInput, cooldown: 3000, contexts: [InteractionContextType.Guild], integrationTypes: [ApplicationIntegrationType.GuildInstall], defaultMemberPermissions: [PermissionFlagsBits.ManageGuild], defer: false, usage: "/giveaway ", options: [ { name: "start", description: "🎉 Start giveaway", type: ApplicationCommandOptionType.Subcommand, //defaultMemberPermissions: [PermissionFlagsBits.ManageGuild], usage: "/giveaway start