diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index d1f0d0851..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "daily" diff --git a/.github/workflows/biome.yml b/.github/workflows/biome.yml deleted file mode 100644 index c9d03920e..000000000 --- a/.github/workflows/biome.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Biome - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - schedule: - - cron: '36 1 * * 6' - -jobs: - biome: - name: Run biome checking - runs-on: ubuntu-latest - permissions: - contents: read - security-events: write - actions: read - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Biome - run: npm install -g @biomejs/biome - - - name: Run Biome Check - run: biome check --write src/ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 04dd677b7..f8d5898d4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -17,16 +17,14 @@ on: pull_request: # The branches below must be a subset of the branches above branches: ['main'] - schedule: - - cron: '18 20 * * 3' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: - actions: read - contents: read + actions: write + contents: write security-events: write strategy: diff --git a/locales/EnglishUS.json b/locales/EnglishUS.json index 41adc0dc3..7f11d7ac3 100644 --- a/locales/EnglishUS.json +++ b/locales/EnglishUS.json @@ -239,8 +239,8 @@ } }, "grab": { - "loading": "Loading...", "description": "Grabs the current playing song on your DM", + "loading": "Loading...", "content": "**Duration:** {length}\n**Requested by:** <@{requester}>\n**Link:** [Click here]({uri})", "check_dm": "Please check your DM.", "dm_failed": "I couldn't send you a DM. Please make sure allow direct messages is turned on." diff --git a/locales/Korean.json b/locales/Korean.json index fcf424ff3..b223b4bf3 100644 --- a/locales/Korean.json +++ b/locales/Korean.json @@ -40,7 +40,7 @@ }, "botinfo": { "description": "봇에 대한 정보를 표시해요", - "content": "봇 정보:\n- **운영체제**: {osInfo}\n- **가동시간**: {osUptime}\n- **컴퓨터 이름**: {osHostname}\n- **CPU 및 아키텍처**: {cpuInfo}\n- **CPU 사용량**: {cpuUsed}%\n- **메모리 사용량**: {memUsed}MB / {memTotal}GB\n- **Node 버전**: {nodeVersion}\n- **Discord 버전**: {discordJsVersion}\n- {guilds} 서버, {channels} 채널, {users} 유저 캐시됨\n- **명령어 수**: {commands}개" + "content": "봇 정보:\n- **운영 체제**: {osInfo}\n- **업타임**: {osUptime}\n- **호스트 이름**: {osHostname}\n- **CPU 아키텍처**: {cpuInfo}\n- **CPU 사용량**: {cpuUsed}%\n- **메모리 사용량**: {memUsed}MB / {memTotal}GB\n- **Node 버전**: {nodeVersion}\n- **Discord 버전**: {discordJsVersion}\n- 서버 {guilds}개, 채널 {channels}개, 멤버 {users}명\n- **명령어 수**: {commands}개" }, "about": { "description": "봇에 대해 알려줘요", @@ -582,10 +582,10 @@ "missing_arguments_description": "`{command}` 명령어에 필요한 인수를 지정해주세요.\n\n예시:\n{examples}", "syntax_footer": "구문: [] = 선택, <> = 필수", "cooldown": "`{command}` 명령어를 사용하려면 {time}초동안 기다려야 해요.", - "no_mention_everyone": "이 명령어는 everyone나 here 멘션으로 사용할 수 없어요. 빗금 명령으로 사용해주세요.", + "no_mention_everyone": "이 명령어는 everyone나 here 멘션으로 사용할 수 없어요. 빗금 명령어로 사용해주세요.", "error": "오류: `{error}`", "no_voice_channel_queue": "대기열에 노래를 추가하려면 음성 채널에 접속해주세요.", - "no_permission_connect_speak": "<#{channel}>에서 연결/말하기 권한이 봇에게 없어요.", + "no_permission_connect_speak": "<#{channel}>에서 연결/말하기 권한이 없어요.", "different_voice_channel_queue": "노래를 대기열에 추가하려면 <#{channel}> 채널에 접속해주세요." }, "setupButton": { diff --git a/src/commands/dev/CreateInvite.ts b/src/commands/dev/CreateInvite.ts new file mode 100644 index 000000000..7d365f791 --- /dev/null +++ b/src/commands/dev/CreateInvite.ts @@ -0,0 +1,71 @@ +import { ChannelType } from "discord.js"; +import { Command, type Context, type Lavamusic } from "../../structures/index.js"; + +export default class CreateInvite extends Command { + constructor(client: Lavamusic) { + super(client, { + name: "createinvite", + description: { + content: "Create a one-time use, unlimited duration invite link for a guild", + examples: ["createinvite 0000000000000000000"], + usage: "createinvite ", + }, + category: "dev", + aliases: ["ci"], + cooldown: 3, + args: true, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: true, + client: ["SendMessages", "CreateInstantInvite", "ReadMessageHistory", "ViewChannel"], + user: [], + }, + slashCommand: false, + options: [], + }); + } + + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const guildId = args[0]; + + const guild = client.guilds.cache.get(guildId); + + if (!guild) { + return await ctx.sendMessage("Guild not found."); + } + + try { + const textChannel = guild.channels.cache.find((channel) => channel.type === ChannelType.GuildText); + + if (!textChannel) { + return await ctx.sendMessage("No text channel found in the guild."); + } + + const invite = await textChannel.createInvite({ + maxUses: 1, + maxAge: 0, + }); + + await ctx.author.send(`Guild: ${guild.name}\nInvite Link: ${invite.url}`); + await ctx.sendMessage("Invite link has been sent to your DM."); + } catch (_error) { + await ctx.sendMessage("Failed to create invite link."); + } + } +} + +/** + * Project: lavamusic + * Author: Appu + * Main Contributor: LucasB25 + * Company: Coders + * Copyright (c) 2024. All rights reserved. + * This code is the property of Coder and may not be reproduced or + * modified without permission. For more information, contact us at + * https://discord.gg/ns8CTk9J3e + */ diff --git a/src/commands/dev/DeleteInvites.ts b/src/commands/dev/DeleteInvites.ts new file mode 100644 index 000000000..9408b9cdf --- /dev/null +++ b/src/commands/dev/DeleteInvites.ts @@ -0,0 +1,65 @@ +import { Command, type Context, type Lavamusic } from "../../structures/index.js"; + +export default class DestroyInvites extends Command { + constructor(client: Lavamusic) { + super(client, { + name: "destroyinvites", + description: { + content: "Destroy all invite links created by the bot in a guild", + examples: ["destroyinvites 0000000000000000000"], + usage: "destroyinvites ", + }, + category: "dev", + aliases: ["di"], + cooldown: 3, + args: true, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: true, + client: ["SendMessages", "ManageGuild", "ReadMessageHistory", "ViewChannel"], + user: [], + }, + slashCommand: false, + options: [], + }); + } + + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const guildId = args[0]; + + const guild = client.guilds.cache.get(guildId); + + if (!guild) { + return await ctx.sendMessage("Guild not found."); + } + + try { + const invites = await guild.invites.fetch(); + + const botInvites = invites.filter((invite) => invite.inviter?.id === client.user?.id); + for (const invite of botInvites.values()) { + await invite.delete(); + } + + await ctx.sendMessage(`Destroyed ${botInvites.size} invite(s) created by the bot.`); + } catch (_error) { + await ctx.sendMessage("Failed to destroy invites."); + } + } +} + +/** + * Project: lavamusic + * Author: Appu + * Main Contributor: LucasB25 + * Company: Coders + * Copyright (c) 2024. All rights reserved. + * This code is the property of Coder and may not be reproduced or + * modified without permission. For more information, contact us at + * https://discord.gg/ns8CTk9J3e + */ diff --git a/src/events/client/MessageCreate.ts b/src/events/client/MessageCreate.ts index 3bafa99e8..3306e87ec 100644 --- a/src/events/client/MessageCreate.ts +++ b/src/events/client/MessageCreate.ts @@ -52,195 +52,196 @@ export default class MessageCreate extends Event { const ctx = new Context(message, args); ctx.setArgs(args); ctx.guildLocale = locale; - if (!message.guild.members.resolve(this.client.user)?.permissions.has(PermissionFlagsBits.ViewChannel)) return; - const clientMember = message.guild.members.resolve(this.client.user); - if (!clientMember.permissions.has(PermissionFlagsBits.SendMessages)) { - await message.author - .send({ - content: T(locale, "event.message.no_send_message", { - guild: message.guild.name, - channel: `<#${message.channelId}>`, - }), - }) - .catch(() => {}); - return; - } + const isDev = this.client.config.owners?.includes(message.author.id); - if (!clientMember.permissions.has(PermissionFlagsBits.EmbedLinks)) { - await message.reply({ - content: T(locale, "event.message.no_embed_links"), - }); - return; - } + if (!isDev) { + if (!message.guild.members.resolve(this.client.user)?.permissions.has(PermissionFlagsBits.ViewChannel)) return; - const logs = this.client.channels.cache.get(this.client.config.commandLogs); - - if (command.permissions) { - if (command.permissions.client && !clientMember.permissions.has(command.permissions.client)) { - await message.reply({ - content: T(locale, "event.message.no_permission"), - }); + const clientMember = message.guild.members.resolve(this.client.user); + if (!clientMember.permissions.has(PermissionFlagsBits.SendMessages)) { + await message.author + .send({ + content: T(locale, "event.message.no_send_message", { + guild: message.guild.name, + channel: `<#${message.channelId}>`, + }), + }) + .catch(() => {}); return; } - if (command.permissions.user && !message.member.permissions.has(command.permissions.user)) { + if (!clientMember.permissions.has(PermissionFlagsBits.EmbedLinks)) { await message.reply({ - content: T(locale, "event.message.no_user_permission"), + content: T(locale, "event.message.no_embed_links"), }); return; } - if (command.permissions.dev && this.client.config.owners) { - const isDev = this.client.config.owners.includes(message.author.id); - if (!isDev) return; - } - } - if (command.vote && this.client.config.topGG) { - const voted = await this.client.topGG.hasVoted(message.author.id); - if (!voted) { - const voteBtn = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel("Vote for Me!") - .setURL(`https://top.gg/bot/${this.client.config.clientId}/vote`) - .setStyle(ButtonStyle.Link), - ); - - await message.reply({ - content: "Wait! Before using this command, you must vote on top.gg. Thank you.", - components: [voteBtn], - }); - } - } - if (command.player) { - if (command.player.voice) { - if (!message.member.voice.channel) { + if (command.permissions) { + if (command.permissions.client && !clientMember.permissions.has(command.permissions.client)) { await message.reply({ - content: T(locale, "event.message.no_voice_channel", { - command: command.name, - }), + content: T(locale, "event.message.no_permission"), }); return; } - if (!clientMember.permissions.has(PermissionFlagsBits.Connect)) { + if (command.permissions.user && !message.member.permissions.has(command.permissions.user)) { await message.reply({ - content: T(locale, "event.message.no_connect_permission", { command: command.name }), - }); - return; - } - - if (!clientMember.permissions.has(PermissionFlagsBits.Speak)) { - await message.reply({ - content: T(locale, "event.message.no_speak_permission", { command: command.name }), + content: T(locale, "event.message.no_user_permission"), }); return; } + } - if ( - message.member.voice.channel.type === ChannelType.GuildStageVoice && - !clientMember.permissions.has(PermissionFlagsBits.RequestToSpeak) - ) { - await message.reply({ - content: T(locale, "event.message.no_request_to_speak", { command: command.name }), - }); - return; - } + if (command.vote && this.client.config.topGG) { + const voted = await this.client.topGG.hasVoted(message.author.id); + if (!voted) { + const voteBtn = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("Vote for Me!") + .setURL(`https://top.gg/bot/${this.client.config.clientId}/vote`) + .setStyle(ButtonStyle.Link), + ); - if (clientMember.voice.channel && clientMember.voice.channelId !== message.member.voice.channelId) { await message.reply({ - content: T(locale, "event.message.different_voice_channel", { - channel: `<#${clientMember.voice.channelId}>`, - command: command.name, - }), + content: "Wait! Before using this command, you must vote on top.gg. Thank you.", + components: [voteBtn], }); return; } } - if (command.player.active) { - const queue = this.client.queue.get(message.guildId); - if (!queue?.queue && queue.current) { - await message.reply({ - content: T(locale, "event.message.no_music_playing"), - }); - return; + if (command.player) { + if (command.player.voice) { + if (!message.member.voice.channel) { + await message.reply({ + content: T(locale, "event.message.no_voice_channel", { + command: command.name, + }), + }); + return; + } + + if (!clientMember.permissions.has(PermissionFlagsBits.Connect)) { + await message.reply({ + content: T(locale, "event.message.no_connect_permission", { command: command.name }), + }); + return; + } + + if (!clientMember.permissions.has(PermissionFlagsBits.Speak)) { + await message.reply({ + content: T(locale, "event.message.no_speak_permission", { command: command.name }), + }); + return; + } + + if ( + message.member.voice.channel.type === ChannelType.GuildStageVoice && + !clientMember.permissions.has(PermissionFlagsBits.RequestToSpeak) + ) { + await message.reply({ + content: T(locale, "event.message.no_request_to_speak", { command: command.name }), + }); + return; + } + + if (clientMember.voice.channel && clientMember.voice.channelId !== message.member.voice.channelId) { + await message.reply({ + content: T(locale, "event.message.different_voice_channel", { + channel: `<#${clientMember.voice.channelId}>`, + command: command.name, + }), + }); + return; + } } - } - if (command.player.dj) { - const dj = await this.client.db.getDj(message.guildId); - if (dj?.mode) { - const djRole = await this.client.db.getRoles(message.guildId); - if (!djRole) { + if (command.player.active) { + const queue = this.client.queue.get(message.guildId); + if (!queue?.queue && queue.current) { await message.reply({ - content: T(locale, "event.message.no_dj_role"), + content: T(locale, "event.message.no_music_playing"), }); return; } - const findDJRole = message.member.roles.cache.find((x: any) => djRole.map((y: any) => y.roleId).includes(x.id)); - if (!findDJRole) { - if (!message.member.permissions.has(PermissionFlagsBits.ManageGuild)) { - await message - .reply({ - content: T(locale, "event.message.no_dj_permission"), - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); + } + + if (command.player.dj) { + const dj = await this.client.db.getDj(message.guildId); + if (dj?.mode) { + const djRole = await this.client.db.getRoles(message.guildId); + if (!djRole) { + await message.reply({ + content: T(locale, "event.message.no_dj_role"), + }); return; } + const findDJRole = message.member.roles.cache.find((x: any) => djRole.map((y: any) => y.roleId).includes(x.id)); + if (!findDJRole) { + if (!message.member.permissions.has(PermissionFlagsBits.ManageGuild)) { + await message + .reply({ + content: T(locale, "event.message.no_dj_permission"), + }) + .then((msg) => setTimeout(() => msg.delete(), 5000)); + return; + } + } } } } - } - if (command.args && !args.length) { - const embed = this.client - .embed() - .setColor(this.client.color.red) - .setTitle(T(locale, "event.message.missing_arguments")) - .setDescription( - T(locale, "event.message.missing_arguments_description", { - command: command.name, - examples: command.description.examples ? command.description.examples.join("\n") : "None", - }), - ) - .setFooter({ text: T(locale, "event.message.syntax_footer") }); - await message.reply({ embeds: [embed] }); - return; - } + if (command.args && !args.length) { + const embed = this.client + .embed() + .setColor(this.client.color.red) + .setTitle(T(locale, "event.message.missing_arguments")) + .setDescription( + T(locale, "event.message.missing_arguments_description", { + command: command.name, + examples: command.description.examples ? command.description.examples.join("\n") : "None", + }), + ) + .setFooter({ text: T(locale, "event.message.syntax_footer") }); + await message.reply({ embeds: [embed] }); + return; + } - if (!this.client.cooldown.has(cmd)) { - this.client.cooldown.set(cmd, new Collection()); - } + if (!this.client.cooldown.has(cmd)) { + this.client.cooldown.set(cmd, new Collection()); + } - const now = Date.now(); - const timestamps = this.client.cooldown.get(cmd)!; - const cooldownAmount = (command.cooldown || 5) * 1000; + const now = Date.now(); + const timestamps = this.client.cooldown.get(cmd)!; + const cooldownAmount = (command.cooldown || 5) * 1000; - if (timestamps.has(message.author.id)) { - const expirationTime = timestamps.get(message.author.id)! + cooldownAmount; - const timeLeft = (expirationTime - now) / 1000; - if (now < expirationTime && timeLeft > 0.9) { + if (timestamps.has(message.author.id)) { + const expirationTime = timestamps.get(message.author.id)! + cooldownAmount; + const timeLeft = (expirationTime - now) / 1000; + if (now < expirationTime && timeLeft > 0.9) { + await message.reply({ + content: T(locale, "event.message.cooldown", { + time: timeLeft.toFixed(1), + command: cmd, + }), + }); + return; + } + timestamps.set(message.author.id, now); + setTimeout(() => timestamps.delete(message.author.id), cooldownAmount); + } else { + timestamps.set(message.author.id, now); + setTimeout(() => timestamps.delete(message.author.id), cooldownAmount); + } + + if (args.includes("@everyone") || args.includes("@here")) { await message.reply({ - content: T(locale, "event.message.cooldown", { - time: timeLeft.toFixed(1), - command: cmd, - }), + content: T(locale, "event.message.no_mention_everyone"), }); return; } - timestamps.set(message.author.id, now); - setTimeout(() => timestamps.delete(message.author.id), cooldownAmount); - } else { - timestamps.set(message.author.id, now); - setTimeout(() => timestamps.delete(message.author.id), cooldownAmount); - } - - if (args.includes("@everyone") || args.includes("@here")) { - await message.reply({ - content: T(locale, "event.message.no_mention_everyone"), - }); - return; } try { @@ -251,6 +252,7 @@ export default class MessageCreate extends Event { content: T(locale, "event.message.error", { error }), }); } finally { + const logs = this.client.channels.cache.get(this.client.config.commandLogs); if (logs) { const embed = new EmbedBuilder() .setAuthor({