diff --git a/.env.example b/.env.example index 307dd77ef..8e9ab5b7a 100644 --- a/.env.example +++ b/.env.example @@ -1,25 +1,18 @@ -TOKEN= "" # Your bot token. -CLIENT_ID= "" # Your bot's client ID (If this value is left blank, bots cannot be invited using /invite or /about commands.). -DEFAULT_LANGUAGE= "EnglishUS" # Default language for bot -PREFIX= "!" # Your prefix. -OWNER_IDS= ["",""] # Your discord id (You can add multiple ids.). -GUILD_ID= "" # Your server ID (If you want to use the bot for a single server). -TOPGG= "" # Your Top.gg API key. Obtain this from https://top.gg -KEEP_ALIVE= "false" # true for keep alive in https://replit.com -LOG_CHANNEL_ID= "" # If you enter this, you will be able to receive the status of Lavalink nodes and guild join/leave logs through the corresponding channel. -LOG_COMMANDS_ID= "" # The channel ID where command usage logs will be sent. -BOT_STATUS= "online" # Your bot status (online, dnd, idle, invisible or offline). -BOT_ACTIVITY_TYPE= 0 # Activity type is a number from 0 to 5. See more here: https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-types -BOT_ACTIVITY=" Lavamusic" # Your bot activity. -DATABASE_URL= "" # Your database url (If you want to use sqlite, then you can leave it blank.). -AUTO_NODE=" false" # true for auto node. It is given from lavainfo-api (https://lavainfo-api.deno.dev). -SEARCH_ENGINE= "YouTubeMusic" # Search engine to be used when playing the song. You can use: YouTube, YouTubeMusic, SoundCloud, Spotify, Apple, Deezer, Yandex and JioSaavn -MAX_PLAYLIST_SIZE= "100" # Max playlist size. -MAX_QUEUE_SIZE= "100" # Max queue size. -GENIUS_API= "" # Sign up and get your own api at (https://genius.com/) to fetch your lyrics (CLIENT TOKEN) - -# Configuration for multiple Lavalink servers -LAVALINK_SERVERS = '[ - {"url":"localhost:2333","auth":"youshallnotpass","name":"Local Node","secure":false}, - {"url":"localhost:2333","auth":"youshallnotpass2","name":"Another Node","secure":false} -]' +TOKEN="" # Your bot token. +CLIENT_ID="" # Your bot's client ID (If this value is left blank, bots cannot be invited using /invite or /about commands.). +DEFAULT_LANGUAGE="EnglishUS" # Default language for bot +PREFIX="!" # Your prefix. +OWNER_IDS=["",""] # Your discord id (You can add multiple ids.). +GUILD_ID="" # Your server ID (If you want to use the bot for a single server). +TOPGG="" # Your Top.gg API key. Obtain this from https://top.gg +KEEP_ALIVE="false" # true for keep alive in https://replit.com +LOG_CHANNEL_ID="" # If you enter this, you will be able to receive the status of Lavalink nodes and guild join/leave logs through the corresponding channel. +LOG_COMMANDS_ID="" # The channel ID where command usage logs will be sent. +BOT_STATUS="online" # Your bot status (online, dnd, idle, invisible or offline). +BOT_ACTIVITY_TYPE=0 # Activity type is a number from 0 to 5. See more here: https://discord.com/developers/docs/topics/gateway-events#activity-object-activity-types +BOT_ACTIVITY=="Lavamusic" # Your bot activity. +DATABASE_URL="" # Your database url (If you want to use sqlite, then you can leave it blank.). +AUTO_NODE=="false" # true for auto node. It is given from lavainfo-api (https://lavainfo-api.deno.dev). +SEARCH_ENGINE="YouTubeMusic" # Search engine to be used when playing the song. You can use: YouTube, YouTubeMusic, SoundCloud, Spotify, Apple, Deezer, Yandex and JioSaavn +GENIUS_API="" # Sign up and get your own api at (https://genius.com/) to fetch your lyrics (CLIENT TOKEN) +NODES=[{"id":"Local Node","host":"localhost","port":2333,"authorization":"youshallnotpass"}] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 22c14f9f6..fc85263e1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -43,8 +43,7 @@ body: description: "Specify the version of Node.js that you are using." placeholder: "e.g., 14.17.0" validations: - required: false - + required: true - type: input id: java-version attributes: @@ -52,7 +51,7 @@ body: description: "Specify the version of Java that you are using." placeholder: "e.g., 17" validations: - required: false + required: true - type: input id: app-version diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 0076e841f..5777c6042 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -61,4 +61,4 @@ body: description: "Add any other context or screenshots that could help explain your feature request." placeholder: "Additional context, links, or references" validations: - required: false \ No newline at end of file + required: false diff --git a/.gitignore b/.gitignore index 6af8d2784..d9f01b479 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ yarn-debug.log* yarn-error.log* lerna-debug.log* .pnpm-debug.log* - +bun.lockb # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/.hintrc b/.hintrc new file mode 100644 index 000000000..f45382f88 --- /dev/null +++ b/.hintrc @@ -0,0 +1,6 @@ +{ + "extends": ["development"], + "hints": { + "typescript-config/consistent-casing": "off" + } +} diff --git a/Dockerfile b/Dockerfile index 67a223ae8..c99191f3b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,26 +3,16 @@ FROM node:22 AS builder WORKDIR /opt/lavamusic/ -# Copy package files and install dependencies +# Copy only package files and install dependencies COPY package*.json ./ +RUN npm install --legacy-peer-deps -# Install necessary tools and update npm -RUN apt-get update && apt-get install -y openssl git \ - && npm install -g npm@latest -RUN npm install -RUN npm config set global --legacy-peer-deps - -# Copy source code +# Copy source code and configuration COPY . . -# Copy tsconfig.json -COPY tsconfig.json ./ -# Copy prisma -COPY prisma ./prisma -# Generate Prisma client -RUN npx prisma db push -# Build TypeScript -RUN npm run build +# Generate Prisma client and build TypeScript +RUN npx prisma db push && \ + npm run build # Stage 2: Create production image FROM node:22-slim @@ -32,18 +22,20 @@ ENV NODE_ENV=production WORKDIR /opt/lavamusic/ # Install necessary tools -RUN apt-get update && apt-get install -y openssl +RUN apt-get update && apt-get install -y --no-install-recommends openssl && \ + rm -rf /var/lib/apt/lists/* -# Copy compiled code and other necessary files from the builder stage +# Copy compiled code and necessary files from the builder stage COPY --from=builder /opt/lavamusic/dist ./dist -COPY --from=builder /opt/lavamusic/src/utils/LavaLogo.txt ./src/utils/LavaLogo.txt COPY --from=builder /opt/lavamusic/prisma ./prisma COPY --from=builder /opt/lavamusic/scripts ./scripts -COPY --from=builder /opt/lavamusic/package*.json ./ COPY --from=builder /opt/lavamusic/locales ./locales +# Install production dependencies +COPY --from=builder /opt/lavamusic/package*.json ./ RUN npm install --omit=dev +# Generate Prisma client RUN npx prisma generate RUN npx prisma db push @@ -53,12 +45,9 @@ RUN rm -rf /opt/lavamusic/application.yml && \ # Run as non-root user RUN addgroup --gid 322 --system lavamusic && \ - adduser --uid 322 --system lavamusic - -# Change ownership of the folder -RUN chown -R lavamusic:lavamusic /opt/lavamusic/ + adduser --uid 322 --system lavamusic && \ + chown -R lavamusic:lavamusic /opt/lavamusic/ -# Switch to the appropriate user USER lavamusic CMD ["node", "dist/index.js"] diff --git a/Lavalink/example.application.yml b/Lavalink/example.application.yml index d2ae0f771..7b309cbcd 100644 --- a/Lavalink/example.application.yml +++ b/Lavalink/example.application.yml @@ -42,7 +42,7 @@ plugins: spotify: clientId: "your client id" clientSecret: "your client secret" - spDc: "your sp dc cookie" # the sp dc cookie used for accessing the spotify lyrics api + # spDc: "your sp dc cookie" # the sp dc cookie used for accessing the spotify lyrics api countryCode: "US" # the country code you want to use for filtering the artists top tracks. See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 playlistLoadLimit: 6 # The number of pages at 100 tracks each albumLoadLimit: 6 # The number of pages at 50 tracks each @@ -101,15 +101,15 @@ lavalink: - dependency: "com.github.appujet:jiosaavn-plugin:0.1.7" repository: "https://jitpack.io" - dependency: "com.dunctebot:skybot-lavalink-plugin:1.7.0" - snapshot: false # set to true if you want to use snapshot builds. + snapshot: false # set to true if you want to use snapshot builds - dependency: "com.github.topi314.lavasearch:lavasearch-plugin:1.0.0" - snapshot: false # set to true if you want to use snapshot builds. + snapshot: false # set to true if you want to use snapshot builds - dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.2.0" - snapshot: false # set to true if you want to use snapshot builds. + snapshot: false # set to true if you want to use snapshot builds - dependency: "com.github.topi314.sponsorblock:sponsorblock-plugin:3.0.1" - snapshot: false # set to true if you want to use snapshot builds. - - dependency: "dev.lavalink.youtube:youtube-plugin:1.7.2" - snapshot: false # set to true if you want to use snapshot builds. + snapshot: false # set to true if you want to use snapshot builds + - dependency: "dev.lavalink.youtube:youtube-plugin:1.8.0" + snapshot: false # set to true if you want to use snapshot builds pluginsDir: './plugins' server: password: "youshallnotpass" diff --git a/README.md b/README.md index 2a5ed466f..ff1aa3afc 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@

Lavamusic

-

Lavamusic is a Discord music bot that uses Discord.js, Shoukaku, and TypeScript. +

Lavamusic is a Discord music bot that uses Discord.js, lavalink-client, and TypeScript.

Invite Lavamusic @@ -32,7 +32,7 @@ - User-friendly and Easy to Use - Highly Configurable - Customizable Prefix -- Multilingual support +- Multilingual support [Here](/Translation.md) - Hybrid Command Handling (Slash and Normal Commands) - Developed using TypeScript and Discord.js v14 - Advanced Music System @@ -242,4 +242,4 @@ Thanks go to these wonderful people: [license-shield]: https://img.shields.io/github/license/appujet/lavamusic.svg?style=for-the-badge [license-url]: https://github.com/appujet/lavamusic/blob/master/LICENSE [support-server]: https://discord.gg/PMpJnJaHmy -[support-shield]: https://img.shields.io/discord/942117923001098260.svg?style=for-the-badge&logo=discord&colorB=7289DA \ No newline at end of file +[support-shield]: https://img.shields.io/discord/942117923001098260.svg?style=for-the-badge&logo=discord&colorB=7289DA diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 034e84803..000000000 --- a/SECURITY.md +++ /dev/null @@ -1,21 +0,0 @@ -# Security Policy - -## Supported Versions - -Use this section to tell people about which versions of your project are -currently being supported with security updates. - -| Version | Supported | -| ------- | ------------------ | -| 5.1.x | :white_check_mark: | -| 5.0.x | :x: | -| 4.0.x | :white_check_mark: | -| < 4.0 | :x: | - -## Reporting a Vulnerability - -Use this section to tell people how to report a vulnerability. - -Tell them where to go, how often they can expect to get an update on a -reported vulnerability, what to expect if the vulnerability is accepted or -declined, etc. diff --git a/Translation.md b/Translation.md index 6655ed71a..0a2cbdeb3 100644 --- a/Translation.md +++ b/Translation.md @@ -6,7 +6,6 @@ 2. 📋 Copy the contents of the `EnglishUS.json` file into the new file. 3. 🌐 Translate the strings in the new file to the desired language. - ### 📚 Available Translations - [x] English (US) - `EnglishUS.json` (Default) @@ -42,7 +41,6 @@ - [ ] Ukrainian - `Ukrainian.json` (Not Started) - [x] Vietnamese - `Vietnamese.json` [by @nhutlamm](https://github.com/nhutlamm) (Ai Translation - Not Accurate) - ## 📚 How to Use the Translations 1. 📁 Create a new file in the `locales` directory with the name of the language in the format `language_code.json`. For example, `EnglishUS.json` for English, `SpanishES.json` for Spanish, etc. @@ -51,8 +49,8 @@ 3. 🌐 Translate the strings in the new file to the desired language. - ## Have a language to contribute? 🎉 + - Fork the repository. - Add the translation file in the `locales` directory. - Create a pull request with the changes. @@ -65,60 +63,61 @@ - **Do not** add any new keys to the translation JSON file. - **Do not** add any new directories to the repository. - - ## 📝 Translation JSON Structure The translation JSON file should be structured as follows: ```json { - "category": { - "command": { - "description": "Description of the command.", - "content": "Command content.", - "key": "value" - } - } + "category": { + "command": { + "description": "Description of the command.", + "content": "Command content.", + "key": "value" + } + } } ``` ### Example Translation JSON **EnglishUS:** + ```json { - "cmd": { - "ping": { - "description": "Shows the bot's ping.", - "content": "Pinging...", - "bot_latency": "Bot Latency", - "api_latency": "API Latency", - "requested_by": "Requested by {author}" - } - } + "cmd": { + "ping": { + "description": "Shows the bot's ping.", + "content": "Pinging...", + "bot_latency": "Bot Latency", + "api_latency": "API Latency", + "requested_by": "Requested by {author}" + } + } } ``` **Hindi:** + ```json { - "cmd": { - "ping": { - "description": "बॉट का पिंग दिखाता है।", - "content": "पिंगिंग...", - "bot_latency": "पिंगिंग...", - "api_latency": "एपीआई लेटेंसी", - "requested_by": "{author} द्वारा अनुरोधित" - } - } + "cmd": { + "ping": { + "description": "बॉट का पिंग दिखाता है।", + "content": "पिंगिंग...", + "bot_latency": "पिंगिंग...", + "api_latency": "एपीआई लेटेंसी", + "requested_by": "{author} द्वारा अनुरोधित" + } + } } ``` ### Formatting Tags for i18n NPM -To ensure `{}` are not removed during translations, use the format tags: `["{", "}"]`. +To ensure `{}` are not removed during translations, use the format tags: `["{", "}"]`. ## 📚 Resources + - [i18n NPM](https://www.npmjs.com/package/i18n) - [Discord Developer Portal - Locales](https://discord.com/developers/docs/reference#locales) diff --git a/biome.json b/biome.json index 0411b661d..47187d652 100644 --- a/biome.json +++ b/biome.json @@ -1,89 +1,85 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json", - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "all": true, - "suspicious": { - "noConfusingVoidType": "off", - "noConsole": "off", - "noConsoleLog": "off", - "noEmptyBlockStatements": "off", - "noEvolvingTypes": "off", - "noExplicitAny": "off", - "noGlobalIsFinite": "off", - "noGlobalIsNan": "off", - "useAwait": "off" - }, - "style": { - "noDefaultExport": "off", - "noInferrableTypes": "off", - "noNamespaceImport": "off", - "noNonNullAssertion": "off", - "noParameterAssign": "off", - "useBlockStatements": "off", - "useDefaultSwitchClause": "off", - "useFilenamingConvention": "off", - "useNamingConvention": "off", - "useNumberNamespace": "off", - "useSingleCaseStatement": "off" - }, - "complexity": { - "noBannedTypes": "off", - "noForEach": "off", - "noStaticOnlyClass": "off", - "noExcessiveCognitiveComplexity": { - "level": "warn", - "options": { - "maxAllowedComplexity": 255 - } - } - }, - "security": { - "noGlobalEval": "off" - }, - "correctness": { - "noNodejsModules": "off", - "noVoidTypeReturn": "off" - }, - "performance": { - "noBarrelFile": "off", - "useTopLevelRegex": "off" - } - } - }, - "formatter": { - "enabled": true, - "indentWidth": 4, - "indentStyle": "space", - "lineEnding": "crlf", - "lineWidth": 140, - "formatWithErrors": true - }, - "json": { - "linter": { - "enabled": true - }, - "formatter": { - "enabled": true, - "indentWidth": 2, - "lineEnding": "crlf", - "lineWidth": 80 - } - }, - "javascript": { - "formatter": { - "quoteStyle": "double", - "arrowParentheses": "always", - "bracketSameLine": true, - "semicolons": "always" - } - }, - "files": { - "ignoreUnknown": false, - "ignore": [".vscode", "dist", "locales", "node_modules"] - } + "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true, + "defaultBranch": "main" + }, + "files": { + "ignoreUnknown": true, + "ignore": ["node_modules/", "dist", "package.json", "tsconfig.json", ".vscode"] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "indentWidth": 2, + "lineWidth": 120, + "lineEnding": "crlf", + "formatWithErrors": true + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": false, + "all": true, + "security": { + "noGlobalEval": "off" + }, + "suspicious": { + "noExplicitAny": "off", + "noAssignInExpressions": "off", + "useAwait": "off", + "noConfusingVoidType": "off", + "noAsyncPromiseExecutor": "off", + "noUnsafeDeclarationMerging": "off", + "noEmptyInterface": "off", + "noThenProperty": "off" + }, + "correctness": { + "noNodejsModules": "off", + "useImportExtensions": "off", + "noUnusedFunctionParameters": "off", + "noUnusedVariables": "off" + }, + "style": { + "noDefaultExport": "off", + "useBlockStatements": "off", + "noParameterProperties": "off", + "useNamingConvention": "off", + "noNonNullAssertion": "off", + "useForOf": "off", + "useDefaultSwitchClause": "off", + "noParameterAssign": "off", + "useFilenamingConvention": "off", + "useEnumInitializers": "off", + "useExplicitLengthCheck": "off", + "noNamespaceImport": "off", + "noInferrableTypes": "info" + }, + "complexity": { + "noForEach": "off", + "noExcessiveCognitiveComplexity": "off", + "noUselessConstructor": "off", + "noBannedTypes": "off" + }, + "performance": { + "noBarrelFile": "off", + "noDelete": "off", + "noReExportAll": "off", + "useTopLevelRegex": "off" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "always", + "arrowParentheses": "asNeeded", + "bracketSameLine": true + } + } } diff --git a/docker-compose.yml b/docker-compose.yml index 8d42fdbd1..fc21b8cb0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -63,14 +63,8 @@ services: container_name: lavamusic image: ghcr.io/appujet/lavamusic:main environment: - # Your lavalink url - - LAVALINK_URL=lavalink:2333 - # Your lavalink password - - LAVALINK_AUTH=youshallnotpass - # Your lavalink name - - LAVALINK_NAME=LavaMusic - # Your lavalink secure (true or false) - - LAVALINK_SECURE=false + # lavalink nodes + - NODES=[{"id":"LavaMusic","host":"lavalink","port":2333,"authorization":"youshallnotpass"}] # database url # - DATABASE_URL= put your database url here (mongodb or postgres) # - DATABASE_URL=postgresql://lavamusic:lavamusic@postgres:5432/lavamusic (for postgres) diff --git a/locales/ChineseCN.json b/locales/ChineseCN.json index 83ef7e43f..f823bfbc2 100644 --- a/locales/ChineseCN.json +++ b/locales/ChineseCN.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | 低音增强滤镜已`启用`。\n**请注意,音量过大会损害您的听力!**", "filter_disabled": "`✅` | 低音增强滤镜已`禁用`。" + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -494,6 +497,9 @@ "playlist_stolen": "已成功从 {user} 窃取播放列表 `{playlist}`。", "error_occurred": "窃取播放列表时出错。" } + }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track" } }, "buttons": { @@ -613,7 +619,6 @@ "no_previous_track": "没有上一首曲目。", "playing_previous": "正在播放上一首曲目。", "previous_footer": "由 {displayName} 播放上一首曲目", - "rewind_limit": "您不能将音乐倒回到超过歌曲长度的位置。", "rewinded": "已倒回音乐。", "rewind_footer": "由 {displayName} 倒回", "forward_limit": "您不能将音乐快进到超过歌曲长度的位置。", @@ -627,4 +632,4 @@ "Leave a guild": "离开服务器", "List all guilds the bot is in": "列出机器人所在的所有服务器", "Restart the bot": "重启机器人" -} \ No newline at end of file +} diff --git a/locales/ChineseTW.json b/locales/ChineseTW.json index 39e6fb2bb..7b8b9e8d1 100644 --- a/locales/ChineseTW.json +++ b/locales/ChineseTW.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | 低音增強等化器已`啟用`。\n**請注意,音量過大會損害您的聽力!**", "filter_disabled": "`✅` | 低音增強等化器已`停用`。" + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -494,6 +497,9 @@ "playlist_stolen": "已成功從 {user} 竊取播放清單 `{playlist}`。", "error_occurred": "竊取播放清單時出錯。" } + }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track" } }, "buttons": { @@ -613,7 +619,6 @@ "no_previous_track": "沒有上一首曲目。", "playing_previous": "正在播放上一首曲目。", "previous_footer": "由 {displayName} 播放上一首曲目", - "rewind_limit": "您不能將音樂倒帶到超過歌曲長度的位置。", "rewinded": "已倒帶音樂。", "rewind_footer": "由 {displayName} 資訊", "forward_limit": "您不能將音樂快轉到超過歌曲長度的位置。", @@ -627,4 +632,4 @@ "Leave a guild": "離開伺服器", "List all guilds the bot is in": "列出機器人所在的所有伺服器", "Restart the bot": "重啟機器人" -} \ No newline at end of file +} diff --git a/locales/EnglishUS.json b/locales/EnglishUS.json index d671e74eb..d33e950a7 100644 --- a/locales/EnglishUS.json +++ b/locales/EnglishUS.json @@ -33,7 +33,7 @@ "options": { "command": "The command you want to get info on" }, - "content": "Hey there! I'm {bot}, a music bot made with [Lavamusic](https://github.com/appujet/lavamusic) and Discord. You can use `{prefix}help ` to get more info on a command.", + "content": "Hey there! I'm {bot}, a music bot made with [Lavamusic](https://github.com/appujet/lavamusic) and Discord.js. You can use `{prefix}help ` to get more info on a command.", "title": "Help Menu", "not_found": "This `{cmdName}` command does not exist.", "help_cmd": "**Description:** {description}\n**Usage:** {usage}\n**Examples:** {examples}\n**Aliases:** {aliases}\n**Category:** {category}\n**Cooldown:** {cooldown} seconds\n**Permissions:** {premUser}\n**Bot Permissions:** {premBot}\n**Developer Only:** {dev}\n**Slash Command:** {slash}\n**Args:** {args}\n**Player:** {player}\n**DJ:** {dj}\n**DJ Permissions:** {djPerm}\n**Voice:** {voice}", @@ -41,7 +41,7 @@ }, "botinfo": { "description": "Information about the bot", - "content": "Bot Information:\n- **Operating System**: {osInfo}\n- **Uptime**: {osUptime}\n- **Hostname**: {osHostname}\n- **CPU Architecture**: {cpuInfo}\n- **CPU Usage**: {cpuUsed}%\n- **Memory Usage**: {memUsed}MB / {memTotal}GB\n- **Node Version**: {nodeVersion}\n- **Discord Version**: {discordJsVersion}\n- **Connected to** {guilds} guilds, {channels} channels, and {users} users\n- **Total Commands**: {commands}" + "content": "Bot Information:\n- **Operating System**: {osInfo}\n- **Uptime**: {osUptime}\n- **Hostname**: {osHostname}\n- **CPU Architecture**: {cpuInfo}\n- **CPU Usage**: {cpuUsed}%\n- **Memory Usage**: {memUsed}MB / {memTotal}GB\n- **Node Version**: {nodeVersion}\n- **Discord.js Version**: {discordJsVersion}\n- **Connected to** {guilds} guilds, {channels} channels, and {users} users\n- **Total Commands**: {commands}" }, "about": { "description": "Shows information about the bot", @@ -110,7 +110,7 @@ "errors": { "channel_exists": "The song request channel already exists.", "channel_not_exists": "The song request channel doesn't exist.", - "channel_delete_fail": "The song request channel has been deleted. If the channel is not deleted normally, please delete it yourself." + "channel_delete_fail": "The setup channel has been deleted from the database. Please delete the channel yourself." }, "messages": { "channel_created": "The song request channel has been created in <#{channelId}>.", @@ -132,9 +132,14 @@ }, "bassboost": { "description": "on/off bassboost filter", + "options": { + "level": "The bassboost level you want to set" + }, "messages": { - "filter_enabled": "`✅` | Bassboost filter has been `ENABLED`. \n**Be careful, listening too loudly can damage your hearing!**", - "filter_disabled": "`✅` | Bassboost filter has been `DISABLED`." + "high": "`✅` | High bassboost filter has been `ENABLED`.", + "low": "`✅` | Low bassboost filter has been `ENABLED`.", + "medium": "`✅` | Medium bassboost filter has been `ENABLED`.", + "off": "`✅` | Bassboost filter has been `DISABLED`." } }, "distorsion": { @@ -626,7 +631,6 @@ "no_previous_track": "There is no previous track.", "playing_previous": "Playing the previous track.", "previous_footer": "Playing the previous track by {displayName}", - "rewind_limit": "You cannot rewind the music more than the length of the song.", "rewinded": "Rewinded the music.", "rewind_footer": "Rewinded by {displayName}", "forward_limit": "You cannot forward the music more than the length of the song.", @@ -640,4 +644,4 @@ "Leave a guild": "Leave a guild", "List all guilds the bot is in": "List all guilds the bot is in", "Restart the bot": "Restart the bot" -} \ No newline at end of file +} diff --git a/locales/French.json b/locales/French.json index cee09e0bc..f07637397 100644 --- a/locales/French.json +++ b/locales/French.json @@ -121,6 +121,9 @@ "messages": { "filter_enabled": "`✅` | Le filtre de basses a été `ACTIVÉ`. \n**Attention, écouter trop fort peut endommager votre audition !**", "filter_disabled": "`✅` | Le filtre de basses a été `DÉSACTIVÉ`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -494,6 +497,9 @@ "language": "La langue que vous souhaitez définir", "reset": "Change la langue par défaut" } + }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track" } }, "buttons": { @@ -613,7 +619,6 @@ "no_previous_track": "Il n'y a pas de piste précédente.", "playing_previous": "Lecture de la piste précédente.", "previous_footer": "Lecture de la piste précédente par {displayName}", - "rewind_limit": "Vous ne pouvez pas rembobiner la musique plus que la longueur de la chanson.", "rewinded": "Rembobinage de la musique.", "rewind_footer": "Rembobiné par {displayName}", "forward_limit": "Vous ne pouvez pas avancer la musique plus que la longueur de la chanson.", @@ -627,4 +632,4 @@ "Leave a guild": "Leave a guild", "List all guilds the bot is in": "List all guilds the bot is in", "Restart the bot": "Restart the bot" -} \ No newline at end of file +} diff --git a/locales/German.json b/locales/German.json index 9d423d5bb..6fbaba4cf 100644 --- a/locales/German.json +++ b/locales/German.json @@ -121,6 +121,9 @@ "messages": { "filter_enabled": "`✅` | Bassboost-Filter wurde `AKTIVIERT`. \n**Vorsicht, zu lautes Hören kann deine Ohren schädigen!**", "filter_disabled": "`✅` | Bassboost-Filter wurde `DEAKTIVIERT`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -494,6 +497,9 @@ "language": "Die Sprache, die du einstellen möchtest", "reset": "Ändere die Sprache zurück zur Standardsprache" } + }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track" } }, "buttons": { @@ -613,7 +619,6 @@ "no_previous_track": "Es gibt keinen vorherigen Track.", "playing_previous": "Spiele den vorherigen Track ab.", "previous_footer": "Spiele den vorherigen Track ab von {displayName}", - "rewind_limit": "Du kannst die Musik nicht mehr als die Länge des Songs zurückspulen.", "rewinded": "Musik zurückgespult.", "rewind_footer": "Zurückgespult von {displayName}", "forward_limit": "Du kannst die Musik nicht mehr als die Länge des Songs vorspulen.", @@ -627,4 +632,4 @@ "Leave a guild": "Leave a guild", "List all guilds the bot is in": "List all guilds the bot is in", "Restart the bot": "Restart the bot" -} \ No newline at end of file +} diff --git a/locales/Hindi.json b/locales/Hindi.json index 6585ccb86..0eae2fa7b 100644 --- a/locales/Hindi.json +++ b/locales/Hindi.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | Bassboost filter `SAKRIYA` kar diya gaya hai. \n**Savdhan rahein, tej awaz mein sunne se aapke kaano ko nuksan pahunch sakta hai!**", "filter_disabled": "`✅` | Bassboost filter `NIRAKRIYA` kar diya gaya hai." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -494,6 +497,9 @@ "playlist_stolen": "{user} se playlist `{playlist}` ko safaltapoorvak chura लिया गया है.", "error_occurred": "Playlist ko churaate समय एक error aaya." } + }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track" } }, "buttons": { @@ -613,7 +619,6 @@ "no_previous_track": "Koi pichhla track nahin hai.", "playing_previous": "Pichhla track play कर रहा हूँ.", "previous_footer": "{displayName} dwara pichhla track play kiya जा रहा है", - "rewind_limit": "Aap music ko song ki lambai se zyada rewind nahin kar sakte.", "rewinded": "Music ko rewind कर diya गया है.", "rewind_footer": "{displayName} dwara rewind kiya गया", "forward_limit": "Aap music ko song ki lambai se zyada forward nahin kar sakte.", @@ -627,4 +632,4 @@ "Leave a guild": "Leave a guild", "List all guilds the bot is in": "List all guilds the bot is in", "Restart the bot": "Restart the bot" -} \ No newline at end of file +} diff --git a/locales/Indonesian.json b/locales/Indonesian.json index 313551799..b741f8492 100644 --- a/locales/Indonesian.json +++ b/locales/Indonesian.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | Filter bassboost telah `DIAKTIFKAN`. \n**Hati-hati, mendengarkan terlalu keras dapat merusak pendengaran Anda!**", "filter_disabled": "`✅` | Filter bassboost telah `DINONAKTIFKAN`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -494,6 +497,9 @@ "playlist_stolen": "Berhasil mencuri playlist `{playlist}` dari {user}.", "error_occurred": "Terjadi kesalahan saat mencuri Playlist." } + }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track" } }, "buttons": { @@ -613,7 +619,6 @@ "no_previous_track": "Tidak ada lagu sebelumnya.", "playing_previous": "Memutar lagu sebelumnya.", "previous_footer": "Memutar lagu sebelumnya oleh {displayName}", - "rewind_limit": "Anda tidak dapat memutar mundur lagu lebih dari panjang lagu.", "rewinded": "Memutar mundur lagu.", "rewind_footer": "Diputar mundur oleh {displayName}", "forward_limit": "Anda tidak dapat memajukan lagu lebih dari panjang lagu.", @@ -627,4 +632,4 @@ "Leave a guild": "Tinggalkan server", "List all guilds the bot is in": "Daftar semua server tempat bot berada", "Restart the bot": "Restart bot" -} \ No newline at end of file +} diff --git a/locales/Japanese.json b/locales/Japanese.json index 07de728d6..3c2ef6f15 100644 --- a/locales/Japanese.json +++ b/locales/Japanese.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | ベースブーストフィルターが`有効`になりました。\n**音量を上げすぎると聴覚に悪影響を与える可能性があるので注意してください!**", "filter_disabled": "`✅` | ベースブーストフィルターが`無効`になりました。" + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -494,6 +497,9 @@ "playlist_stolen": "{user} からプレイリスト `{playlist}` を正常に盗みました。", "error_occurred": "プレイリストの盗難中にエラーが発生しました。" } + }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track" } }, "buttons": { @@ -613,7 +619,6 @@ "no_previous_track": "前のトラックがありません。", "playing_previous": "前のトラックを再生中です。", "previous_footer": "{displayName} によって前のトラックを再生中です", - "rewind_limit": "曲の長さ以上に巻き戻すことはできません。", "rewinded": "音楽を巻き戻しました。", "rewind_footer": "{displayName} によって巻き戻されました", "forward_limit": "曲の長さ以上に早送りすることはできません。", @@ -627,4 +632,4 @@ "Leave a guild": "ギルドを離れる", "List all guilds the bot is in": "ボットが所属しているすべてのギルドを一覧表示", "Restart the bot": "ボットを再起動" -} \ No newline at end of file +} diff --git a/locales/Korean.json b/locales/Korean.json index 7f3025359..ca1dd509f 100644 --- a/locales/Korean.json +++ b/locales/Korean.json @@ -33,7 +33,7 @@ "options": { "command": "특정 명령어의 정보를 얻고 싶다면 여기에 입력해주세요" }, - "content": "안녕하세요! 저는 [Lavamusic](https://github.com/appujet/lavamusic)과 Discord로 만들어진 음악 봇이에요. `{prefix}help <명령어>`를 사용하여 그 명령어의 자세한 정보를 얻을 수 있어요.", + "content": "안녕하세요! 저는 [Lavamusic](https://github.com/appujet/lavamusic)와 Discord.js로 만들어진 음악 봇, {bot}이에요. 명령어에 대한 자세한 정보를 얻고 싶다면 `{prefix}help <명령어>`를 사용해보세요.", "title": "도움말 메뉴", "not_found": "`{cmdName}` 명령어는 존재하지 않아요.", "help_cmd": "**설명:** {description}\n**사용 방법:** {usage}\n**예시:** {examples}\n**줄인 명령어:** {aliases}\n**카테고리:** {category}\n**쿨다운:** {cooldown}초\n**필요한 권한:** {premUser}\n**봇에게 필요한 권한:** {premBot}\n**개발자 전용:** {dev}\n**빗금 명령어 사용 가능:** {slash}\n**인수 필요:** {args}\n**노래 재생 중에만 사용 가능:** {player}\n**DJ만 사용 가능:** {dj}\n**DJ에게 필요한 권한:** {djPerm}\n**음성 채널 접속 중에만 사용 가능:** {voice}", @@ -41,7 +41,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.js 버전**: {discordJsVersion}\n- 서버 {guilds}개, 채널 {channels}개, 유저 {users}명\n- **명령어 수**: {commands}개" }, "about": { "description": "봇에 대한 정보를 확인해요", @@ -132,9 +132,14 @@ }, "bassboost": { "description": "베이스부스트 필터를 토글해요", + "options": { + "level": "설정할 베이스부스트 레벨" + }, "messages": { - "filter_enabled": "`✅` | 베이스부스트 필터가 `활성화되었어요`. \n**조심하세요, 너무 크게 들으면 귀가 나갈 수도 있어요!**", - "filter_disabled": "`✅` | 베이스부스트 필터가 `비활성화되었어요`." + "high": "`✅` | High 베이스부스트 필터가 `활성화되었어요`.", + "low": "`✅` | Low 베이스부스트 필터가 `활성화되었어요`.", + "medium": "`✅` | Medium 베이스부스트 필터가 `활성화되었어요`.", + "off": "`✅` | 베이스부스트 필터가 `비활성화되었어요`." } }, "distorsion": { @@ -264,9 +269,9 @@ "looping_off": "**반복이 꺼졌어요.**" }, "lyrics": { - "description": "현재 재생중인 노래의 가사를 가져와요", - "lyrics_track": "### [{trackTitle}]({trackUrl})의 노래 가사\n**`{lyrics}`**", - "searching": "`🔍` **{trackTitle}**의 가사 검색 중...", + "description": "현재 재생중인 노래의 가사를 확인해요", + "lyrics_track": "### [{trackTitle}]({trackUrl}) 노래 가사\n**`{lyrics}`**", + "searching": "`🔍` **{trackTitle}** 노래 가사 검색 중...", "errors": { "no_results": "가사를 찾지 못했어요.", "lyrics_error": "가사를 검색하는 도중 오류가 발생했어요." @@ -551,8 +556,8 @@ "error_searching": "노래를 검색하는 도중 오류가 발생했어요.", "no_results": "검색결과가 없어요.", "nothing_playing": "재생 중인 노래 없음", - "queue_too_long": "대기열에 노래가 너무 많아요. 노래는 최대 {maxQueueSize}개까지만 추가할 수 있어요.", - "playlist_too_long": "플레이리스트 또는 대기열에 노래가 너무 많아요. 노래는 최대 {maxPlaylistSize}개까지만 추가할 수 있어요.", + "queue_too_long": "대기열에 노래가 너무 많아요. 노래는 최대 {maxQueueSize}개까지만 추가할 수 있어요.", + "playlist_too_long": "플레이리스트 또는 대기열에 노래가 너무 많아요. 노래는 최대 {maxPlaylistSize}개까지만 추가할 수 있어요.", "added_to_queue": "대기열에 추가되었어요: [{title}]({uri})", "added_playlist_to_queue": "[{length}]개의 노래가 추가되었어요." } @@ -595,15 +600,15 @@ "cooldown": "`{command}` 명령어를 사용하려면 {time}초동안 기다려야 해요.", "no_mention_everyone": "이 명령어는 everyone나 here 멘션으로 사용할 수 없어요. 빗금 명령어로 사용해주세요.", "error": "오류: `{error}`", - "no_voice_channel_queue": "대기열에 노래를 추가하려면 음성 채널에 접속해주세요.", + "no_voice_channel_queue": "대기열에 노래를 추가하려면 음성 채널에 있어야 해요.", "no_permission_connect_speak": "<#{channel}>에서 연결/말하기 권한이 없어요.", "different_voice_channel_queue": "노래를 대기열에 추가하려면 <#{channel}> 채널에 있어야 해요.", "vote_button": "투표하기", "vote_message": "잠깐! 이 명령어를 사용하려면 top.gg에서 투표해야 해요." }, "setupButton": { - "no_voice_channel_button": "이 버튼을 사용하려면 음성 채널에 접속해주세요.", - "different_voice_channel_button": "이 버튼을 사용하려면 {channel} 채널에 접속해주세요.", + "no_voice_channel_button": "이 버튼을 사용하려면 음성 채널에 있어야 해요.", + "different_voice_channel_button": "이 버튼을 사용하려면 {channel} 채널에 있어야 해요.", "now_playing": "재생 중", "live": "라이브", "requested_by": "요청자: <@{requester}>", @@ -620,16 +625,15 @@ "stopped": "노래를 정지했어요.", "stopped_footer": "{displayName}님에 의해 정지됨", "nothing_playing": "재생 중인 노래 없음", - "loop_set": "{loop} 반복으로 설정했어요.", - "loop_footer": "{displayName}님에 의해 {loop} 반복으로 설정됨", + "loop_set": "반복 모드가 {loop}로 변경되었어요.", + "loop_footer": "{displayName}님에 의해 반복 모드가 {loop}로 설정됨", "shuffled": "노래를 섞었어요.", "no_previous_track": "이전 노래가 없어요.", "playing_previous": "이전 노래를 재생할게요.", "previous_footer": "{displayName}님에 의해 이전 노래 재생 중", - "rewind_limit": "현재 노래 길이보다 더 되감기할 수 없어요.", "rewinded": "노래를 되감기했어요.", "rewind_footer": "{displayName}님에 의해 노래 되감기됨", - "forward_limit": "현재 노래 길이보다 더 빨리감기할 수 없어요.", + "forward_limit": "더 이상 빨리감기할 수 없어요.", "forwarded": "노래를 빨리감기했어요.", "forward_footer": "{displayName}님에 의해 빨리감기됨", "button_not_available": "이 버튼은 사용할 수 없어요.", @@ -640,4 +644,4 @@ "Leave a guild": "서버를 떠나요", "List all guilds the bot is in": "봇이 들어가 있는 서버를 알려줘요", "Restart the bot": "봇을 재시작해요" -} \ No newline at end of file +} diff --git a/locales/Norwegian.json b/locales/Norwegian.json index dcbf8baf3..d2707dd03 100644 --- a/locales/Norwegian.json +++ b/locales/Norwegian.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | Bassboost-filteret er `AKTIVERT`. \n**Vær forsiktig, å lytte for høyt kan skade hørselen!**", "filter_disabled": "`✅` | Bassboost-filteret er `DEAKTIVERT`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -494,6 +497,9 @@ "playlist_stolen": "Stjal spillelisten `{playlist}` fra {user}.", "error_occurred": "En feil oppstod under stjeling av spillelisten." } + }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track" } }, "buttons": { @@ -613,7 +619,6 @@ "no_previous_track": "Det er ingen forrige spor.", "playing_previous": "Spiller av forrige spor.", "previous_footer": "Spiller av forrige spor av {displayName}", - "rewind_limit": "Du kan ikke spole musikken tilbake mer enn lengden på sangen.", "rewinded": "Spolet musikken tilbake.", "rewind_footer": "Spolet tilbake av {displayName}", "forward_limit": "Du kan ikke spole musikken fremover mer enn lengden på sangen.", @@ -627,4 +632,4 @@ "Leave a guild": "Forlat en server", "List all guilds the bot is in": "List opp alle servere boten er på", "Restart the bot": "Start boten på nytt" -} \ No newline at end of file +} diff --git a/locales/Polish.json b/locales/Polish.json index afc79c502..316103047 100644 --- a/locales/Polish.json +++ b/locales/Polish.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | Filtr wzmocnienia basów został `WŁĄCZONY`. \n**Uwaga, zbyt głośne słuchanie może uszkodzić słuch!**", "filter_disabled": "`✅` | Filtr wzmocnienia basów został `WYŁĄCZONY`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -494,6 +497,9 @@ "playlist_stolen": "Pomyślnie skradziono playlistę `{playlist}` od {user}.", "error_occurred": "Wystąpił błąd podczas kradzieży playlisty." } + }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track" } }, "buttons": { @@ -613,7 +619,6 @@ "no_previous_track": "Nie ma żadnego poprzedniego utworu.", "playing_previous": "Odtwarzanie poprzedniego utworu", "previous_footer": "Utwór ostatnio odtwarzany przez {displayName}", - "rewind_limit": "Nie można cofnąć muzyki o więcej niż długość utworu.", "rewinded": "Cofnięto muzykę.", "rewind_footer": "Cofnięty przez {displayName}", "forward_limit": "Nie można przesunąć muzyki do przodu o więcej niż długość utworu.", @@ -627,4 +632,4 @@ "Leave a guild": "Opusć serwer", "List all guilds the bot is in": "Lista wszystkich serwerów, w których jest bot", "Restart the bot": "Uruchom ponownie bota" -} \ No newline at end of file +} diff --git a/locales/PortuguesePT.json b/locales/PortuguesePT.json index 55515ed2f..62e0773a4 100644 --- a/locales/PortuguesePT.json +++ b/locales/PortuguesePT.json @@ -613,7 +613,6 @@ "no_previous_track": "Não há nenhuma faixa anterior.", "playing_previous": "A tocar a faixa anterior.", "previous_footer": "A tocar a faixa anterior por {displayName}", - "rewind_limit": "Não pode retroceder a música mais do que o comprimento da música.", "rewinded": "A música foi retrocedida.", "rewind_footer": "Retrocedido por {displayName}", "forward_limit": "Não pode avançar a música mais do que o comprimento da música.", @@ -623,4 +622,4 @@ "no_music_playing": "Nada está a ser reproduzido neste momento." } } -} \ No newline at end of file +} diff --git a/locales/Russian.json b/locales/Russian.json index 6ed4e962f..2ec622eca 100644 --- a/locales/Russian.json +++ b/locales/Russian.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | Фильтр усиления басов был `ВКЛЮЧЕН`. \n**Будьте осторожны, слишком громкое прослушивание может повредить слух!**", "filter_disabled": "`✅` | Фильтр усиления басов был `ОТКЛЮЧЕН`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -494,6 +497,9 @@ "playlist_stolen": "Успешно украден плейлист `{playlist}` у {user}.", "error_occurred": "Произошла ошибка при краже плейлиста." } + }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track" } }, "buttons": { @@ -613,7 +619,6 @@ "no_previous_track": "Нет предыдущей дорожки.", "playing_previous": "Проигрывается предыдущая дорожка.", "previous_footer": "Проигрывается предыдущая дорожка {displayName}", - "rewind_limit": "Вы не можете перемотать музыку больше, чем длина песни.", "rewinded": "Музыка перемотана назад.", "rewind_footer": "Перемотано назад {displayName}", "forward_limit": "Вы не можете перемотать музыку вперед больше, чем длина песни.", @@ -627,4 +632,4 @@ "Leave a guild": "Leave a guild", "List all guilds the bot is in": "List all guilds the bot is in", "Restart the bot": "Restart the bot" -} \ No newline at end of file +} diff --git a/locales/SpanishES.json b/locales/SpanishES.json index c27a668e1..988a1ff5a 100644 --- a/locales/SpanishES.json +++ b/locales/SpanishES.json @@ -121,6 +121,9 @@ "messages": { "filter_enabled": "`✅` | El filtro de refuerzo de graves se ha `ACTIVADO`. \n**¡Ten cuidado, escuchar demasiado alto puede dañar tu oído!**", "filter_disabled": "`✅` | El filtro de refuerzo de graves se ha `DESACTIVADO`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -494,6 +497,9 @@ "language": "El idioma que quieres establecer", "reset": "Cambia el idioma de nuevo al idioma predeterminado" } + }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track" } }, "buttons": { @@ -613,7 +619,6 @@ "no_previous_track": "No hay ninguna pista anterior.", "playing_previous": "Reproduciendo la pista anterior.", "previous_footer": "Reproduciendo la pista anterior por {displayName}", - "rewind_limit": "No puedes rebobinar la música más de la duración de la canción.", "rewinded": "Se ha rebobinado la música.", "rewind_footer": "Rebobinado por {displayName}", "forward_limit": "No puedes avanzar la música más de la duración de la canción.", @@ -627,4 +632,4 @@ "Leave a guild": "Leave a guild", "List all guilds the bot is in": "List all guilds the bot is in", "Restart the bot": "Restart the bot" -} \ No newline at end of file +} diff --git a/locales/Vietnamese.json b/locales/Vietnamese.json index 92f7d1b08..a1560056b 100644 --- a/locales/Vietnamese.json +++ b/locales/Vietnamese.json @@ -134,6 +134,9 @@ "messages": { "filter_enabled": "`✅` | Bộ lọc Bassboost đã được `BẬT`. \n**Cẩn thận, nghe quá to có thể gây hại cho thính giác của bạn!**", "filter_disabled": "`✅` | Bộ lọc Bassboost đã được `TẮT`." + }, + "options": { + "level": "The bassboost level you want to set" } }, "distorsion": { @@ -494,6 +497,9 @@ "playlist_stolen": "Đã đánh cắp danh sách phát `{playlist}` từ {user} thành công.", "error_occurred": "Đã xảy ra lỗi khi đánh cắp danh sách phát." } + }, + "lyrics": { + "description": "Get's the lyrics of the currently playing track" } }, "buttons": { @@ -613,7 +619,6 @@ "no_previous_track": "Không có bài hát trước.", "playing_previous": "Đang phát bài hát trước.", "previous_footer": "Đang phát bài hát trước bởi {displayName}", - "rewind_limit": "Bạn không thể quay lại nhạc nhiều hơn độ dài của bài hát.", "rewinded": "Đã quay lại nhạc.", "rewind_footer": "Quay lại bởi {displayName}", "forward_limit": "Bạn không thể tiến tới nhạc nhiều hơn độ dài của bài hát.", @@ -627,4 +632,4 @@ "Leave a guild": "Rời khỏi một guild", "List all guilds the bot is in": "Danh sách tất cả guild mà bot đang ở", "Restart the bot": "Khởi động lại bot" -} \ No newline at end of file +} diff --git a/package.json b/package.json index 55f74b00c..7f16de3b0 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,8 @@ { "name": "lavamusic", - "version": "4.6.7", - "description": "LavaMusic is a music bot for Discord, written in JavaScript using the Discord.js, Typescript, Shoukaku (Lavalink) library.", + "version": "5.0.0-beta", + "description": "LavaMusic is a music bot for Discord, written in JavaScript using the Discord.js, Typescript, lavalink-client (Lavalink) library.", "main": "dist/index.js", - "type": "module", "scripts": { "start": "npm run clean && node .", "db:push": "npx prisma db push", @@ -22,7 +21,7 @@ "music", "bot", "lavalink", - "shoukaku", + "lavalink-client", "lavamusic", "typescript", "prisma" @@ -34,11 +33,12 @@ }, "homepage": "https://github.com/appujet/lavamusic#readme", "devDependencies": { - "@biomejs/biome": "^1.9.0", + "@biomejs/biome": "^1.9.2", "@types/i18n": "^0.13.12", - "@types/node": "^22.5.4", + "@types/node": "^22.5.5", "@types/signale": "^1.4.7", "prisma": "^5.19.1", + "tslib": "^2.7.0", "typescript": "^5.6.2" }, "dependencies": { @@ -48,12 +48,12 @@ "dotenv": "^16.4.5", "genius-lyrics-api": "^3.2.1", "i18n": "^0.15.1", + "lavalink-client": "https://pkg.pr.new/moe-music/lavalink-client@1c1f8f3", "node-system-stats": "^1.3.0", - "shoukaku": "^4.1.1", "signale": "^1.4.0", "topgg-autoposter": "^2.0.2", - "tslib": "^2.7.0", - "undici": "^6.19.8" + "undici": "^6.19.8", + "zod": "^3.23.8" }, "signale": { "displayScope": true, diff --git a/prisma/example.mongodb.schema.prisma b/prisma/example.mongodb.schema.prisma index bc9a024ae..f8e3a20dc 100644 --- a/prisma/example.mongodb.schema.prisma +++ b/prisma/example.mongodb.schema.prisma @@ -14,96 +14,54 @@ datasource db { url = env("DATABASE_URL") } +model Bot { + botId String @unique + totalPlaySong Int +} + model Guild { - guildId String @id @map("_id") - prefix String - language String? - stay Stay? - dj Dj? - roles Role[] - setup Setup? + guildId String @id + prefix String + language String? @default("EnglishUS") + stay Stay? + dj Dj? + roles Role[] + setup Setup? } model Stay { - guildId String @id @map("_id") - textId String - voiceId String - Guild Guild @relation(fields: [guildId], references: [guildId]) + guildId String @id + textId String + voiceId String + Guild Guild @relation(fields: [guildId], references: [guildId]) } model Dj { - guildId String @id @map("_id") - mode Boolean - Guild Guild @relation(fields: [guildId], references: [guildId]) + guildId String @id + mode Boolean + Guild Guild @relation(fields: [guildId], references: [guildId]) } model Role { - guildId String @id @map("_id") - roleId String - Guild Guild @relation(fields: [guildId], references: [guildId]) + guildId String + roleId String + Guild Guild @relation(fields: [guildId], references: [guildId]) - @@unique([guildId, roleId]) + @@unique([guildId, roleId]) } model Playlist { - id String @id @default(cuid()) @map("_id") - userId String - name String - songs Song[] - - @@unique([userId, name]) -} - -model Song { - id String @id @map("_id") @default(cuid()) - track String - playlistId String - playlist Playlist @relation(fields: [playlistId], references: [id]) + id String @id @default(uuid()) + userId String + name String + tracks String? // Store the array of encoded tracks as a JSON string - @@unique([track, playlistId]) + @@unique([userId, name]) } model Setup { - guildId String @id @map("_id") - textId String - messageId String - Guild Guild @relation(fields: [guildId], references: [guildId]) -} - -model Premium { - userId String @id @map("_id") - guildId String -} - -enum Languages { - EnglishUS - EnglishGB - German - Bulgarian - ChineseCN - ChineseTW - Croatian - Czech - Danish - Dutch - Finnish - French - Greek - Hindi - Hungarian - Italian - Japanese - Korean - Lithuanian - Norwegian - Polish - PortugueseBR - Romanian - Russian - SpanishES - Swedish - Thai - Turkish - Ukrainian - Vietnamese -} + guildId String @id + textId String + messageId String + Guild Guild @relation(fields: [guildId], references: [guildId]) +} \ No newline at end of file diff --git a/prisma/example.postgresql.schema.prisma b/prisma/example.postgresql.schema.prisma index 3d131913d..a2f5f8307 100644 --- a/prisma/example.postgresql.schema.prisma +++ b/prisma/example.postgresql.schema.prisma @@ -14,14 +14,19 @@ datasource db { url = env("DATABASE_URL") } +model Bot { + botId String @unique + totalPlaySong Int +} + model Guild { - guildId String @id - prefix String - language String? - stay Stay? - dj Dj? - roles Role[] - setup Setup? + guildId String @id + prefix String + language String? @default("EnglishUS") + stay Stay? + dj Dj? + roles Role[] + setup Setup? } model Stay { @@ -46,64 +51,17 @@ model Role { } model Playlist { - id String @id @default(uuid()) - userId String - name String - songs Song[] + id String @id @default(uuid()) + userId String + name String + tracks String? // Store the array of encoded tracks as a JSON string @@unique([userId, name]) } -model Song { - id String @id @default(uuid()) - track String - playlistId String - playlist Playlist @relation(fields: [playlistId], references: [id]) - - @@unique([track, playlistId]) -} - model Setup { guildId String @id textId String messageId String Guild Guild @relation(fields: [guildId], references: [guildId]) -} - -model Premium { - userId String @id - guildId String -} - -enum Languages { - EnglishUS - EnglishGB - German - Bulgarian - ChineseCN - ChineseTW - Croatian - Czech - Danish - Dutch - Finnish - French - Greek - Hindi - Hungarian - Italian - Japanese - Korean - Lithuanian - Norwegian - Polish - PortugueseBR - Romanian - Russian - SpanishES - Swedish - Thai - Turkish - Ukrainian - Vietnamese -} +} \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ad4099269..2187ca2dd 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -51,23 +51,14 @@ model Role { } model Playlist { - id String @id @default(uuid()) - userId String - name String - songs Song[] + id String @id @default(uuid()) + userId String + name String + tracks String? // Store the array of encoded tracks as a JSON string @@unique([userId, name]) } -model Song { - id String @id @default(uuid()) - track String - playlistId String - playlist Playlist @relation(fields: [playlistId], references: [id]) - - @@unique([track, playlistId]) -} - model Setup { guildId String @id textId String diff --git a/process.json b/process.json index bda8eaceb..62bcfb731 100644 --- a/process.json +++ b/process.json @@ -1,10 +1,10 @@ { - "apps": [ - { - "name": "lavamusic", - "script": "dist/index.js", - "node_args": ["--enable-source-maps"], - "restart_delay": 10000 - } - ] + "apps": [ + { + "name": "lavamusic", + "script": "dist/index.js", + "node_args": ["--enable-source-maps"], + "restart_delay": 10000 + } + ] } diff --git a/run.bat b/run.bat index 5b7f8f7b8..a2fe91338 100644 --- a/run.bat +++ b/run.bat @@ -1,19 +1,2 @@ @echo off -setlocal enabledelayedexpansion - -:: Check if pnpm is installed - -where pnpm >nul 2>nul -if %errorlevel% equ 0 ( - set package_manager=pnpm -) else ( - set package_manager=npm -) - -:: Check if node_modules exists -if not exist node_modules ( - %package_manager% install -) - -:: start the project -%package_manager% run start \ No newline at end of file +npm run start diff --git a/scripts/clean.js b/scripts/clean.js index 4ce4104c4..11693396a 100644 --- a/scripts/clean.js +++ b/scripts/clean.js @@ -1,17 +1,28 @@ -import { existsSync } from "node:fs"; -import { rm } from "node:fs/promises"; -import { resolve } from "node:path"; +const fs = require('node:fs'); +const { rm } = require('node:fs').promises; +const path = require('node:path'); async function clean() { - try { - const path = resolve("dist"); - if (existsSync(path)) { - await rm(path, { recursive: true, force: true }); - } - } catch (error) { - console.error("Error while cleaning dist folder:", error); - process.exit(1); - } + try { + const distPath = path.resolve('dist'); + if (fs.existsSync(distPath)) { + await rm(distPath, { recursive: true, force: true }); + } + } catch (error) { + console.error('Error while cleaning dist folder:', error); + process.exit(1); + } } clean(); + +/** + * 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/scripts/restart.js b/scripts/restart.js new file mode 100644 index 000000000..9460fcaf3 --- /dev/null +++ b/scripts/restart.js @@ -0,0 +1,26 @@ +const { exec } = require('node:child_process'); + +async function startLavamusic() { + exec('npm start', (error, stdout, stderr) => { + if (error) { + console.error(`Error starting Lavamusic: ${error}`); + return; + } + if (stderr) { + console.error(`Error starting Lavamusic: ${stderr}`); + } + }); +} + +setTimeout(startLavamusic, 5000); + +/** + * 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/scripts/restart.ts b/scripts/restart.ts deleted file mode 100644 index f2ba2a50f..000000000 --- a/scripts/restart.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { exec } from "node:child_process"; - -async function startLavamusic(): Promise { - exec("npm start", (error, stderr) => { - if (error) { - console.error(`Error starting Lavamusic: ${error.message}`); - return; - } - if (stderr) { - console.error(`Error output: ${stderr}`); - } - }); -} - -setTimeout(startLavamusic, 5000); diff --git a/src/LavaClient.ts b/src/LavaClient.ts index 2c1663f04..867e73264 100644 --- a/src/LavaClient.ts +++ b/src/LavaClient.ts @@ -1,16 +1,16 @@ -import { type ClientOptions, GatewayIntentBits } from "discord.js"; -import config from "./config.js"; -import Lavamusic from "./structures/Lavamusic.js"; +import { type ClientOptions, GatewayIntentBits } from 'discord.js'; +import { env } from './env'; +import Lavamusic from './structures/Lavamusic'; const { GuildMembers, MessageContent, GuildVoiceStates, GuildMessages, Guilds, GuildMessageTyping } = GatewayIntentBits; const clientOptions: ClientOptions = { - intents: [Guilds, GuildMessages, MessageContent, GuildVoiceStates, GuildMembers, GuildMessageTyping], - allowedMentions: { parse: ["users", "roles"], repliedUser: false }, + intents: [Guilds, GuildMessages, MessageContent, GuildVoiceStates, GuildMembers, GuildMessageTyping], + allowedMentions: { parse: ['users', 'roles'], repliedUser: false }, }; const client = new Lavamusic(clientOptions); -client.start(config.token); +client.start(env.TOKEN); /** * Project: lavamusic diff --git a/src/commands/config/247.ts b/src/commands/config/247.ts index 25e8d5f31..1bbe2f759 100644 --- a/src/commands/config/247.ts +++ b/src/commands/config/247.ts @@ -1,72 +1,75 @@ -import type { GuildMember } from "discord.js"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import type { GuildMember } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class _247 extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "247", - description: { - content: "cmd.247.description", - examples: ["247"], - usage: "247", - }, - category: "config", - aliases: ["stay"], - cooldown: 3, - args: false, - vote: true, - player: { - voice: true, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: ["ManageGuild"], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: '247', + description: { + content: 'cmd.247.description', + examples: ['247'], + usage: '247', + }, + category: 'config', + aliases: ['stay'], + cooldown: 3, + args: false, + vote: true, + player: { + voice: true, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: ['ManageGuild'], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const embed = this.client.embed(); - let player = client.shoukaku.players.get(ctx.guild!.id) as any; - try { - const data = await client.db.get_247(ctx.guild!.id); - const member = ctx.member as GuildMember; - if (!member.voice.channel) { - return await ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.247.errors.not_in_voice")).setColor(client.color.red)], - }); - } - if (data) { - await client.db.delete_247(ctx.guild!.id); - return await ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.247.messages.disabled")).setColor(client.color.red)], - }); - } - await client.db.set_247(ctx.guild!.id, ctx.channel.id, member.voice.channel.id); - if (!player) { - player = await client.queue.create( - ctx.guild, - member.voice.channel, - ctx.channel, - client.shoukaku.options.nodeResolver(client.shoukaku.nodes), - ); - } - return await ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.247.messages.enabled")).setColor(this.client.color.main)], - }); - } catch (error) { - console.error("Error in 247 command:", error); - return await ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.247.errors.generic")).setColor(client.color.red)], - }); - } - } + public async run(client: Lavamusic, ctx: Context): Promise { + const embed = this.client.embed(); + let player = client.manager.getPlayer(ctx.guild!.id); + try { + const data = await client.db.get_247(ctx.guild!.id); + const member = ctx.member as GuildMember; + if (!member.voice.channel) { + return await ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.247.errors.not_in_voice')).setColor(client.color.red)], + }); + } + if (data) { + await client.db.delete_247(ctx.guild!.id); + return await ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.247.messages.disabled')).setColor(client.color.red)], + }); + } + await client.db.set_247(ctx.guild!.id, ctx.channel!.id, member.voice.channel.id); + if (!player) { + player = client.manager.createPlayer({ + guildId: ctx.guild!.id, + voiceChannelId: member.voice.channel.id, + textChannelId: ctx.channel!.id, + selfMute: false, + selfDeaf: true, + vcRegion: member.voice.channel.rtcRegion!, + }); + } + if (!player.connected) await player.connect(); + return await ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.247.messages.enabled')).setColor(this.client.color.main)], + }); + } catch (error) { + console.error('Error in 247 command:', error); + return await ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.247.errors.generic')).setColor(client.color.red)], + }); + } + } } /** diff --git a/src/commands/config/Dj.ts b/src/commands/config/Dj.ts index 2cafdaf6e..afbcbb944 100644 --- a/src/commands/config/Dj.ts +++ b/src/commands/config/Dj.ts @@ -1,185 +1,189 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Dj extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "dj", - description: { - content: "cmd.dj.description", - examples: ["dj add @role", "dj remove @role", "dj clear", "dj toggle"], - usage: "dj", - }, - category: "general", - aliases: ["dj"], - cooldown: 3, - args: true, - vote: true, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: ["ManageGuild"], - }, - slashCommand: true, - options: [ - { - name: "add", - description: "cmd.dj.options.add", - type: 1, - options: [ - { - name: "role", - description: "cmd.dj.options.role", - type: 8, - required: true, - }, - ], - }, - { - name: "remove", - description: "cmd.dj.options.remove", - type: 1, - options: [ - { - name: "role", - description: "cmd.dj.options.role", - type: 8, - required: true, - }, - ], - }, - { - name: "clear", - description: "cmd.dj.options.clear", - type: 1, - }, - { - name: "toggle", - description: "cmd.dj.options.toggle", - type: 1, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'dj', + description: { + content: 'cmd.dj.description', + examples: ['dj add @role', 'dj remove @role', 'dj clear', 'dj toggle'], + usage: 'dj', + }, + category: 'general', + aliases: ['dj'], + cooldown: 3, + args: true, + vote: true, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: ['ManageGuild'], + }, + slashCommand: true, + options: [ + { + name: 'add', + description: 'cmd.dj.options.add', + type: 1, + options: [ + { + name: 'role', + description: 'cmd.dj.options.role', + type: 8, + required: true, + }, + ], + }, + { + name: 'remove', + description: 'cmd.dj.options.remove', + type: 1, + options: [ + { + name: 'role', + description: 'cmd.dj.options.role', + type: 8, + required: true, + }, + ], + }, + { + name: 'clear', + description: 'cmd.dj.options.clear', + type: 1, + }, + { + name: 'toggle', + description: 'cmd.dj.options.toggle', + type: 1, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const embed = this.client.embed().setColor(this.client.color.main); - const dj = await client.db.getDj(ctx.guild!.id); - let subCommand: string; - let role: any; + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const embed = this.client.embed().setColor(this.client.color.main); + const dj = await client.db.getDj(ctx.guild!.id); + let subCommand: string | undefined; + let role: any | undefined; - if (ctx.isInteraction) { - subCommand = ctx.interaction.options.data[0].name; - if (subCommand === "add" || subCommand === "remove") { - role = ctx.interaction.options.data[0].options[0].role; - } - } else { - subCommand = args[0]; - role = ctx.message.mentions.roles.first() || ctx.guild.roles.cache.get(args[1]); - } + if (ctx.isInteraction) { + subCommand = ctx.options.getSubCommand(); + if (subCommand === 'add' || subCommand === 'remove') { + role = ctx.options.getRole('role'); + } + } else { + subCommand = args[0]; + role = ctx.message?.mentions.roles.first() || ctx.guild?.roles.cache.get(args[1]); + } - switch (subCommand) { - case "add": - if (!role) { - return ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.dj.errors.provide_role"))], - }); - } - if (await client.db.getRoles(ctx.guild!.id).then((r) => r.some((re) => re.roleId === role.id))) { - return ctx.sendMessage({ - embeds: [ - embed.setDescription( - ctx.locale("cmd.dj.messages.role_exists", { - roleId: role.id, - }), - ), - ], - }); - } - await client.db.addRole(ctx.guild!.id, role.id); - await client.db.setDj(ctx.guild!.id, true); - return ctx.sendMessage({ - embeds: [ - embed.setDescription( - ctx.locale("cmd.dj.messages.role_added", { - roleId: role.id, - }), - ), - ], - }); + switch (subCommand) { + case 'add': { + if (!role) { + return ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.dj.errors.provide_role'))], + }); + } + if (await client.db.getRoles(ctx.guild!.id).then(r => r.some(re => re.roleId === role.id))) { + return ctx.sendMessage({ + embeds: [ + embed.setDescription( + ctx.locale('cmd.dj.messages.role_exists', { + roleId: role.id, + }), + ), + ], + }); + } + await client.db.addRole(ctx.guild!.id, role.id); + await client.db.setDj(ctx.guild!.id, true); + return ctx.sendMessage({ + embeds: [ + embed.setDescription( + ctx.locale('cmd.dj.messages.role_added', { + roleId: role.id, + }), + ), + ], + }); + } - case "remove": - if (!role) { - return ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.dj.errors.provide_role"))], - }); - } - if (!(await client.db.getRoles(ctx.guild!.id).then((r) => r.some((re) => re.roleId === role.id)))) { - return ctx.sendMessage({ - embeds: [ - embed.setDescription( - ctx.locale("cmd.dj.messages.role_not_found", { - roleId: role.id, - }), - ), - ], - }); - } - await client.db.removeRole(ctx.guild!.id, role.id); - return ctx.sendMessage({ - embeds: [ - embed.setDescription( - ctx.locale("cmd.dj.messages.role_removed", { - roleId: role.id, - }), - ), - ], - }); + case 'remove': { + if (!role) { + return ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.dj.errors.provide_role'))], + }); + } + if (!(await client.db.getRoles(ctx.guild!.id).then(r => r.some(re => re.roleId === role.id)))) { + return ctx.sendMessage({ + embeds: [ + embed.setDescription( + ctx.locale('cmd.dj.messages.role_not_found', { + roleId: role.id, + }), + ), + ], + }); + } + await client.db.removeRole(ctx.guild!.id, role.id); + return ctx.sendMessage({ + embeds: [ + embed.setDescription( + ctx.locale('cmd.dj.messages.role_removed', { + roleId: role.id, + }), + ), + ], + }); + } - case "clear": - if (!dj) { - return ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.dj.errors.no_roles"))], - }); - } - await client.db.clearRoles(ctx.guild!.id); - return ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.dj.messages.all_roles_cleared"))], - }); + case 'clear': { + if (!dj) { + return ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.dj.errors.no_roles'))], + }); + } + await client.db.clearRoles(ctx.guild!.id); + return ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.dj.messages.all_roles_cleared'))], + }); + } - case "toggle": - if (!dj) { - return ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.dj.errors.no_roles"))], - }); - } - await client.db.setDj(ctx.guild!.id, !dj.mode); - return ctx.sendMessage({ - embeds: [ - embed.setDescription( - ctx.locale("cmd.dj.messages.toggle", { - status: dj.mode ? "disabled" : "enabled", - }), - ), - ], - }); + case 'toggle': { + if (!dj) { + return ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.dj.errors.no_roles'))], + }); + } + await client.db.setDj(ctx.guild!.id, !dj.mode); + return ctx.sendMessage({ + embeds: [ + embed.setDescription( + ctx.locale('cmd.dj.messages.toggle', { + status: dj.mode ? 'disabled' : 'enabled', + }), + ), + ], + }); + } - default: - return ctx.sendMessage({ - embeds: [ - embed.setDescription(ctx.locale("cmd.dj.errors.invalid_subcommand")).addFields({ - name: ctx.locale("cmd.dj.subcommands"), - value: "`add`, `remove`, `clear`, `toggle`", - }), - ], - }); - } - } + default: + return ctx.sendMessage({ + embeds: [ + embed.setDescription(ctx.locale('cmd.dj.errors.invalid_subcommand')).addFields({ + name: ctx.locale('cmd.dj.subcommands'), + value: '`add`, `remove`, `clear`, `toggle`', + }), + ], + }); + } + } } /** diff --git a/src/commands/config/Language.ts b/src/commands/config/Language.ts index 050160751..f19f553bd 100644 --- a/src/commands/config/Language.ts +++ b/src/commands/config/Language.ts @@ -1,147 +1,158 @@ -import type { AutocompleteInteraction } from "discord.js"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; -import { Language, LocaleFlags } from "../../types.js"; +import type { AutocompleteInteraction } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; +import { Language, LocaleFlags } from '../../types'; export default class LanguageCommand extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "language", - description: { - content: "cmd.language.description", - examples: ["language set `EnglishUS`", "language reset"], - usage: "language", - }, - category: "config", - aliases: ["lang"], - cooldown: 3, - args: true, - vote: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: ["ManageGuild"], - }, - slashCommand: true, - options: [ - { - name: "set", - description: "cmd.language.options.set", - type: 1, - options: [ - { - name: "language", - description: "cmd.language.options.language", - type: 3, - required: true, - autocomplete: true, - }, - ], - }, - { - name: "reset", - description: "cmd.language.options.reset", - type: 1, - }, - ], - }); - } - - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - let subCommand: string; - - if (ctx.isInteraction) { - subCommand = ctx.interaction.options.data[0].name; - } else { - subCommand = args.shift(); - } - if (subCommand === "set") { - const embed = client.embed().setColor(this.client.color.main); - - const locale = await client.db.getLanguage(ctx.guild!.id); - - let lang: string; - - if (ctx.isInteraction) { - lang = ctx.interaction.options.data[0].options[0].value as string; - } else { - lang = args[0]; - } - - if (!Object.values(Language).includes(lang as Language)) { - const availableLanguages = Object.entries(LocaleFlags) - .map(([key, value]) => `${value}:\`${key}\``) - .reduce((acc, curr, index) => { - if (index % 2 === 0) { - return acc + curr + (index === Object.entries(LocaleFlags).length - 1 ? "" : " "); - } - return `${acc + curr}\n`; - }, ""); - return ctx.sendMessage({ - embeds: [ - embed.setDescription( - ctx.locale("cmd.language.invalid_language", { - languages: availableLanguages, - }), - ), - ], - }); - } - - if (locale && locale === lang) { - return ctx.sendMessage({ - embeds: [ - embed.setDescription( - ctx.locale("cmd.language.already_set", { - language: lang, - }), - ), - ], - }); - } - - await client.db.updateLanguage(ctx.guild!.id, lang); - ctx.guildLocale = lang; - - return ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.language.set", { language: lang }))], - }); - } - if (subCommand === "reset") { - const embed = client.embed().setColor(this.client.color.main); - - const locale = await client.db.getLanguage(ctx.guild!.id); - - if (!locale) { - return ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.language.not_set"))], - }); - } - - await client.db.updateLanguage(ctx.guild!.id, Language.EnglishUS); - ctx.guildLocale = Language.EnglishUS; - - return ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.language.reset"))], - }); - } - } - - public async autocomplete(interaction: AutocompleteInteraction): Promise { - const focusedValue = interaction.options.getFocused(); - - const languages = Object.values(Language).map((language) => ({ - name: language, - value: language, - })); - - const filtered = languages.filter((language) => language.name.toLowerCase().includes(focusedValue.toLowerCase())); - - await interaction.respond(filtered.slice(0, 25)).catch(console.error); - } + constructor(client: Lavamusic) { + super(client, { + name: 'language', + description: { + content: 'cmd.language.description', + examples: ['language set `EnglishUS`', 'language reset'], + usage: 'language', + }, + category: 'config', + aliases: ['lang'], + cooldown: 3, + args: true, + vote: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: ['ManageGuild'], + }, + slashCommand: true, + options: [ + { + name: 'set', + description: 'cmd.language.options.set', + type: 1, + options: [ + { + name: 'language', + description: 'cmd.language.options.language', + type: 3, + required: true, + autocomplete: true, + }, + ], + }, + { + name: 'reset', + description: 'cmd.language.options.reset', + type: 1, + }, + ], + }); + } + + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + let subCommand: string | undefined; + + if (ctx.isInteraction) { + subCommand = ctx.options.getSubCommand(); + } else { + subCommand = args.shift(); + } + if (subCommand === 'set') { + const embed = client.embed().setColor(this.client.color.main); + + const locale = await client.db.getLanguage(ctx.guild!.id); + + let lang: string; + + if (ctx.isInteraction) { + lang = ctx.options.get('language')?.value as string; + } else { + lang = args[0]; + } + + if (!Object.values(Language).includes(lang as Language)) { + const availableLanguages = Object.entries(LocaleFlags) + .map(([key, value]) => `${value}:\`${key}\``) + .reduce((acc, curr, index) => { + if (index % 2 === 0) { + return acc + curr + (index === Object.entries(LocaleFlags).length - 1 ? '' : ' '); + } + return `${acc + curr}\n`; + }, ''); + return ctx.sendMessage({ + embeds: [ + embed.setDescription( + ctx.locale('cmd.language.invalid_language', { + languages: availableLanguages, + }), + ), + ], + }); + } + + if (locale && locale === lang) { + return ctx.sendMessage({ + embeds: [ + embed.setDescription( + ctx.locale('cmd.language.already_set', { + language: lang, + }), + ), + ], + }); + } + + await client.db.updateLanguage(ctx.guild!.id, lang); + ctx.guildLocale = lang; + + return ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.language.set', { language: lang }))], + }); + } + if (subCommand === 'reset') { + const embed = client.embed().setColor(this.client.color.main); + + const locale = await client.db.getLanguage(ctx.guild!.id); + + if (!locale) { + return ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.language.not_set'))], + }); + } + + await client.db.updateLanguage(ctx.guild!.id, Language.EnglishUS); + ctx.guildLocale = Language.EnglishUS; + + return ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.language.reset'))], + }); + } + } + + public async autocomplete(interaction: AutocompleteInteraction): Promise { + const focusedValue = interaction.options.getFocused(); + + const languages = Object.values(Language).map(language => ({ + name: language, + value: language, + })); + + const filtered = languages.filter(language => language.name.toLowerCase().includes(focusedValue.toLowerCase())); + + await interaction.respond(filtered.slice(0, 25)).catch(console.error); + } } + +/** + * 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/config/Prefix.ts b/src/commands/config/Prefix.ts index b745b03bd..9970846b6 100644 --- a/src/commands/config/Prefix.ts +++ b/src/commands/config/Prefix.ts @@ -1,110 +1,110 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Prefix extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "prefix", - description: { - content: "cmd.prefix.description", - examples: ["prefix set !", "prefix reset"], - usage: "prefix", - }, - category: "general", - aliases: ["pf"], - cooldown: 3, - args: true, - vote: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: ["ManageGuild"], - }, - slashCommand: true, - options: [ - { - name: "set", - description: "cmd.prefix.options.set", - type: 1, - options: [ - { - name: "prefix", - description: "cmd.prefix.options.prefix", - type: 3, - required: true, - }, - ], - }, - { - name: "reset", - description: "cmd.prefix.options.reset", - type: 1, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'prefix', + description: { + content: 'cmd.prefix.description', + examples: ['prefix set !', 'prefix reset'], + usage: 'prefix', + }, + category: 'general', + aliases: ['pf'], + cooldown: 3, + args: true, + vote: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: ['ManageGuild'], + }, + slashCommand: true, + options: [ + { + name: 'set', + description: 'cmd.prefix.options.set', + type: 1, + options: [ + { + name: 'prefix', + description: 'cmd.prefix.options.prefix', + type: 3, + required: true, + }, + ], + }, + { + name: 'reset', + description: 'cmd.prefix.options.reset', + type: 1, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const embed = client.embed().setColor(this.client.color.main); - const guildId = ctx.guild!.id; - const guildData = await client.db.get(guildId); - const isInteraction = ctx.isInteraction; - let subCommand: string; - let prefix: string; + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const embed = client.embed().setColor(this.client.color.main); + const guildId = ctx.guild!.id; + const guildData = await client.db.get(guildId); + const isInteraction = ctx.isInteraction; + let subCommand: string | undefined; + let prefix: string | undefined; - if (isInteraction) { - subCommand = ctx.interaction.options.data[0].name; - prefix = ctx.interaction.options.data[0].options[0]?.value.toString(); - } else { - subCommand = args[0] || ""; - prefix = args[1] || ""; - } + if (isInteraction) { + subCommand = ctx.options.getSubCommand(); + prefix = ctx.options.get('prefix')?.value?.toString(); + } else { + subCommand = args[0] || ''; + prefix = args[1] || ''; + } - switch (subCommand) { - case "set": { - if (!prefix) { - const currentPrefix = guildData ? guildData.prefix : client.config.prefix; - embed.setDescription( - ctx.locale("cmd.prefix.messages.current_prefix", { - prefix: currentPrefix, - }), - ); - return await ctx.sendMessage({ embeds: [embed] }); - } - if (prefix.length > 3) { - embed.setDescription(ctx.locale("cmd.prefix.errors.prefix_too_long")); - return await ctx.sendMessage({ embeds: [embed] }); - } - await client.db.setPrefix(guildId, prefix); - embed.setDescription(ctx.locale("cmd.prefix.messages.prefix_set", { prefix })); - return await ctx.sendMessage({ embeds: [embed] }); - } - case "reset": { - const defaultPrefix = client.config.prefix; - await client.db.setPrefix(guildId, defaultPrefix); - embed.setDescription( - ctx.locale("cmd.prefix.messages.prefix_reset", { - prefix: defaultPrefix, - }), - ); - return await ctx.sendMessage({ embeds: [embed] }); - } - default: { - const currentPrefix = guildData ? guildData.prefix : client.config.prefix; - embed.setDescription( - ctx.locale("cmd.prefix.messages.current_prefix", { - prefix: currentPrefix, - }), - ); - return await ctx.sendMessage({ embeds: [embed] }); - } - } - } + switch (subCommand) { + case 'set': { + if (!prefix) { + const currentPrefix = guildData ? guildData.prefix : client.env.PREFIX; + embed.setDescription( + ctx.locale('cmd.prefix.messages.current_prefix', { + prefix: currentPrefix, + }), + ); + return await ctx.sendMessage({ embeds: [embed] }); + } + if (prefix.length > 3) { + embed.setDescription(ctx.locale('cmd.prefix.errors.prefix_too_long')); + return await ctx.sendMessage({ embeds: [embed] }); + } + await client.db.setPrefix(guildId, prefix); + embed.setDescription(ctx.locale('cmd.prefix.messages.prefix_set', { prefix })); + return await ctx.sendMessage({ embeds: [embed] }); + } + case 'reset': { + const defaultPrefix = client.env.PREFIX; + await client.db.setPrefix(guildId, defaultPrefix); + embed.setDescription( + ctx.locale('cmd.prefix.messages.prefix_reset', { + prefix: defaultPrefix, + }), + ); + return await ctx.sendMessage({ embeds: [embed] }); + } + default: { + const currentPrefix = guildData ? guildData.prefix : client.env.PREFIX; + embed.setDescription( + ctx.locale('cmd.prefix.messages.current_prefix', { + prefix: currentPrefix, + }), + ); + return await ctx.sendMessage({ embeds: [embed] }); + } + } + } } /** diff --git a/src/commands/config/Setup.ts b/src/commands/config/Setup.ts index f261efd97..c381a3c61 100644 --- a/src/commands/config/Setup.ts +++ b/src/commands/config/Setup.ts @@ -1,181 +1,183 @@ -import { ChannelType, OverwriteType, PermissionFlagsBits } from "discord.js"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; -import { getButtons } from "../../utils/Buttons.js"; +import { ChannelType, OverwriteType, PermissionFlagsBits } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; +import { getButtons } from '../../utils/Buttons'; export default class Setup extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "setup", - description: { - content: "cmd.setup.description", - examples: ["setup create", "setup delete", "setup info"], - usage: "setup", - }, - category: "config", - aliases: ["set"], - cooldown: 3, - args: true, - vote: true, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks", "ManageChannels"], - user: ["ManageGuild"], - }, - slashCommand: true, - options: [ - { - name: "create", - description: "cmd.setup.options.create", - type: 1, - }, - { - name: "delete", - description: "cmd.setup.options.delete", - type: 1, - }, - { - name: "info", - description: "cmd.setup.options.info", - type: 1, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'setup', + description: { + content: 'cmd.setup.description', + examples: ['setup create', 'setup delete', 'setup info'], + usage: 'setup', + }, + category: 'config', + aliases: ['set'], + cooldown: 3, + args: true, + vote: true, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks', 'ManageChannels'], + user: ['ManageGuild'], + }, + slashCommand: true, + options: [ + { + name: 'create', + description: 'cmd.setup.options.create', + type: 1, + }, + { + name: 'delete', + description: 'cmd.setup.options.delete', + type: 1, + }, + { + name: 'info', + description: 'cmd.setup.options.info', + type: 1, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const subCommand = ctx.isInteraction ? ctx.interaction.options.data[0].name : args[0]; - const embed = client.embed().setColor(this.client.color.main); - switch (subCommand) { - case "create": { - const data = await client.db.getSetup(ctx.guild!.id); - if (data?.textId && data.messageId) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.setup.errors.channel_exists"), - color: client.color.red, - }, - ], - }); - } - const textChannel = await ctx.guild.channels.create({ - name: `${this.client.user.username}-song-requests`, - type: ChannelType.GuildText, - topic: "Song requests for the music bot.", - permissionOverwrites: [ - { - type: OverwriteType.Member, - id: this.client.user.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.EmbedLinks, - PermissionFlagsBits.ReadMessageHistory, - ], - }, - { - type: OverwriteType.Role, - id: ctx.guild.roles.everyone.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.ReadMessageHistory, - ], - }, - ], - }); - const player = this.client.queue.get(ctx.guild!.id); - const image = this.client.config.links.img; - const desc = - player?.queue && player.current - ? `[${player.current.info.title}](${player.current.info.uri})` - : ctx.locale("player.setupStart.nothing_playing"); - embed.setDescription(desc).setImage(image); - await textChannel - .send({ - embeds: [embed], - components: getButtons(player, client), - }) - .then((msg) => { - client.db.setSetup(ctx.guild!.id, textChannel.id, msg.id); - }); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.setup.messages.channel_created", { channelId: textChannel.id }), - color: this.client.color.main, - }, - ], - }); - break; - } - case "delete": { - const data2 = await client.db.getSetup(ctx.guild!.id); - if (!data2) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.setup.errors.channel_not_exists"), - color: client.color.red, - }, - ], - }); - } - client.db.deleteSetup(ctx.guild!.id); - const textChannel = ctx.guild.channels.cache.get(data2.textId); - if (textChannel) await textChannel.delete().catch(() => {}); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.setup.messages.channel_deleted"), - color: this.client.color.main, - }, - ], - }); - break; - } - case "info": { - const data3 = await client.db.getSetup(ctx.guild!.id); - if (!data3) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.setup.errors.channel_not_exists"), - color: client.color.red, - }, - ], - }); - } - const channel = ctx.guild.channels.cache.get(data3.textId); - if (channel) { - embed.setDescription( - ctx.locale("cmd.setup.messages.channel_info", { - channelId: channel.id, - }), - ); - await ctx.sendMessage({ embeds: [embed] }); - } else { - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.setup.errors.channel_not_exists"), - color: client.color.red, - }, - ], - }); - } - break; - } - default: - break; - } - } + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const subCommand = ctx.isInteraction ? ctx.options.getSubCommand() : args[0]; + const embed = client.embed().setColor(this.client.color.main); + switch (subCommand) { + case 'create': { + const data = await client.db.getSetup(ctx.guild!.id); + if (data?.textId && data.messageId) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.setup.errors.channel_exists'), + color: client.color.red, + }, + ], + }); + } + const textChannel = await ctx.guild.channels.create({ + name: `${client.user?.username}-song-requests`, + type: ChannelType.GuildText, + topic: 'Song requests for the music bot.', + permissionOverwrites: [ + { + type: OverwriteType.Member, + id: client.user?.id!, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, + PermissionFlagsBits.EmbedLinks, + PermissionFlagsBits.ReadMessageHistory, + ], + }, + { + type: OverwriteType.Role, + id: ctx.guild.roles.everyone.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, + PermissionFlagsBits.ReadMessageHistory, + ], + }, + ], + }); + const player = this.client.manager.getPlayer(ctx.guild!.id); + const image = this.client.config.links.img; + const desc = player?.queue.current + ? `[${player.queue.current.info.title}](${player.queue.current.info.uri})` + : ctx.locale('player.setupStart.nothing_playing'); + embed.setDescription(desc).setImage(image); + await textChannel + .send({ + embeds: [embed], + components: getButtons(player, client), + }) + .then(msg => { + client.db.setSetup(ctx.guild!.id, textChannel.id, msg.id); + }); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.setup.messages.channel_created', { channelId: textChannel.id }), + color: this.client.color.main, + }, + ], + }); + break; + } + case 'delete': { + const data2 = await client.db.getSetup(ctx.guild!.id); + if (!data2) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.setup.errors.channel_not_exists'), + color: client.color.red, + }, + ], + }); + } + client.db.deleteSetup(ctx.guild!.id); + const textChannel = ctx.guild.channels.cache.get(data2.textId); + if (textChannel) + await textChannel.delete().catch(() => { + null; + }); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.setup.messages.channel_deleted'), + color: this.client.color.main, + }, + ], + }); + break; + } + case 'info': { + const data3 = await client.db.getSetup(ctx.guild!.id); + if (!data3) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.setup.errors.channel_not_exists'), + color: client.color.red, + }, + ], + }); + } + const channel = ctx.guild.channels.cache.get(data3.textId); + if (channel) { + embed.setDescription( + ctx.locale('cmd.setup.messages.channel_info', { + channelId: channel.id, + }), + ); + await ctx.sendMessage({ embeds: [embed] }); + } else { + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.setup.errors.channel_not_exists'), + color: client.color.red, + }, + ], + }); + } + break; + } + default: + break; + } + } } /** diff --git a/src/commands/dev/CreateInvite.ts b/src/commands/dev/CreateInvite.ts index fc0bcdfd1..47b51c2dd 100644 --- a/src/commands/dev/CreateInvite.ts +++ b/src/commands/dev/CreateInvite.ts @@ -1,70 +1,77 @@ -import { ChannelType, PermissionFlagsBits, type TextChannel } from "discord.js"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { ChannelType, PermissionFlagsBits, type TextChannel } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class CreateInvite extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "createinvite", - description: { - content: "Create an invite link for a guild", - examples: ["createinvite 0000000000000000000"], - usage: "createinvite ", - }, - category: "dev", - aliases: ["ci", "gi", "ginvite", "guildinvite"], - cooldown: 3, - args: true, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: true, - client: ["SendMessages", "CreateInstantInvite", "ReadMessageHistory", "EmbedLinks", "ViewChannel"], - user: [], - }, - slashCommand: false, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'createinvite', + description: { + content: 'Create an invite link for a guild', + examples: ['createinvite 0000000000000000000'], + usage: 'createinvite ', + }, + category: 'dev', + aliases: ['ci', 'gi', 'ginvite', 'guildinvite'], + cooldown: 3, + args: true, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: true, + client: ['SendMessages', 'CreateInstantInvite', 'ReadMessageHistory', 'EmbedLinks', 'ViewChannel'], + user: [], + }, + slashCommand: false, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const guild = client.guilds.cache.get(args[0]); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const guild = client.guilds.cache.get(args[0]); - if (!guild) { - return await ctx.sendMessage({ - embeds: [this.client.embed().setColor(this.client.color.red).setDescription("Guild not found")], - }); - } + if (!guild) { + return await ctx.sendMessage({ + embeds: [this.client.embed().setColor(this.client.color.red).setDescription('Guild not found')], + }); + } - const textChannel = guild.channels.cache.find( - (c) => - c.type === ChannelType.GuildText && - c - .permissionsFor(guild.members.me!) - ?.has(PermissionFlagsBits.CreateInstantInvite | PermissionFlagsBits.SendMessages | PermissionFlagsBits.ViewChannel), - ) as TextChannel | undefined; + const textChannel = guild.channels.cache.find( + c => + c.type === ChannelType.GuildText && + c + .permissionsFor(guild.members.me!) + ?.has( + PermissionFlagsBits.CreateInstantInvite | + PermissionFlagsBits.SendMessages | + PermissionFlagsBits.ViewChannel, + ), + ) as TextChannel | undefined; - if (!textChannel) { - return await ctx.sendMessage({ - embeds: [this.client.embed().setColor(this.client.color.red).setDescription("No suitable channel found")], - }); - } + if (!textChannel) { + return await ctx.sendMessage({ + embeds: [this.client.embed().setColor(this.client.color.red).setDescription('No suitable channel found')], + }); + } - const invite = await textChannel.createInvite({ - maxAge: 3600, - maxUses: 0, - reason: `Requested by developer: ${ctx.author.username}`, - }); + const invite = await textChannel.createInvite({ + maxAge: 3600, + maxUses: 0, + reason: `Requested by developer: ${ctx.author?.username}`, + }); - return await ctx.sendMessage({ - embeds: [ - this.client.embed().setColor(this.client.color.main).setDescription(`Invite link for ${guild.name}: [Link](${invite.url})`), - ], - }); - } + return await ctx.sendMessage({ + embeds: [ + this.client + .embed() + .setColor(this.client.color.main) + .setDescription(`Invite link for ${guild.name}: [Link](${invite.url})`), + ], + }); + } } /** diff --git a/src/commands/dev/DeleteInvites.ts b/src/commands/dev/DeleteInvites.ts index c63e6f72d..1d03df006 100644 --- a/src/commands/dev/DeleteInvites.ts +++ b/src/commands/dev/DeleteInvites.ts @@ -1,51 +1,51 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; 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: [], - }); - } + 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 guild = client.guilds.cache.get(args[0]); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const guild = client.guilds.cache.get(args[0]); - if (!guild) { - return await ctx.sendMessage("Guild not found."); - } + if (!guild) { + return await ctx.sendMessage('Guild not found.'); + } - try { - const botInvites = (await guild.invites.fetch()).filter((invite) => invite.inviter?.id === client.user?.id); + try { + const botInvites = (await guild.invites.fetch()).filter(invite => invite.inviter?.id === client.user?.id); - await Promise.all(botInvites.map((invite) => invite.delete())); + await Promise.all(botInvites.map(invite => invite.delete())); - return await ctx.sendMessage(`Destroyed ${botInvites.size} invite(s) created by the bot.`); - } catch { - return await ctx.sendMessage("Failed to destroy invites."); - } - } + return await ctx.sendMessage(`Destroyed ${botInvites.size} invite(s) created by the bot.`); + } catch { + return await ctx.sendMessage('Failed to destroy invites.'); + } + } } /** diff --git a/src/commands/dev/Deploy.ts b/src/commands/dev/Deploy.ts index 9203a3397..b8da8c881 100644 --- a/src/commands/dev/Deploy.ts +++ b/src/commands/dev/Deploy.ts @@ -1,101 +1,109 @@ -import { ActionRowBuilder, ButtonBuilder, type ButtonInteraction, ButtonStyle, ComponentType, type Message } from "discord.js"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { + ActionRowBuilder, + ButtonBuilder, + type ButtonInteraction, + ButtonStyle, + ComponentType, + type Message, + type TextChannel, +} from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Deploy extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "deploy", - description: { - content: "Deploy commands", - examples: ["deploy"], - usage: "deploy", - }, - category: "dev", - aliases: ["deploy-commands"], - cooldown: 3, - args: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: true, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: false, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'deploy', + description: { + content: 'Deploy commands', + examples: ['deploy'], + usage: 'deploy', + }, + category: 'dev', + aliases: ['deploy-commands'], + cooldown: 3, + args: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: true, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: false, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context, _args: string[]): Promise { - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder().setCustomId("deploy-global").setLabel("Global").setStyle(ButtonStyle.Secondary), - new ButtonBuilder().setCustomId("deploy-guild").setLabel("Guild").setStyle(ButtonStyle.Secondary), - ); + public async run(client: Lavamusic, ctx: Context, _args: string[]): Promise { + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder().setCustomId('deploy-global').setLabel('Global').setStyle(ButtonStyle.Secondary), + new ButtonBuilder().setCustomId('deploy-guild').setLabel('Guild').setStyle(ButtonStyle.Secondary), + ); - let msg: Message | undefined; - try { - msg = await ctx.sendMessage({ - content: "Where do you want to deploy the commands?", - components: [row], - }); - } catch (error) { - console.error("Failed to send the initial message:", error); - return; - } + let msg: Message | undefined; + try { + msg = await ctx.sendMessage({ + content: 'Where do you want to deploy the commands?', + components: [row], + }); + } catch (error) { + console.error('Failed to send the initial message:', error); + return; + } - const filter = (interaction: ButtonInteraction<"cached">) => { - if (interaction.user.id !== ctx.author.id) { - interaction - .reply({ - content: "You can't interact with this message", - ephemeral: true, - }) - .catch(console.error); - return false; - } - return true; - }; + const filter = (interaction: ButtonInteraction<'cached'>) => { + if (interaction.user.id !== ctx.author?.id) { + interaction + .reply({ + content: "You can't interact with this message", + ephemeral: true, + }) + .catch(console.error); + return false; + } + return true; + }; - const collector = ctx.channel.createMessageComponentCollector({ - filter, - componentType: ComponentType.Button, - time: 30000, - }); + const collector = (ctx.channel as TextChannel).createMessageComponentCollector({ + filter, + componentType: ComponentType.Button, + time: 30000, + }); - collector.on("collect", async (interaction) => { - try { - if (interaction.customId === "deploy-global") { - await client.deployCommands(); - await ctx.editMessage({ - content: "Commands deployed globally.", - components: [], - }); - } else if (interaction.customId === "deploy-guild") { - await client.deployCommands(interaction.guild!.id); - await ctx.editMessage({ - content: "Commands deployed in this guild.", - components: [], - }); - } - } catch (error) { - console.error("Failed to handle interaction:", error); - } - }); + collector.on('collect', async interaction => { + try { + if (interaction.customId === 'deploy-global') { + await client.deployCommands(); + await ctx.editMessage({ + content: 'Commands deployed globally.', + components: [], + }); + } else if (interaction.customId === 'deploy-guild') { + await client.deployCommands(interaction.guild!.id); + await ctx.editMessage({ + content: 'Commands deployed in this guild.', + components: [], + }); + } + } catch (error) { + console.error('Failed to handle interaction:', error); + } + }); - collector.on("end", async (_collected, reason) => { - if (reason === "time" && msg) { - try { - await msg.delete(); - } catch (error) { - console.error("Failed to delete the message:", error); - } - } - }); - } + collector.on('end', async (_collected, reason) => { + if (reason === 'time' && msg) { + try { + await msg.delete(); + } catch (error) { + console.error('Failed to delete the message:', error); + } + } + }); + } } /** diff --git a/src/commands/dev/Eval.ts b/src/commands/dev/Eval.ts index 6f0e890fc..1a5eda618 100644 --- a/src/commands/dev/Eval.ts +++ b/src/commands/dev/Eval.ts @@ -1,81 +1,81 @@ -import util from "node:util"; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; -import { fetch } from "undici"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import util from 'node:util'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import { fetch } from 'undici'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Eval extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "eval", - description: { - content: "Evaluate code", - examples: ["eval"], - usage: "eval", - }, - category: "dev", - aliases: ["ev"], - cooldown: 3, - args: true, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: true, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: false, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'eval', + description: { + content: 'Evaluate code', + examples: ['eval'], + usage: 'eval', + }, + category: 'dev', + aliases: ['ev'], + cooldown: 3, + args: true, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: true, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: false, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const code = args.join(" "); - try { - let evaled = eval(code); - if (evaled === client.config) evaled = "Nice try"; + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const code = args.join(' '); + try { + let evaled = eval(code); + if (evaled === client.config) evaled = 'Nice try'; - if (typeof evaled !== "string") evaled = util.inspect(evaled); - if (evaled.length > 2000) { - const response = await fetch("https://hasteb.in/post", { - method: "POST", - headers: { - "Content-Type": "text/plain", - }, - body: evaled, - }); - const json: any = await response.json(); - evaled = `https://hasteb.in/${json.key}`; - return await ctx.sendMessage({ - content: evaled, - }); - } + if (typeof evaled !== 'string') evaled = util.inspect(evaled); + if (evaled.length > 2000) { + const response = await fetch('https://hasteb.in/post', { + method: 'POST', + headers: { + 'Content-Type': 'text/plain', + }, + body: evaled, + }); + const json: any = await response.json(); + evaled = `https://hasteb.in/${json.key}`; + return await ctx.sendMessage({ + content: evaled, + }); + } - const button = new ButtonBuilder().setStyle(ButtonStyle.Danger).setLabel("Delete").setCustomId("eval-delete"); - const row = new ActionRowBuilder().addComponents(button); + const button = new ButtonBuilder().setStyle(ButtonStyle.Danger).setLabel('Delete').setCustomId('eval-delete'); + const row = new ActionRowBuilder().addComponents(button); - const msg = await ctx.sendMessage({ - content: `\`\`\`js\n${evaled}\n\`\`\``, - components: [row], - }); + const msg = await ctx.sendMessage({ + content: `\`\`\`js\n${evaled}\n\`\`\``, + components: [row], + }); - const filter = (i: any) => i.customId === "eval-delete" && i.user.id === ctx.author.id; - const collector = msg.createMessageComponentCollector({ - time: 60000, - filter: filter, - }); + const filter = (i: any) => i.customId === 'eval-delete' && i.user.id === ctx.author?.id; + const collector = msg.createMessageComponentCollector({ + time: 60000, + filter: filter, + }); - collector.on("collect", async (i) => { - await i.deferUpdate(); - await msg.delete(); - }); - } catch (e) { - ctx.sendMessage(`\`\`\`js\n${e}\n\`\`\``); - } - } + collector.on('collect', async i => { + await i.deferUpdate(); + await msg.delete(); + }); + } catch (e) { + ctx.sendMessage(`\`\`\`js\n${e}\n\`\`\``); + } + } } /** diff --git a/src/commands/dev/GuildLeave.ts b/src/commands/dev/GuildLeave.ts index e08c6b65b..f038ca065 100644 --- a/src/commands/dev/GuildLeave.ts +++ b/src/commands/dev/GuildLeave.ts @@ -1,75 +1,75 @@ -import { ChannelType, type TextChannel } from "discord.js"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { ChannelType, type TextChannel } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class GuildLeave extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "guildleave", - description: { - content: "Leave a guild", - examples: ["guildleave "], - usage: "guildleave ", - }, - category: "dev", - aliases: ["gl"], - cooldown: 3, - args: true, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: true, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: false, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'guildleave', + description: { + content: 'Leave a guild', + examples: ['guildleave '], + usage: 'guildleave ', + }, + category: 'dev', + aliases: ['gl'], + cooldown: 3, + args: true, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: true, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: false, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const guildId = args[0]; + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const guildId = args[0]; - const guild = await client.shard - .broadcastEval( - (c, { guildId }) => { - const guild = c.guilds.cache.get(guildId); - return guild ? { id: guild.id, name: guild.name } : null; - }, - { context: { guildId } }, - ) - .then((results) => results.find((g) => g !== null)); + const guild = await client.shard + ?.broadcastEval( + (c, { guildId }) => { + const guild = c.guilds.cache.get(guildId); + return guild ? { id: guild.id, name: guild.name } : null; + }, + { context: { guildId } }, + ) + .then(results => results.find(g => g !== null)); - if (!guild) { - return await ctx.sendMessage("Guild not found."); - } + if (!guild) { + return await ctx.sendMessage('Guild not found.'); + } - try { - await client.shard.broadcastEval( - async (c, { guildId }) => { - const guild = c.guilds.cache.get(guildId); - if (guild) { - await guild.leave(); - } - }, - { context: { guildId } }, - ); - await ctx.sendMessage(`Left guild ${guild.name}`); - } catch { - await ctx.sendMessage(`Failed to leave guild ${guild.name}`); - } + try { + await client.shard?.broadcastEval( + async (c, { guildId }) => { + const guild = c.guilds.cache.get(guildId); + if (guild) { + await guild.leave(); + } + }, + { context: { guildId } }, + ); + await ctx.sendMessage(`Left guild ${guild.name}`); + } catch { + await ctx.sendMessage(`Failed to leave guild ${guild.name}`); + } - const logChannelId = process.env.LOG_CHANNEL_ID; - if (logChannelId) { - const logChannel = client.channels.cache.get(logChannelId) as TextChannel; - if (logChannel && logChannel.type === ChannelType.GuildText) { - await logChannel.send(`Bot has left guild: ${guild.name} (ID: ${guild.id})`); - } - } - } + const logChannelId = process.env.LOG_CHANNEL_ID; + if (logChannelId) { + const logChannel = client.channels.cache.get(logChannelId) as TextChannel; + if (logChannel && logChannel.type === ChannelType.GuildText) { + await logChannel.send(`Bot has left guild: ${guild.name} (ID: ${guild.id})`); + } + } + } } /** diff --git a/src/commands/dev/GuildList.ts b/src/commands/dev/GuildList.ts index 7d7373aa3..43ed3e15b 100644 --- a/src/commands/dev/GuildList.ts +++ b/src/commands/dev/GuildList.ts @@ -1,49 +1,51 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class GuildList extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "guildlist", - description: { - content: "List all guilds the bot is in", - examples: ["guildlist"], - usage: "guildlist", - }, - category: "dev", - aliases: ["glst"], - cooldown: 3, - args: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: true, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: false, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'guildlist', + description: { + content: 'List all guilds the bot is in', + examples: ['guildlist'], + usage: 'guildlist', + }, + category: 'dev', + aliases: ['glst'], + cooldown: 3, + args: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: true, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: false, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const guilds = await client.shard.broadcastEval((c) => c.guilds.cache.map((guild) => ({ name: guild.name, id: guild.id }))); - const allGuilds = guilds.reduce((acc, val) => acc.concat(val), []); + public async run(client: Lavamusic, ctx: Context): Promise { + const guilds = await client.shard?.broadcastEval(c => + c.guilds.cache.map(guild => ({ name: guild.name, id: guild.id })), + ); + const allGuilds = guilds?.reduce((acc, val) => acc.concat(val), []); - const guildList = allGuilds.map((guild) => `- **${guild.name}** - (${guild.id})`); - const chunks = client.utils.chunk(guildList, 10) || [[]]; - const pages = chunks.map((chunk, index) => { - return this.client - .embed() - .setColor(this.client.color.main) - .setDescription(chunk.join("\n")) - .setFooter({ text: `Page ${index + 1} of ${chunks.length}` }); - }); - await client.utils.paginate(client, ctx, pages); - } + const guildList = allGuilds?.map(guild => `- **${guild.name}** - ${guild.id}`); + const chunks = client.utils.chunk(guildList!, 10) || [[]]; + const pages = chunks.map((chunk, index) => { + return this.client + .embed() + .setColor(this.client.color.main) + .setDescription(chunk.join('\n')) + .setFooter({ text: `Page ${index + 1} of ${chunks.length}` }); + }); + await client.utils.paginate(client, ctx, pages); + } } /** diff --git a/src/commands/dev/Restart.ts b/src/commands/dev/Restart.ts index b947d7981..357ef6719 100644 --- a/src/commands/dev/Restart.ts +++ b/src/commands/dev/Restart.ts @@ -1,79 +1,82 @@ -import { exec } from "node:child_process"; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { exec } from 'node:child_process'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Restart extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "restart", - description: { - content: "Restart the bot", - examples: ["restart"], - usage: "restart", - }, - category: "dev", - aliases: ["reboot"], - cooldown: 3, - args: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: true, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: false, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'restart', + description: { + content: 'Restart the bot', + examples: ['restart'], + usage: 'restart', + }, + category: 'dev', + aliases: ['reboot'], + cooldown: 3, + args: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: true, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: false, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const embed = this.client.embed(); - const button = new ButtonBuilder().setStyle(ButtonStyle.Danger).setLabel("Confirm Restart").setCustomId("confirm-restart"); - const row = new ActionRowBuilder().addComponents(button); - const restartEmbed = embed - .setColor(this.client.color.red) - .setDescription(`**Are you sure you want to restart **\`${client.user.username}\`?`) - .setTimestamp(); + public async run(client: Lavamusic, ctx: Context): Promise { + const embed = this.client.embed(); + const button = new ButtonBuilder() + .setStyle(ButtonStyle.Danger) + .setLabel('Confirm Restart') + .setCustomId('confirm-restart'); + const row = new ActionRowBuilder().addComponents(button); + const restartEmbed = embed + .setColor(this.client.color.red) + .setDescription(`**Are you sure you want to restart **\`${client.user?.username}\`?`) + .setTimestamp(); - const msg = await ctx.sendMessage({ - embeds: [restartEmbed], - components: [row], - }); + const msg = await ctx.sendMessage({ + embeds: [restartEmbed], + components: [row], + }); - const filter = (i: any) => i.customId === "confirm-restart" && i.user.id === ctx.author.id; - const collector = msg.createMessageComponentCollector({ - time: 30000, - filter, - }); + const filter = (i: any) => i.customId === 'confirm-restart' && i.user.id === ctx.author?.id; + const collector = msg.createMessageComponentCollector({ + time: 30000, + filter, + }); - collector.on("collect", async (i) => { - await i.deferUpdate(); + collector.on('collect', async i => { + await i.deferUpdate(); - await msg.edit({ - content: "Restarting the bot...", - embeds: [], - components: [], - }); + await msg.edit({ + content: 'Restarting the bot...', + embeds: [], + components: [], + }); - await client.destroy(); - exec("node scripts/restart.ts"); - process.exit(0); - }); + await client.destroy(); + exec('node scripts/restart.js'); + process.exit(0); + }); - collector.on("end", async () => { - if (collector.collected.size === 0) { - await msg.edit({ - content: "Restart cancelled.", - components: [], - }); - } - }); - } + collector.on('end', async () => { + if (collector.collected.size === 0) { + await msg.edit({ + content: 'Restart cancelled.', + components: [], + }); + } + }); + } } /** diff --git a/src/commands/filters/8d.ts b/src/commands/filters/8d.ts index cf10b89d1..8d106a29c 100644 --- a/src/commands/filters/8d.ts +++ b/src/commands/filters/8d.ts @@ -1,64 +1,61 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index.js'; export default class _8d extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "8d", - description: { - content: "cmd.8d.description", - examples: ["8d"], - usage: "8d", - }, - category: "filters", - aliases: ["3d"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: '8d', + description: { + content: 'cmd.8d.description', + examples: ['8d'], + usage: '8d', + }, + category: 'filters', + aliases: ['3d'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const filterEnabled = player.filters.includes("8D"); - const rotationConfig = filterEnabled ? {} : { rotationHz: 0.2 }; + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const filterEnabled = player.filterManager.filters.rotation; - await player.player.setRotation(rotationConfig); - - if (filterEnabled) { - player.filters = player.filters.filter((filter) => filter !== "8D"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.8d.messages.filter_disabled"), - color: this.client.color.main, - }, - ], - }); - } else { - player.filters.push("8D"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.8d.messages.filter_enabled"), - color: this.client.color.main, - }, - ], - }); - } - } + if (filterEnabled) { + await player.filterManager.toggleRotation(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.8d.messages.filter_disabled'), + color: this.client.color.main, + }, + ], + }); + } else { + await player.filterManager.toggleRotation(0.2); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.8d.messages.filter_enabled'), + color: this.client.color.main, + }, + ], + }); + } + } } /** diff --git a/src/commands/filters/BassBoost.ts b/src/commands/filters/BassBoost.ts index 75188421f..fa95cef36 100644 --- a/src/commands/filters/BassBoost.ts +++ b/src/commands/filters/BassBoost.ts @@ -1,68 +1,104 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { ApplicationCommandOptionType } from 'discord.js'; +import { EQList } from 'lavalink-client'; +import { Command, type Context, type Lavamusic } from '../../structures/index.js'; export default class BassBoost extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "bassboost", - description: { - content: "cmd.bassboost.description", - examples: ["bassboost"], - usage: "bassboost", - }, - category: "filters", - aliases: ["bb"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'bassboost', + description: { + content: 'cmd.bassboost.description', + examples: ['bassboost high', 'bassboost medium', 'bassboost low', 'bassboost off'], + usage: 'bassboost [level]', + }, + category: 'filters', + aliases: ['bb'], + cooldown: 3, + args: true, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'level', + description: 'cmd.bassboost.options.level', + type: ApplicationCommandOptionType.String, + required: true, + choices: [ + { name: 'high', value: 'high' }, + { name: 'medium', value: 'medium' }, + { name: 'low', value: 'low' }, + { name: 'off', value: 'off' }, + ], + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const filterEnabled = player.filters.includes("bassboost"); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); - if (filterEnabled) { - await player.player.setEqualizer([]); - player.filters = player.filters.filter((filter) => filter !== "bassboost"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.bassboost.messages.filter_disabled"), - color: this.client.color.main, - }, - ], - }); - } else { - await player.player.setEqualizer([ - { band: 0, gain: 0.34 }, - { band: 1, gain: 0.34 }, - { band: 2, gain: 0.34 }, - { band: 3, gain: 0.34 }, - ]); - player.filters.push("bassboost"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.bassboost.messages.filter_enabled"), - color: this.client.color.main, - }, - ], - }); - } - } + switch (ctx.args[0]?.toLowerCase()) { + case 'high': { + await player.filterManager.setEQ(EQList.BassboostHigh); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.bassboost.messages.high'), + color: this.client.color.main, + }, + ], + }); + break; + } + case 'medium': { + await player.filterManager.setEQ(EQList.BassboostMedium); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.bassboost.messages.medium'), + color: this.client.color.main, + }, + ], + }); + break; + } + case 'low': { + await player.filterManager.setEQ(EQList.BassboostLow); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.bassboost.messages.low'), + color: this.client.color.main, + }, + ], + }); + break; + } + case 'off': { + await player.filterManager.clearEQ(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.bassboost.messages.off'), + color: this.client.color.main, + }, + ], + }); + break; + } + } + } } /** diff --git a/src/commands/filters/Distorsion.ts b/src/commands/filters/Distorsion.ts deleted file mode 100644 index 289588984..000000000 --- a/src/commands/filters/Distorsion.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; - -export default class Distorsion extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "distorsion", - description: { - content: "cmd.distorsion.description", - examples: ["distorsion"], - usage: "distorsion", - }, - category: "filters", - aliases: ["dt"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } - - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const filterEnabled = player.filters.includes("distorsion"); - - if (filterEnabled) { - await player.player.setDistortion({}); - player.filters = player.filters.filter((filter) => filter !== "distorsion"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.distorsion.messages.filter_disabled"), - color: this.client.color.main, - }, - ], - }); - } else { - await player.player.setDistortion({ - sinOffset: 0, - sinScale: 1, - cosOffset: 0, - cosScale: 1, - tanOffset: 0, - tanScale: 1, - offset: 0, - scale: 1, - }); - player.filters.push("distorsion"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.distorsion.messages.filter_enabled"), - color: this.client.color.main, - }, - ], - }); - } - } -} - -/** - * 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/filters/Karaoke.ts b/src/commands/filters/Karaoke.ts index c41e3d358..f23ed6367 100644 --- a/src/commands/filters/Karaoke.ts +++ b/src/commands/filters/Karaoke.ts @@ -1,68 +1,61 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index.js'; export default class Karaoke extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "karaoke", - description: { - content: "cmd.karaoke.description", - examples: ["karaoke"], - usage: "karaoke", - }, - category: "filters", - aliases: ["kk"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'karaoke', + description: { + content: 'cmd.karaoke.description', + examples: ['karaoke'], + usage: 'karaoke', + }, + category: 'filters', + aliases: ['kk'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const filterEnabled = player.filters.includes("karaoke"); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const filterEnabled = player.filterManager.filters.karaoke; - if (filterEnabled) { - await player.player.setKaraoke(); - player.filters = player.filters.filter((filter) => filter !== "karaoke"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.karaoke.messages.filter_disabled"), - color: this.client.color.main, - }, - ], - }); - } else { - await player.player.setKaraoke({ - level: 1, - monoLevel: 1, - filterBand: 220, - filterWidth: 100, - }); - player.filters.push("karaoke"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.karaoke.messages.filter_enabled"), - color: this.client.color.main, - }, - ], - }); - } - } + if (filterEnabled) { + await player.filterManager.toggleKaraoke(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.karaoke.messages.filter_disabled'), + color: this.client.color.main, + }, + ], + }); + } else { + await player.filterManager.toggleKaraoke(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.karaoke.messages.filter_enabled'), + color: this.client.color.main, + }, + ], + }); + } + } } /** diff --git a/src/commands/filters/LowPass.ts b/src/commands/filters/LowPass.ts index da33a65e9..1d751a19c 100644 --- a/src/commands/filters/LowPass.ts +++ b/src/commands/filters/LowPass.ts @@ -1,63 +1,61 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index.js'; export default class LowPass extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "lowpass", - description: { - content: "cmd.lowpass.description", - examples: ["lowpass"], - usage: "lowpass ", - }, - category: "filters", - aliases: ["lp"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'lowpass', + description: { + content: 'cmd.lowpass.description', + examples: ['lowpass'], + usage: 'lowpass ', + }, + category: 'filters', + aliases: ['lp'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const filterEnabled = player.filters.includes("lowpass"); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const filterEnabled = player.filterManager.filters.lowPass; - if (filterEnabled) { - await player.player.setLowPass({ smoothing: 0 }); - player.filters = player.filters.filter((filter) => filter !== "lowpass"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.lowpass.messages.filter_disabled"), - color: this.client.color.main, - }, - ], - }); - } else { - await player.player.setLowPass({ smoothing: 20 }); - player.filters.push("lowpass"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.lowpass.messages.filter_enabled"), - color: this.client.color.main, - }, - ], - }); - } - } + if (filterEnabled) { + await player.filterManager.toggleLowPass(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.lowpass.messages.filter_disabled'), + color: this.client.color.main, + }, + ], + }); + } else { + await player.filterManager.toggleLowPass(20); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.lowpass.messages.filter_enabled'), + color: this.client.color.main, + }, + ], + }); + } + } } /** diff --git a/src/commands/filters/NightCore.ts b/src/commands/filters/NightCore.ts index 57cbae9d9..043a25f5a 100644 --- a/src/commands/filters/NightCore.ts +++ b/src/commands/filters/NightCore.ts @@ -1,63 +1,61 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index.js'; export default class NightCore extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "nightcore", - description: { - content: "cmd.nightcore.description", - examples: ["nightcore"], - usage: "nightcore", - }, - category: "filters", - aliases: ["nc"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'nightcore', + description: { + content: 'cmd.nightcore.description', + examples: ['nightcore'], + usage: 'nightcore', + }, + category: 'filters', + aliases: ['nc'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const filterEnabled = player.filters.includes("nightcore"); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const filterEnabled = player.filterManager.filters.nightcore; - if (filterEnabled) { - await player.player.setTimescale({}); - player.filters = player.filters.filter((filter) => filter !== "nightcore"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.nightcore.messages.filter_disabled"), - color: this.client.color.main, - }, - ], - }); - } else { - await player.player.setTimescale({ rate: 1.2 }); - player.filters.push("nightcore"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.nightcore.messages.filter_enabled"), - color: this.client.color.main, - }, - ], - }); - } - } + if (filterEnabled) { + await player.filterManager.toggleNightcore(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.nightcore.messages.filter_disabled'), + color: this.client.color.main, + }, + ], + }); + } else { + await player.filterManager.toggleNightcore(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.nightcore.messages.filter_enabled'), + color: this.client.color.main, + }, + ], + }); + } + } } /** diff --git a/src/commands/filters/Pitch.ts b/src/commands/filters/Pitch.ts index 5cb2105d4..7098df309 100644 --- a/src/commands/filters/Pitch.ts +++ b/src/commands/filters/Pitch.ts @@ -1,72 +1,72 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index.js'; export default class Pitch extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "pitch", - description: { - content: "cmd.pitch.description", - examples: ["pitch 1", "pitch 1.5", "pitch 1,5"], - usage: "pitch ", - }, - category: "filters", - aliases: ["ph"], - cooldown: 3, - args: true, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "pitch", - description: "cmd.pitch.options.pitch", - type: 3, - required: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'pitch', + description: { + content: 'cmd.pitch.description', + examples: ['pitch 1', 'pitch 1.5', 'pitch 1,5'], + usage: 'pitch ', + }, + category: 'filters', + aliases: ['ph'], + cooldown: 3, + args: true, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'pitch', + description: 'cmd.pitch.options.pitch', + type: 3, + required: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const player = client.queue.get(ctx.guild!.id); - const pitchString = args[0].replace(",", "."); - const isValidNumber = /^[0-9]*\.?[0-9]+$/.test(pitchString); - const pitch = parseFloat(pitchString); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const pitchString = args[0].replace(',', '.'); + const isValidNumber = /^[0-9]*\.?[0-9]+$/.test(pitchString); + const pitch = Number.parseFloat(pitchString); - if (!isValidNumber || isNaN(pitch) || pitch < 0.5 || pitch > 5) { - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.pitch.errors.invalid_number"), - color: this.client.color.red, - }, - ], - }); - return; - } + if (!isValidNumber || Number.isNaN(pitch) || pitch < 0.5 || pitch > 5) { + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.pitch.errors.invalid_number'), + color: this.client.color.red, + }, + ], + }); + return; + } - await player.player.setTimescale({ pitch }); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.pitch.messages.pitch_set", { - pitch, - }), - color: this.client.color.main, - }, - ], - }); - } + await player.filterManager.setPitch(pitch); + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.pitch.messages.pitch_set', { + pitch, + }), + color: this.client.color.main, + }, + ], + }); + } } /** diff --git a/src/commands/filters/Rate.ts b/src/commands/filters/Rate.ts index f9e1733d3..8c8204178 100644 --- a/src/commands/filters/Rate.ts +++ b/src/commands/filters/Rate.ts @@ -1,72 +1,72 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index.js'; export default class Rate extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "rate", - description: { - content: "cmd.rate.description", - examples: ["rate 1", "rate 1.5", "rate 1,5"], - usage: "rate ", - }, - category: "filters", - aliases: ["rt"], - cooldown: 3, - args: true, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "rate", - description: "cmd.rate.options.rate", - type: 3, - required: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'rate', + description: { + content: 'cmd.rate.description', + examples: ['rate 1', 'rate 1.5', 'rate 1,5'], + usage: 'rate ', + }, + category: 'filters', + aliases: ['rt'], + cooldown: 3, + args: true, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'rate', + description: 'cmd.rate.options.rate', + type: 3, + required: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const player = client.queue.get(ctx.guild!.id); - const rateString = args[0].replace(",", "."); - const isValidNumber = /^[0-9]*\.?[0-9]+$/.test(rateString); - const rate = parseFloat(rateString); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const rateString = args[0].replace(',', '.'); + const isValidNumber = /^[0-9]*\.?[0-9]+$/.test(rateString); + const rate = Number.parseFloat(rateString); - if (!isValidNumber || isNaN(rate) || rate < 0.5 || rate > 5) { - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.rate.errors.invalid_number"), - color: this.client.color.red, - }, - ], - }); - return; - } + if (!isValidNumber || Number.isNaN(rate) || rate < 0.5 || rate > 5) { + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.rate.errors.invalid_number'), + color: this.client.color.red, + }, + ], + }); + return; + } - await player.player.setTimescale({ rate }); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.rate.messages.rate_set", { - rate, - }), - color: this.client.color.main, - }, - ], - }); - } + await player.filterManager.setRate(rate); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.rate.messages.rate_set', { + rate, + }), + color: this.client.color.main, + }, + ], + }); + } } /** diff --git a/src/commands/filters/Reset.ts b/src/commands/filters/Reset.ts index cced01703..f527932ac 100644 --- a/src/commands/filters/Reset.ts +++ b/src/commands/filters/Reset.ts @@ -1,48 +1,48 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index.js'; export default class Reset extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "reset", - description: { - content: "cmd.reset.description", - examples: ["reset"], - usage: "reset", - }, - category: "filters", - aliases: ["rs"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'reset', + description: { + content: 'cmd.reset.description', + examples: ['reset'], + usage: 'reset', + }, + category: 'filters', + aliases: ['rs'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - player.player.clearFilters(); - player.filters = []; - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.reset.messages.filters_reset"), - color: this.client.color.main, - }, - ], - }); - } + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + player.filterManager.resetFilters(); + player.filterManager.clearEQ(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.reset.messages.filters_reset'), + color: this.client.color.main, + }, + ], + }); + } } /** diff --git a/src/commands/filters/Rotation.ts b/src/commands/filters/Rotation.ts index 6519a80d1..32cff3e57 100644 --- a/src/commands/filters/Rotation.ts +++ b/src/commands/filters/Rotation.ts @@ -1,61 +1,59 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index.js'; export default class Rotation extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "rotation", - description: { - content: "cmd.rotation.description", - examples: ["rotation"], - usage: "rotation", - }, - category: "filters", - aliases: ["rt"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'rotation', + description: { + content: 'cmd.rotation.description', + examples: ['rotation'], + usage: 'rotation', + }, + category: 'filters', + aliases: ['rt'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - if (player.filters.includes("rotation")) { - player.player.setRotation(); - player.filters = player.filters.filter((filter) => filter !== "rotation"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.rotation.messages.disabled"), - color: this.client.color.main, - }, - ], - }); - } else { - player.player.setRotation({ rotationHz: 0 }); - player.filters.push("rotation"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.rotation.messages.enabled"), - color: this.client.color.main, - }, - ], - }); - } - } + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + if (player.filterManager.filters.rotation) { + player.filterManager.toggleRotation(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.rotation.messages.disabled'), + color: this.client.color.main, + }, + ], + }); + } else { + player.filterManager.toggleRotation(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.rotation.messages.enabled'), + color: this.client.color.main, + }, + ], + }); + } + } } /** diff --git a/src/commands/filters/Speed.ts b/src/commands/filters/Speed.ts index cc467c539..1c26e0f9a 100644 --- a/src/commands/filters/Speed.ts +++ b/src/commands/filters/Speed.ts @@ -1,72 +1,72 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index.js'; export default class Speed extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "speed", - description: { - content: "cmd.speed.description", - examples: ["speed 1.5", "speed 1,5"], - usage: "speed ", - }, - category: "filters", - aliases: ["spd"], - cooldown: 3, - args: true, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "speed", - description: "cmd.speed.options.speed", - type: 3, - required: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'speed', + description: { + content: 'cmd.speed.description', + examples: ['speed 1.5', 'speed 1,5'], + usage: 'speed ', + }, + category: 'filters', + aliases: ['spd'], + cooldown: 3, + args: true, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'speed', + description: 'cmd.speed.options.speed', + type: 3, + required: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const player = client.queue.get(ctx.guild!.id); - const speedString = args[0].replace(",", "."); - const isValidNumber = /^[0-9]*\.?[0-9]+$/.test(speedString); - const speed = parseFloat(speedString); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const speedString = args[0].replace(',', '.'); + const isValidNumber = /^[0-9]*\.?[0-9]+$/.test(speedString); + const speed = Number.parseFloat(speedString); - if (!isValidNumber || isNaN(speed) || speed < 0.5 || speed > 5) { - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.speed.messages.invalid_number"), - color: this.client.color.red, - }, - ], - }); - return; - } + if (!isValidNumber || Number.isNaN(speed) || speed < 0.5 || speed > 5) { + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.speed.messages.invalid_number'), + color: this.client.color.red, + }, + ], + }); + return; + } - player.player.setTimescale({ speed }); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.speed.messages.set_speed", { - speed, - }), - color: this.client.color.main, - }, - ], - }); - } + player.filterManager.setSpeed(speed); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.speed.messages.set_speed', { + speed, + }), + color: this.client.color.main, + }, + ], + }); + } } /** diff --git a/src/commands/filters/Tremolo.ts b/src/commands/filters/Tremolo.ts index 0d31189a8..bb38235d5 100644 --- a/src/commands/filters/Tremolo.ts +++ b/src/commands/filters/Tremolo.ts @@ -1,63 +1,61 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index.js'; export default class Tremolo extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "tremolo", - description: { - content: "cmd.tremolo.description", - examples: ["tremolo"], - usage: "tremolo", - }, - category: "filters", - aliases: ["tr"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'tremolo', + description: { + content: 'cmd.tremolo.description', + examples: ['tremolo'], + usage: 'tremolo', + }, + category: 'filters', + aliases: ['tr'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const tremoloEnabled = player.filters.includes("tremolo"); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const tremoloEnabled = player.filterManager.filters.tremolo; - if (tremoloEnabled) { - player.player.setTremolo(); - player.filters.splice(player.filters.indexOf("tremolo"), 1); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.tremolo.messages.disabled"), - color: this.client.color.main, - }, - ], - }); - } else { - player.player.setTremolo({ depth: 0.75, frequency: 4 }); - player.filters.push("tremolo"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.tremolo.messages.enabled"), - color: this.client.color.main, - }, - ], - }); - } - } + if (tremoloEnabled) { + player.filterManager.toggleTremolo(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.tremolo.messages.disabled'), + color: this.client.color.main, + }, + ], + }); + } else { + player.filterManager.toggleTremolo(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.tremolo.messages.enabled'), + color: this.client.color.main, + }, + ], + }); + } + } } /** diff --git a/src/commands/filters/Vibrato.ts b/src/commands/filters/Vibrato.ts index 4dad08598..10eee4423 100644 --- a/src/commands/filters/Vibrato.ts +++ b/src/commands/filters/Vibrato.ts @@ -1,63 +1,61 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index.js'; export default class Vibrato extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "vibrato", - description: { - content: "cmd.vibrato.description", - examples: ["vibrato"], - usage: "vibrato", - }, - category: "filters", - aliases: ["vb"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'vibrato', + description: { + content: 'cmd.vibrato.description', + examples: ['vibrato'], + usage: 'vibrato', + }, + category: 'filters', + aliases: ['vb'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const vibratoEnabled = player.filters.includes("vibrato"); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const vibratoEnabled = player.filterManager.filters.vibrato; - if (vibratoEnabled) { - player.player.setVibrato(); - player.filters.splice(player.filters.indexOf("vibrato"), 1); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.vibrato.messages.disabled"), - color: this.client.color.main, - }, - ], - }); - } else { - player.player.setVibrato({ depth: 0.75, frequency: 4 }); - player.filters.push("vibrato"); - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.vibrato.messages.enabled"), - color: this.client.color.main, - }, - ], - }); - } - } + if (vibratoEnabled) { + player.filterManager.toggleVibrato(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.vibrato.messages.disabled'), + color: this.client.color.main, + }, + ], + }); + } else { + player.filterManager.toggleVibrato(); + await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.vibrato.messages.enabled'), + color: this.client.color.main, + }, + ], + }); + } + } } /** diff --git a/src/commands/info/About.ts b/src/commands/info/About.ts index 7865d9c01..73093e8b6 100644 --- a/src/commands/info/About.ts +++ b/src/commands/info/About.ts @@ -1,84 +1,86 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class About extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "about", - description: { - content: "cmd.about.description", - examples: ["about"], - usage: "about", - }, - category: "info", - aliases: ["ab"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'about', + description: { + content: 'cmd.about.description', + examples: ['about'], + usage: 'about', + }, + category: 'info', + aliases: ['ab'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const inviteButton = new ButtonBuilder() - .setLabel(ctx.locale("buttons.invite")) - .setStyle(ButtonStyle.Link) - .setURL( - `https://discord.com/api/oauth2/authorize?client_id=${client.config.clientId}&permissions=8&scope=bot%20applications.commands`, - ); - const supportButton = new ButtonBuilder() - .setLabel(ctx.locale("buttons.support")) - .setStyle(ButtonStyle.Link) - .setURL("https://discord.gg/ns8CTk9J3e"); - const row = new ActionRowBuilder().addComponents(inviteButton, supportButton); - const embed = this.client - .embed() - .setAuthor({ - name: "Lavamusic", - iconURL: "https://media.discordapp.net/attachments/876035356460462090/888434725235097610/20210820_124325.png", - }) - .setThumbnail("https://media.discordapp.net/attachments/876035356460462090/888434725235097610/20210820_124325.png") - .setColor(this.client.color.main) - .addFields( - { - name: ctx.locale("cmd.about.fields.creator"), - value: "[appujet](https://github.com/appujet)", - inline: true, - }, - { - name: ctx.locale("cmd.about.fields.repository"), - value: "[Here](https://github.com/appujet/lavamusic)", - inline: true, - }, - { - name: ctx.locale("cmd.about.fields.support"), - value: "[Here](https://discord.gg/ns8CTk9J3e)", - inline: true, - }, - { - name: "\u200b", - value: ctx.locale("cmd.about.fields.description"), - inline: true, - }, - ); - await ctx.sendMessage({ - content: "", - embeds: [embed], - components: [row], - }); - } + public async run(client: Lavamusic, ctx: Context): Promise { + const inviteButton = new ButtonBuilder() + .setLabel(ctx.locale('buttons.invite')) + .setStyle(ButtonStyle.Link) + .setURL( + `https://discord.com/api/oauth2/authorize?client_id=${client.env.CLIENT_ID}&permissions=8&scope=bot%20applications.commands`, + ); + const supportButton = new ButtonBuilder() + .setLabel(ctx.locale('buttons.support')) + .setStyle(ButtonStyle.Link) + .setURL('https://discord.gg/ns8CTk9J3e'); + const row = new ActionRowBuilder().addComponents(inviteButton, supportButton); + const embed = this.client + .embed() + .setAuthor({ + name: 'Lavamusic', + iconURL: 'https://media.discordapp.net/attachments/876035356460462090/888434725235097610/20210820_124325.png', + }) + .setThumbnail( + 'https://media.discordapp.net/attachments/876035356460462090/888434725235097610/20210820_124325.png', + ) + .setColor(this.client.color.main) + .addFields( + { + name: ctx.locale('cmd.about.fields.creator'), + value: '[appujet](https://github.com/appujet)', + inline: true, + }, + { + name: ctx.locale('cmd.about.fields.repository'), + value: '[Here](https://github.com/appujet/lavamusic)', + inline: true, + }, + { + name: ctx.locale('cmd.about.fields.support'), + value: '[Here](https://discord.gg/ns8CTk9J3e)', + inline: true, + }, + { + name: '\u200b', + value: ctx.locale('cmd.about.fields.description'), + inline: true, + }, + ); + await ctx.sendMessage({ + content: '', + embeds: [embed], + components: [row], + }); + } } /** diff --git a/src/commands/info/Botinfo.ts b/src/commands/info/Botinfo.ts index c4038bfea..a073f0406 100644 --- a/src/commands/info/Botinfo.ts +++ b/src/commands/info/Botinfo.ts @@ -1,83 +1,83 @@ -import os from "node:os"; -import { version } from "discord.js"; -import { showTotalMemory, usagePercent } from "node-system-stats"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import os from 'node:os'; +import { version } from 'discord.js'; +import { showTotalMemory, usagePercent } from 'node-system-stats'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Botinfo extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "botinfo", - description: { - content: "cmd.botinfo.description", - examples: ["botinfo"], - usage: "botinfo", - }, - category: "info", - aliases: ["bi", "info", "stats", "status"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'botinfo', + description: { + content: 'cmd.botinfo.description', + examples: ['botinfo'], + usage: 'botinfo', + }, + category: 'info', + aliases: ['bi', 'info', 'stats', 'status'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const osInfo = `${os.type()} ${os.release()}`; - const osUptime = client.utils.formatTime(os.uptime()); - const osHostname = os.hostname(); - const cpuInfo = `${os.arch()} (${os.cpus().length} cores)`; - const cpuUsed = (await usagePercent({ coreIndex: 0, sampleMs: 2000 })).percent; - const memTotal = showTotalMemory(true); - const memUsed = (process.memoryUsage().rss / 1024 ** 2).toFixed(2); - const nodeVersion = process.version; - const discordJsVersion = version; - const commands = client.commands.size; + public async run(client: Lavamusic, ctx: Context): Promise { + const osInfo = `${os.type()} ${os.release()}`; + const osUptime = client.utils.formatTime(os.uptime()); + const osHostname = os.hostname(); + const cpuInfo = `${os.arch()} (${os.cpus().length} cores)`; + const cpuUsed = (await usagePercent({ coreIndex: 0, sampleMs: 2000 })).percent; + const memTotal = showTotalMemory(true); + const memUsed = (process.memoryUsage().rss / 1024 ** 2).toFixed(2); + const nodeVersion = process.version; + const discordJsVersion = version; + const commands = client.commands.size; - const promises = [ - client.shard.broadcastEval((client) => client.guilds.cache.size), - client.shard.broadcastEval((client) => client.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0)), - client.shard.broadcastEval((client) => client.channels.cache.size), - ]; - return Promise.all(promises).then(async (results) => { - const guilds = results[0].reduce((acc, guildCount) => acc + guildCount, 0); - const users = results[1].reduce((acc, memberCount) => acc + memberCount, 0); - const channels = results[2].reduce((acc, channelCount) => acc + channelCount, 0); + const promises = [ + client.shard?.broadcastEval(client => client.guilds.cache.size), + client.shard?.broadcastEval(client => client.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0)), + client.shard?.broadcastEval(client => client.channels.cache.size), + ]; + return Promise.all(promises).then(async results => { + const guilds = results[0]?.reduce((acc, guildCount) => acc + guildCount, 0); + const users = results[1]?.reduce((acc, memberCount) => acc + memberCount, 0); + const channels = results[2]?.reduce((acc, channelCount) => acc + channelCount, 0); - const botInfo = ctx.locale("cmd.botinfo.content", { - osInfo, - osUptime, - osHostname, - cpuInfo, - cpuUsed, - memUsed, - memTotal, - nodeVersion, - discordJsVersion, - guilds, - channels, - users, - commands, - }); + const botInfo = ctx.locale('cmd.botinfo.content', { + osInfo, + osUptime, + osHostname, + cpuInfo, + cpuUsed, + memUsed, + memTotal, + nodeVersion, + discordJsVersion, + guilds, + channels, + users, + commands, + }); - const embed = this.client.embed().setColor(this.client.color.main).setDescription(botInfo); + const embed = this.client.embed().setColor(this.client.color.main).setDescription(botInfo); - return await ctx.sendMessage({ - embeds: [embed], - }); - }); - } + return await ctx.sendMessage({ + embeds: [embed], + }); + }); + } } /** diff --git a/src/commands/info/Help.ts b/src/commands/info/Help.ts index eddcc5617..65400ea36 100644 --- a/src/commands/info/Help.ts +++ b/src/commands/info/Help.ts @@ -1,112 +1,114 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Help extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "help", - description: { - content: "cmd.help.description", - examples: ["help"], - usage: "help", - }, - category: "info", - aliases: ["h"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "command", - description: "cmd.help.options.command", - type: 3, - required: false, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'help', + description: { + content: 'cmd.help.description', + examples: ['help'], + usage: 'help', + }, + category: 'info', + aliases: ['h'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'command', + description: 'cmd.help.options.command', + type: 3, + required: false, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const embed = this.client.embed(); - const guild = await client.db.get(ctx.guild.id); - const commands = this.client.commands.filter((cmd) => cmd.category !== "dev"); - const categories = [...new Set(commands.map((cmd) => cmd.category))]; + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const embed = this.client.embed(); + const guild = await client.db.get(ctx.guild!.id); + const commands = this.client.commands.filter(cmd => cmd.category !== 'dev'); + const categories = [...new Set(commands.map(cmd => cmd.category))]; - if (args[0]) { - const command = this.client.commands.get(args[0].toLowerCase()); - if (!command) { - return await ctx.sendMessage({ - embeds: [ - embed.setColor(this.client.color.red).setDescription( - ctx.locale("cmd.help.not_found", { - cmdName: args[0], - }), - ), - ], - }); - } - const helpEmbed = embed - .setColor(client.color.main) - .setTitle(`${ctx.locale("cmd.help.title")} - ${command.name}`) - .setDescription( - ctx.locale("cmd.help.help_cmd", { - description: ctx.locale(command.description.content), - usage: `${guild.prefix}${command.description.usage}`, - examples: command.description.examples.map((example) => `${guild.prefix}${example}`).join(", "), - aliases: command.aliases.map((alias) => `\`${alias}\``).join(", "), - category: command.category, - cooldown: command.cooldown, - premUser: - command.permissions.user.length > 0 ? command.permissions.user.map((perm) => `\`${perm}\``).join(", ") : "None", - premBot: command.permissions.client.map((perm) => `\`${perm}\``).join(", "), - dev: command.permissions.dev ? "Yes" : "No", - slash: command.slashCommand ? "Yes" : "No", - args: command.args ? "Yes" : "No", - player: command.player.active ? "Yes" : "No", - dj: command.player.dj ? "Yes" : "No", - djPerm: command.player.djPerm ? command.player.djPerm : "None", - voice: command.player.voice ? "Yes" : "No", - }), - ); - return await ctx.sendMessage({ embeds: [helpEmbed] }); - } + if (args[0]) { + const command = this.client.commands.get(args[0].toLowerCase()); + if (!command) { + return await ctx.sendMessage({ + embeds: [ + embed.setColor(this.client.color.red).setDescription( + ctx.locale('cmd.help.not_found', { + cmdName: args[0], + }), + ), + ], + }); + } + const helpEmbed = embed + .setColor(client.color.main) + .setTitle(`${ctx.locale('cmd.help.title')} - ${command.name}`) + .setDescription( + ctx.locale('cmd.help.help_cmd', { + description: ctx.locale(command.description.content), + usage: `${guild?.prefix}${command.description.usage}`, + examples: command.description.examples.map((example: string) => `${guild.prefix}${example}`).join(', '), + aliases: command.aliases.map((alias: string) => `\`${alias}\``).join(', '), + category: command.category, + cooldown: command.cooldown, + premUser: + command.permissions.user.length > 0 + ? command.permissions.user.map((perm: string) => `\`${perm}\``).join(', ') + : 'None', + premBot: command.permissions.client.map((perm: string) => `\`${perm}\``).join(', '), + dev: command.permissions.dev ? 'Yes' : 'No', + slash: command.slashCommand ? 'Yes' : 'No', + args: command.args ? 'Yes' : 'No', + player: command.player.active ? 'Yes' : 'No', + dj: command.player.dj ? 'Yes' : 'No', + djPerm: command.player.djPerm ? command.player.djPerm : 'None', + voice: command.player.voice ? 'Yes' : 'No', + }), + ); + return await ctx.sendMessage({ embeds: [helpEmbed] }); + } - const fields = categories.map((category) => ({ - name: category, - value: commands - .filter((cmd) => cmd.category === category) - .map((cmd) => `\`${cmd.name}\``) - .join(", "), - inline: false, - })); + const fields = categories.map(category => ({ + name: category, + value: commands + .filter(cmd => cmd.category === category) + .map(cmd => `\`${cmd.name}\``) + .join(', '), + inline: false, + })); - const helpEmbed = embed - .setColor(client.color.main) - .setTitle(ctx.locale("cmd.help.title")) - .setDescription( - ctx.locale("cmd.help.content", { - bot: client.user.username, - prefix: guild.prefix, - }), - ) - .setFooter({ - text: ctx.locale("cmd.help.footer", { prefix: guild.prefix }), - }) - .addFields(...fields); + const helpEmbed = embed + .setColor(client.color.main) + .setTitle(ctx.locale('cmd.help.title')) + .setDescription( + ctx.locale('cmd.help.content', { + bot: client.user?.username, + prefix: guild.prefix, + }), + ) + .setFooter({ + text: ctx.locale('cmd.help.footer', { prefix: guild.prefix }), + }) + .addFields(...fields); - return await ctx.sendMessage({ embeds: [helpEmbed] }); - } + return await ctx.sendMessage({ embeds: [helpEmbed] }); + } } /** diff --git a/src/commands/info/Invite.ts b/src/commands/info/Invite.ts index 3e222457f..871e32e81 100644 --- a/src/commands/info/Invite.ts +++ b/src/commands/info/Invite.ts @@ -1,52 +1,55 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Invite extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "invite", - description: { - content: "cmd.invite.description", - examples: ["invite"], - usage: "invite", - }, - category: "info", - aliases: ["iv"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'invite', + description: { + content: 'cmd.invite.description', + examples: ['invite'], + usage: 'invite', + }, + category: 'info', + aliases: ['iv'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const embed = this.client.embed(); - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel(ctx.locale("buttons.invite")) - .setStyle(ButtonStyle.Link) - .setURL( - `https://discord.com/api/oauth2/authorize?client_id=${client.config.clientId}&permissions=8&scope=bot%20applications.commands`, - ), - new ButtonBuilder().setLabel(ctx.locale("buttons.support")).setStyle(ButtonStyle.Link).setURL("https://discord.gg/STXurwnZD5"), - ); - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale("cmd.invite.content"))], - components: [row], - }); - } + public async run(client: Lavamusic, ctx: Context): Promise { + const embed = this.client.embed(); + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel(ctx.locale('buttons.invite')) + .setStyle(ButtonStyle.Link) + .setURL( + `https://discord.com/api/oauth2/authorize?client_id=${client.env.CLIENT_ID}&permissions=8&scope=bot%20applications.commands`, + ), + new ButtonBuilder() + .setLabel(ctx.locale('buttons.support')) + .setStyle(ButtonStyle.Link) + .setURL('https://discord.gg/STXurwnZD5'), + ); + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale('cmd.invite.content'))], + components: [row], + }); + } } /** diff --git a/src/commands/info/LavaLink.ts b/src/commands/info/LavaLink.ts index feccfe55c..9f0cb5cc8 100644 --- a/src/commands/info/LavaLink.ts +++ b/src/commands/info/LavaLink.ts @@ -1,88 +1,88 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class LavaLink extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "lavalink", - description: { - content: "cmd.lavalink.description", - examples: ["lavalink"], - usage: "lavalink", - }, - category: "info", - aliases: ["ll"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'lavalink', + description: { + content: 'cmd.lavalink.description', + examples: ['lavalink'], + usage: 'lavalink', + }, + category: 'info', + aliases: ['ll'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const nodes = client.shoukaku.nodes; - const nodesPerPage = 2; + public async run(client: Lavamusic, ctx: Context): Promise { + const nodes = client.manager.nodeManager.nodes; + const nodesPerPage = 2; - const nodeArray = Array.from(nodes.values()); - const chunks = client.utils.chunk(nodeArray, nodesPerPage); + const nodeArray = Array.from(nodes.values()); + const chunks = client.utils.chunk(nodeArray, nodesPerPage); - if (chunks.length === 0) chunks.push(nodeArray); + if (chunks.length === 0) chunks.push(nodeArray); - const pages = chunks.map((chunk, index) => { - const embed = this.client - .embed() - .setTitle(ctx.locale("cmd.lavalink.title")) - .setColor(this.client.color.main) - .setThumbnail(this.client.user.avatarURL()) - .setTimestamp(); + const pages = chunks.map((chunk, index) => { + const embed = this.client + .embed() + .setTitle(ctx.locale('cmd.lavalink.title')) + .setColor(this.client.color.main) + .setThumbnail(client.user?.avatarURL()!) + .setTimestamp(); - chunk.forEach((node) => { - const statusEmoji = node.stats ? "🟢" : "🔴"; - const stats = node.stats || { - players: 0, - playingPlayers: 0, - uptime: 0, - cpu: { cores: 0, systemLoad: 0, lavalinkLoad: 0 }, - memory: { used: 0, reservable: 0 }, - }; + chunk.forEach(node => { + const statusEmoji = node.stats ? '🟢' : '🔴'; + const stats = node.stats || { + players: 0, + playingPlayers: 0, + uptime: 0, + cpu: { cores: 0, systemLoad: 0, lavalinkLoad: 0 }, + memory: { used: 0, reservable: 0 }, + }; - embed.addFields({ - name: `${node.name} (${statusEmoji})`, - value: `\`\`\`yaml\n${ctx.locale("cmd.lavalink.content", { - players: stats.players, - playingPlayers: stats.playingPlayers, - uptime: client.utils.formatTime(stats.uptime), - cores: stats.cpu.cores, - used: client.utils.formatBytes(stats.memory.used), - reservable: client.utils.formatBytes(stats.memory.reservable), - systemLoad: (stats.cpu.systemLoad * 100).toFixed(2), - lavalinkLoad: (stats.cpu.lavalinkLoad * 100).toFixed(2), - })}\n\`\`\``, - }); - }); + embed.addFields({ + name: `${node.name} (${statusEmoji})`, + value: `\`\`\`yaml\n${ctx.locale('cmd.lavalink.content', { + players: stats.players, + playingPlayers: stats.playingPlayers, + uptime: client.utils.formatTime(stats.uptime), + cores: stats.cpu.cores, + used: client.utils.formatBytes(stats.memory.used), + reservable: client.utils.formatBytes(stats.memory.reservable), + systemLoad: (stats.cpu.systemLoad * 100).toFixed(2), + lavalinkLoad: (stats.cpu.lavalinkLoad * 100).toFixed(2), + })}\n\`\`\``, + }); + }); - embed.setFooter({ - text: ctx.locale("cmd.lavalink.page_info", { - index: index + 1, - total: chunks.length, - }), - }); + embed.setFooter({ + text: ctx.locale('cmd.lavalink.page_info', { + index: index + 1, + total: chunks.length, + }), + }); - return embed; - }); - return await client.utils.paginate(client, ctx, pages); - } + return embed; + }); + return await client.utils.paginate(client, ctx, pages); + } } /** diff --git a/src/commands/info/Ping.ts b/src/commands/info/Ping.ts index 298795cef..079101fd9 100644 --- a/src/commands/info/Ping.ts +++ b/src/commands/info/Ping.ts @@ -1,73 +1,73 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Ping extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "ping", - description: { - content: "cmd.ping.description", - examples: ["ping"], - usage: "ping", - }, - category: "general", - aliases: ["pong"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'ping', + description: { + content: 'cmd.ping.description', + examples: ['ping'], + usage: 'ping', + }, + category: 'general', + aliases: ['pong'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(_client: Lavamusic, ctx: Context): Promise { - const msg = await ctx.sendDeferMessage(ctx.locale("cmd.ping.content")); + public async run(client: Lavamusic, ctx: Context): Promise { + const msg = await ctx.sendDeferMessage(ctx.locale('cmd.ping.content')); - const botLatency = msg.createdTimestamp - ctx.createdTimestamp; - const apiLatency = Math.round(ctx.client.ws.ping); + const botLatency = msg.createdTimestamp - ctx.createdTimestamp; + const apiLatency = Math.round(ctx.client.ws.ping); - const botLatencySign = botLatency < 600 ? "+" : "-"; - const apiLatencySign = apiLatency < 500 ? "+" : "-"; + const botLatencySign = botLatency < 600 ? '+' : '-'; + const apiLatencySign = apiLatency < 500 ? '+' : '-'; - const embed = this.client - .embed() - .setAuthor({ - name: "Pong", - iconURL: this.client.user.displayAvatarURL(), - }) - .setColor(this.client.color.main) - .addFields([ - { - name: ctx.locale("cmd.ping.bot_latency"), - value: `\`\`\`diff\n${botLatencySign} ${botLatency}ms\n\`\`\``, - inline: true, - }, - { - name: ctx.locale("cmd.ping.api_latency"), - value: `\`\`\`diff\n${apiLatencySign} ${apiLatency}ms\n\`\`\``, - inline: true, - }, - ]) - .setFooter({ - text: ctx.locale("cmd.ping.requested_by", { - author: ctx.author.tag, - }), - iconURL: ctx.author.avatarURL({}), - }) - .setTimestamp(); + const embed = this.client + .embed() + .setAuthor({ + name: 'Pong', + iconURL: client.user?.displayAvatarURL(), + }) + .setColor(this.client.color.main) + .addFields([ + { + name: ctx.locale('cmd.ping.bot_latency'), + value: `\`\`\`diff\n${botLatencySign} ${botLatency}ms\n\`\`\``, + inline: true, + }, + { + name: ctx.locale('cmd.ping.api_latency'), + value: `\`\`\`diff\n${apiLatencySign} ${apiLatency}ms\n\`\`\``, + inline: true, + }, + ]) + .setFooter({ + text: ctx.locale('cmd.ping.requested_by', { + author: ctx.author?.tag, + }), + iconURL: ctx.author?.displayAvatarURL({}), + }) + .setTimestamp(); - return await ctx.editMessage({ content: "", embeds: [embed] }); - } + return await ctx.editMessage({ content: '', embeds: [embed] }); + } } /** diff --git a/src/commands/music/Autoplay.ts b/src/commands/music/Autoplay.ts index d3266b486..3a457aa52 100644 --- a/src/commands/music/Autoplay.ts +++ b/src/commands/music/Autoplay.ts @@ -1,60 +1,61 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Autoplay extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "autoplay", - description: { - content: "cmd.autoplay.description", - examples: ["autoplay"], - usage: "autoplay", - }, - category: "music", - aliases: ["ap"], - cooldown: 3, - args: false, - vote: true, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'autoplay', + description: { + content: 'cmd.autoplay.description', + examples: ['autoplay'], + usage: 'autoplay', + }, + category: 'music', + aliases: ['ap'], + cooldown: 3, + args: false, + vote: true, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - if (!player) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("player.errors.no_player"), - color: this.client.color.red, - }, - ], - }); - } + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + if (!player) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('player.errors.no_player'), + color: this.client.color.red, + }, + ], + }); + } - const embed = this.client.embed(); - const autoplay = player.autoplay; - player.setAutoplay(!autoplay); + const embed = this.client.embed(); + const autoplay = player.get('autoplay'); - if (autoplay) { - embed.setDescription(ctx.locale("cmd.autoplay.messages.disabled")).setColor(this.client.color.main); - } else { - embed.setDescription(ctx.locale("cmd.autoplay.messages.enabled")).setColor(this.client.color.main); - } + player.set('autoplay', !autoplay); - await ctx.sendMessage({ embeds: [embed] }); - } + if (autoplay) { + embed.setDescription(ctx.locale('cmd.autoplay.messages.disabled')).setColor(this.client.color.main); + } else { + embed.setDescription(ctx.locale('cmd.autoplay.messages.enabled')).setColor(this.client.color.main); + } + + await ctx.sendMessage({ embeds: [embed] }); + } } /** diff --git a/src/commands/music/ClearQueue.ts b/src/commands/music/ClearQueue.ts index 3087c0f82..05eb69d0e 100644 --- a/src/commands/music/ClearQueue.ts +++ b/src/commands/music/ClearQueue.ts @@ -1,56 +1,56 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class ClearQueue extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "clearqueue", - description: { - content: "cmd.clearqueue.description", - examples: ["clearqueue"], - usage: "clearqueue", - }, - category: "music", - aliases: ["cq"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'clearqueue', + description: { + content: 'cmd.clearqueue.description', + examples: ['clearqueue'], + usage: 'clearqueue', + }, + category: 'music', + aliases: ['cq'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); - if (!player) { - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("player.errors.no_player"))], - }); - } + if (!player) { + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('player.errors.no_player'))], + }); + } - if (player.queue.length === 0) { - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("player.errors.no_song"))], - }); - } + if (player.queue.tracks.length === 0) { + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('player.errors.no_song'))], + }); + } - player.queue = []; - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale("cmd.clearqueue.messages.cleared"))], - }); - } + player.queue.tracks.splice(0, player.queue.tracks.length); + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale('cmd.clearqueue.messages.cleared'))], + }); + } } /** diff --git a/src/commands/music/Grab.ts b/src/commands/music/Grab.ts index ee91ab13a..fc9768f6b 100644 --- a/src/commands/music/Grab.ts +++ b/src/commands/music/Grab.ts @@ -1,78 +1,81 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; +import type { Requester } from '../../types'; export default class Grab extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "grab", - description: { - content: "cmd.grab.description", - examples: ["grab"], - usage: "grab", - }, - category: "music", - aliases: ["gr"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: false, - dj: false, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'grab', + description: { + content: 'cmd.grab.description', + examples: ['grab'], + usage: 'grab', + }, + category: 'music', + aliases: ['gr'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: false, + dj: false, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); - await ctx.sendDeferMessage(ctx.locale("cmd.grab.loading")); + await ctx.sendDeferMessage(ctx.locale('cmd.grab.loading')); - if (!player?.current) { - return await ctx.sendMessage({ - embeds: [this.client.embed().setColor(this.client.color.red).setDescription(ctx.locale("player.errors.no_song"))], - }); - } + if (!player?.queue.current) { + return await ctx.sendMessage({ + embeds: [ + this.client.embed().setColor(this.client.color.red).setDescription(ctx.locale('player.errors.no_song')), + ], + }); + } - const song = player.current; + const song = player.queue.current; - const songInfo = ctx.locale("cmd.grab.content", { - title: song.info.title, - uri: song.info.uri, - artworkUrl: song.info.artworkUrl, - length: song.info.isStream ? "LIVE" : client.utils.formatTime(song.info.length), - requester: song.info.requester, - }); + const songInfo = ctx.locale('cmd.grab.content', { + title: song.info.title, + uri: song.info.uri, + artworkUrl: song.info.artworkUrl, + length: song.info.isStream ? 'LIVE' : client.utils.formatTime(song.info.duration), + requester: (song.requester as Requester).id, + }); - try { - await ctx.author?.send({ - embeds: [ - this.client - .embed() - .setTitle(`**${song.info.title}**`) - .setURL(song.info.uri!) - .setThumbnail(song.info.artworkUrl!) - .setDescription(songInfo) - .setColor(this.client.color.main), - ], - }); + try { + await ctx.author?.send({ + embeds: [ + this.client + .embed() + .setTitle(`**${song.info.title}**`) + .setURL(song.info.uri!) + .setThumbnail(song.info.artworkUrl!) + .setDescription(songInfo) + .setColor(this.client.color.main), + ], + }); - return await ctx.editMessage({ - embeds: [this.client.embed().setDescription(ctx.locale("cmd.grab.check_dm")).setColor(this.client.color.green)], - }); - } catch (_e) { - return await ctx.editMessage({ - embeds: [this.client.embed().setDescription(ctx.locale("cmd.grab.dm_failed")).setColor(this.client.color.red)], - }); - } - } + return await ctx.editMessage({ + embeds: [this.client.embed().setDescription(ctx.locale('cmd.grab.check_dm')).setColor(this.client.color.green)], + }); + } catch (_e) { + return await ctx.editMessage({ + embeds: [this.client.embed().setDescription(ctx.locale('cmd.grab.dm_failed')).setColor(this.client.color.red)], + }); + } + } } /** diff --git a/src/commands/music/Join.ts b/src/commands/music/Join.ts index 7f2eab587..01428353e 100644 --- a/src/commands/music/Join.ts +++ b/src/commands/music/Join.ts @@ -1,77 +1,78 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import type { VoiceChannel } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Join extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "join", - description: { - content: "cmd.join.description", - examples: ["join"], - usage: "join", - }, - category: "music", - aliases: ["come", "j"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks", "Connect", "Speak"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'join', + description: { + content: 'cmd.join.description', + examples: ['join'], + usage: 'join', + }, + category: 'music', + aliases: ['come', 'j'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks', 'Connect', 'Speak'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const embed = this.client.embed(); - let player = client.queue.get(ctx.guild!.id); + public async run(client: Lavamusic, ctx: Context): Promise { + const embed = this.client.embed(); + let player = client.manager.getPlayer(ctx.guild!.id); - if (player) { - const channelId = player.node.manager.connections.get(ctx.guild!.id)!.channelId; - return await ctx.sendMessage({ - embeds: [ - embed.setColor(this.client.color.main).setDescription( - ctx.locale("cmd.join.already_connected", { - channelId, - }), - ), - ], - }); - } + if (player) { + return await ctx.sendMessage({ + embeds: [ + embed.setColor(this.client.color.main).setDescription( + ctx.locale('cmd.join.already_connected', { + channelId: player.voiceChannelId, + }), + ), + ], + }); + } - const memberVoiceChannel = (ctx.member as any).voice.channel; - if (!memberVoiceChannel) { - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.join.no_voice_channel"))], - }); - } + const memberVoiceChannel = (ctx.member as any).voice.channel as VoiceChannel; + if (!memberVoiceChannel) { + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('cmd.join.no_voice_channel'))], + }); + } - player = await client.queue.create( - ctx.guild!, - memberVoiceChannel, - ctx.channel, - client.shoukaku.options.nodeResolver(client.shoukaku.nodes), - ); - - const joinedChannelId = player.node.manager.connections.get(ctx.guild!.id)!.channelId; - return await ctx.sendMessage({ - embeds: [ - embed.setColor(this.client.color.main).setDescription( - ctx.locale("cmd.join.joined", { - channelId: joinedChannelId, - }), - ), - ], - }); - } + player = client.manager.createPlayer({ + guildId: ctx.guild!.id, + voiceChannelId: memberVoiceChannel.id, + textChannelId: ctx.channel.id, + selfMute: false, + selfDeaf: true, + vcRegion: memberVoiceChannel.rtcRegion!, + }); + if (!player.connected) await player.connect(); + return await ctx.sendMessage({ + embeds: [ + embed.setColor(this.client.color.main).setDescription( + ctx.locale('cmd.join.joined', { + channelId: player.voiceChannelId, + }), + ), + ], + }); + } } /** diff --git a/src/commands/music/Leave.ts b/src/commands/music/Leave.ts index 8673a86a8..1e17c5240 100644 --- a/src/commands/music/Leave.ts +++ b/src/commands/music/Leave.ts @@ -1,50 +1,50 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Leave extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "leave", - description: { - content: "cmd.leave.description", - examples: ["leave"], - usage: "leave", - }, - category: "music", - aliases: ["l"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'leave', + description: { + content: 'cmd.leave.description', + examples: ['leave'], + usage: 'leave', + }, + category: 'music', + aliases: ['l'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); - if (player) { - const channelId = player.node.manager.connections.get(ctx.guild!.id)!.channelId; - player.destroy(); - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale("cmd.leave.left", { channelId }))], - }); - } - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.leave.not_in_channel"))], - }); - } + if (player) { + const channelId = player.voiceChannelId; + player.destroy(); + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale('cmd.leave.left', { channelId }))], + }); + } + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('cmd.leave.not_in_channel'))], + }); + } } /** diff --git a/src/commands/music/Loop.ts b/src/commands/music/Loop.ts index f4b090424..079c7fa29 100644 --- a/src/commands/music/Loop.ts +++ b/src/commands/music/Loop.ts @@ -1,59 +1,62 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Loop extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "loop", - description: { - content: "cmd.loop.description", - examples: ["loop", "loop queue", "loop song"], - usage: "loop", - }, - category: "general", - aliases: ["loop"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: false, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'loop', + description: { + content: 'cmd.loop.description', + examples: ['loop', 'loop queue', 'loop song'], + usage: 'loop', + }, + category: 'general', + aliases: ['loop'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: false, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const embed = this.client.embed().setColor(this.client.color.main); - const player = client.queue.get(ctx.guild!.id); - let loopMessage = ""; + public async run(client: Lavamusic, ctx: Context): Promise { + const embed = this.client.embed().setColor(this.client.color.main); + const player = client.manager.getPlayer(ctx.guild!.id); + let loopMessage = ''; - switch (player?.loop) { - case "off": - player.loop = "repeat"; - loopMessage = ctx.locale("cmd.loop.looping_song"); - break; - case "repeat": - player.loop = "queue"; - loopMessage = ctx.locale("cmd.loop.looping_queue"); - break; - case "queue": - player.loop = "off"; - loopMessage = ctx.locale("cmd.loop.looping_off"); - break; - } + switch (player?.repeatMode) { + case 'off': { + player.setRepeatMode('track'); + loopMessage = ctx.locale('cmd.loop.looping_song'); + break; + } + case 'track': { + player.setRepeatMode('queue'); + loopMessage = ctx.locale('cmd.loop.looping_queue'); + break; + } + case 'queue': { + player.setRepeatMode('off'); + loopMessage = ctx.locale('cmd.loop.looping_off'); + break; + } + } - return await ctx.sendMessage({ - embeds: [embed.setDescription(loopMessage)], - }); - } + return await ctx.sendMessage({ + embeds: [embed.setDescription(loopMessage)], + }); + } } /** diff --git a/src/commands/music/Lyrics.ts b/src/commands/music/Lyrics.ts deleted file mode 100644 index c6d9dc4f2..000000000 --- a/src/commands/music/Lyrics.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } from "discord.js"; -import { getLyrics } from "genius-lyrics-api"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; - -export default class Lyrics extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "lyrics", - description: { - content: "cmd.lyrics.description", - examples: ["lyrics"], - usage: "lyrics", - }, - category: "music", - aliases: ["ly"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: false, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } - - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); - - const currentTrack = player.current; - const trackTitle = currentTrack.info.title.replace(/\[.*?\]/g, "").trim(); - const artistName = currentTrack.info.author.replace(/\[.*?\]/g, "").trim(); - const trackUrl = currentTrack.info.uri; - const artworkUrl = currentTrack.info.artworkUrl; - - await ctx.sendDeferMessage(ctx.locale("cmd.lyrics.searching", { trackTitle })); - - const options = { - apiKey: client.config.lyricsApi, - title: trackTitle, - artist: artistName, - optimizeQuery: true, - }; - - try { - const lyrics = await getLyrics(options); - if (lyrics) { - const lyricsPages = this.paginateLyrics(lyrics); - let currentPage = 0; - - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId("prev") - .setEmoji(this.client.emoji.page.back) - .setStyle(ButtonStyle.Secondary) - .setDisabled(true), - new ButtonBuilder().setCustomId("stop").setEmoji(this.client.emoji.page.cancel).setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setCustomId("next") - .setEmoji(this.client.emoji.page.next) - .setStyle(ButtonStyle.Secondary) - .setDisabled(lyricsPages.length <= 1), - ); - - await ctx.editMessage({ - embeds: [ - embed - .setColor(client.color.main) - .setDescription( - ctx.locale("cmd.lyrics.lyrics_track", { trackTitle, trackUrl, lyrics: lyricsPages[currentPage] }), - ) - .setThumbnail(artworkUrl) - .setTimestamp(), - ], - components: [row], - }); - - const filter = (interaction) => interaction.user.id === ctx.author.id; - const collector = ctx.channel.createMessageComponentCollector({ - filter, - componentType: ComponentType.Button, - time: 60000, - }); - - collector.on("collect", async (interaction) => { - if (interaction.customId === "prev") { - currentPage--; - } else if (interaction.customId === "next") { - currentPage++; - } else if (interaction.customId === "stop") { - collector.stop(); - return interaction.update({ components: [] }); - } - - await interaction.update({ - embeds: [ - embed - .setDescription( - ctx.locale("cmd.lyrics.lyrics_track", { trackTitle, trackUrl, lyrics: lyricsPages[currentPage] }), - ) - .setThumbnail(artworkUrl) - .setTimestamp(), - ], - components: [ - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId("prev") - .setEmoji(this.client.emoji.page.back) - .setStyle(ButtonStyle.Secondary) - .setDisabled(currentPage === 0), - new ButtonBuilder() - .setCustomId("stop") - .setEmoji(this.client.emoji.page.cancel) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setCustomId("next") - .setEmoji(this.client.emoji.page.next) - .setStyle(ButtonStyle.Secondary) - .setDisabled(currentPage === lyricsPages.length - 1), - ), - ], - }); - }); - - collector.on("end", () => { - ctx.editMessage({ components: [] }); - }); - } else { - await ctx.editMessage({ - embeds: [embed.setColor(client.color.red).setDescription(ctx.locale("cmd.lyrics.errors.no_results"))], - }); - } - } catch (error) { - console.error(error); - await ctx.editMessage({ - embeds: [embed.setColor(client.color.red).setDescription(ctx.locale("cmd.lyrics.errors.lyrics_error"))], - }); - } - } - - paginateLyrics(lyrics) { - const lines = lyrics.split("\n"); - const pages = []; - let page = ""; - - for (const line of lines) { - if (page.length + line.length > 2048) { - pages.push(page); - page = ""; - } - page += `${line}\n`; - } - - if (page) pages.push(page); - return pages; - } -} - -/** - * 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/music/Lyrics.ts.txt b/src/commands/music/Lyrics.ts.txt new file mode 100644 index 000000000..4273611ae --- /dev/null +++ b/src/commands/music/Lyrics.ts.txt @@ -0,0 +1,187 @@ +/* import { + ActionRowBuilder, + ButtonBuilder, + type ButtonInteraction, + ButtonStyle, + ComponentType, + type TextChannel, +} from 'discord.js'; +import { getLyrics } from 'genius-lyrics-api'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; + +export default class Lyrics extends Command { + constructor(client: Lavamusic) { + super(client, { + name: 'lyrics', + description: { + content: 'cmd.lyrics.description', + examples: ['lyrics'], + usage: 'lyrics', + }, + category: 'music', + aliases: ['ly'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: false, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } + + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); + + const currentTrack = player.queue.current!; + const trackTitle = currentTrack.info.title.replace(/\[.*?\]/g, '').trim(); + const artistName = currentTrack.info.author.replace(/\[.*?\]/g, '').trim(); + const trackUrl = currentTrack.info.uri; + const artworkUrl = currentTrack.info.artworkUrl; + + await ctx.sendDeferMessage(ctx.locale('cmd.lyrics.searching', { trackTitle })); + + const options = { + apiKey: client.env.GENIUS_API, + title: trackTitle, + artist: artistName, + optimizeQuery: true, + }; + + try { + const lyrics = await getLyrics(options); + if (lyrics) { + const lyricsPages = this.paginateLyrics(lyrics); + let currentPage = 0; + + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('prev') + .setEmoji(this.client.emoji.page.back) + .setStyle(ButtonStyle.Secondary) + .setDisabled(true), + new ButtonBuilder().setCustomId('stop').setEmoji(this.client.emoji.page.cancel).setStyle(ButtonStyle.Danger), + new ButtonBuilder() + .setCustomId('next') + .setEmoji(this.client.emoji.page.next) + .setStyle(ButtonStyle.Secondary) + .setDisabled(lyricsPages.length <= 1), + ); + + await ctx.editMessage({ + embeds: [ + embed + .setColor(client.color.main) + .setDescription( + ctx.locale('cmd.lyrics.lyrics_track', { trackTitle, trackUrl, lyrics: lyricsPages[currentPage] }), + ) + .setThumbnail(artworkUrl) + .setTimestamp(), + ], + components: [row], + }); + + const filter = (interaction: ButtonInteraction<'cached'>) => interaction.user.id === ctx.author?.id; + const collector = (ctx.channel as TextChannel).createMessageComponentCollector({ + filter, + componentType: ComponentType.Button, + time: 60000, + }); + + collector.on('collect', async (interaction: ButtonInteraction) => { + if (interaction.customId === 'prev') { + currentPage--; + } else if (interaction.customId === 'next') { + currentPage++; + } else if (interaction.customId === 'stop') { + collector.stop(); + return interaction.update({ components: [] }); + } + + await interaction.update({ + embeds: [ + embed + .setDescription( + ctx.locale('cmd.lyrics.lyrics_track', { trackTitle, trackUrl, lyrics: lyricsPages[currentPage] }), + ) + .setThumbnail(artworkUrl) + .setTimestamp(), + ], + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('prev') + .setEmoji(this.client.emoji.page.back) + .setStyle(ButtonStyle.Secondary) + .setDisabled(currentPage === 0), + new ButtonBuilder() + .setCustomId('stop') + .setEmoji(this.client.emoji.page.cancel) + .setStyle(ButtonStyle.Danger), + new ButtonBuilder() + .setCustomId('next') + .setEmoji(this.client.emoji.page.next) + .setStyle(ButtonStyle.Secondary) + .setDisabled(currentPage === lyricsPages.length - 1), + ), + ], + }); + return; + }); + + collector.on('end', () => { + ctx.editMessage({ components: [] }); + }); + } else { + await ctx.editMessage({ + embeds: [embed.setColor(client.color.red).setDescription(ctx.locale('cmd.lyrics.errors.no_results'))], + }); + } + } catch (error) { + // biome-ignore lint/suspicious/noConsole: + console.error(error); + await ctx.editMessage({ + embeds: [embed.setColor(client.color.red).setDescription(ctx.locale('cmd.lyrics.errors.lyrics_error'))], + }); + } + } + + paginateLyrics(lyrics: string) { + const lines = lyrics.split('\n'); + const pages: any = []; + let page = ''; + + for (const line of lines) { + if (page.length + line.length > 2048) { + pages.push(page); + page = ''; + } + page += `${line}\n`; + } + + if (page) pages.push(page); + return pages; + } +} +*/ + +/** + * 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 + */ \ No newline at end of file diff --git a/src/commands/music/Nowplaying.ts b/src/commands/music/Nowplaying.ts index 55766cceb..ac9d7a014 100644 --- a/src/commands/music/Nowplaying.ts +++ b/src/commands/music/Nowplaying.ts @@ -1,65 +1,65 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Nowplaying extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "nowplaying", - description: { - content: "cmd.nowplaying.description", - examples: ["nowplaying"], - usage: "nowplaying", - }, - category: "music", - aliases: ["np"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: false, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'nowplaying', + description: { + content: 'cmd.nowplaying.description', + examples: ['nowplaying'], + usage: 'nowplaying', + }, + category: 'music', + aliases: ['np'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: false, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id)!; - const track = player.current!; - const position = player.player.position; - const duration = track.info.length; - const bar = client.utils.progressBar(position, duration, 20); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const track = player.queue.current!; + const position = player.position; + const duration = track.info.duration; + const bar = client.utils.progressBar(position, duration, 20); - const embed = this.client - .embed() - .setColor(this.client.color.main) - .setAuthor({ - name: ctx.locale("cmd.nowplaying.now_playing"), - iconURL: ctx.guild?.iconURL({})!, - }) - .setThumbnail(track.info.artworkUrl!) - .setDescription( - ctx.locale("cmd.nowplaying.track_info", { - title: track.info.title, - uri: track.info.uri, - requester: track.info.requester, - bar: bar, - }), - ) - .addFields({ - name: "\u200b", - value: `\`${client.utils.formatTime(position)} / ${client.utils.formatTime(duration)}\``, - }); + const embed = this.client + .embed() + .setColor(this.client.color.main) + .setAuthor({ + name: ctx.locale('cmd.nowplaying.now_playing'), + iconURL: ctx.guild?.iconURL({})!, + }) + .setThumbnail(track.info.artworkUrl!) + .setDescription( + ctx.locale('cmd.nowplaying.track_info', { + title: track.info.title, + uri: track.info.uri, + requester: (track.requester as any).id, + bar: bar, + }), + ) + .addFields({ + name: '\u200b', + value: `\`${client.utils.formatTime(position)} / ${client.utils.formatTime(duration)}\``, + }); - return await ctx.sendMessage({ embeds: [embed] }); - } + return await ctx.sendMessage({ embeds: [embed] }); + } } /** diff --git a/src/commands/music/Pause.ts b/src/commands/music/Pause.ts index 48923906b..0a78a879f 100644 --- a/src/commands/music/Pause.ts +++ b/src/commands/music/Pause.ts @@ -1,51 +1,51 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Pause extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "pause", - description: { - content: "cmd.pause.description", - examples: ["pause"], - usage: "pause", - }, - category: "music", - aliases: ["pu"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: false, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'pause', + description: { + content: 'cmd.pause.description', + examples: ['pause'], + usage: 'pause', + }, + category: 'music', + aliases: ['pu'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: false, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); - if (player?.paused) { - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("player.errors.already_paused"))], - }); - } + if (player?.paused) { + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('player.errors.already_paused'))], + }); + } - player?.pause(); + player?.pause(); - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale("cmd.pause.successfully_paused"))], - }); - } + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale('cmd.pause.successfully_paused'))], + }); + } } /** diff --git a/src/commands/music/Play.ts b/src/commands/music/Play.ts index 9ca06e567..b05483263 100644 --- a/src/commands/music/Play.ts +++ b/src/commands/music/Play.ts @@ -1,189 +1,129 @@ -import type { AutocompleteInteraction } from "discord.js"; -import { LoadType } from "shoukaku"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import type { ApplicationCommandOptionChoiceData, AutocompleteInteraction, VoiceChannel } from 'discord.js'; +import type { SearchResult } from 'lavalink-client'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Play extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "play", - description: { - content: "cmd.play.description", - examples: [ - "play example", - "play https://www.youtube.com/watch?v=example", - "play https://open.spotify.com/track/example", - "play http://www.example.com/example.mp3", - ], - usage: "play ", - }, - category: "music", - aliases: ["p"], - cooldown: 3, - args: true, - vote: false, - player: { - voice: true, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks", "Connect", "Speak"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "song", - description: "cmd.play.options.song", - type: 3, - required: true, - autocomplete: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'play', + description: { + content: 'cmd.play.description', + examples: [ + 'play example', + 'play https://www.youtube.com/watch?v=example', + 'play https://open.spotify.com/track/example', + 'play http://www.example.com/example.mp3', + ], + usage: 'play ', + }, + category: 'music', + aliases: ['p'], + cooldown: 3, + args: true, + vote: false, + player: { + voice: true, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks', 'Connect', 'Speak'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'song', + description: 'cmd.play.options.song', + type: 3, + required: true, + autocomplete: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const query = args.join(" "); - await ctx.sendDeferMessage(ctx.locale("cmd.play.loading")); - let player = client.queue.get(ctx.guild!.id); - const vc = ctx.member as any; - if (!player) player = await client.queue.create(ctx.guild, vc.voice.channel, ctx.channel); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const query = args.join(' '); + await ctx.sendDeferMessage(ctx.locale('cmd.play.loading')); + let player = client.manager.getPlayer(ctx.guild!.id); + const memberVoiceChannel = (ctx.member as any).voice.channel as VoiceChannel; - const res = await this.client.queue.search(query); - const embed = this.client.embed(); + if (!player) + player = client.manager.createPlayer({ + guildId: ctx.guild!.id, + voiceChannelId: memberVoiceChannel.id, + textChannelId: ctx.channel.id, + selfMute: false, + selfDeaf: true, + vcRegion: memberVoiceChannel.rtcRegion!, + }); + if (!player.connected) await player.connect(); - switch (res.loadType) { - case LoadType.ERROR: - await ctx.editMessage({ - content: "", - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.play.errors.search_error"))], - }); - break; - case LoadType.EMPTY: - await ctx.editMessage({ - content: "", - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.play.errors.no_results"))], - }); - break; - case LoadType.TRACK: { - const track = player.buildTrack(res.data, ctx.author); - if (player.queue.length > client.config.maxQueueSize) - return await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.red) - .setDescription(ctx.locale("cmd.play.errors.queue_too_long", { maxQueueSize: client.config.maxQueueSize })), - ], - }); - player.queue.push(track); - await player.isPlaying(); - await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.main) - .setDescription(ctx.locale("cmd.play.added_to_queue", { title: res.data.info.title, uri: res.data.info.uri })), - ], - }); - break; - } - case LoadType.PLAYLIST: { - if (res.data.tracks.length > client.config.maxPlaylistSize) - return await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.red) - .setDescription( - ctx.locale("cmd.play.errors.playlist_too_long", { maxPlaylistSize: client.config.maxPlaylistSize }), - ), - ], - }); - for (const track of res.data.tracks) { - const pl = player.buildTrack(track, ctx.author); - if (player.queue.length > client.config.maxQueueSize) - return await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.red) - .setDescription( - ctx.locale("cmd.play.errors.queue_too_long", { maxQueueSize: client.config.maxQueueSize }), - ), - ], - }); - player.queue.push(pl); - } - await player.isPlaying(); - await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.main) - .setDescription(ctx.locale("cmd.play.added_playlist_to_queue", { length: res.data.tracks.length })), - ], - }); - break; - } - case LoadType.SEARCH: { - const track1 = player.buildTrack(res.data[0], ctx.author); - if (player.queue.length > client.config.maxQueueSize) - return await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.red) - .setDescription(ctx.locale("cmd.play.errors.queue_too_long", { maxQueueSize: client.config.maxQueueSize })), - ], - }); - player.queue.push(track1); - await player.isPlaying(); - await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.main) - .setDescription( - ctx.locale("cmd.play.added_to_queue", { title: res.data[0].info.title, uri: res.data[0].info.uri }), - ), - ], - }); - break; - } - } - } - public async autocomplete(interaction: AutocompleteInteraction): Promise { - const focusedValue = interaction.options.getFocused(); + const response = (await player.search({ query: query }, ctx.author)) as SearchResult; + const embed = this.client.embed(); - const res = await this.client.queue.search(focusedValue); - const songs = []; + if (!response || response.tracks?.length === 0) { + return await ctx.editMessage({ + content: '', + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('cmd.play.errors.search_error'))], + }); + } - if (res?.loadType) { - if (res.loadType === LoadType.SEARCH && res.data.length > 0) { - res.data.slice(0, 10).forEach((track) => { - const name = `${track.info.title} by ${track.info.author}`; - songs.push({ - name: name.length > 100 ? `${name.substring(0, 97)}...` : name, - value: track.info.uri, - }); - }); - } else if (res.loadType === LoadType.PLAYLIST && res.data.tracks.length > 0) { - res.data.tracks.slice(0, 10).forEach((track) => { - const name = `${track.info.title} by ${track.info.author}`; - songs.push({ - name: name.length > 100 ? `${name.substring(0, 97)}...` : name, - value: track.info.uri, - }); - }); - } - } + await player.queue.add(response.loadType === 'playlist' ? response.tracks : response.tracks[0]); - return await interaction.respond(songs).catch(console.error); - } + if (response.loadType === 'playlist') { + await ctx.editMessage({ + content: '', + embeds: [ + embed + .setColor(this.client.color.main) + .setDescription(ctx.locale('cmd.play.added_playlist_to_queue', { length: response.tracks.length })), + ], + }); + } else { + await ctx.editMessage({ + content: '', + embeds: [ + embed.setColor(this.client.color.main).setDescription( + ctx.locale('cmd.play.added_to_queue', { + title: response.tracks[0].info.title, + uri: response.tracks[0].info.uri, + }), + ), + ], + }); + } + if (!player.playing) await player.play({ paused: false }); + } + public async autocomplete(interaction: AutocompleteInteraction): Promise { + const focusedValue = interaction.options.getFocused(); + + if (!focusedValue) { + return interaction.respond([]).catch(() => { + null; + }); + } + + const res = await this.client.manager.search(focusedValue, interaction.user); + const songs: ApplicationCommandOptionChoiceData[] = []; + + if (res.loadType === 'search') { + res.tracks.slice(0, 10).forEach(track => { + const name = `${track.info.title} by ${track.info.author}`; + songs.push({ + name: name.length > 100 ? `${name.substring(0, 97)}...` : name, + value: track.info.uri, + }); + }); + } + + return await interaction.respond(songs).catch(() => { + null; + }); + } } /** diff --git a/src/commands/music/PlayNext.ts b/src/commands/music/PlayNext.ts index 6ef71ee4c..a0b167532 100644 --- a/src/commands/music/PlayNext.ts +++ b/src/commands/music/PlayNext.ts @@ -1,187 +1,125 @@ -import type { AutocompleteInteraction } from "discord.js"; -import { LoadType } from "shoukaku"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import type { ApplicationCommandOptionChoiceData, AutocompleteInteraction, VoiceChannel } from 'discord.js'; +import type { SearchResult } from 'lavalink-client'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class PlayNext extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "playnext", - description: { - content: "cmd.playnext.description", - examples: [ - "playnext example", - "playnext https://www.youtube.com/watch?v=example", - "playnext https://open.spotify.com/track/example", - "playnext http://www.example.com/example.mp3", - ], - usage: "playnext ", - }, - category: "music", - aliases: ["pn"], - cooldown: 3, - args: true, - vote: false, - player: { - voice: true, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks", "Connect", "Speak"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "song", - description: "cmd.playnext.options.song", - type: 3, - required: true, - autocomplete: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'playnext', + description: { + content: 'cmd.playnext.description', + examples: [ + 'playnext example', + 'playnext https://www.youtube.com/watch?v=example', + 'playnext https://open.spotify.com/track/example', + 'playnext http://www.example.com/example.mp3', + ], + usage: 'playnext ', + }, + category: 'music', + aliases: ['pn'], + cooldown: 3, + args: true, + vote: false, + player: { + voice: true, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks', 'Connect', 'Speak'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'song', + description: 'cmd.playnext.options.song', + type: 3, + required: true, + autocomplete: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const query = args.join(" "); - let player = client.queue.get(ctx.guild!.id); - const vc = ctx.member as any; - if (!player) player = await client.queue.create(ctx.guild, vc.voice.channel, ctx.channel); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const query = args.join(' '); + let player = client.manager.getPlayer(ctx.guild!.id); + const memberVoiceChannel = (ctx.member as any).voice.channel as VoiceChannel; - await ctx.sendDeferMessage(ctx.locale("cmd.playnext.loading")); - const res = await this.client.queue.search(query); - const embed = this.client.embed(); - switch (res.loadType) { - case LoadType.ERROR: - await ctx.editMessage({ - content: "", - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.playnext.errors.search_error"))], - }); - break; - case LoadType.EMPTY: - await ctx.editMessage({ - content: "", - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.playnext.errors.no_results"))], - }); - break; - case LoadType.TRACK: { - const track = player.buildTrack(res.data, ctx.author); - if (player.queue.length > client.config.maxQueueSize) - return await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.red) - .setDescription( - ctx.locale("cmd.playnext.errors.queue_too_long", { maxQueueSize: client.config.maxQueueSize }), - ), - ], - }); - player.queue.splice(0, 0, track); - await player.isPlaying(); - await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.main) - .setDescription( - ctx.locale("cmd.playnext.added_to_play_next", { title: res.data.info.title, uri: res.data.info.uri }), - ), - ], - }); - break; - } - case LoadType.PLAYLIST: { - if (res.data.tracks.length > client.config.maxPlaylistSize) - return await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.red) - .setDescription( - ctx.locale("cmd.playnext.errors.playlist_too_long", { maxPlaylistSize: client.config.maxPlaylistSize }), - ), - ], - }); - for (const track of res.data.tracks) { - const pl = player.buildTrack(track, ctx.author); - if (player.queue.length > client.config.maxQueueSize) - return await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.red) - .setDescription( - ctx.locale("cmd.playnext.errors.queue_too_long", { maxQueueSize: client.config.maxQueueSize }), - ), - ], - }); - player.queue.splice(0, 0, pl); - } - await player.isPlaying(); - await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.main) - .setDescription(ctx.locale("cmd.playnext.added_playlist_to_play_next", { length: res.data.tracks.length })), - ], - }); - break; - } - case LoadType.SEARCH: { - const track1 = player.buildTrack(res.data[0], ctx.author); - if (player.queue.length > client.config.maxQueueSize) - return await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.red) - .setDescription( - ctx.locale("cmd.playnext.errors.queue_too_long", { maxQueueSize: client.config.maxQueueSize }), - ), - ], - }); - player.queue.splice(0, 0, track1); - await player.isPlaying(); - await ctx.editMessage({ - content: "", - embeds: [ - embed - .setColor(this.client.color.main) - .setDescription( - ctx.locale("cmd.playnext.added_to_play_next", { title: res.data[0].info.title, uri: res.data[0].info.uri }), - ), - ], - }); - break; - } - } - } - public async autocomplete(interaction: AutocompleteInteraction): Promise { - const focusedValue = interaction.options.getFocused(); + if (!player) + player = client.manager.createPlayer({ + guildId: ctx.guild!.id, + voiceChannelId: memberVoiceChannel.id, + textChannelId: ctx.channel.id, + selfMute: false, + selfDeaf: true, + vcRegion: memberVoiceChannel.rtcRegion!, + }); + if (!player.connected) await player.connect(); - const res = await this.client.queue.search(focusedValue); - const songs = []; + await ctx.sendDeferMessage(ctx.locale('cmd.playnext.loading')); - if (res.loadType === LoadType.SEARCH && res.data.length > 0) { - res.data.slice(0, 10).forEach((x) => { - let name = `${x.info.title} by ${x.info.author}`; - if (name.length > 100) { - name = `${name.substring(0, 97)}...`; - } - songs.push({ - name: name, - value: x.info.uri, - }); - }); - } + const response = (await player.search({ query: query }, ctx.author)) as SearchResult; + const embed = this.client.embed(); - return await interaction.respond(songs).catch(console.error); - } + if (!response || response.tracks?.length === 0) { + return await ctx.editMessage({ + content: '', + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('cmd.play.errors.search_error'))], + }); + } + await player.queue.splice(0, 0, response.loadType === 'playlist' ? response.tracks : response.tracks[0]); + + if (response.loadType === 'playlist') { + await ctx.editMessage({ + content: '', + embeds: [ + embed + .setColor(this.client.color.main) + .setDescription(ctx.locale('cmd.playnext.added_playlist_to_play_next', { length: response.tracks.length })), + ], + }); + } else { + await ctx.editMessage({ + content: '', + embeds: [ + embed.setColor(this.client.color.main).setDescription( + ctx.locale('cmd.playnext.added_to_play_next', { + title: response.tracks[0].info.title, + uri: response.tracks[0].info.uri, + }), + ), + ], + }); + } + if (!player.playing) await player.play({ paused: false }); + } + public async autocomplete(interaction: AutocompleteInteraction): Promise { + const focusedValue = interaction.options.getFocused(); + + if (!focusedValue) { + return; + } + + const res = await this.client.manager.search(focusedValue, interaction.user); + const songs: ApplicationCommandOptionChoiceData[] = []; + + if (res.loadType === 'search') { + res.tracks.slice(0, 10).forEach(track => { + const name = `${track.info.title} by ${track.info.author}`; + songs.push({ + name: name.length > 100 ? `${name.substring(0, 97)}...` : name, + value: track.info.uri, + }); + }); + } + + return await interaction.respond(songs).catch(console.error); + } } /** diff --git a/src/commands/music/Queue.ts b/src/commands/music/Queue.ts index a6707fcc7..947e75349 100644 --- a/src/commands/music/Queue.ts +++ b/src/commands/music/Queue.ts @@ -1,89 +1,89 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Queue extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "queue", - description: { - content: "cmd.queue.description", - examples: ["queue"], - usage: "queue", - }, - category: "music", - aliases: ["q"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: false, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'queue', + description: { + content: 'cmd.queue.description', + examples: ['queue'], + usage: 'queue', + }, + category: 'music', + aliases: ['q'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: false, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); - if (player.queue.length === 0) { - return await ctx.sendMessage({ - embeds: [ - embed.setColor(this.client.color.main).setDescription( - ctx.locale("cmd.queue.now_playing", { - title: player.current.info.title, - uri: player.current.info.uri, - requester: player.current?.info.requester, - duration: player.current.info.isStream - ? ctx.locale("cmd.queue.live") - : client.utils.formatTime(player.current.info.length), - }), - ), - ], - }); - } - const songStrings = []; - for (let i = 0; i < player.queue.length; i++) { - const track = player.queue[i]; - songStrings.push( - ctx.locale("cmd.queue.track_info", { - index: i + 1, - title: track.info.title, - uri: track.info.uri, - requester: track?.info.requester, - duration: track.info.isStream ? ctx.locale("cmd.queue.live") : client.utils.formatTime(track.info.length), - }), - ); - } - let chunks = client.utils.chunk(songStrings, 10); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); + if (player.queue.current && player.queue.tracks.length === 0) { + return await ctx.sendMessage({ + embeds: [ + embed.setColor(this.client.color.main).setDescription( + ctx.locale('cmd.queue.now_playing', { + title: player.queue.current.info.title, + uri: player.queue.current.info.uri, + requester: (player.queue.current.requester as any).id, + duration: player.queue.current.info.isStream + ? ctx.locale('cmd.queue.live') + : client.utils.formatTime(player.queue.current.info.duration), + }), + ), + ], + }); + } + const songStrings: string[] = []; + for (let i = 0; i < player.queue.tracks.length; i++) { + const track = player.queue.tracks[i]; + songStrings.push( + ctx.locale('cmd.queue.track_info', { + index: i + 1, + title: track.info.title, + uri: track.info.uri, + requester: (track.requester as any).id, + duration: track.info.isStream ? ctx.locale('cmd.queue.live') : client.utils.formatTime(track.info.duration!), + }), + ); + } + let chunks = client.utils.chunk(songStrings, 10); - if (chunks.length === 0) chunks = [songStrings]; + if (chunks.length === 0) chunks = [songStrings]; - const pages = chunks.map((chunk, index) => { - return this.client - .embed() - .setColor(this.client.color.main) - .setAuthor({ - name: ctx.locale("cmd.queue.title"), - iconURL: ctx.guild.iconURL({}), - }) - .setDescription(chunk.join("\n")) - .setFooter({ - text: ctx.locale("cmd.queue.page_info", { - index: index + 1, - total: chunks.length, - }), - }); - }); - return await client.utils.paginate(client, ctx, pages); - } + const pages = chunks.map((chunk, index) => { + return this.client + .embed() + .setColor(this.client.color.main) + .setAuthor({ + name: ctx.locale('cmd.queue.title'), + iconURL: ctx.guild.icon ? ctx.guild.iconURL()! : ctx.author?.displayAvatarURL(), + }) + .setDescription(chunk.join('\n')) + .setFooter({ + text: ctx.locale('cmd.queue.page_info', { + index: index + 1, + total: chunks.length, + }), + }); + }); + return await client.utils.paginate(client, ctx, pages); + } } /** diff --git a/src/commands/music/Remove.ts b/src/commands/music/Remove.ts index 83445236a..92d7264a8 100644 --- a/src/commands/music/Remove.ts +++ b/src/commands/music/Remove.ts @@ -1,68 +1,68 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Remove extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "remove", - description: { - content: "cmd.remove.description", - examples: ["remove 1"], - usage: "remove ", - }, - category: "music", - aliases: ["rm"], - cooldown: 3, - args: true, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "song", - description: "cmd.remove.options.song", - type: 4, - required: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'remove', + description: { + content: 'cmd.remove.description', + examples: ['remove 1'], + usage: 'remove ', + }, + category: 'music', + aliases: ['rm'], + cooldown: 3, + args: true, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'song', + description: 'cmd.remove.options.song', + type: 4, + required: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); - if (player.queue.length === 0) - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.remove.errors.no_songs"))], - }); + if (player.queue.tracks.length === 0) + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('cmd.remove.errors.no_songs'))], + }); - const songNumber = Number(args[0]); - if (isNaN(songNumber) || songNumber <= 0 || songNumber > player.queue.length) - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.remove.errors.invalid_number"))], - }); + const songNumber = Number(args[0]); + if (Number.isNaN(songNumber) || songNumber <= 0 || songNumber > player.queue.tracks.length) + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('cmd.remove.errors.invalid_number'))], + }); - player.remove(songNumber - 1); - return await ctx.sendMessage({ - embeds: [ - embed.setColor(this.client.color.main).setDescription( - ctx.locale("cmd.remove.messages.removed", { - songNumber, - }), - ), - ], - }); - } + player.queue.remove(songNumber - 1); + return await ctx.sendMessage({ + embeds: [ + embed.setColor(this.client.color.main).setDescription( + ctx.locale('cmd.remove.messages.removed', { + songNumber, + }), + ), + ], + }); + } } /** diff --git a/src/commands/music/Replay.ts b/src/commands/music/Replay.ts index d1a32907d..5b63284b4 100644 --- a/src/commands/music/Replay.ts +++ b/src/commands/music/Replay.ts @@ -1,50 +1,50 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Replay extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "replay", - description: { - content: "cmd.replay.description", - examples: ["replay"], - usage: "replay", - }, - category: "music", - aliases: ["rp"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: false, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'replay', + description: { + content: 'cmd.replay.description', + examples: ['replay'], + usage: 'replay', + }, + category: 'music', + aliases: ['rp'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: false, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); - if (!player.current?.info.isSeekable) { - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.replay.errors.not_seekable"))], - }); - } + if (!player.queue.current?.info.isSeekable) { + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('cmd.replay.errors.not_seekable'))], + }); + } - player.seek(0); - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale("cmd.replay.messages.replaying"))], - }); - } + player.seek(0); + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale('cmd.replay.messages.replaying'))], + }); + } } /** diff --git a/src/commands/music/Resume.ts b/src/commands/music/Resume.ts index d55eddfa5..020d6ea7a 100644 --- a/src/commands/music/Resume.ts +++ b/src/commands/music/Resume.ts @@ -1,50 +1,50 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Resume extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "resume", - description: { - content: "cmd.resume.description", - examples: ["resume"], - usage: "resume", - }, - category: "music", - aliases: ["r"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: false, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'resume', + description: { + content: 'cmd.resume.description', + examples: ['resume'], + usage: 'resume', + }, + category: 'music', + aliases: ['r'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: false, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); - if (!player.paused) { - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.resume.errors.not_paused"))], - }); - } + if (!player.paused) { + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('cmd.resume.errors.not_paused'))], + }); + } - player.pause(); - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale("cmd.resume.messages.resumed"))], - }); - } + player.resume(); + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale('cmd.resume.messages.resumed'))], + }); + } } /** diff --git a/src/commands/music/Search.ts b/src/commands/music/Search.ts index a26412b72..3735bbde7 100644 --- a/src/commands/music/Search.ts +++ b/src/commands/music/Search.ts @@ -1,124 +1,112 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; -import { LoadType } from "shoukaku"; -import type { Song } from "../../structures/Dispatcher.js"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type TextChannel, type VoiceChannel } from 'discord.js'; +import type { SearchResult, Track } from 'lavalink-client'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Search extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "search", - description: { - content: "cmd.search.description", - examples: ["search example"], - usage: "search ", - }, - category: "music", - aliases: ["sc"], - cooldown: 3, - args: true, - vote: true, - player: { - voice: true, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "song", - description: "cmd.search.options.song", - type: 3, - required: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'search', + description: { + content: 'cmd.search.description', + examples: ['search example'], + usage: 'search ', + }, + category: 'music', + aliases: ['sc'], + cooldown: 3, + args: true, + vote: true, + player: { + voice: true, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'song', + description: 'cmd.search.options.song', + type: 3, + required: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const embed = this.client.embed().setColor(this.client.color.main); - let player = client.queue.get(ctx.guild!.id); - const query = args.join(" "); - if (!player) { - const vc = ctx.member as any; - player = await client.queue.create( - ctx.guild, - vc.voice.channel, - ctx.channel, - client.shoukaku.options.nodeResolver(client.shoukaku.nodes), - ); - } - const res = await this.client.queue.search(query); - if (!res) { - return await ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.search.errors.no_results")).setColor(this.client.color.red)], - }); - } - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder().setCustomId("1").setLabel("1").setStyle(ButtonStyle.Primary), - new ButtonBuilder().setCustomId("2").setLabel("2").setStyle(ButtonStyle.Primary), - new ButtonBuilder().setCustomId("3").setLabel("3").setStyle(ButtonStyle.Primary), - new ButtonBuilder().setCustomId("4").setLabel("4").setStyle(ButtonStyle.Primary), - new ButtonBuilder().setCustomId("5").setLabel("5").setStyle(ButtonStyle.Primary), - ); - switch (res.loadType) { - case LoadType.ERROR: - ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.search.errors.search_error"))], - }); - break; - case LoadType.EMPTY: - ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.search.errors.no_results"))], - }); - break; - case LoadType.SEARCH: { - const tracks = res.data.slice(0, 5); - const embeds = tracks.map( - (track: Song, index: number) => `${index + 1}. [${track.info.title}](${track.info.uri}) - \`${track.info.author}\``, - ); - await ctx.sendMessage({ - embeds: [embed.setDescription(embeds.join("\n"))], - components: [row], - }); - break; - } - } - const collector = ctx.channel.createMessageComponentCollector({ - filter: (f: any) => f.user.id === ctx.author.id, - max: 1, - time: 60000, - idle: 60000 / 2, - }); - collector.on("collect", async (int: any) => { - const track = res.data[parseInt(int.customId) - 1]; - await int.deferUpdate(); - if (!track) return; - const song = player.buildTrack(track, ctx.author); - player.queue.push(song); - player.isPlaying(); - await ctx.editMessage({ - embeds: [ - embed.setDescription( - ctx.locale("cmd.search.messages.added_to_queue", { - title: song.info.title, - uri: song.info.uri, - }), - ), - ], - components: [], - }); - return collector.stop(); - }); - collector.on("end", async () => { - await ctx.editMessage({ components: [] }); - }); - } + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const embed = this.client.embed().setColor(this.client.color.main); + let player = client.manager.getPlayer(ctx.guild!.id); + const query = args.join(' '); + const memberVoiceChannel = (ctx.member as any).voice.channel as VoiceChannel; + + if (!player) + player = client.manager.createPlayer({ + guildId: ctx.guild!.id, + voiceChannelId: memberVoiceChannel.id, + textChannelId: ctx.channel.id, + selfMute: false, + selfDeaf: true, + vcRegion: memberVoiceChannel.rtcRegion!, + }); + if (!player.connected) await player.connect(); + const response = (await player.search({ query: query }, ctx.author)) as SearchResult; + if (!response || response.tracks?.length === 0) { + return await ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.search.errors.no_results')).setColor(this.client.color.red)], + }); + } + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder().setCustomId('1').setLabel('1').setStyle(ButtonStyle.Primary), + new ButtonBuilder().setCustomId('2').setLabel('2').setStyle(ButtonStyle.Primary), + new ButtonBuilder().setCustomId('3').setLabel('3').setStyle(ButtonStyle.Primary), + new ButtonBuilder().setCustomId('4').setLabel('4').setStyle(ButtonStyle.Primary), + new ButtonBuilder().setCustomId('5').setLabel('5').setStyle(ButtonStyle.Primary), + ); + if (response.loadType === 'search' && response.tracks.length > 5) { + const embeds = response.tracks.map( + (track: Track, index: number) => + `${index + 1}. [${track.info.title}](${track.info.uri}) - \`${track.info.author}\``, + ); + await ctx.sendMessage({ + embeds: [embed.setDescription(embeds.join('\n'))], + components: [row], + }); + } + const collector = (ctx.channel as TextChannel).createMessageComponentCollector({ + filter: (f: any) => f.user.id === ctx.author?.id, + max: 1, + time: 60000, + idle: 60000 / 2, + }); + collector.on('collect', async (int: any) => { + const track = response.tracks[Number.parseInt(int.customId) - 1]; + await int.deferUpdate(); + if (!track) return; + player.queue.add(track); + if (!player.playing) await player.play({ paused: false }); + await ctx.editMessage({ + embeds: [ + embed.setDescription( + ctx.locale('cmd.search.messages.added_to_queue', { + title: track.info.title, + uri: track.info.uri, + }), + ), + ], + components: [], + }); + return collector.stop(); + }); + collector.on('end', async () => { + await ctx.editMessage({ components: [] }); + }); + } } /** diff --git a/src/commands/music/Seek.ts b/src/commands/music/Seek.ts index f17c21489..8b62f79a3 100644 --- a/src/commands/music/Seek.ts +++ b/src/commands/music/Seek.ts @@ -1,79 +1,79 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Seek extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "seek", - description: { - content: "cmd.seek.description", - examples: ["seek 1m, seek 1h 30m", "seek 1h 30m 30s"], - usage: "seek ", - }, - category: "music", - aliases: ["s"], - cooldown: 3, - args: true, - vote: false, - player: { - voice: true, - dj: false, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "duration", - description: "cmd.seek.options.duration", - type: 3, - required: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'seek', + description: { + content: 'cmd.seek.description', + examples: ['seek 1m, seek 1h 30m', 'seek 1h 30m 30s'], + usage: 'seek ', + }, + category: 'music', + aliases: ['s'], + cooldown: 3, + args: true, + vote: false, + player: { + voice: true, + dj: false, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'duration', + description: 'cmd.seek.options.duration', + type: 3, + required: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const player = client.queue.get(ctx.guild!.id); - const current = player.current.info; - const embed = this.client.embed(); - const duration = client.utils.parseTime(args.join(" ")); - if (!duration) { - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.seek.errors.invalid_format"))], - }); - } - if (!current.isSeekable) { - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.seek.errors.not_seekable"))], - }); - } - if (duration > current.length) { - return await ctx.sendMessage({ - embeds: [ - embed.setColor(this.client.color.red).setDescription( - ctx.locale("cmd.seek.errors.beyond_duration", { - length: client.utils.formatTime(current.length), - }), - ), - ], - }); - } - player.seek(duration); - return await ctx.sendMessage({ - embeds: [ - embed.setColor(this.client.color.main).setDescription( - ctx.locale("cmd.seek.messages.seeked_to", { - duration: client.utils.formatTime(duration), - }), - ), - ], - }); - } + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const current = player.queue.current?.info; + const embed = this.client.embed(); + const duration = client.utils.parseTime(args.join(' ')); + if (!duration) { + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('cmd.seek.errors.invalid_format'))], + }); + } + if (!current?.isSeekable) { + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('cmd.seek.errors.not_seekable'))], + }); + } + if (duration > current.duration) { + return await ctx.sendMessage({ + embeds: [ + embed.setColor(this.client.color.red).setDescription( + ctx.locale('cmd.seek.errors.beyond_duration', { + length: client.utils.formatTime(current.duration), + }), + ), + ], + }); + } + player.seek(duration); + return await ctx.sendMessage({ + embeds: [ + embed.setColor(this.client.color.main).setDescription( + ctx.locale('cmd.seek.messages.seeked_to', { + duration: client.utils.formatTime(duration), + }), + ), + ], + }); + } } /** diff --git a/src/commands/music/Shuffle.ts b/src/commands/music/Shuffle.ts index 0fb4d908b..4a3eeaaf4 100644 --- a/src/commands/music/Shuffle.ts +++ b/src/commands/music/Shuffle.ts @@ -1,48 +1,48 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Shuffle extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "shuffle", - description: { - content: "cmd.shuffle.description", - examples: ["shuffle"], - usage: "shuffle", - }, - category: "music", - aliases: ["sh"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'shuffle', + description: { + content: 'cmd.shuffle.description', + examples: ['shuffle'], + usage: 'shuffle', + }, + category: 'music', + aliases: ['sh'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); - if (player.queue.length === 0) { - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("player.errors.no_song"))], - }); - } - player.setShuffle(); - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale("cmd.shuffle.messages.shuffled"))], - }); - } + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); + if (player.queue.tracks.length === 0) { + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('player.errors.no_song'))], + }); + } + player.queue.shuffle(); + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale('cmd.shuffle.messages.shuffled'))], + }); + } } /** diff --git a/src/commands/music/Skip.ts b/src/commands/music/Skip.ts index 057b415a5..e31aa161e 100644 --- a/src/commands/music/Skip.ts +++ b/src/commands/music/Skip.ts @@ -1,59 +1,59 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Skip extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "skip", - description: { - content: "cmd.skip.description", - examples: ["skip"], - usage: "skip", - }, - category: "music", - aliases: ["sk"], - cooldown: 3, - args: false, - vote: true, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'skip', + description: { + content: 'cmd.skip.description', + examples: ['skip'], + usage: 'skip', + }, + category: 'music', + aliases: ['sk'], + cooldown: 3, + args: false, + vote: true, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); - if (player.queue.length === 0) { - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("player.errors.no_song"))], - }); - } - const currentTrack = player.current.info; - player.skip(); - if (ctx.isInteraction) { - return await ctx.sendMessage({ - embeds: [ - embed.setColor(this.client.color.main).setDescription( - ctx.locale("cmd.skip.messages.skipped", { - title: currentTrack.title, - uri: currentTrack.uri, - }), - ), - ], - }); - } - ctx.message?.react("👍"); - } + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); + if (player.queue.tracks.length === 0) { + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('player.errors.no_song'))], + }); + } + const currentTrack = player.queue.current?.info; + player.skip(); + if (ctx.isInteraction) { + return await ctx.sendMessage({ + embeds: [ + embed.setColor(this.client.color.main).setDescription( + ctx.locale('cmd.skip.messages.skipped', { + title: currentTrack?.title, + uri: currentTrack?.uri, + }), + ), + ], + }); + } + ctx.message?.react('👍'); + } } /** diff --git a/src/commands/music/Skipto.ts b/src/commands/music/Skipto.ts index e0bad9876..5820d1c10 100644 --- a/src/commands/music/Skipto.ts +++ b/src/commands/music/Skipto.ts @@ -1,64 +1,64 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Skipto extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "skipto", - description: { - content: "cmd.skipto.description", - examples: ["skipto 3"], - usage: "skipto ", - }, - category: "music", - aliases: ["skt"], - cooldown: 3, - args: true, - vote: true, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "number", - description: "cmd.skipto.options.number", - type: 4, - required: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'skipto', + description: { + content: 'cmd.skipto.description', + examples: ['skipto 3'], + usage: 'skipto ', + }, + category: 'music', + aliases: ['skt'], + cooldown: 3, + args: true, + vote: true, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'number', + description: 'cmd.skipto.options.number', + type: 4, + required: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); - const num = Number(args[0]); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); + const num = Number(args[0]); - if (player.queue.length === 0 || isNaN(num) || num > player.queue.length || num < 1) { - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale("cmd.skipto.errors.invalid_number"))], - }); - } + if (player.queue.tracks.length === 0 || Number.isNaN(num) || num > player.queue.tracks.length || num < 1) { + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(ctx.locale('cmd.skipto.errors.invalid_number'))], + }); + } - player.skip(num); - return await ctx.sendMessage({ - embeds: [ - embed.setColor(this.client.color.main).setDescription( - ctx.locale("cmd.skipto.messages.skipped_to", { - number: num, - }), - ), - ], - }); - } + player.skip(num); + return await ctx.sendMessage({ + embeds: [ + embed.setColor(this.client.color.main).setDescription( + ctx.locale('cmd.skipto.messages.skipped_to', { + number: num, + }), + ), + ], + }); + } } /** diff --git a/src/commands/music/Stop.ts b/src/commands/music/Stop.ts index e8f97a802..12054564c 100644 --- a/src/commands/music/Stop.ts +++ b/src/commands/music/Stop.ts @@ -1,46 +1,45 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Stop extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "stop", - description: { - content: "cmd.stop.description", - examples: ["stop"], - usage: "stop", - }, - category: "music", - aliases: ["sp"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'stop', + description: { + content: 'cmd.stop.description', + examples: ['stop'], + usage: 'stop', + }, + category: 'music', + aliases: ['sp'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); + public async run(client: Lavamusic, ctx: Context): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); - player.queue = []; - player.stop(); + player.stopPlaying(true, false); - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale("cmd.stop.messages.stopped"))], - }); - } + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.main).setDescription(ctx.locale('cmd.stop.messages.stopped'))], + }); + } } /** diff --git a/src/commands/music/Volume.ts b/src/commands/music/Volume.ts index 36ef52acb..91dcc0f7f 100644 --- a/src/commands/music/Volume.ts +++ b/src/commands/music/Volume.ts @@ -1,71 +1,71 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class Volume extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "volume", - description: { - content: "cmd.volume.description", - examples: ["volume 100"], - usage: "volume ", - }, - category: "music", - aliases: ["v", "vol"], - cooldown: 3, - args: true, - vote: true, - player: { - voice: true, - dj: true, - active: true, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "number", - description: "cmd.volume.options.number", - type: 4, - required: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'volume', + description: { + content: 'cmd.volume.description', + examples: ['volume 100'], + usage: 'volume ', + }, + category: 'music', + aliases: ['v', 'vol'], + cooldown: 3, + args: true, + vote: true, + player: { + voice: true, + dj: true, + active: true, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'number', + description: 'cmd.volume.options.number', + type: 4, + required: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const player = client.queue.get(ctx.guild!.id); - const embed = this.client.embed(); - const number = Number(args[0]); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const player = client.manager.getPlayer(ctx.guild!.id); + const embed = this.client.embed(); + const number = Number(args[0]); - if (isNaN(number) || number < 0 || number > 200) { - let description = ""; - if (isNaN(number)) description = ctx.locale("cmd.volume.messages.invalid_number"); - else if (number < 0) description = ctx.locale("cmd.volume.messages.too_low"); - else if (number > 200) description = ctx.locale("cmd.volume.messages.too_high"); + if (Number.isNaN(number) || number < 0 || number > 200) { + let description = ''; + if (Number.isNaN(number)) description = ctx.locale('cmd.volume.messages.invalid_number'); + else if (number < 0) description = ctx.locale('cmd.volume.messages.too_low'); + else if (number > 200) description = ctx.locale('cmd.volume.messages.too_high'); - return await ctx.sendMessage({ - embeds: [embed.setColor(this.client.color.red).setDescription(description)], - }); - } + return await ctx.sendMessage({ + embeds: [embed.setColor(this.client.color.red).setDescription(description)], + }); + } - await player.player.setGlobalVolume(number); - const currentVolume = player.player.volume; + await player.setVolume(number); + const currentVolume = player.volume; - return await ctx.sendMessage({ - embeds: [ - embed.setColor(this.client.color.main).setDescription( - ctx.locale("cmd.volume.messages.set", { - volume: currentVolume, - }), - ), - ], - }); - } + return await ctx.sendMessage({ + embeds: [ + embed.setColor(this.client.color.main).setDescription( + ctx.locale('cmd.volume.messages.set', { + volume: currentVolume, + }), + ), + ], + }); + } } /** diff --git a/src/commands/playlist/AddSong.ts b/src/commands/playlist/AddSong.ts index c4ef21ff3..491bf0367 100644 --- a/src/commands/playlist/AddSong.ts +++ b/src/commands/playlist/AddSong.ts @@ -1,146 +1,139 @@ -import type { AutocompleteInteraction } from "discord.js"; -import { LoadType } from "shoukaku"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import type { AutocompleteInteraction } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class AddSong extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "addsong", - description: { - content: "cmd.addsong.description", - examples: ["addsong test exemple", "addsong exemple https://www.youtube.com/watch?v=example"], - usage: "addsong ", - }, - category: "playlist", - aliases: ["as"], - cooldown: 3, - args: true, - vote: true, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "playlist", - description: "cmd.addsong.options.playlist", - type: 3, - required: true, - autocomplete: true, - }, - { - name: "song", - description: "cmd.addsong.options.song", - type: 3, - required: true, - }, - ], - }); - } - - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const playlist = args.shift(); - const song = args.join(" "); - - if (!playlist) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.addsong.messages.no_playlist"), - color: this.client.color.red, - }, - ], - }); - } - - if (!song) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.addsong.messages.no_song"), - color: this.client.color.red, - }, - ], - }); - } - const res = await client.queue.search(song); - - if (!res || res.loadType === LoadType.EMPTY) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.addsong.messages.no_songs_found"), - color: this.client.color.red, - }, - ], - }); - } - - const playlistData = await client.db.getPlaylist(ctx.author.id, playlist); - - if (!playlistData) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.addsong.messages.playlist_not_found"), - color: this.client.color.red, - }, - ], - }); - } - - let trackStrings: any; - let count: number; - - if (res.loadType === LoadType.PLAYLIST) { - trackStrings = res.data.tracks; - count = res.data.tracks.length; - } else if (res.loadType === LoadType.TRACK) { - trackStrings = [res.data]; - count = 1; - } else if (res.loadType === LoadType.SEARCH) { - trackStrings = [res.data[0]]; - count = 1; - } - - await client.db.addSong(ctx.author.id, playlist, trackStrings); - - await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.addsong.messages.added", { - count, - playlist: playlistData.name, - }), - color: this.client.color.main, - }, - ], - }); - } - - public async autocomplete(interaction: AutocompleteInteraction): Promise { - const focusedValue = interaction.options.getFocused(); - const userId = interaction.user.id; - - const playlists = await this.client.db.getUserPlaylists(userId); - - const filtered = playlists.filter((playlist) => playlist.name.toLowerCase().startsWith(focusedValue.toLowerCase())); - - return await interaction.respond( - filtered.map((playlist) => ({ - name: playlist.name, - value: playlist.name, - })), - ); - } + constructor(client: Lavamusic) { + super(client, { + name: 'addsong', + description: { + content: 'cmd.addsong.description', + examples: ['addsong test exemple', 'addsong exemple https://www.youtube.com/watch?v=example'], + usage: 'addsong ', + }, + category: 'playlist', + aliases: ['as'], + cooldown: 3, + args: true, + vote: true, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'playlist', + description: 'cmd.addsong.options.playlist', + type: 3, + required: true, + autocomplete: true, + }, + { + name: 'song', + description: 'cmd.addsong.options.song', + type: 3, + required: true, + }, + ], + }); + } + + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const playlist = args.shift(); + const song = args.join(' '); + + if (!playlist) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.addsong.messages.no_playlist'), + color: this.client.color.red, + }, + ], + }); + } + + if (!song) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.addsong.messages.no_song'), + color: this.client.color.red, + }, + ], + }); + } + const res = await client.manager.search(song, ctx.author); + if (!res) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.addsong.messages.no_songs_found'), + color: this.client.color.red, + }, + ], + }); + } + + const playlistData = await client.db.getPlaylist(ctx.author?.id!, playlist); + if (!playlistData) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.addsong.messages.playlist_not_found'), + color: this.client.color.red, + }, + ], + }); + } + + let trackStrings: any; + let count = 0; + if (res.loadType === 'playlist') { + trackStrings = res.tracks.map(track => track.encoded); + count = res.tracks.length; + } else if (res.loadType === 'track') { + trackStrings = [res.tracks[0].encoded]; + count = 1; + } else if (res.loadType === 'search') { + trackStrings = [res.tracks[0].encoded]; + count = 1; + } + + await client.db.addTracksToPlaylist(ctx.author?.id!, playlist, trackStrings); + + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.addsong.messages.added', { playlist: playlistData.name, count }), + color: this.client.color.green, + }, + ], + }); + } + + public async autocomplete(interaction: AutocompleteInteraction): Promise { + const focusedValue = interaction.options.getFocused(); + const userId = interaction.user.id; + + const playlists = await this.client.db.getUserPlaylists(userId); + + const filtered = playlists.filter(playlist => playlist.name.toLowerCase().startsWith(focusedValue.toLowerCase())); + + return await interaction.respond( + filtered.map(playlist => ({ + name: playlist.name, + value: playlist.name, + })), + ); + } } /** diff --git a/src/commands/playlist/Create.ts b/src/commands/playlist/Create.ts index c6b80a7ea..2aa9d947b 100644 --- a/src/commands/playlist/Create.ts +++ b/src/commands/playlist/Create.ts @@ -1,72 +1,74 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class CreatePlaylist extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "create", - description: { - content: "cmd.create.description", - examples: ["create "], - usage: "create ", - }, - category: "playlist", - aliases: ["cre"], - cooldown: 3, - args: true, - vote: true, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "name", - description: "cmd.create.options.name", - type: 3, - required: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'create', + description: { + content: 'cmd.create.description', + examples: ['create '], + usage: 'create ', + }, + category: 'playlist', + aliases: ['cre'], + cooldown: 3, + args: true, + vote: true, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'name', + description: 'cmd.create.options.name', + type: 3, + required: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const name = args.join(" ").trim(); - const embed = this.client.embed(); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const name = args.join(' ').trim(); + const embed = this.client.embed(); - if (name.length > 50) { - return await ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.create.messages.name_too_long")).setColor(this.client.color.red)], - }); - } + if (name.length > 50) { + return await ctx.sendMessage({ + embeds: [embed.setDescription(ctx.locale('cmd.create.messages.name_too_long')).setColor(this.client.color.red)], + }); + } - const playlistExists = await client.db.getPlaylist(ctx.author.id, name); - if (playlistExists) { - return await ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.create.messages.playlist_exists")).setColor(this.client.color.red)], - }); - } + const playlistExists = await client.db.getPlaylist(ctx.author?.id!, name); + if (playlistExists) { + return await ctx.sendMessage({ + embeds: [ + embed.setDescription(ctx.locale('cmd.create.messages.playlist_exists')).setColor(this.client.color.red), + ], + }); + } - await client.db.createPlaylist(ctx.author.id, name); - return await ctx.sendMessage({ - embeds: [ - embed - .setDescription( - ctx.locale("cmd.create.messages.playlist_created", { - name, - }), - ) - .setColor(this.client.color.green), - ], - }); - } + await client.db.createPlaylist(ctx.author?.id!, name); + return await ctx.sendMessage({ + embeds: [ + embed + .setDescription( + ctx.locale('cmd.create.messages.playlist_created', { + name, + }), + ) + .setColor(this.client.color.green), + ], + }); + } } /** diff --git a/src/commands/playlist/Delete.ts b/src/commands/playlist/Delete.ts index 663bd4518..4a0846e9b 100644 --- a/src/commands/playlist/Delete.ts +++ b/src/commands/playlist/Delete.ts @@ -1,87 +1,89 @@ -import type { AutocompleteInteraction } from "discord.js"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import type { AutocompleteInteraction } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class DeletePlaylist extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "delete", - description: { - content: "cmd.delete.description", - examples: ["delete "], - usage: "delete ", - }, - category: "playlist", - aliases: ["del"], - cooldown: 3, - args: true, - vote: true, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "playlist", - description: "cmd.delete.options.playlist", - type: 3, - required: true, - autocomplete: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'delete', + description: { + content: 'cmd.delete.description', + examples: ['delete '], + usage: 'delete ', + }, + category: 'playlist', + aliases: ['del'], + cooldown: 3, + args: true, + vote: true, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'playlist', + description: 'cmd.delete.options.playlist', + type: 3, + required: true, + autocomplete: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const playlistName = args.join(" ").trim(); - const embed = this.client.embed(); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const playlistName = args.join(' ').trim(); + const embed = this.client.embed(); - const playlistExists = await client.db.getPlaylist(ctx.author.id, playlistName); - if (!playlistExists) { - return await ctx.sendMessage({ - embeds: [embed.setDescription(ctx.locale("cmd.delete.messages.playlist_not_found")).setColor(this.client.color.red)], - }); - } + const playlistExists = await client.db.getPlaylist(ctx.author?.id!, playlistName); + if (!playlistExists) { + return await ctx.sendMessage({ + embeds: [ + embed.setDescription(ctx.locale('cmd.delete.messages.playlist_not_found')).setColor(this.client.color.red), + ], + }); + } - // First, delete all songs from the playlist - await client.db.deleteSongsFromPlaylist(ctx.author.id, playlistName); + // First, delete all songs from the playlist + await client.db.deleteSongsFromPlaylist(ctx.author?.id!, playlistName); - await client.db.deletePlaylist(ctx.author.id, playlistName); - return await ctx.sendMessage({ - embeds: [ - embed - .setDescription( - ctx.locale("cmd.delete.messages.playlist_deleted", { - playlistName, - }), - ) - .setColor(this.client.color.green), - ], - }); - } + await client.db.deletePlaylist(ctx.author?.id!, playlistName); + return await ctx.sendMessage({ + embeds: [ + embed + .setDescription( + ctx.locale('cmd.delete.messages.playlist_deleted', { + playlistName, + }), + ) + .setColor(this.client.color.green), + ], + }); + } - public async autocomplete(interaction: AutocompleteInteraction): Promise { - const focusedValue = interaction.options.getFocused(); - const userId = interaction.user.id; + public async autocomplete(interaction: AutocompleteInteraction): Promise { + const focusedValue = interaction.options.getFocused(); + const userId = interaction.user.id; - const playlists = await this.client.db.getUserPlaylists(userId); + const playlists = await this.client.db.getUserPlaylists(userId); - const filtered = playlists.filter((playlist) => playlist.name.toLowerCase().startsWith(focusedValue.toLowerCase())); + const filtered = playlists.filter(playlist => playlist.name.toLowerCase().startsWith(focusedValue.toLowerCase())); - await interaction.respond( - filtered.map((playlist) => ({ - name: playlist.name, - value: playlist.name, - })), - ); - } + await interaction.respond( + filtered.map(playlist => ({ + name: playlist.name, + value: playlist.name, + })), + ); + } } /** diff --git a/src/commands/playlist/List.ts b/src/commands/playlist/List.ts index 73e136c91..842438e56 100644 --- a/src/commands/playlist/List.ts +++ b/src/commands/playlist/List.ts @@ -1,128 +1,128 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class GetPlaylists extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "list", - description: { - content: "cmd.list.description", - examples: ["list", "list @user"], - usage: "list [@user]", - }, - category: "playlist", - aliases: ["lst"], - cooldown: 3, - args: false, - vote: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "user", - description: "cmd.list.options.user", - type: 6, - required: false, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'list', + description: { + content: 'cmd.list.description', + examples: ['list', 'list @user'], + usage: 'list [@user]', + }, + category: 'playlist', + aliases: ['lst'], + cooldown: 3, + args: false, + vote: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'user', + description: 'cmd.list.options.user', + type: 6, + required: false, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context): Promise { - try { - let userId: string | null = null; - let targetUser = ctx.args[0]; + public async run(client: Lavamusic, ctx: Context): Promise { + try { + let userId: string | undefined; + let targetUser = ctx.args[0]; - if (targetUser?.startsWith("<@") && targetUser.endsWith(">")) { - targetUser = targetUser.slice(2, -1); + if (targetUser?.startsWith('<@') && targetUser.endsWith('>')) { + targetUser = targetUser.slice(2, -1); - if (targetUser.startsWith("!")) { - targetUser = targetUser.slice(1); - } + if (targetUser.startsWith('!')) { + targetUser = targetUser.slice(1); + } - targetUser = await client.users.fetch(targetUser); - userId = targetUser.id; - } else if (targetUser) { - try { - targetUser = await client.users.fetch(targetUser); - userId = targetUser.id; - } catch (_error) { - const users = client.users.cache.filter((user) => user.username.toLowerCase() === targetUser.toLowerCase()); + targetUser = await client.users.fetch(targetUser); + userId = targetUser.id; + } else if (targetUser) { + try { + targetUser = await client.users.fetch(targetUser); + userId = targetUser.id; + } catch (_error) { + const users = client.users.cache.filter(user => user.username.toLowerCase() === targetUser.toLowerCase()); - if (users.size > 0) { - targetUser = users.first(); - userId = targetUser?.id ?? null; - } else { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.list.messages.invalid_username"), - color: this.client.color.red, - }, - ], - }); - } - } - } else { - userId = ctx.author.id; - targetUser = ctx.author; - } + if (users.size > 0) { + targetUser = users.first(); + userId = targetUser?.id ?? null; + } else { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.list.messages.invalid_username'), + color: this.client.color.red, + }, + ], + }); + } + } + } else { + userId = ctx.author?.id; + targetUser = ctx.author; + } - if (!userId) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.list.messages.invalid_userid"), - color: this.client.color.red, - }, - ], - }); - } + if (!userId) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.list.messages.invalid_userid'), + color: this.client.color.red, + }, + ], + }); + } - const playlists = await client.db.getUserPlaylists(userId); + const playlists = await client.db.getUserPlaylists(userId); - if (!playlists || playlists.length === 0) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.list.messages.no_playlists"), - color: this.client.color.red, - }, - ], - }); - } + if (!playlists || playlists.length === 0) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.list.messages.no_playlists'), + color: this.client.color.red, + }, + ], + }); + } - const targetUsername = targetUser ? targetUser.username : ctx.locale("cmd.list.messages.your"); - return await ctx.sendMessage({ - embeds: [ - { - title: ctx.locale("cmd.list.messages.playlists_title", { username: targetUsername }), - description: playlists.map((playlist: any) => playlist.name).join("\n"), - color: this.client.color.main, - }, - ], - }); - } catch (error) { - console.error(error); - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.list.messages.error"), - color: this.client.color.red, - }, - ], - }); - } - } + const targetUsername = targetUser ? targetUser.username : ctx.locale('cmd.list.messages.your'); + return await ctx.sendMessage({ + embeds: [ + { + title: ctx.locale('cmd.list.messages.playlists_title', { username: targetUsername }), + description: playlists.map((playlist: any) => playlist.name).join('\n'), + color: this.client.color.main, + }, + ], + }); + } catch (error) { + console.error(error); + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.list.messages.error'), + color: this.client.color.red, + }, + ], + }); + } + } } /** diff --git a/src/commands/playlist/Load.ts b/src/commands/playlist/Load.ts index 618e7c10c..9f6ca3689 100644 --- a/src/commands/playlist/Load.ts +++ b/src/commands/playlist/Load.ts @@ -1,115 +1,129 @@ -import type { AutocompleteInteraction } from "discord.js"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import type { AutocompleteInteraction, GuildMember } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class LoadPlaylist extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "load", - description: { - content: "cmd.load.description", - examples: ["load "], - usage: "load ", - }, - category: "playlist", - aliases: ["lo"], - cooldown: 3, - args: true, - vote: true, - player: { - voice: true, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "playlist", - description: "cmd.load.options.playlist", - type: 3, - required: true, - autocomplete: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'load', + description: { + content: 'cmd.load.description', + examples: ['load '], + usage: 'load ', + }, + category: 'playlist', + aliases: ['lo'], + cooldown: 3, + args: true, + vote: true, + player: { + voice: true, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'playlist', + description: 'cmd.load.options.playlist', + type: 3, + required: true, + autocomplete: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - let player = client.queue.get(ctx.guild!.id); - const playlistName = args.join(" ").trim(); - const playlistData = await client.db.getPlaylist(ctx.author.id, playlistName); - if (!playlistData) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.load.messages.playlist_not_exist"), - color: this.client.color.red, - }, - ], - }); - } + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + let player = client.manager.getPlayer(ctx.guild!.id); + const playlistName = args.join(' ').trim(); + const playlistData = await client.db.getPlaylist(ctx.author?.id!, playlistName); + if (!playlistData) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.load.messages.playlist_not_exist'), + color: this.client.color.red, + }, + ], + }); + } - const songs = await client.db.getSongs(ctx.author.id, playlistName); - if (songs.length === 0) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.load.messages.playlist_empty"), - color: client.color.red, - }, - ], - }); - } + const songs = await client.db.getTracksFromPlaylist(ctx.author?.id!, playlistName); + if (songs.length === 0) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.load.messages.playlist_empty'), + color: client.color.red, + }, + ], + }); + } - const vc = ctx.member as any; - if (!player) { - player = await client.queue.create( - ctx.guild, - vc.voice.channel, - ctx.channel, - client.shoukaku.options.nodeResolver(client.shoukaku.nodes), - ); - } + const member = ctx.member as GuildMember; + if (!player) { + player = client.manager.createPlayer({ + guildId: ctx.guild!.id, + voiceChannelId: member.voice.channelId!, + textChannelId: ctx.channel.id, + selfMute: false, + selfDeaf: true, + vcRegion: member.voice.channel?.rtcRegion!, + }); + if (!player.connected) await player.connect(); + } - for (const song of songs) { - const trackData = JSON.parse(song.track); - for (const track of trackData) { - const builtTrack = player.buildTrack(track, ctx.author as any); - player.queue.push(builtTrack); - } - } + const nodes = client.manager.nodeManager.leastUsedNodes(); + const node = nodes[Math.floor(Math.random() * nodes.length)]; + const tracks = await node.decode.multipleTracks(songs as any, ctx.author); + if (tracks.length === 0) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.load.messages.playlist_empty'), + color: client.color.red, + }, + ], + }); + } + player.queue.add(tracks); - await player.isPlaying(); - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.load.messages.playlist_loaded", { name: playlistData.name, count: songs.length }), - color: this.client.color.main, - }, - ], - }); - } + if (!player.playing) await player.play({ paused: false }); - public async autocomplete(interaction: AutocompleteInteraction): Promise { - const focusedValue = interaction.options.getFocused(); - const userId = interaction.user.id; + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.load.messages.playlist_loaded', { + name: playlistData.name, + count: songs.length, + }), + color: this.client.color.main, + }, + ], + }); + } - const playlists = await this.client.db.getUserPlaylists(userId); + public async autocomplete(interaction: AutocompleteInteraction): Promise { + const focusedValue = interaction.options.getFocused(); + const userId = interaction.user.id; - const filtered = playlists.filter((playlist) => playlist.name.toLowerCase().startsWith(focusedValue.toLowerCase())); + const playlists = await this.client.db.getUserPlaylists(userId); - await interaction.respond( - filtered.map((playlist) => ({ - name: playlist.name, - value: playlist.name, - })), - ); - } + const filtered = playlists.filter(playlist => playlist.name.toLowerCase().startsWith(focusedValue.toLowerCase())); + + await interaction.respond( + filtered.map(playlist => ({ + name: playlist.name, + value: playlist.name, + })), + ); + } } /** diff --git a/src/commands/playlist/RemoveSong.ts b/src/commands/playlist/RemoveSong.ts index 04c2e7201..ca60f6a99 100644 --- a/src/commands/playlist/RemoveSong.ts +++ b/src/commands/playlist/RemoveSong.ts @@ -1,82 +1,81 @@ -import type { AutocompleteInteraction } from "discord.js"; -import { LoadType } from "shoukaku"; -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import type { AutocompleteInteraction } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class RemoveSong extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "removesong", - description: { - content: "cmd.removesong.description", - examples: ["removesong "], - usage: "removesong ", - }, - category: "playlist", - aliases: ["rs"], - cooldown: 3, - args: true, - vote: true, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "playlist", - description: "cmd.removesong.options.playlist", - type: 3, - required: true, - autocomplete: true, - }, - { - name: "song", - description: "cmd.removesong.options.song", - type: 3, - required: true, - }, - ], - }); - } + constructor(client: Lavamusic) { + super(client, { + name: 'removesong', + description: { + content: 'cmd.removesong.description', + examples: ['removesong '], + usage: 'removesong ', + }, + category: 'playlist', + aliases: ['rs'], + cooldown: 3, + args: true, + vote: true, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'playlist', + description: 'cmd.removesong.options.playlist', + type: 3, + required: true, + autocomplete: true, + }, + { + name: 'song', + description: 'cmd.removesong.options.song', + type: 3, + required: true, + }, + ], + }); + } - public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { - const playlist = args.shift(); - const song = args.join(" "); + public async run(client: Lavamusic, ctx: Context, args: string[]): Promise { + const playlist = args.shift(); + const song = args.join(' '); - if (!playlist) { - const errorMessage = this.client - .embed() - .setDescription(ctx.locale("cmd.removesong.messages.provide_playlist")) - .setColor(this.client.color.red); - return await ctx.sendMessage({ embeds: [errorMessage] }); - } + if (!playlist) { + const errorMessage = this.client + .embed() + .setDescription(ctx.locale('cmd.removesong.messages.provide_playlist')) + .setColor(this.client.color.red); + return await ctx.sendMessage({ embeds: [errorMessage] }); + } - if (!song) { - const errorMessage = this.client - .embed() - .setDescription(ctx.locale("cmd.removesong.messages.provide_song")) - .setColor(this.client.color.red); - return await ctx.sendMessage({ embeds: [errorMessage] }); - } + if (!song) { + const errorMessage = this.client + .embed() + .setDescription(ctx.locale('cmd.removesong.messages.provide_song')) + .setColor(this.client.color.red); + return await ctx.sendMessage({ embeds: [errorMessage] }); + } - const playlistData = await client.db.getPlaylist(ctx.author.id, playlist); + const playlistData = await client.db.getPlaylist(ctx.author?.id!, playlist); - if (!playlistData) { - const playlistNotFoundError = this.client - .embed() - .setDescription(ctx.locale("cmd.removesong.messages.playlist_not_exist")) - .setColor(this.client.color.red); - return await ctx.sendMessage({ embeds: [playlistNotFoundError] }); - } + if (!playlistData) { + const playlistNotFoundError = this.client + .embed() + .setDescription(ctx.locale('cmd.removesong.messages.playlist_not_exist')) + .setColor(this.client.color.red); + return await ctx.sendMessage({ embeds: [playlistNotFoundError] }); + } - const res = await client.queue.search(song); + /* const res = await client.queue.search(song); if (!res || res.loadType !== LoadType.TRACK) { const noSongsFoundError = this.client @@ -108,23 +107,23 @@ export default class RemoveSong extends Command { .setDescription(ctx.locale("cmd.removesong.messages.error_occurred")) .setColor(this.client.color.red); return await ctx.sendMessage({ embeds: [genericError] }); - } - } - public async autocomplete(interaction: AutocompleteInteraction): Promise { - const focusedValue = interaction.options.getFocused(); - const userId = interaction.user.id; + } */ + } + public async autocomplete(interaction: AutocompleteInteraction): Promise { + const focusedValue = interaction.options.getFocused(); + const userId = interaction.user.id; - const playlists = await this.client.db.getUserPlaylists(userId); + const playlists = await this.client.db.getUserPlaylists(userId); - const filtered = playlists.filter((playlist) => playlist.name.toLowerCase().startsWith(focusedValue.toLowerCase())); + const filtered = playlists.filter(playlist => playlist.name.toLowerCase().startsWith(focusedValue.toLowerCase())); - await interaction.respond( - filtered.map((playlist) => ({ - name: playlist.name, - value: playlist.name, - })), - ); - } + await interaction.respond( + filtered.map(playlist => ({ + name: playlist.name, + value: playlist.name, + })), + ); + } } /** diff --git a/src/commands/playlist/Steal.ts b/src/commands/playlist/Steal.ts index c780c7a21..eef61c6ae 100644 --- a/src/commands/playlist/Steal.ts +++ b/src/commands/playlist/Steal.ts @@ -1,197 +1,199 @@ -import { Command, type Context, type Lavamusic } from "../../structures/index.js"; +import type { AutocompleteInteraction } from 'discord.js'; +import { Command, type Context, type Lavamusic } from '../../structures/index'; export default class StealPlaylist extends Command { - constructor(client: Lavamusic) { - super(client, { - name: "steal", - description: { - content: "cmd.steal.description", - examples: ["steal <@user> "], - usage: "steal <@user> ", - }, - category: "playlist", - aliases: ["st"], - cooldown: 3, - args: true, - vote: false, - player: { - voice: false, - dj: false, - active: false, - djPerm: null, - }, - permissions: { - dev: false, - client: ["SendMessages", "ReadMessageHistory", "ViewChannel", "EmbedLinks"], - user: [], - }, - slashCommand: true, - options: [ - { - name: "user", - description: "cmd.steal.options.user", - type: 6, - required: true, - }, - { - name: "playlist", - description: "cmd.steal.options.playlist", - type: 3, - required: true, - autocomplete: true, - }, - ], - }); - } - - public async run(client: Lavamusic, ctx: Context): Promise { - let targetUser = ctx.args[0]; - const playlistName = ctx.args[1]; - let targetUserId: string | null = null; - - if (targetUser?.startsWith("<@") && targetUser.endsWith(">")) { - targetUser = targetUser.slice(2, -1); - if (targetUser.startsWith("!")) { - targetUser = targetUser.slice(1); - } - targetUser = await client.users.fetch(targetUser); - targetUserId = targetUser.id; - } else if (targetUser) { - try { - targetUser = await client.users.fetch(targetUser); - targetUserId = targetUser.id; - } catch (_error) { - const users = client.users.cache.filter((user) => user.username.toLowerCase() === targetUser.toLowerCase()); - - if (users.size > 0) { - targetUser = users.first(); - targetUserId = targetUser.id; - } else { - return await ctx.sendMessage({ - embeds: [ - { - description: "Invalid username or user not found.", - color: this.client.color.red, - }, - ], - }); - } - } - } - - if (!playlistName) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.steal.messages.provide_playlist"), - color: this.client.color.red, - }, - ], - }); - } - - if (!targetUserId) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.steal.messages.provide_user"), - color: this.client.color.red, - }, - ], - }); - } - - try { - const targetPlaylist = await client.db.getPlaylist(targetUserId, playlistName); - - if (!targetPlaylist) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.steal.messages.playlist_not_exist"), - color: this.client.color.red, - }, - ], - }); - } - - const targetSongs = await client.db.getSongs(targetUserId, playlistName); - - const existingPlaylist = await client.db.getPlaylist(ctx.author.id, playlistName); - if (existingPlaylist) { - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.steal.messages.playlist_exists", { playlist: playlistName }), - color: this.client.color.red, - }, - ], - }); - } - - await client.db.createPlaylistWithSongs(ctx.author.id, playlistName, targetSongs); - - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.steal.messages.playlist_stolen", { - playlist: playlistName, - user: targetUser.username, - }), - color: this.client.color.main, - }, - ], - }); - } catch (error) { - console.error(error); - return await ctx.sendMessage({ - embeds: [ - { - description: ctx.locale("cmd.steal.messages.error_occurred"), - color: this.client.color.red, - }, - ], - }); - } - } - - public async autocomplete(interaction) { - try { - const focusedValue = interaction.options.getFocused(); - const userOptionId = interaction.options.get("user")?.value; - - if (!userOptionId) { - await interaction - .respond([{ name: "Please specify a user to search their playlists.", value: "NoUser" }]) - .catch(console.error); - return; - } - - const user = await interaction.client.users.fetch(userOptionId); - if (!user) { - await interaction.respond([{ name: "User not found.", value: "NoUserFound" }]).catch(console.error); - return; - } - - const playlists = await this.client.db.getUserPlaylists(user.id); - - if (!playlists || playlists.length === 0) { - await interaction.respond([{ name: "No playlists found for this user.", value: "NoPlaylists" }]).catch(console.error); - return; - } - - const filtered = playlists.filter((playlist) => playlist.name.toLowerCase().startsWith(focusedValue.toLowerCase())); - - return await interaction - .respond(filtered.map((playlist) => ({ name: playlist.name, value: playlist.name }))) - .catch(console.error); - } catch (error) { - console.error("Error in autocomplete interaction:", error); - return await interaction - .respond([{ name: "An error occurred while fetching playlists.", value: "Error" }]) - .catch(console.error); - } - } + constructor(client: Lavamusic) { + super(client, { + name: 'steal', + description: { + content: 'cmd.steal.description', + examples: ['steal <@user> '], + usage: 'steal <@user> ', + }, + category: 'playlist', + aliases: ['st'], + cooldown: 3, + args: true, + vote: false, + player: { + voice: false, + dj: false, + active: false, + djPerm: null, + }, + permissions: { + dev: false, + client: ['SendMessages', 'ReadMessageHistory', 'ViewChannel', 'EmbedLinks'], + user: [], + }, + slashCommand: true, + options: [ + { + name: 'user', + description: 'cmd.steal.options.user', + type: 6, + required: true, + }, + { + name: 'playlist', + description: 'cmd.steal.options.playlist', + type: 3, + required: true, + autocomplete: true, + }, + ], + }); + } + + public async run(client: Lavamusic, ctx: Context): Promise { + let targetUser = ctx.args[0]; + const playlistName = ctx.args[1]; + let targetUserId: string | null = null; + + if (targetUser?.startsWith('<@') && targetUser.endsWith('>')) { + targetUser = targetUser.slice(2, -1); + if (targetUser.startsWith('!')) { + targetUser = targetUser.slice(1); + } + targetUser = await client.users.fetch(targetUser); + targetUserId = targetUser.id; + } else if (targetUser) { + try { + targetUser = await client.users.fetch(targetUser); + targetUserId = targetUser.id; + } catch (_error) { + const users = client.users.cache.filter(user => user.username.toLowerCase() === targetUser.toLowerCase()); + + if (users.size > 0) { + targetUser = users.first(); + targetUserId = targetUser.id; + } else { + return await ctx.sendMessage({ + embeds: [ + { + description: 'Invalid username or user not found.', + color: this.client.color.red, + }, + ], + }); + } + } + } + + if (!playlistName) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.steal.messages.provide_playlist'), + color: this.client.color.red, + }, + ], + }); + } + + if (!targetUserId) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.steal.messages.provide_user'), + color: this.client.color.red, + }, + ], + }); + } + + try { + const targetPlaylist = await client.db.getPlaylist(targetUserId, playlistName); + + if (!targetPlaylist) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.steal.messages.playlist_not_exist'), + color: this.client.color.red, + }, + ], + }); + } + + const targetSongs = await client.db.getTracksFromPlaylist(targetUserId, playlistName); + + const existingPlaylist = await client.db.getPlaylist(ctx.author?.id!, playlistName); + if (existingPlaylist) { + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.steal.messages.playlist_exists', { playlist: playlistName }), + color: this.client.color.red, + }, + ], + }); + } + + await client.db.createPlaylistWithTracks(ctx.author?.id!, playlistName, targetSongs); + + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.steal.messages.playlist_stolen', { + playlist: playlistName, + user: targetUser.username, + }), + color: this.client.color.main, + }, + ], + }); + } catch (error) { + console.error(error); + return await ctx.sendMessage({ + embeds: [ + { + description: ctx.locale('cmd.steal.messages.error_occurred'), + color: this.client.color.red, + }, + ], + }); + } + } + + public async autocomplete(interaction: AutocompleteInteraction) { + try { + const focusedValue = interaction.options.getFocused(); + const userOptionId = interaction.options.get('user')?.value as string; + + if (!userOptionId) { + await interaction + .respond([{ name: 'Please specify a user to search their playlists.', value: 'NoUser' }]) + .catch(console.error); + return; + } + + const user = await interaction.client.users.fetch(userOptionId); + if (!user) { + await interaction.respond([{ name: 'User not found.', value: 'NoUserFound' }]).catch(console.error); + return; + } + + const playlists = await this.client.db.getUserPlaylists(user.id); + + if (!playlists || playlists.length === 0) { + await interaction + .respond([{ name: 'No playlists found for this user.', value: 'NoPlaylists' }]) + .catch(console.error); + return; + } + + const filtered = playlists.filter(playlist => playlist.name.toLowerCase().startsWith(focusedValue.toLowerCase())); + + return await interaction + .respond(filtered.map(playlist => ({ name: playlist.name, value: playlist.name }))) + .catch(console.error); + } catch (error) { + return await interaction + .respond([{ name: 'An error occurred while fetching playlists.', value: 'Error' }]) + .catch(console.error); + } + } } /** diff --git a/src/config.ts b/src/config.ts index 7368abb93..792c8a04a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,82 +1,46 @@ -import "dotenv/config"; -import { SearchEngine } from "./types.js"; - -const parseBoolean = (value) => { - if (typeof value !== "string") return false; - return value.trim().toLowerCase() === "true"; -}; - export default { - token: process.env.TOKEN, - prefix: process.env.PREFIX, - color: { - red: 0xff0000, - green: 0x00ff00, - blue: 0x0000ff, - yellow: 0xffff00, - main: 0x2f3136, - }, - emoji: { - // You can add custom emoji with ID format (e.g., <:emojiName:123456789012345678>) - pause: "⏸️", - resume: "▶️", - stop: "⏹️", - skip: "⏭️", - previous: "⏮️", - forward: "⏩", - rewind: "⏪", - voldown: "🔉", - volup: "🔊", - shuffle: "🔀", - loop: { - none: "🔁", - track: "🔂", - }, - page: { - last: "⏩", - first: "⏪", - back: "⬅️", - next: "➡️", - cancel: "⏹️", - }, - }, - defaultLanguage: process.env.DEFAULT_LANGUAGE, - topGG: process.env.TOPGG, - keepAlive: parseBoolean(process.env.KEEP_ALIVE), - autoNode: parseBoolean(process.env.AUTO_NODE), - searchEngine: SearchEngine[process.env.SEARCH_ENGINE] || SearchEngine.YouTubeMusic, - maxPlaylistSize: parseInt(process.env.MAX_PLAYLIST_SIZE || "100"), - botStatus: process.env.BOT_STATUS || "online", - botActivity: process.env.BOT_ACTIVITY || "Lavamusic", - botActivityType: parseInt(process.env.BOT_ACTIVITY_TYPE || "2"), - maxQueueSize: parseInt(process.env.MAX_QUEUE_SIZE || "100"), - owners: process.env.OWNER_IDS ? JSON.parse(process.env.OWNER_IDS) : [], - clientId: process.env.CLIENT_ID, - guildId: process.env.GUILD_ID, - logChannelId: process.env.LOG_CHANNEL_ID, - commandLogs: process.env.LOG_COMMANDS_ID, - lyricsApi: process.env.GENIUS_API, - links: { - img: process.env.IMG_LINK || "https://i.imgur.com/ud3EWNh.jpg", - }, - icons: { - youtube: "https://i.imgur.com/xzVHhFY.png", - spotify: "https://i.imgur.com/qvdqtsc.png", - soundcloud: "https://i.imgur.com/MVnJ7mj.png", - applemusic: "https://i.imgur.com/Wi0oyYm.png", - deezer: "https://i.imgur.com/xyZ43FG.png", - jiosaavn: "https://i.imgur.com/N9Nt80h.png", - }, - lavalink: process.env.LAVALINK_SERVERS - ? JSON.parse(process.env.LAVALINK_SERVERS).map((server) => { - return { - url: server.url, - auth: server.auth, - name: server.name, - secure: parseBoolean(server.secure), - }; - }) - : [], + color: { + red: 0xff0000, + green: 0x00ff00, + blue: 0x0000ff, + yellow: 0xffff00, + main: 0x2f3136, + }, + emoji: { + // You can add custom emoji with ID format (e.g., <:emojiName:123456789012345678>) + pause: '⏸️', + resume: '▶️', + stop: '⏹️', + skip: '⏭️', + previous: '⏮️', + forward: '⏩', + rewind: '⏪', + voldown: '🔉', + volup: '🔊', + shuffle: '🔀', + loop: { + none: '🔁', + track: '🔂', + }, + page: { + last: '⏩', + first: '⏪', + back: '⬅️', + next: '➡️', + cancel: '⏹️', + }, + }, + icons: { + youtube: 'https://i.imgur.com/xzVHhFY.png', + spotify: 'https://i.imgur.com/qvdqtsc.png', + soundcloud: 'https://i.imgur.com/MVnJ7mj.png', + applemusic: 'https://i.imgur.com/Wi0oyYm.png', + deezer: 'https://i.imgur.com/xyZ43FG.png', + jiosaavn: 'https://i.imgur.com/N9Nt80h.png', + } as any, + links: { + img: 'https://i.imgur.com/ud3EWNh.jpg', + }, }; /** diff --git a/src/database/server.ts b/src/database/server.ts index 1710a8321..6f3a4248e 100644 --- a/src/database/server.ts +++ b/src/database/server.ts @@ -1,212 +1,270 @@ -import { type Dj, type Guild, type Playlist, PrismaClient, type Role, type Setup, type Song, type Stay } from "@prisma/client"; -import config from "../config.js"; +import { type Dj, type Guild, type Playlist, PrismaClient, type Role, type Setup, type Stay } from '@prisma/client'; +import { env } from '../env'; export default class ServerData { - private prisma: PrismaClient; - - constructor() { - this.prisma = new PrismaClient(); - } - - public async get(guildId: string): Promise { - return (await this.prisma.guild.findUnique({ where: { guildId } })) ?? this.createGuild(guildId); - } - - private async createGuild(guildId: string): Promise { - return await this.prisma.guild.create({ - data: { - guildId, - prefix: config.prefix, - }, - }); - } - - public async setPrefix(guildId: string, prefix: string): Promise { - await this.prisma.guild.upsert({ - where: { guildId }, - update: { prefix }, - create: { guildId, prefix }, - }); - } - - public async getPrefix(guildId: string): Promise { - const guild = await this.get(guildId); - return guild?.prefix ?? config.prefix; - } - - public async updateLanguage(guildId: string, language: string): Promise { - await this.prisma.guild.update({ - where: { guildId }, - data: { language }, - }); - } - - public async getLanguage(guildId: string): Promise { - const guild = await this.get(guildId); - return guild?.language ?? config.defaultLanguage; - } - - public async getSetup(guildId: string): Promise { - return await this.prisma.setup.findUnique({ where: { guildId } }); - } - - public async setSetup(guildId: string, textId: string, messageId: string): Promise { - await this.prisma.setup.upsert({ - where: { guildId }, - update: { textId, messageId }, - create: { guildId, textId, messageId }, - }); - } - - public async deleteSetup(guildId: string): Promise { - await this.prisma.setup.delete({ where: { guildId } }); - } - - public async set_247(guildId: string, textId: string, voiceId: string): Promise { - await this.prisma.stay.upsert({ - where: { guildId }, - update: { textId, voiceId }, - create: { guildId, textId, voiceId }, - }); - } - - public async delete_247(guildId: string): Promise { - await this.prisma.stay.delete({ where: { guildId } }); - } - - public async get_247(guildId?: string): Promise { - if (guildId) { - return await this.prisma.stay.findUnique({ where: { guildId } }); - } - return this.prisma.stay.findMany(); - } - - public async setDj(guildId: string, mode: boolean): Promise { - await this.prisma.dj.upsert({ - where: { guildId }, - update: { mode }, - create: { guildId, mode }, - }); - } - - public async getDj(guildId: string): Promise { - return await this.prisma.dj.findUnique({ where: { guildId } }); - } - - public async getRoles(guildId: string): Promise { - return await this.prisma.role.findMany({ where: { guildId } }); - } - - public async addRole(guildId: string, roleId: string): Promise { - await this.prisma.role.create({ data: { guildId, roleId } }); - } - - public async removeRole(guildId: string, roleId: string): Promise { - await this.prisma.role.deleteMany({ where: { guildId, roleId } }); - } - - public async clearRoles(guildId: string): Promise { - await this.prisma.role.deleteMany({ where: { guildId } }); - } - - public async getPlaylist(userId: string, name: string): Promise { - return await this.prisma.playlist.findUnique({ - where: { userId_name: { userId, name } }, - }); - } - - public async getUserPlaylists(userId: string): Promise { - return await this.prisma.playlist.findMany({ - where: { userId }, - }); - } - - public async createPlaylist(userId: string, name: string): Promise { - await this.prisma.playlist.create({ data: { userId, name } }); - } - - public async createPlaylistWithSongs(userId: string, name: string, songs: any[]): Promise { - await this.prisma.playlist.create({ - data: { - userId, - name, - songs: { - create: songs.map((song) => ({ track: song.track })), - }, - }, - }); - } - - public async deletePlaylist(userId: string, name: string): Promise { - await this.prisma.playlist.delete({ - where: { userId_name: { userId, name } }, - }); - } - - public async deleteSongsFromPlaylist(userId: string, playlistName: string): Promise { - const playlist = await this.getPlaylist(userId, playlistName); - if (playlist) { - await this.prisma.song.deleteMany({ - where: { - playlistId: playlist.id, - }, - }); - } - } - - public async addSong(userId: string, name: string, song: string): Promise { - const playlist = await this.getPlaylist(userId, name); - if (playlist) { - await this.prisma.song.create({ - data: { - track: JSON.stringify(song), - playlistId: playlist.id, - }, - }); - } else { - await this.createPlaylist(userId, name); - await this.addSong(userId, name, song); - } - } - - public async removeSong(userId: string, playlistName: string, encodedSong: string): Promise { - const playlist = await this.getPlaylist(userId, playlistName); - if (playlist) { - await this.prisma.song.deleteMany({ - where: { - playlistId: playlist.id, - track: { contains: encodedSong }, - }, - }); - } - } - - public async getSongs(userId: string, name: string): Promise { - const playlist = await this.getPlaylist(userId, name); - if (playlist) { - return this.prisma.song.findMany({ where: { playlistId: playlist.id } }); - } - return []; - } - - public async clearPlaylist(userId: string, name: string): Promise { - const playlist = await this.getPlaylist(userId, name); - if (playlist) { - await this.prisma.song.deleteMany({ where: { playlistId: playlist.id } }); - } - } - - public async clearPlaylists(userId: string): Promise { - await this.prisma.playlist.deleteMany({ where: { userId } }); - } - - public async clearAllPlaylists(): Promise { - await this.prisma.playlist.deleteMany(); - } - - public async clearAllSongs(): Promise { - await this.prisma.song.deleteMany(); - } + private prisma: PrismaClient; + + constructor() { + this.prisma = new PrismaClient(); + } + + public async get(guildId: string): Promise { + return (await this.prisma.guild.findUnique({ where: { guildId } })) ?? this.createGuild(guildId); + } + + private async createGuild(guildId: string): Promise { + return await this.prisma.guild.create({ + data: { + guildId, + prefix: env.PREFIX, + }, + }); + } + + public async setPrefix(guildId: string, prefix: string): Promise { + await this.prisma.guild.upsert({ + where: { guildId }, + update: { prefix }, + create: { guildId, prefix }, + }); + } + + public async getPrefix(guildId: string): Promise { + const guild = await this.get(guildId); + return guild?.prefix ?? env.PREFIX; + } + + public async updateLanguage(guildId: string, language: string): Promise { + await this.prisma.guild.update({ + where: { guildId }, + data: { language }, + }); + } + + public async getLanguage(guildId: string): Promise { + const guild = await this.get(guildId); + return guild?.language ?? env.DEFAULT_LANGUAGE; + } + + public async getSetup(guildId: string): Promise { + return await this.prisma.setup.findUnique({ where: { guildId } }); + } + + public async setSetup(guildId: string, textId: string, messageId: string): Promise { + await this.prisma.setup.upsert({ + where: { guildId }, + update: { textId, messageId }, + create: { guildId, textId, messageId }, + }); + } + + public async deleteSetup(guildId: string): Promise { + await this.prisma.setup.delete({ where: { guildId } }); + } + + public async set_247(guildId: string, textId: string, voiceId: string): Promise { + await this.prisma.stay.upsert({ + where: { guildId }, + update: { textId, voiceId }, + create: { guildId, textId, voiceId }, + }); + } + + public async delete_247(guildId: string): Promise { + await this.prisma.stay.delete({ where: { guildId } }); + } + + public async get_247(guildId?: string): Promise { + if (guildId) { + //return await this.prisma.stay.findUnique({ where: { guildId } }); + const stay = await this.prisma.stay.findUnique({ where: { guildId } }); + if (stay) return stay; + return null; + } + return this.prisma.stay.findMany(); + } + + public async setDj(guildId: string, mode: boolean): Promise { + await this.prisma.dj.upsert({ + where: { guildId }, + update: { mode }, + create: { guildId, mode }, + }); + } + + public async getDj(guildId: string): Promise { + return await this.prisma.dj.findUnique({ where: { guildId } }); + } + + public async getRoles(guildId: string): Promise { + return await this.prisma.role.findMany({ where: { guildId } }); + } + + public async addRole(guildId: string, roleId: string): Promise { + await this.prisma.role.create({ data: { guildId, roleId } }); + } + + public async removeRole(guildId: string, roleId: string): Promise { + await this.prisma.role.deleteMany({ where: { guildId, roleId } }); + } + + public async clearRoles(guildId: string): Promise { + await this.prisma.role.deleteMany({ where: { guildId } }); + } + + public async getPlaylist(userId: string, name: string): Promise { + return await this.prisma.playlist.findUnique({ + where: { userId_name: { userId, name } }, + }); + } + + public async getUserPlaylists(userId: string): Promise { + return await this.prisma.playlist.findMany({ + where: { userId }, + }); + } + + public async createPlaylist(userId: string, name: string): Promise { + await this.prisma.playlist.create({ data: { userId, name } }); + } + + // createPlaylist with tracks + public async createPlaylistWithTracks(userId: string, name: string, tracks: string[]): Promise { + await this.prisma.playlist.create({ + data: { + userId, + name, + tracks: JSON.stringify(tracks), + }, + }); + } + /** + * Deletes a playlist from the database + * + * @param userId The ID of the user that owns the playlist + * @param name The name of the playlist to delete + */ + public async deletePlaylist(userId: string, name: string): Promise { + await this.prisma.playlist.delete({ + where: { userId_name: { userId, name } }, + }); + } + + public async deleteSongsFromPlaylist(userId: string, playlistName: string): Promise { + // Fetch the playlist + const playlist = await this.getPlaylist(userId, playlistName); + + if (playlist) { + // Update the playlist and reset the tracks to an empty array + await this.prisma.playlist.update({ + where: { + userId_name: { + userId, + name: playlistName, + }, + }, + data: { + tracks: JSON.stringify([]), // Set tracks to an empty array + }, + }); + } + } + + public async addTracksToPlaylist(userId: string, playlistName: string, tracks: string[]) { + // Serialize the tracks array into a JSON string + const tracksJson = JSON.stringify(tracks); + + // Check if the playlist already exists for the user + const playlist = await this.prisma.playlist.findUnique({ + where: { + userId_name: { + userId, + name: playlistName, + }, + }, + }); + + if (playlist) { + // If the playlist exists, handle existing tracks + const existingTracks = playlist.tracks ? JSON.parse(playlist.tracks) : []; // Initialize as an empty array if null + + if (Array.isArray(existingTracks)) { + // Merge new and existing tracks + const updatedTracks = [...existingTracks, ...tracks]; + + // Update the playlist with the new tracks + await this.prisma.playlist.update({ + where: { + userId_name: { + userId, + name: playlistName, + }, + }, + data: { + tracks: JSON.stringify(updatedTracks), // Store the updated tracks as a serialized JSON string + }, + }); + } else { + throw new Error('Existing tracks are not in an array format.'); + } + } else { + // If no playlist exists, create a new one with the provided tracks + await this.prisma.playlist.create({ + data: { + userId, + name: playlistName, + tracks: tracksJson, // Store the serialized JSON string + }, + }); + } + } + + public async removeSong(userId: string, playlistName: string, encodedSong: string): Promise { + const playlist = await this.getPlaylist(userId, playlistName); + if (playlist) { + const tracks: string[] = JSON.parse(playlist?.tracks!); + + // Find the index of the song to remove + const songIndex = tracks.indexOf(encodedSong); + + if (songIndex !== -1) { + // Remove the song from the array + tracks.splice(songIndex, 1); + + // Update the playlist with the new list of tracks + await this.prisma.playlist.update({ + where: { + userId_name: { + userId, + name: playlistName, + }, + }, + data: { + tracks: JSON.stringify(tracks), // Re-serialize the updated array back to a string + }, + }); + } + } + } + + public async getTracksFromPlaylist(userId: string, playlistName: string) { + const playlist = await this.prisma.playlist.findUnique({ + where: { + userId_name: { + userId, + name: playlistName, + }, + }, + }); + + if (!playlist) { + return null; + } + + // Deserialize the tracks JSON string back into an array + const tracks = JSON.parse(playlist.tracks!); + return tracks; + } } /** diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 000000000..754ca6be3 --- /dev/null +++ b/src/env.ts @@ -0,0 +1,154 @@ +import path from 'node:path'; +import { config } from 'dotenv'; +import { z } from 'zod'; + +config({ + path: path.join(__dirname, '../.env'), +}); + +const LavalinkNodeSchema = z.object({ + id: z.string(), + host: z.string(), + port: z.number(), + authorization: z.string(), + secure: z.boolean().optional(), + sessionId: z.string().optional(), + regions: z.string().array().optional(), + retryAmount: z.number().optional(), + retryDelay: z.number().optional(), + requestSignalTimeoutMS: z.number().optional(), + closeOnError: z.boolean().optional(), + heartBeatInterval: z.number().optional(), + enablePingOnStatsCheck: z.boolean().optional(), +}); + +const envSchema = z.object({ + /** + * The discord app token + */ + TOKEN: z.string(), + + /** + * The client id + */ + CLIENT_ID: z.string(), + + /** + * The default language + */ + DEFAULT_LANGUAGE: z.string().default('EnglishUS'), + + /** + * The bot prefix + */ + PREFIX: z.string().default('!'), + + /** + * The owner ids + */ + OWNER_IDS: z.preprocess(val => (typeof val === 'string' ? JSON.parse(val) : val), z.string().array().optional()), + + /** + * The guild id for devlopment purposes + */ + GUILD_ID: z.string().optional(), + + /** + * The Top.gg api key + */ + TOPGG: z.string().optional(), + + /** + * The keep alive boolean + */ + KEEP_ALIVE: z.preprocess(val => val === 'true', z.boolean().default(false)), + + /** + * The log channel id + */ + LOG_CHANNEL_ID: z.string().optional(), + + /** + * The log command id + */ + LOG_COMMANDS_ID: z.string().optional(), + + /** + * The bot status online | idle | dnd | invisible + */ + BOT_STATUS: z.preprocess( + val => { + if (typeof val === 'string') { + return val.toLowerCase(); + } + return val; + }, + z.enum(['online', 'idle', 'dnd', 'invisible']).default('online'), + ), + + /** + * The bot activity + */ + BOT_ACTIVITY: z.string().default('Lavamusic'), + + /** + * The bot activity type + */ + BOT_ACTIVITY_TYPE: z.preprocess(val => { + if (typeof val === 'string') { + return Number.parseInt(val, 10); + } + return val; + }, z.number().default(0)), + /** + * The database url + */ + DATABASE_URL: z.string().optional(), + + /** + * Search engine + */ + SEARCH_ENGINE: z.preprocess( + val => { + if (typeof val === 'string') { + return val.toLowerCase(); + } + return val; + }, + z + .enum(['youtube', 'youtubemusic', 'soundcloud', 'spotify', 'apple', 'deezer', 'yandex', 'jiosaavn']) + .default('youtube'), + ), + /** + * Node in json + */ + NODES: z.preprocess(val => (typeof val === 'string' ? JSON.parse(val) : val), z.array(LavalinkNodeSchema)), + /** + * Genius api + */ + GENIUS_API: z.string().optional(), +}); + +type Env = z.infer; + +/** + * The environment variables + */ +export const env: Env = envSchema.parse(process.env); + +for (const key in env) { + if (!(key in env)) { + throw new Error(`Missing env variable: ${key}`); + } +} + +/** + * 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/ChannelDelete.ts b/src/events/client/ChannelDelete.ts index 1fc1ceeaf..796064225 100644 --- a/src/events/client/ChannelDelete.ts +++ b/src/events/client/ChannelDelete.ts @@ -1,42 +1,48 @@ -import { Event, type Lavamusic } from "../../structures/index.js"; +import { Event, type Lavamusic } from '../../structures/index'; export default class ChannelDelete extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "channelDelete", - }); - } + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'channelDelete', + }); + } - public async run(channel: any): Promise { - const { guild } = channel; - const setup = await this.client.db.getSetup(guild.id); - const stay = await this.client.db.get_247(guild.id); + public async run(channel: any): Promise { + const { guild } = channel; + const setup = await this.client.db.getSetup(guild.id); + const stay = await this.client.db.get_247(guild.id); - if (Array.isArray(stay)) { - for (const s of stay) { - if (channel.type === 2 && s.voiceId === channel.id) { - await this.client.db.delete_247(guild.id); - break; - } - } - } else if (stay) { - if (channel.type === 2 && stay.voiceId === channel.id) { - await this.client.db.delete_247(guild.id); - } - } + if (Array.isArray(stay)) { + for (const s of stay) { + if (channel.type === 2 && s.voiceId === channel.id) { + await this.client.db.delete_247(guild.id); + break; + } + } + } else if (stay) { + if (channel.type === 2 && stay.voiceId === channel.id) { + await this.client.db.delete_247(guild.id); + } + } - if (setup && channel.type === 0 && setup.textId === channel.id) { - await this.client.db.deleteSetup(guild.id); - } + if (setup && channel.type === 0 && setup.textId === channel.id) { + await this.client.db.deleteSetup(guild.id); + } - const queue = this.client.queue.get(guild.id); - if (queue) { - if ( - queue.channelId === channel.id || - (queue.player && queue.node.manager.connections.get(guild.id)!.channelId === channel.id) - ) { - queue.stop(); - } - } - } + const player = this.client.manager.getPlayer(guild.id); + if (player && player.voiceChannelId === channel.id) { + player.destroy(); + } + } } + +/** + * 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/GuildCreate.ts b/src/events/client/GuildCreate.ts index e7b2a27e4..681cf3db7 100644 --- a/src/events/client/GuildCreate.ts +++ b/src/events/client/GuildCreate.ts @@ -1,74 +1,74 @@ -import { EmbedBuilder, type Guild, type GuildMember, type TextChannel } from "discord.js"; -import { Event, type Lavamusic } from "../../structures/index.js"; +import { EmbedBuilder, type Guild, type GuildMember, type TextChannel } from 'discord.js'; +import { Event, type Lavamusic } from '../../structures/index'; export default class GuildCreate extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "guildCreate", - }); - } + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'guildCreate', + }); + } - public async run(guild: Guild): Promise { - let owner: GuildMember | undefined; - try { - owner = await guild.members.fetch(guild.ownerId); - } catch (e) { - this.client.logger.error(`Error fetching owner for guild ${guild.id}: ${e}`); - } + public async run(guild: Guild): Promise { + let owner: GuildMember | undefined; + try { + owner = await guild.members.fetch(guild.ownerId); + } catch (e) { + this.client.logger.error(`Error fetching owner for guild ${guild.id}: ${e}`); + } - const embed = new EmbedBuilder() - .setColor(this.client.config.color.green) - .setAuthor({ - name: guild.name, - iconURL: guild.iconURL({ extension: "jpeg" }), - }) - .setDescription(`**${guild.name}** has been added to my guilds!`) - .setThumbnail(guild.iconURL({ extension: "jpeg" })) - .addFields( - { - name: "Owner", - value: owner ? owner.user.tag : "Unknown#0000", - inline: true, - }, - { - name: "Members", - value: guild.memberCount.toString(), - inline: true, - }, - { - name: "Created At", - value: ``, - inline: true, - }, - { - name: "Joined At", - value: ``, - inline: true, - }, - { name: "ID", value: guild.id, inline: true }, - ) - .setTimestamp(); + const embed = new EmbedBuilder() + .setColor(this.client.config.color.green) + .setAuthor({ + name: guild.name, + iconURL: guild.iconURL({ extension: 'jpeg' })!, + }) + .setDescription(`**${guild.name}** has been added to my guilds!`) + .setThumbnail(guild.iconURL({ extension: 'jpeg' })) + .addFields( + { + name: 'Owner', + value: owner ? owner.user.tag : 'Unknown#0000', + inline: true, + }, + { + name: 'Members', + value: guild.memberCount.toString(), + inline: true, + }, + { + name: 'Created At', + value: ``, + inline: true, + }, + { + name: 'Joined At', + value: ``, + inline: true, + }, + { name: 'ID', value: guild.id, inline: true }, + ) + .setTimestamp(); - const logChannelId = this.client.config.logChannelId; - if (!logChannelId) { - this.client.logger.error("Log channel ID not found in configuration."); - return; - } + const logChannelId = this.client.env.LOG_CHANNEL_ID; + if (!logChannelId) { + this.client.logger.error('Log channel ID not found in configuration.'); + return; + } - try { - const channel = (await this.client.channels.fetch(logChannelId)) as TextChannel; - if (!channel) { - this.client.logger.error( - `Log channel not found with ID ${logChannelId}. Please change the settings in .env or, if you have a channel, invite me to that guild.`, - ); - return; - } + try { + const channel = (await this.client.channels.fetch(logChannelId)) as TextChannel; + if (!channel) { + this.client.logger.error( + `Log channel not found with ID ${logChannelId}. Please change the settings in .env or, if you have a channel, invite me to that guild.`, + ); + return; + } - await channel.send({ embeds: [embed] }); - } catch (error) { - this.client.logger.error(`Error sending message to log channel ${logChannelId}: ${error}`); - } - } + await channel.send({ embeds: [embed] }); + } catch (error) { + this.client.logger.error(`Error sending message to log channel ${logChannelId}: ${error}`); + } + } } /** diff --git a/src/events/client/GuildDelete.ts b/src/events/client/GuildDelete.ts index 9df21f6f8..f5bdb0ac7 100644 --- a/src/events/client/GuildDelete.ts +++ b/src/events/client/GuildDelete.ts @@ -1,73 +1,74 @@ -import { EmbedBuilder, type Guild, type GuildMember, type TextChannel } from "discord.js"; -import { Event, type Lavamusic } from "../../structures/index.js"; +import { EmbedBuilder, type Guild, type GuildMember, type TextChannel } from 'discord.js'; +import { Event, type Lavamusic } from '../../structures/index'; export default class GuildDelete extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "guildDelete", - }); - } + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'guildDelete', + }); + } - public async run(guild: Guild): Promise { - let owner: GuildMember | undefined; - try { - owner = await guild.members.fetch(guild.ownerId); - } catch (error) { - this.client.logger.error(`Error fetching owner for guild ${guild.id}: ${error}`); - } + public async run(guild: Guild): Promise { + if (!guild) return; + let owner: GuildMember | undefined; + try { + owner = await guild.members.fetch(guild.ownerId); + } catch (error) { + this.client.logger.error(`Error fetching owner for guild ${guild.id}: ${error}`); + } - const embed = new EmbedBuilder() - .setColor(this.client.config.color.red) - .setAuthor({ - name: guild.name || "Unknown Guild", - iconURL: guild.iconURL({ extension: "jpeg" }), - }) - .setDescription(`**${guild.name}** has been removed from my guilds!`) - .setThumbnail(guild.iconURL({ extension: "jpeg" })) - .addFields( - { - name: "Owner", - value: owner ? owner.user.tag : "Unknown#0000", - inline: true, - }, - { - name: "Members", - value: guild.memberCount?.toString() || "Unknown", - inline: true, - }, - { - name: "Created At", - value: ``, - inline: true, - }, - { - name: "Removed At", - value: ``, - inline: true, - }, - { name: "ID", value: guild.id, inline: true }, - ) - .setTimestamp(); + const embed = new EmbedBuilder() + .setColor(this.client.config.color.red) + .setAuthor({ + name: guild.name || 'Unknown Guild', + iconURL: guild.iconURL({ extension: 'jpeg' })!, + }) + .setDescription(`**${guild.name}** has been removed from my guilds!`) + .setThumbnail(guild.iconURL({ extension: 'jpeg' })) + .addFields( + { + name: 'Owner', + value: owner ? owner.user.tag : 'Unknown#0000', + inline: true, + }, + { + name: 'Members', + value: guild.memberCount?.toString() || 'Unknown', + inline: true, + }, + { + name: 'Created At', + value: ``, + inline: true, + }, + { + name: 'Removed At', + value: ``, + inline: true, + }, + { name: 'ID', value: guild.id, inline: true }, + ) + .setTimestamp(); - const logChannelId = this.client.config.logChannelId; - if (!logChannelId) { - this.client.logger.error("Log channel ID not found in configuration."); - return; - } + const logChannelId = this.client.env.LOG_CHANNEL_ID; + if (!logChannelId) { + this.client.logger.error('Log channel ID not found in configuration.'); + return; + } - try { - const channel = (await this.client.channels.fetch(logChannelId)) as TextChannel; - if (!channel) { - this.client.logger.error( - `Log channel not found with ID ${logChannelId}. Please change the settings in .env or, if you have a channel, invite me to that guild.`, - ); - return; - } - await channel.send({ embeds: [embed] }); - } catch (error) { - this.client.logger.error(`Error sending message to log channel ${logChannelId}: ${error}`); - } - } + try { + const channel = (await this.client.channels.fetch(logChannelId)) as TextChannel; + if (!channel) { + this.client.logger.error( + `Log channel not found with ID ${logChannelId}. Please change the settings in .env or, if you have a channel, invite me to that guild.`, + ); + return; + } + await channel.send({ embeds: [embed] }); + } catch (error) { + this.client.logger.error(`Error sending message to log channel ${logChannelId}: ${error}`); + } + } } /** diff --git a/src/events/client/InteractionCreate.ts b/src/events/client/InteractionCreate.ts index 800f563ac..893d8f6ca 100644 --- a/src/events/client/InteractionCreate.ts +++ b/src/events/client/InteractionCreate.ts @@ -1,258 +1,274 @@ import { - ActionRowBuilder, - type AutocompleteInteraction, - ButtonBuilder, - ButtonStyle, - ChannelType, - Collection, - CommandInteraction, - EmbedBuilder, - type GuildMember, - InteractionType, - PermissionFlagsBits, - type TextChannel, -} from "discord.js"; -import { T } from "../../structures/I18n.js"; -import { Context, Event, type Lavamusic } from "../../structures/index.js"; + ActionRowBuilder, + type AutocompleteInteraction, + ButtonBuilder, + ButtonStyle, + ChannelType, + Collection, + CommandInteraction, + EmbedBuilder, + type GuildMember, + InteractionType, + PermissionFlagsBits, + type TextChannel, +} from 'discord.js'; +import { T } from '../../structures/I18n'; +import { Context, Event, type Lavamusic } from '../../structures/index'; export default class InteractionCreate extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "interactionCreate", - }); - } + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'interactionCreate', + }); + } - public async run(interaction: CommandInteraction | AutocompleteInteraction): Promise { - if (interaction instanceof CommandInteraction && interaction.isCommand()) { - const setup = await this.client.db.getSetup(interaction.guildId); - const allowedCategories = ["filters", "music", "playlist"]; - const commandInSetup = this.client.commands.get(interaction.commandName); - const locale = await this.client.db.getLanguage(interaction.guildId); + public async run(interaction: CommandInteraction | AutocompleteInteraction): Promise { + if (!(interaction.guild && interaction.guildId)) return; + if (interaction instanceof CommandInteraction && interaction.isCommand()) { + const setup = await this.client.db.getSetup(interaction.guildId); + const allowedCategories = ['filters', 'music', 'playlist']; + const commandInSetup = this.client.commands.get(interaction.commandName); + const locale = await this.client.db.getLanguage(interaction.guildId); - if ( - setup && - interaction.channelId === setup.textId && - !(commandInSetup && allowedCategories.includes(commandInSetup.category)) - ) { - return await interaction.reply({ - content: T(locale, "event.interaction.setup_channel"), - ephemeral: true, - }); - } + if ( + setup && + interaction.channelId === setup.textId && + !(commandInSetup && allowedCategories.includes(commandInSetup.category)) + ) { + return await interaction.reply({ + content: T(locale, 'event.interaction.setup_channel'), + ephemeral: true, + }); + } - const { commandName } = interaction; - await this.client.db.get(interaction.guildId); + const { commandName } = interaction; + await this.client.db.get(interaction.guildId); - const command = this.client.commands.get(commandName); - if (!command) return; + const command = this.client.commands.get(commandName); + if (!command) return; - const ctx = new Context(interaction as any, interaction.options.data as any); - ctx.setArgs(interaction.options.data as any); - ctx.guildLocale = locale; - const clientMember = interaction.guild.members.resolve(this.client.user); - if (!(interaction.inGuild() && interaction.channel.permissionsFor(clientMember)?.has(PermissionFlagsBits.ViewChannel))) return; + const ctx = new Context(interaction as any, interaction.options.data as any); + ctx.setArgs(interaction.options.data as any); + ctx.guildLocale = locale; + const clientMember = interaction.guild.members.resolve(this.client.user!)!; + if ( + !( + interaction.inGuild() && + interaction.channel?.permissionsFor(clientMember)?.has(PermissionFlagsBits.ViewChannel) + ) + ) + return; - if ( - !( - clientMember.permissions.has(PermissionFlagsBits.ViewChannel) && - clientMember.permissions.has(PermissionFlagsBits.SendMessages) && - clientMember.permissions.has(PermissionFlagsBits.EmbedLinks) && - clientMember.permissions.has(PermissionFlagsBits.ReadMessageHistory) - ) - ) { - return await (interaction.member as GuildMember) - .send({ - content: T(locale, "event.interaction.no_send_message"), - }) - .catch(() => {}); - } + if ( + !( + clientMember.permissions.has(PermissionFlagsBits.ViewChannel) && + clientMember.permissions.has(PermissionFlagsBits.SendMessages) && + clientMember.permissions.has(PermissionFlagsBits.EmbedLinks) && + clientMember.permissions.has(PermissionFlagsBits.ReadMessageHistory) + ) + ) { + return await (interaction.member as GuildMember) + .send({ + content: T(locale, 'event.interaction.no_send_message'), + }) + .catch(() => { + null; + }); + } - const logs = this.client.channels.cache.get(this.client.config.commandLogs); + const logs = this.client.channels.cache.get(this.client.env.LOG_COMMANDS_ID!); - if (command.permissions) { - if (command.permissions?.client) { - const missingClientPermissions = command.permissions.client.filter((perm) => !clientMember.permissions.has(perm)); + if (command.permissions) { + if (command.permissions?.client) { + const missingClientPermissions = command.permissions.client.filter( + (perm: any) => !clientMember.permissions.has(perm), + ); - if (missingClientPermissions.length > 0) { - return await interaction.reply({ - content: T(locale, "event.interaction.no_permission", { - permissions: missingClientPermissions.map((perm) => `\`${perm}\``).join(", "), - }), - ephemeral: true, - }); - } - } + if (missingClientPermissions.length > 0) { + return await interaction.reply({ + content: T(locale, 'event.interaction.no_permission', { + permissions: missingClientPermissions.map((perm: string) => `\`${perm}\``).join(', '), + }), + ephemeral: true, + }); + } + } - if (command.permissions?.user && !(interaction.member as GuildMember).permissions.has(command.permissions.user)) { - await interaction.reply({ - content: T(locale, "event.interaction.no_user_permission"), - ephemeral: true, - }); - return; - } + if ( + command.permissions?.user && + !(interaction.member as GuildMember).permissions.has(command.permissions.user) + ) { + await interaction.reply({ + content: T(locale, 'event.interaction.no_user_permission'), + ephemeral: true, + }); + return; + } - if (command.permissions?.dev && this.client.config.owners) { - const isDev = this.client.config.owners.includes(interaction.user.id); - if (!isDev) return; - } - } - if (command.vote && this.client.config.topGG) { - const voted = await this.client.topGG.hasVoted(interaction.user.id); - if (!voted) { - const voteBtn = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel(T(locale, "event.interaction.vote_button")) - .setURL(`https://top.gg/bot/${this.client.config.clientId}/vote`) - .setStyle(ButtonStyle.Link), - ); + if (command.permissions?.dev && this.client.env.OWNER_IDS) { + const isDev = this.client.env.OWNER_IDS.includes(interaction.user.id); + if (!isDev) return; + } + } + if (command.vote && this.client.env.TOPGG) { + const voted = await this.client.topGG.hasVoted(interaction.user.id); + if (!voted) { + const voteBtn = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel(T(locale, 'event.interaction.vote_button')) + .setURL(`https://top.gg/bot/${this.client.user?.id}/vote`) + .setStyle(ButtonStyle.Link), + ); - return await interaction.reply({ - content: T(locale, "event.interaction.vote_message"), - components: [voteBtn], - ephemeral: true, - }); - } - } - if (command.player) { - if (command.player.voice) { - if (!(interaction.member as GuildMember).voice.channel) { - return await interaction.reply({ - content: T(locale, "event.interaction.no_voice_channel", { command: command.name }), - }); - } + return await interaction.reply({ + content: T(locale, 'event.interaction.vote_message'), + components: [voteBtn], + ephemeral: true, + }); + } + } + if (command.player) { + if (command.player.voice) { + if (!(interaction.member as GuildMember).voice.channel) { + return await interaction.reply({ + content: T(locale, 'event.interaction.no_voice_channel', { command: command.name }), + }); + } - if (!clientMember.permissions.has(PermissionFlagsBits.Connect)) { - return await interaction.reply({ - content: T(locale, "event.interaction.no_connect_permission", { command: command.name }), - }); - } + if (!clientMember.permissions.has(PermissionFlagsBits.Connect)) { + return await interaction.reply({ + content: T(locale, 'event.interaction.no_connect_permission', { command: command.name }), + }); + } - if (!clientMember.permissions.has(PermissionFlagsBits.Speak)) { - return await interaction.reply({ - content: T(locale, "event.interaction.no_speak_permission", { command: command.name }), - }); - } + if (!clientMember.permissions.has(PermissionFlagsBits.Speak)) { + return await interaction.reply({ + content: T(locale, 'event.interaction.no_speak_permission', { command: command.name }), + }); + } - if ( - (interaction.member as GuildMember).voice.channel.type === ChannelType.GuildStageVoice && - !clientMember.permissions.has(PermissionFlagsBits.RequestToSpeak) - ) { - return await interaction.reply({ - content: T(locale, "event.interaction.no_request_to_speak", { command: command.name }), - }); - } + if ( + (interaction.member as GuildMember).voice.channel?.type === ChannelType.GuildStageVoice && + !clientMember.permissions.has(PermissionFlagsBits.RequestToSpeak) + ) { + return await interaction.reply({ + content: T(locale, 'event.interaction.no_request_to_speak', { command: command.name }), + }); + } - if ( - clientMember.voice.channel && - clientMember.voice.channelId !== (interaction.member as GuildMember).voice.channelId - ) { - return await interaction.reply({ - content: T(locale, "event.interaction.different_voice_channel", { - channel: `<#${clientMember.voice.channelId}>`, - command: command.name, - }), - }); - } - } + if ( + clientMember.voice.channel && + clientMember.voice.channelId !== (interaction.member as GuildMember).voice.channelId + ) { + return await interaction.reply({ + content: T(locale, 'event.interaction.different_voice_channel', { + channel: `<#${clientMember.voice.channelId}>`, + command: command.name, + }), + }); + } + } - if (command.player.active) { - const queue = this.client.queue.get(interaction.guildId); - if (!(queue?.queue && queue.current)) { - return await interaction.reply({ - content: T(locale, "event.interaction.no_music_playing"), - }); - } - } + if (command.player.active) { + const queue = this.client.manager.getPlayer(interaction.guildId); + if (!queue.queue.current) { + return await interaction.reply({ + content: T(locale, 'event.interaction.no_music_playing'), + }); + } + } - if (command.player.dj) { - const dj = await this.client.db.getDj(interaction.guildId); - if (dj?.mode) { - const djRole = await this.client.db.getRoles(interaction.guildId); - if (!djRole) { - return await interaction.reply({ - content: T(locale, "event.interaction.no_dj_role"), - }); - } + if (command.player.dj) { + const dj = await this.client.db.getDj(interaction.guildId); + if (dj?.mode) { + const djRole = await this.client.db.getRoles(interaction.guildId); + if (!djRole) { + return await interaction.reply({ + content: T(locale, 'event.interaction.no_dj_role'), + }); + } - const hasDJRole = (interaction.member as GuildMember).roles.cache.some((role) => - djRole.map((r) => r.roleId).includes(role.id), - ); - if (!(hasDJRole && !(interaction.member as GuildMember).permissions.has(PermissionFlagsBits.ManageGuild))) { - return await interaction.reply({ - content: T(locale, "event.interaction.no_dj_permission"), - ephemeral: true, - }); - } - } - } - } + const hasDJRole = (interaction.member as GuildMember).roles.cache.some(role => + djRole.map(r => r.roleId).includes(role.id), + ); + if (!(hasDJRole && !(interaction.member as GuildMember).permissions.has(PermissionFlagsBits.ManageGuild))) { + return await interaction.reply({ + content: T(locale, 'event.interaction.no_dj_permission'), + ephemeral: true, + }); + } + } + } + } - if (!this.client.cooldown.has(commandName)) { - this.client.cooldown.set(commandName, new Collection()); - } + if (!this.client.cooldown.has(commandName)) { + this.client.cooldown.set(commandName, new Collection()); + } - const now = Date.now(); - const timestamps = this.client.cooldown.get(commandName)!; - const cooldownAmount = (command.cooldown || 5) * 1000; + const now = Date.now(); + const timestamps = this.client.cooldown.get(commandName)!; + const cooldownAmount = (command.cooldown || 5) * 1000; - if (timestamps.has(interaction.user.id)) { - const expirationTime = timestamps.get(interaction.user.id)! + cooldownAmount; - const timeLeft = (expirationTime - now) / 1000; - if (now < expirationTime && timeLeft > 0.9) { - return await interaction.reply({ - content: T(locale, "event.interaction.cooldown", { - time: timeLeft.toFixed(1), - command: commandName, - }), - }); - } - timestamps.set(interaction.user.id, now); - setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount); - } else { - timestamps.set(interaction.user.id, now); - setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount); - } + if (timestamps.has(interaction.user.id)) { + const expirationTime = timestamps.get(interaction.user.id)! + cooldownAmount; + const timeLeft = (expirationTime - now) / 1000; + if (now < expirationTime && timeLeft > 0.9) { + return await interaction.reply({ + content: T(locale, 'event.interaction.cooldown', { + time: timeLeft.toFixed(1), + command: commandName, + }), + }); + } + timestamps.set(interaction.user.id, now); + setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount); + } else { + timestamps.set(interaction.user.id, now); + setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount); + } - try { - await command.run(this.client, ctx, ctx.args); - if (setup && interaction.channelId === setup.textId && allowedCategories.includes(command.category)) { - setTimeout(() => { - interaction.deleteReply().catch(() => {}); - }, 5000); - } - if (logs) { - const embed = new EmbedBuilder() - .setAuthor({ - name: "Slash - Command Logs", - iconURL: this.client.user?.avatarURL({ - size: 2048, - }), - }) - .setColor(this.client.config.color.blue) - .setDescription( - `**\`${command.name}\`** | Used By **${interaction.user.tag} \`${interaction.user.id}\`** From **${interaction.guild.name} \`${interaction.guild.id}\`**`, - ) - .setTimestamp(); + try { + await command.run(this.client, ctx, ctx.args); + if (setup && interaction.channelId === setup.textId && allowedCategories.includes(command.category)) { + setTimeout(() => { + interaction.deleteReply().catch(() => { + null; + }); + }, 5000); + } + if (logs) { + const embed = new EmbedBuilder() + .setAuthor({ + name: 'Slash - Command Logs', + iconURL: this.client.user?.avatarURL({ + size: 2048, + })!, + }) + .setColor(this.client.config.color.blue) + .setDescription( + `**\`${command.name}\`** | Used By **${interaction.user.tag} \`${interaction.user.id}\`** From **${interaction.guild.name} \`${interaction.guild.id}\`**`, + ) + .setTimestamp(); - await (logs as TextChannel).send({ embeds: [embed] }); - } - } catch (error) { - this.client.logger.error(error); - await interaction.reply({ - content: T(locale, "event.interaction.error", { error }), - }); - } - } else if (interaction.type === InteractionType.ApplicationCommandAutocomplete) { - const command = this.client.commands.get(interaction.commandName); - if (!command) return; + await (logs as TextChannel).send({ embeds: [embed] }); + } + } catch (error) { + this.client.logger.error(error); + await interaction.reply({ + content: T(locale, 'event.interaction.error', { error }), + }); + } + } else if (interaction.type === InteractionType.ApplicationCommandAutocomplete) { + const command = this.client.commands.get(interaction.commandName); + if (!command) return; - try { - await command.autocomplete(interaction); - } catch (error) { - console.error(error); - } - } - } + try { + await command.autocomplete(interaction); + } catch (error) { + console.error(error); + } + } + } } /** diff --git a/src/events/client/MessageCreate.ts b/src/events/client/MessageCreate.ts index bdb31688c..ef36a26d4 100644 --- a/src/events/client/MessageCreate.ts +++ b/src/events/client/MessageCreate.ts @@ -1,260 +1,273 @@ import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - ChannelType, - Collection, - EmbedBuilder, - type GuildMember, - type Message, - PermissionFlagsBits, - type TextChannel, -} from "discord.js"; -import { T } from "../../structures/I18n.js"; -import { Context, Event, type Lavamusic } from "../../structures/index.js"; + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ChannelType, + Collection, + EmbedBuilder, + type GuildMember, + type Message, + PermissionFlagsBits, + type TextChannel, +} from 'discord.js'; +import { T } from '../../structures/I18n'; +import { Context, Event, type Lavamusic } from '../../structures/index'; export default class MessageCreate extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "messageCreate", - }); - } + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'messageCreate', + }); + } - public async run(message: Message): Promise { - if (message.author.bot) return; + public async run(message: Message): Promise { + if (message.author.bot) return; + if (!(message.guild && message.guildId)) return; + const setup = await this.client.db.getSetup(message.guildId); + if (setup && setup.textId === message.channelId) { + return this.client.emit('setupSystem', message); + } + const locale = await this.client.db.getLanguage(message.guildId); - const setup = await this.client.db.getSetup(message.guildId); - if (setup && setup.textId === message.channelId) { - return this.client.emit("setupSystem", message); - } - const locale = await this.client.db.getLanguage(message.guildId); + const guild = await this.client.db.get(message.guildId); + const mention = new RegExp(`^<@!?${this.client.user?.id}>( |)$`); + if (mention.test(message.content)) { + await message.reply({ + content: T(locale, 'event.message.prefix_mention', { + prefix: guild?.prefix, + }), + }); + return; + } - const guild = await this.client.db.get(message.guildId); - const mention = new RegExp(`^<@!?${this.client.user.id}>( |)$`); - if (mention.test(message.content)) { - await message.reply({ - content: T(locale, "event.message.prefix_mention", { - prefix: guild.prefix, - }), - }); - return; - } + const escapeRegex = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const prefixRegex = new RegExp(`^(<@!?${this.client.user?.id}>|${escapeRegex(guild.prefix)})\\s*`); + if (!prefixRegex.test(message.content)) return; + const match = message.content.match(prefixRegex); + if (!match) return; + const [matchedPrefix] = match; + const args = message.content.slice(matchedPrefix.length).trim().split(/ +/g); + const cmd = args.shift()?.toLowerCase(); + if (!cmd) return; + const command = this.client.commands.get(cmd) || this.client.commands.get(this.client.aliases.get(cmd) as string); + if (!command) return; - const escapeRegex = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const prefixRegex = new RegExp(`^(<@!?${this.client.user.id}>|${escapeRegex(guild.prefix)})\\s*`); - if (!prefixRegex.test(message.content)) return; + const ctx = new Context(message, args); + ctx.setArgs(args); + ctx.guildLocale = locale; - const [matchedPrefix] = message.content.match(prefixRegex); - const args = message.content.slice(matchedPrefix.length).trim().split(/ +/g); - const cmd = args.shift()?.toLowerCase(); - const command = this.client.commands.get(cmd) || this.client.commands.get(this.client.aliases.get(cmd) as string); - if (!command) return; + const clientMember = message.guild.members.resolve(this.client.user!)!; + const isDev = this.client.env.OWNER_IDS?.includes(message.author.id); - const ctx = new Context(message, args); - ctx.setArgs(args); - ctx.guildLocale = locale; + if (!(message.inGuild() && message.channel.permissionsFor(clientMember)?.has(PermissionFlagsBits.ViewChannel))) + return; - const clientMember = message.guild.members.resolve(this.client.user); - if (!(message.inGuild() && message.channel.permissionsFor(clientMember)?.has(PermissionFlagsBits.ViewChannel))) return; + if ( + !( + clientMember.permissions.has(PermissionFlagsBits.ViewChannel) && + clientMember.permissions.has(PermissionFlagsBits.SendMessages) && + clientMember.permissions.has(PermissionFlagsBits.EmbedLinks) && + clientMember.permissions.has(PermissionFlagsBits.ReadMessageHistory) + ) + ) { + return await message.author + .send({ + content: T(locale, 'event.message.no_send_message'), + }) + .catch(() => { + null; + }); + } - if ( - !( - clientMember.permissions.has(PermissionFlagsBits.ViewChannel) && - clientMember.permissions.has(PermissionFlagsBits.SendMessages) && - clientMember.permissions.has(PermissionFlagsBits.EmbedLinks) && - clientMember.permissions.has(PermissionFlagsBits.ReadMessageHistory) - ) - ) { - return await message.author - .send({ - content: T(locale, "event.message.no_send_message"), - }) - .catch(() => {}); - } + if (command.permissions) { + if (command.permissions?.client) { + const missingClientPermissions = command.permissions.client.filter( + (perm: any) => !clientMember.permissions.has(perm), + ); - if (command.permissions) { - if (command.permissions?.client) { - const missingClientPermissions = command.permissions.client.filter((perm) => !clientMember.permissions.has(perm)); + if (missingClientPermissions.length > 0) { + return await message.reply({ + content: T(locale, 'event.message.no_permission', { + permissions: missingClientPermissions.map((perm: string) => `\`${perm}\``).join(', '), + }), + }); + } + } - if (missingClientPermissions.length > 0) { - return await message.reply({ - content: T(locale, "event.message.no_permission", { - permissions: missingClientPermissions.map((perm) => `\`${perm}\``).join(", "), - }), - }); - } - } + if (command.permissions?.user) { + if (!(isDev || (message.member as GuildMember).permissions.has(command.permissions.user))) { + return await message.reply({ + content: T(locale, 'event.message.no_user_permission'), + }); + } + } - if (command.permissions?.user) { - if (!(message.member as GuildMember).permissions.has(command.permissions.user)) { - return await message.reply({ - content: T(locale, "event.message.no_user_permission"), - }); - } + if (command.permissions?.dev && this.client.env.OWNER_IDS) { + if (!isDev) 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.env.TOPGG) { + const voted = await this.client.topGG.hasVoted(message.author.id); + if (!(isDev || voted)) { + const voteBtn = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel(T(locale, 'event.message.vote_button')) + .setURL(`https://top.gg/bot/${this.client.user?.id}/vote`) + .setStyle(ButtonStyle.Link), + ); - 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(T(locale, "event.message.vote_button")) - .setURL(`https://top.gg/bot/${this.client.config.clientId}/vote`) - .setStyle(ButtonStyle.Link), - ); + return await message.reply({ + content: T(locale, 'event.message.vote_message'), + components: [voteBtn], + }); + } + } - return await message.reply({ - content: T(locale, "event.message.vote_message"), - components: [voteBtn], - }); - } - } + if (command.player) { + if (command.player.voice) { + if (!(message.member as GuildMember).voice.channel) { + return await message.reply({ + content: T(locale, 'event.message.no_voice_channel', { command: command.name }), + }); + } - if (command.player) { - if (command.player.voice) { - if (!(message.member as GuildMember).voice.channel) { - return await message.reply({ - content: T(locale, "event.message.no_voice_channel", { command: command.name }), - }); - } + if (!clientMember.permissions.has(PermissionFlagsBits.Connect)) { + return await message.reply({ + content: T(locale, 'event.message.no_connect_permission', { command: command.name }), + }); + } - if (!clientMember.permissions.has(PermissionFlagsBits.Connect)) { - return await message.reply({ - content: T(locale, "event.message.no_connect_permission", { command: command.name }), - }); - } + if (!clientMember.permissions.has(PermissionFlagsBits.Speak)) { + return await message.reply({ + content: T(locale, 'event.message.no_speak_permission', { command: command.name }), + }); + } - if (!clientMember.permissions.has(PermissionFlagsBits.Speak)) { - return await message.reply({ - content: T(locale, "event.message.no_speak_permission", { command: command.name }), - }); - } + if ( + (message.member as GuildMember).voice.channel?.type === ChannelType.GuildStageVoice && + !clientMember.permissions.has(PermissionFlagsBits.RequestToSpeak) + ) { + return await message.reply({ + content: T(locale, 'event.message.no_request_to_speak', { command: command.name }), + }); + } - if ( - (message.member as GuildMember).voice.channel.type === ChannelType.GuildStageVoice && - !clientMember.permissions.has(PermissionFlagsBits.RequestToSpeak) - ) { - return await message.reply({ - content: T(locale, "event.message.no_request_to_speak", { command: command.name }), - }); - } + if ( + clientMember.voice.channel && + clientMember.voice.channelId !== (message.member as GuildMember).voice.channelId + ) { + return await message.reply({ + content: T(locale, 'event.message.different_voice_channel', { + channel: `<#${clientMember.voice.channelId}>`, + command: command.name, + }), + }); + } + } - if (clientMember.voice.channel && clientMember.voice.channelId !== (message.member as GuildMember).voice.channelId) { - return await message.reply({ - content: T(locale, "event.message.different_voice_channel", { - channel: `<#${clientMember.voice.channelId}>`, - command: command.name, - }), - }); - } - } + if (command.player.active) { + const queue = this.client.manager.getPlayer(message.guildId); + if (!queue?.queue.current) { + return await message.reply({ + content: T(locale, 'event.message.no_music_playing'), + }); + } + } - if (command.player.active) { - const queue = this.client.queue.get(message.guildId); - if (!(queue?.queue && queue.current)) { - return await message.reply({ - content: T(locale, "event.message.no_music_playing"), - }); - } - } + 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) { + return await message.reply({ + content: T(locale, 'event.message.no_dj_role'), + }); + } - 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) { - return await message.reply({ - content: T(locale, "event.message.no_dj_role"), - }); - } + const hasDJRole = (message.member as GuildMember).roles.cache.some(role => + djRole.map(r => r.roleId).includes(role.id), + ); + if ( + !(isDev || (hasDJRole && !(message.member as GuildMember).permissions.has(PermissionFlagsBits.ManageGuild))) + ) { + return await message.reply({ + content: T(locale, 'event.message.no_dj_permission'), + }); + } + } + } + } - const hasDJRole = (message.member as GuildMember).roles.cache.some((role) => - djRole.map((r) => r.roleId).includes(role.id), - ); - if (!(hasDJRole && !(message.member as GuildMember).permissions.has(PermissionFlagsBits.ManageGuild))) { - return await message.reply({ - content: T(locale, "event.message.no_dj_permission"), - }); - } - } - } - } + if (command.args && args.length === 0) { + 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 === 0) { - 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()); + } + const now = Date.now(); + const timestamps = this.client.cooldown.get(cmd)!; + const cooldownAmount = (command.cooldown || 5) * 1000; - 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; + 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) { + return await message.reply({ + content: T(locale, 'event.message.cooldown', { time: timeLeft.toFixed(1), command: cmd }), + }); + } + 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 (timestamps.has(message.author.id)) { - const expirationTime = timestamps.get(message.author.id)! + cooldownAmount; - const timeLeft = (expirationTime - now) / 1000; - if (now < expirationTime && timeLeft > 0.9) { - return await message.reply({ - content: T(locale, "event.message.cooldown", { time: timeLeft.toFixed(1), command: cmd }), - }); - } - 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')) { + return await message.reply({ + content: T(locale, 'event.message.no_mention_everyone'), + }); + } - if (args.includes("@everyone") || args.includes("@here")) { - return await message.reply({ - content: T(locale, "event.message.no_mention_everyone"), - }); - } + try { + return command.run(this.client, ctx, ctx.args); + } catch (error: any) { + this.client.logger.error(error); + await message.reply({ + content: T(locale, 'event.message.error', { error: error.message || 'Unknown error' }), + }); + } finally { + const logs = this.client.channels.cache.get(this.client.env.LOG_COMMANDS_ID!); + if (logs) { + const embed = new EmbedBuilder() + .setAuthor({ + name: 'Prefix - Command Logs', + iconURL: this.client.user?.avatarURL({ size: 2048 })!, + }) + .setColor(this.client.config.color.green) + .setDescription( + `**\`${command.name}\`** | Used By **${message.author.tag} \`${message.author.id}\`** From **${message.guild.name} \`${message.guild.id}\`**`, + ) + .setTimestamp(); - try { - return command.run(this.client, ctx, ctx.args); - } catch (error) { - this.client.logger.error(error); - await message.reply({ - content: T(locale, "event.message.error", { error: error.message || "Unknown error" }), - }); - } finally { - const logs = this.client.channels.cache.get(this.client.config.commandLogs); - if (logs) { - const embed = new EmbedBuilder() - .setAuthor({ - name: "Prefix - Command Logs", - iconURL: this.client.user?.avatarURL({ size: 2048 }), - }) - .setColor(this.client.config.color.green) - .setDescription( - `**\`${command.name}\`** | Used By **${message.author.tag} \`${message.author.id}\`** From **${message.guild.name} \`${message.guild.id}\`**`, - ) - .setTimestamp(); - - await (logs as TextChannel).send({ embeds: [embed] }); - } - } - } - } + await (logs as TextChannel).send({ embeds: [embed] }); + } + } + } } /** diff --git a/src/events/client/Raw.ts b/src/events/client/Raw.ts new file mode 100644 index 000000000..3123c99be --- /dev/null +++ b/src/events/client/Raw.ts @@ -0,0 +1,24 @@ +import { Event, type Lavamusic } from '../../structures/index.js'; + +export default class Raw extends Event { + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'raw', + }); + } + + public async run(d: any): Promise { + this.client.manager.sendRawData(d); + } +} + +/** + * 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/Ready.ts b/src/events/client/Ready.ts index ac45ae9d1..7bd1ef8f7 100644 --- a/src/events/client/Ready.ts +++ b/src/events/client/Ready.ts @@ -1,36 +1,39 @@ -import { AutoPoster } from "topgg-autoposter"; -import config from "../../config.js"; -import { Event, type Lavamusic } from "../../structures/index.js"; +import { AutoPoster } from 'topgg-autoposter'; +import { env } from '../../env.js'; +import { Event, type Lavamusic } from '../../structures/index.js'; export default class Ready extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "ready", - }); - } + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'ready', + }); + } - public async run(): Promise { - this.client.logger.success(`${this.client.user?.tag} is ready!`); + public async run(): Promise { + this.client.logger.success(`${this.client.user?.tag} is ready!`); - this.client.user?.setPresence({ - activities: [ - { - name: config.botActivity, - type: config.botActivityType, - }, - ], - status: config.botStatus as any, - }); + this.client.user?.setPresence({ + activities: [ + { + name: env.BOT_ACTIVITY, + type: env.BOT_ACTIVITY_TYPE, + }, + ], + status: env.BOT_STATUS as any, + }); - if (config.topGG) { - const autoPoster = AutoPoster(config.topGG, this.client); - setInterval(() => { - autoPoster.on("posted", (_stats) => {}); - }, 86400000); // 24 hours in milliseconds - } else { - this.client.logger.warn("Top.gg token not found. Skipping auto poster."); - } - } + if (env.TOPGG) { + const autoPoster = AutoPoster(env.TOPGG, this.client); + setInterval(() => { + autoPoster.on('posted', _stats => { + null; + }); + }, 86400000); // 24 hours in milliseconds + } else { + this.client.logger.warn('Top.gg token not found. Skipping auto poster.'); + } + await this.client.manager.init({ ...this.client.user!, shards: 'auto' }); + } } /** diff --git a/src/events/client/SetupButtons.ts b/src/events/client/SetupButtons.ts index 267e56512..7b6c5b0b8 100644 --- a/src/events/client/SetupButtons.ts +++ b/src/events/client/SetupButtons.ts @@ -1,237 +1,264 @@ -import type { Message } from "discord.js"; -import { T } from "../../structures/I18n.js"; -import { Event, type Lavamusic } from "../../structures/index.js"; -import { getButtons } from "../../utils/Buttons.js"; -import { buttonReply } from "../../utils/SetupSystem.js"; -import { checkDj } from "../player/TrackStart.js"; +import type { Message } from 'discord.js'; +import { T } from '../../structures/I18n'; +import { Event, type Lavamusic } from '../../structures/index'; +import type { Requester } from '../../types'; +import { getButtons } from '../../utils/Buttons'; +import { buttonReply } from '../../utils/SetupSystem'; +import { checkDj } from '../player/TrackStart'; export default class SetupButtons extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "setupButtons", - }); - } + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'setupButtons', + }); + } - public async run(interaction: any): Promise { - const locale = await this.client.db.getLanguage(interaction.guildId); + public async run(interaction: any): Promise { + const locale = await this.client.db.getLanguage(interaction.guildId); - if (!interaction.replied) await interaction.deferReply().catch(() => {}); - if (!interaction.member.voice.channel) { - return await buttonReply(interaction, T(locale, "event.setupButton.no_voice_channel_button"), this.client.color.red); - } - const clientMember = interaction.guild.members.cache.get(this.client.user.id); - if (clientMember.voice.channel && clientMember.voice.channelId !== interaction.member.voice.channelId) { - return await buttonReply( - interaction, - T(locale, "event.setupButton.different_voice_channel_button", { - channel: clientMember.voice.channel, - }), - this.client.color.red, - ); - } - const player = this.client.queue.get(interaction.guildId); - if (!player) return await buttonReply(interaction, T(locale, "event.setupButton.no_music_playing"), this.client.color.red); - if (!player.queue) return await buttonReply(interaction, T(locale, "event.setupButton.no_music_playing"), this.client.color.red); - if (!player.current) return await buttonReply(interaction, T(locale, "event.setupButton.no_music_playing"), this.client.color.red); - const data = await this.client.db.getSetup(interaction.guildId); - const { title, uri, length, artworkUrl, sourceName, isStream, requester } = player.current.info; - let message: Message; - try { - message = await interaction.channel.messages.fetch(data.messageId, { - cache: true, - }); - } catch (_e) { - /* empty */ - } + if (!interaction.replied) + await interaction.deferReply().catch(() => { + null; + }); + if (!interaction.member.voice.channel) { + return await buttonReply( + interaction, + T(locale, 'event.setupButton.no_voice_channel_button'), + this.client.color.red, + ); + } + const clientMember = interaction.guild.members.cache.get(this.client.user?.id); + if (clientMember.voice.channel && clientMember.voice.channelId !== interaction.member.voice.channelId) { + return await buttonReply( + interaction, + T(locale, 'event.setupButton.different_voice_channel_button', { + channel: clientMember.voice.channel, + }), + this.client.color.red, + ); + } + const player = this.client.manager.getPlayer(interaction.guildId); + if (!player) + return await buttonReply(interaction, T(locale, 'event.setupButton.no_music_playing'), this.client.color.red); + if (!player.queue) + return await buttonReply(interaction, T(locale, 'event.setupButton.no_music_playing'), this.client.color.red); + if (!player.queue.current) + return await buttonReply(interaction, T(locale, 'event.setupButton.no_music_playing'), this.client.color.red); + const data = await this.client.db.getSetup(interaction.guildId); + const { title, uri, duration, artworkUrl, sourceName, isStream } = player.queue.current.info; + let message: Message | undefined; + try { + message = await interaction.channel.messages.fetch(data?.messageId, { + cache: true, + }); + } catch (_e) { + null; + } - const iconUrl = this.client.config.icons[sourceName] || this.client.user.displayAvatarURL({ extension: "png" }); - const embed = this.client - .embed() - .setAuthor({ - name: T(locale, "event.setupButton.now_playing"), - iconURL: iconUrl, - }) - .setColor(this.client.color.main) - .setDescription( - `[${title}](${uri}) - ${isStream ? T(locale, "event.setupButton.live") : this.client.utils.formatTime(length)} - ${T(locale, "event.setupButton.requested_by", { requester })}`, - ) - .setImage(artworkUrl || this.client.user.displayAvatarURL({ extension: "png" })); + const iconUrl = this.client.config.icons[sourceName] || this.client.user?.displayAvatarURL({ extension: 'png' }); + const embed = this.client + .embed() + .setAuthor({ + name: T(locale, 'event.setupButton.now_playing'), + iconURL: iconUrl, + }) + .setColor(this.client.color.main) + .setDescription( + `[${title}](${uri}) - ${isStream ? T(locale, 'event.setupButton.live') : this.client.utils.formatTime(duration)} - ${T(locale, 'event.setupButton.requested_by', { requester: (player.queue.current.requester as Requester).id })}`, + ) + .setImage(artworkUrl || this.client.user?.displayAvatarURL({ extension: 'png' })!); - if (!interaction.isButton()) return; - if (!(await checkDj(this.client, interaction))) { - return await buttonReply(interaction, T(locale, "event.setupButton.no_dj_permission"), this.client.color.red); - } - if (message) { - const handleVolumeChange = async (change: number) => { - const vol = player.player.volume + change; - player.player.setGlobalVolume(vol); - await buttonReply(interaction, T(locale, "event.setupButton.volume_set", { vol }), this.client.color.main); - await message.edit({ - embeds: [ - embed.setFooter({ - text: T(locale, "event.setupButton.volume_footer", { - vol, - displayName: interaction.member.displayName, - }), - iconURL: interaction.member.displayAvatarURL({}), - }), - ], - }); - }; - switch (interaction.customId) { - case "LOW_VOL_BUT": - await handleVolumeChange(-10); - break; - case "HIGH_VOL_BUT": - await handleVolumeChange(10); - break; - case "PAUSE_BUT": { - const name = player.player.paused ? T(locale, "event.setupButton.resumed") : T(locale, "event.setupButton.paused"); - player.pause(); - await buttonReply(interaction, T(locale, "event.setupButton.pause_resume", { name }), this.client.color.main); - await message.edit({ - embeds: [ - embed.setFooter({ - text: T(locale, "event.setupButton.pause_resume_footer", { - name, - displayName: interaction.member.displayName, - }), - iconURL: interaction.member.displayAvatarURL({}), - }), - ], - components: getButtons(player, this.client), - }); - break; - } - case "SKIP_BUT": - if (player.queue.length === 0) { - return await buttonReply(interaction, T(locale, "event.setupButton.no_music_to_skip"), this.client.color.main); - } - player.skip(); - await buttonReply(interaction, T(locale, "event.setupButton.skipped"), this.client.color.main); - await message.edit({ - embeds: [ - embed.setFooter({ - text: T(locale, "event.setupButton.skipped_footer", { - displayName: interaction.member.displayName, - }), - iconURL: interaction.member.displayAvatarURL({}), - }), - ], - }); - break; - case "STOP_BUT": - player.stop(); - await buttonReply(interaction, T(locale, "event.setupButton.stopped"), this.client.color.main); - await message.edit({ - embeds: [ - embed - .setFooter({ - text: T(locale, "event.setupButton.stopped_footer", { - displayName: interaction.member.displayName, - }), - iconURL: interaction.member.displayAvatarURL({}), - }) - .setDescription(T(locale, "event.setupButton.nothing_playing")) - .setImage(this.client.config.links.img) - .setAuthor({ - name: this.client.user.username, - iconURL: this.client.user.displayAvatarURL({ - extension: "png", - }), - }), - ], - }); - break; - case "LOOP_BUT": { - const loopOptions: Array<"off" | "queue" | "repeat"> = ["off", "queue", "repeat"]; - const newLoop = loopOptions[(loopOptions.indexOf(player.loop) + 1) % loopOptions.length]; - player.setLoop(newLoop); - await buttonReply( - interaction, - T(locale, "event.setupButton.loop_set", { - loop: newLoop, - }), - this.client.color.main, - ); - await message.edit({ - embeds: [ - embed.setFooter({ - text: T(locale, "event.setupButton.loop_footer", { - loop: newLoop, - displayName: interaction.member.displayName, - }), - iconURL: interaction.member.displayAvatarURL({}), - }), - ], - }); - break; - } - case "SHUFFLE_BUT": - player.setShuffle(); - await buttonReply(interaction, T(locale, "event.setupButton.shuffled"), this.client.color.main); - break; - case "PREV_BUT": - if (!player.previous) { - return await buttonReply(interaction, T(locale, "event.setupButton.no_previous_track"), this.client.color.main); - } - player.previousTrack(); - await buttonReply(interaction, T(locale, "event.setupButton.playing_previous"), this.client.color.main); - await message.edit({ - embeds: [ - embed.setFooter({ - text: T(locale, "event.setupButton.previous_footer", { - displayName: interaction.member.displayName, - }), - iconURL: interaction.member.displayAvatarURL({}), - }), - ], - }); - break; - case "REWIND_BUT": { - const time = player.player.position - 10000; - if (time < 0) { - return await buttonReply(interaction, T(locale, "event.setupButton.rewind_limit"), this.client.color.main); - } - player.seek(time); - await buttonReply(interaction, T(locale, "event.setupButton.rewinded"), this.client.color.main); - await message.edit({ - embeds: [ - embed.setFooter({ - text: T(locale, "event.setupButton.rewind_footer", { - displayName: interaction.member.displayName, - }), - iconURL: interaction.member.displayAvatarURL({}), - }), - ], - }); - break; - } - case "FORWARD_BUT": { - const time = player.player.position + 10000; - if (time > player.current.info.length) { - return await buttonReply(interaction, T(locale, "event.setupButton.forward_limit"), this.client.color.main); - } - player.seek(time); - await buttonReply(interaction, T(locale, "event.setupButton.forwarded"), this.client.color.main); - await message.edit({ - embeds: [ - embed.setFooter({ - text: T(locale, "event.setupButton.forward_footer", { - displayName: interaction.member.displayName, - }), - iconURL: interaction.member.displayAvatarURL({}), - }), - ], - }); - break; - } - default: - await buttonReply(interaction, T(locale, "event.setupButton.button_not_available"), this.client.color.main); - break; - } - } - } + if (!interaction.isButton()) return; + if (!(await checkDj(this.client, interaction))) { + return await buttonReply(interaction, T(locale, 'event.setupButton.no_dj_permission'), this.client.color.red); + } + if (message) { + const handleVolumeChange = async (change: number) => { + const vol = player.volume + change; + player.setVolume(vol); + await buttonReply(interaction, T(locale, 'event.setupButton.volume_set', { vol }), this.client.color.main); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, 'event.setupButton.volume_footer', { + vol, + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + }); + }; + switch (interaction.customId) { + case 'PREV_BUT': { + if (!player.queue.previous) { + return await buttonReply( + interaction, + T(locale, 'event.setupButton.no_previous_track'), + this.client.color.main, + ); + } + player.play({ + track: player.queue.previous[0], + }); + await buttonReply(interaction, T(locale, 'event.setupButton.playing_previous'), this.client.color.main); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, 'event.setupButton.previous_footer', { + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + }); + break; + } + case 'REWIND_BUT': { + const time = player.position - 10000; + if (time < 0) { + player.seek(0); + } else { + player.seek(time); + } + await buttonReply(interaction, T(locale, 'event.setupButton.rewinded'), this.client.color.main); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, 'event.setupButton.rewind_footer', { + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + }); + break; + } + case 'PAUSE_BUT': { + const name = player.paused ? T(locale, 'event.setupButton.resumed') : T(locale, 'event.setupButton.paused'); + if (player.paused) { + player.resume(); + } else { + player.pause(); + } + await buttonReply(interaction, T(locale, 'event.setupButton.pause_resume', { name }), this.client.color.main); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, 'event.setupButton.pause_resume_footer', { + name, + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + components: getButtons(player, this.client), + }); + break; + } + case 'FORWARD_BUT': { + const time = player.position + 10000; + if (time > player.queue.current.info.duration) { + return await buttonReply(interaction, T(locale, 'event.setupButton.forward_limit'), this.client.color.main); + } + player.seek(time); + await buttonReply(interaction, T(locale, 'event.setupButton.forwarded'), this.client.color.main); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, 'event.setupButton.forward_footer', { + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + }); + break; + } + case 'SKIP_BUT': { + if (player.queue.tracks.length === 0) { + return await buttonReply( + interaction, + T(locale, 'event.setupButton.no_music_to_skip'), + this.client.color.main, + ); + } + player.skip(); + await buttonReply(interaction, T(locale, 'event.setupButton.skipped'), this.client.color.main); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, 'event.setupButton.skipped_footer', { + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + }); + break; + } + case 'LOW_VOL_BUT': + await handleVolumeChange(-10); + break; + case 'LOOP_BUT': { + const loopOptions: Array<'off' | 'queue' | 'track'> = ['off', 'queue', 'track']; + const newLoop = loopOptions[(loopOptions.indexOf(player.repeatMode) + 1) % loopOptions.length]; + player.setRepeatMode(newLoop); + await buttonReply( + interaction, + T(locale, 'event.setupButton.loop_set', { + loop: newLoop, + }), + this.client.color.main, + ); + await message.edit({ + embeds: [ + embed.setFooter({ + text: T(locale, 'event.setupButton.loop_footer', { + loop: newLoop, + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }), + ], + }); + break; + } + case 'STOP_BUT': { + player.stopPlaying(true, false); + await buttonReply(interaction, T(locale, 'event.setupButton.stopped'), this.client.color.main); + await message.edit({ + embeds: [ + embed + .setFooter({ + text: T(locale, 'event.setupButton.stopped_footer', { + displayName: interaction.member.displayName, + }), + iconURL: interaction.member.displayAvatarURL({}), + }) + .setDescription(T(locale, 'event.setupButton.nothing_playing')) + .setImage(this.client.config.links.img) + .setAuthor({ + name: this.client.user?.username!, + iconURL: this.client.user?.displayAvatarURL({ + extension: 'png', + })!, + }), + ], + }); + break; + } + case 'SHUFFLE_BUT': { + player.queue.shuffle(); + await buttonReply(interaction, T(locale, 'event.setupButton.shuffled'), this.client.color.main); + break; + } + case 'HIGH_VOL_BUT': + await handleVolumeChange(10); + break; + } + } + } } /** diff --git a/src/events/client/SetupSystem.ts b/src/events/client/SetupSystem.ts index 72acb39fb..88b97feac 100644 --- a/src/events/client/SetupSystem.ts +++ b/src/events/client/SetupSystem.ts @@ -1,30 +1,32 @@ -import { type Message, PermissionsBitField, TextChannel } from "discord.js"; -import { T } from "../../structures/I18n.js"; -import { Event, type Lavamusic } from "../../structures/index.js"; -import { oops, setupStart } from "../../utils/SetupSystem.js"; +import { type Message, TextChannel } from 'discord.js'; +import { T } from '../../structures/I18n'; +import { Event, type Lavamusic } from '../../structures/index'; +import { oops, setupStart } from '../../utils/SetupSystem'; export default class SetupSystem extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "setupSystem", - }); - } + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'setupSystem', + }); + } - public async run(message: Message): Promise { - const locale = await this.client.db.getLanguage(message.guildId); - const channel = message.channel as TextChannel; - if (!(channel instanceof TextChannel)) return; - if (!message.member?.voice.channel) { - await oops(channel, T(locale, "event.message.no_voice_channel_queue")); - await message.delete().catch(() => {}); - return; - } + public async run(message: Message): Promise { + const locale = await this.client.db.getLanguage(message.guildId!); + const channel = message.channel as TextChannel; + if (!(channel instanceof TextChannel)) return; + if (!message.member?.voice.channel) { + await oops(channel, T(locale, 'event.message.no_voice_channel_queue')); + await message.delete().catch(() => { + null; + }); + return; + } - const voiceChannel = message.member.voice.channel; - const clientUser = this.client.user; - const clientMember = message.guild.members.cache.get(clientUser.id); + const voiceChannel = message.member.voice.channel; + const clientUser = this.client.user; + const clientMember = message.guild?.members.cache.get(clientUser!.id); - if (!voiceChannel.permissionsFor(clientUser).has(PermissionsBitField.Flags.Connect | PermissionsBitField.Flags.Speak)) { + /* if (voiceChannel && clientUser && !voiceChannel?.permissionsFor(clientUser!).has(PermissionsBitField.Flags.Connect | PermissionsBitField.Flags.Speak)) { await oops( channel, T(locale, "event.message.no_permission_connect_speak", { @@ -33,32 +35,39 @@ export default class SetupSystem extends Event { ); await message.delete().catch(() => {}); return; - } + } */ - if (clientMember?.voice.channel && clientMember.voice.channelId !== voiceChannel.id) { - await oops( - channel, - T(locale, "event.message.different_voice_channel_queue", { - channel: clientMember.voice.channelId, - }), - ); - await message.delete().catch(() => {}); - return; - } + if (clientMember?.voice.channel && clientMember.voice.channelId !== voiceChannel.id) { + await oops( + channel, + T(locale, 'event.message.different_voice_channel_queue', { + channel: clientMember.voice.channelId, + }), + ); + await message.delete().catch(() => { + null; + }); + return; + } - let player = this.client.queue.get(message.guildId); - if (!player) { - player = await this.client.queue.create( - message.guild, - voiceChannel, - message.channel, - this.client.shoukaku.options.nodeResolver(this.client.shoukaku.nodes), - ); - } + let player = this.client.manager.getPlayer(message.guildId!); + if (!player) { + player = this.client.manager.createPlayer({ + guildId: message.guildId!, + voiceChannelId: voiceChannel.id, + textChannelId: message.channelId, + selfMute: false, + selfDeaf: true, + vcRegion: voiceChannel.rtcRegion!, + }); + if (!player.connected) await player.connect(); + } - await setupStart(this.client, message.content, player, message); - await message.delete().catch(() => {}); - } + await setupStart(this.client, message.content, player, message); + await message.delete().catch(() => { + null; + }); + } } /** diff --git a/src/events/client/VoiceStateUpdate.ts b/src/events/client/VoiceStateUpdate.ts index 80c2029d2..c4f245db4 100644 --- a/src/events/client/VoiceStateUpdate.ts +++ b/src/events/client/VoiceStateUpdate.ts @@ -1,80 +1,80 @@ -import { ChannelType, type GuildMember, type VoiceState } from "discord.js"; -import { Event, type Lavamusic } from "../../structures/index.js"; +import { ChannelType, type GuildMember, type VoiceState } from 'discord.js'; +import { Event, type Lavamusic } from '../../structures/index'; export default class VoiceStateUpdate extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "voiceStateUpdate", - }); - } + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'voiceStateUpdate', + }); + } - public async run(_oldState: VoiceState, newState: VoiceState): Promise { - const guildId = newState.guild.id; - if (!guildId) return; + public async run(_oldState: VoiceState, newState: VoiceState): Promise { + const guildId = newState.guild.id; + if (!guildId) return; - const player = this.client.queue.get(guildId); - if (!player) return; + const player = this.client.manager.getPlayer(guildId); + if (!player) return; - const vcConnection = player.node.manager.connections.get(guildId); - if (!vcConnection?.channelId) return; + if (!player?.voiceChannelId) return; - const vc = newState.guild.channels.cache.get(vcConnection.channelId); - if (!(vc && vc.members instanceof Map)) return; + const vc = newState.guild.channels.cache.get(player.voiceChannelId); + if (!(vc && vc.members instanceof Map)) return; - const is247 = await this.client.db.get_247(guildId); + const is247 = await this.client.db.get_247(guildId); - if (!(newState.guild.members.cache.get(this.client.user.id)?.voice.channelId || !is247) && player) { - return player.destroy(); - } + if (!(newState.guild.members.cache.get(this.client.user!.id)?.voice.channelId || !is247) && player) { + return player.destroy(); + } - if ( - newState.id === this.client.user.id && - newState.channelId && - newState.channel.type === ChannelType.GuildStageVoice && - newState.guild.members.me.voice.suppress - ) { - if ( - newState.guild.members.me.permissions.has(["Connect", "Speak"]) || - newState.channel.permissionsFor(newState.guild.members.me).has("MuteMembers") - ) { - await newState.guild.members.me.voice.setSuppressed(false).catch(() => {}); - } - } + if ( + newState.id === this.client.user?.id && + newState.channelId && + newState.channel?.type === ChannelType.GuildStageVoice && + newState.guild.members.me?.voice.suppress + ) { + if ( + newState.guild.members.me.permissions.has(['Connect', 'Speak']) || + newState.channel.permissionsFor(newState.guild.members.me).has('MuteMembers') + ) { + await newState.guild.members.me.voice.setSuppressed(false).catch(() => { + null; + }); + } + } - if (newState.id === this.client.user.id && !newState.serverDeaf) { - const permissions = vc.permissionsFor(newState.guild.members.me); - if (permissions?.has("DeafenMembers")) { - await newState.setDeaf(true); - } - } + if (newState.id === this.client.user?.id && !newState.serverDeaf) { + const permissions = vc.permissionsFor(newState.guild.members.me!); + if (permissions?.has('DeafenMembers')) { + await newState.setDeaf(true); + } + } - if (newState.id === this.client.user.id) { - if (newState.serverMute && !player.paused) { - player.pause(); - } else if (!newState.serverMute && player.paused) { - player.pause(); - } - } + if (newState.id === this.client.user?.id) { + if (newState.serverMute && !player.paused) { + player.pause(); + } else if (!newState.serverMute && player.paused) { + player.pause(); + } + } - if (vc.members instanceof Map && [...vc.members.values()].filter((x: GuildMember) => !x.user.bot).length <= 0) { - setTimeout(async () => { - const vcConnection = player.node.manager.connections.get(guildId); - if (!vcConnection?.channelId) return; + if (vc.members instanceof Map && [...vc.members.values()].filter((x: GuildMember) => !x.user.bot).length <= 0) { + setTimeout(async () => { + if (!player?.voiceChannelId) return; - const playerVoiceChannel = newState.guild.channels.cache.get(vcConnection.channelId); - if ( - player && - playerVoiceChannel && - playerVoiceChannel.members instanceof Map && - [...playerVoiceChannel.members.values()].filter((x: GuildMember) => !x.user.bot).length <= 0 - ) { - if (!is247) { - player.destroy(); - } - } - }, 5000); - } - } + const playerVoiceChannel = newState.guild.channels.cache.get(player?.voiceChannelId); + if ( + player && + playerVoiceChannel && + playerVoiceChannel.members instanceof Map && + [...playerVoiceChannel.members.values()].filter((x: GuildMember) => !x.user.bot).length <= 0 + ) { + if (!is247) { + player.destroy(); + } + } + }, 5000); + } + } } /** diff --git a/src/events/node/Connect.ts b/src/events/node/Connect.ts new file mode 100644 index 000000000..40b1e4110 --- /dev/null +++ b/src/events/node/Connect.ts @@ -0,0 +1,64 @@ +import type { LavalinkNode } from 'lavalink-client'; +import { Event, type Lavamusic } from '../../structures/index'; +import BotLog from '../../utils/BotLog'; + +export default class Connect extends Event { + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'connect', + }); + } + + public async run(node: LavalinkNode): Promise { + this.client.logger.success(`Node ${node.id} is ready!`); + + let data = await this.client.db.get_247(); + if (!data) return; + + if (!Array.isArray(data)) { + data = [data]; + } + + data.forEach((main: { guildId: string; textId: string; voiceId: string }, index: number) => { + setTimeout(async () => { + const guild = this.client.guilds.cache.get(main.guildId); + if (!guild) return; + + const channel = guild.channels.cache.get(main.textId); + const vc = guild.channels.cache.get(main.voiceId); + + if (channel && vc) { + try { + const player = this.client.manager.createPlayer({ + guildId: guild.id, + voiceChannelId: vc.id, + textChannelId: channel.id, + selfDeaf: true, + selfMute: false, + }); + if (!player.connected) await player.connect(); + } catch (error) { + this.client.logger.error(`Failed to create queue for guild ${guild.id}: ${error}`); + } + } else { + this.client.logger.warn( + `Missing channels for guild ${guild.id}. Text channel: ${main.textId}, Voice channel: ${main.voiceId}`, + ); + } + }, index * 1000); + }); + + BotLog.send(this.client, `Node ${node.id} is ready!`, 'success'); + } +} + +/** + * 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/node/Destroy.ts b/src/events/node/Destroy.ts new file mode 100644 index 000000000..1e80bccc9 --- /dev/null +++ b/src/events/node/Destroy.ts @@ -0,0 +1,27 @@ +import type { DestroyReasonsType, LavalinkNode } from 'lavalink-client'; +import { Event, type Lavamusic } from '../../structures/index'; +import BotLog from '../../utils/BotLog'; + +export default class Destroy extends Event { + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'destroy', + }); + } + + public async run(node: LavalinkNode, destroyReason?: DestroyReasonsType): Promise { + this.client.logger.success(`Node ${node.id} is destroyed!`); + BotLog.send(this.client, `Node ${node.id} is destroyed: ${destroyReason}`, 'warn'); + } +} + +/** + * 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/player/NodeConnect.ts b/src/events/player/NodeConnect.ts deleted file mode 100644 index 4a186ce96..000000000 --- a/src/events/player/NodeConnect.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Event, type Lavamusic } from "../../structures/index.js"; -import BotLog from "../../utils/BotLog.js"; - -export default class NodeConnect extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "nodeConnect", - }); - } - - public async run(node: string): Promise { - this.client.logger.success(`Node ${node} is ready!`); - - let data = await this.client.db.get_247(); - if (!data) return; - - if (!Array.isArray(data)) { - data = [data]; - } - - data.forEach((main, index) => { - setTimeout(async () => { - const guild = this.client.guilds.cache.get(main.guildId); - if (!guild) return; - - const channel = guild.channels.cache.get(main.textId); - const vc = guild.channels.cache.get(main.voiceId); - - if (channel && vc) { - try { - await this.client.queue.create(guild, vc, channel); - } catch (error) { - this.client.logger.error(`Failed to create queue for guild ${guild.id}: ${error.message}`); - } - } else { - this.client.logger.warn( - `Missing channels for guild ${guild.id}. Text channel: ${main.textId}, Voice channel: ${main.voiceId}`, - ); - } - }, index * 1000); - }); - - BotLog.send(this.client, `Node ${node} is ready!`, "success"); - } -} diff --git a/src/events/player/NodeDestroy.ts b/src/events/player/NodeDestroy.ts deleted file mode 100644 index 924f7c125..000000000 --- a/src/events/player/NodeDestroy.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Event, type Lavamusic } from "../../structures/index.js"; -import BotLog from "../../utils/BotLog.js"; -let destroyCount = 0; - -export default class NodeDestroy extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "nodeDestroy", - }); - } - - public async run(node: string, code: number, reason: string): Promise { - const message = `Node ${node} destroyed with code ${code} and reason ${reason}.`; - this.client.logger.error(message); - BotLog.send(this.client, message, "error"); - - destroyCount++; - - if (destroyCount >= 5) { - this.client.shoukaku.removeNode(node); - destroyCount = 0; - const warnMessage = `Node ${node} removed from nodes list due to excessive disconnects.`; - this.client.logger.warn(warnMessage); - BotLog.send(this.client, warnMessage, "warn"); - } - } -} - -/** - * 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/player/NodeDisconnect.ts b/src/events/player/NodeDisconnect.ts deleted file mode 100644 index fb8c92e2a..000000000 --- a/src/events/player/NodeDisconnect.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Event, type Lavamusic } from "../../structures/index.js"; -import BotLog from "../../utils/BotLog.js"; - -export default class NodeDisconnect extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "nodeDisconnect", - }); - } - - public async run(node: string, count: number): Promise { - const message = `Node ${node} disconnected ${count} times`; - this.client.logger.warn(message); - BotLog.send(this.client, message, "warn"); - } -} - -/** - * 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/player/NodeError.ts b/src/events/player/NodeError.ts deleted file mode 100644 index 8f3888dba..000000000 --- a/src/events/player/NodeError.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Event, type Lavamusic } from "../../structures/index.js"; -import BotLog from "../../utils/BotLog.js"; - -export default class NodeError extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "nodeError", - }); - } - - public async run(node: string, error: any): Promise { - const errorMessage = JSON.stringify(error, null, 2); - const message = `Node ${node} Error: ${errorMessage}`; - this.client.logger.error(message); - BotLog.send(this.client, message, "error"); - } -} - -/** - * 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/player/NodeRaw.ts b/src/events/player/NodeRaw.ts deleted file mode 100644 index b4d6bd967..000000000 --- a/src/events/player/NodeRaw.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Event, type Lavamusic } from "../../structures/index.js"; - -export default class NodeRaw extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "nodeRaw", - }); - } - - public async run(_payload: any): Promise { - // Uncomment the following line for debugging purposes - // this.client.logger.debug(`Node raw event: ${JSON.stringify(payload)}`); - } -} - -/** - * 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/player/NodeReconnect.ts b/src/events/player/NodeReconnect.ts deleted file mode 100644 index 9a5111c32..000000000 --- a/src/events/player/NodeReconnect.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Event, type Lavamusic } from "../../structures/index.js"; -import BotLog from "../../utils/BotLog.js"; - -export default class NodeReconnect extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "nodeReconnect", - }); - } - - public async run(node: string): Promise { - const message = `Node ${node} reconnected`; - this.client.logger.warn(message); - BotLog.send(this.client, message, "warn"); - } -} - -/** - * 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/player/QueueEnd.ts b/src/events/player/QueueEnd.ts index 202964ae5..94ee90dab 100644 --- a/src/events/player/QueueEnd.ts +++ b/src/events/player/QueueEnd.ts @@ -1,41 +1,35 @@ -import type { Player } from "shoukaku"; -import type { Song } from "../../structures/Dispatcher.js"; -import { type Dispatcher, Event, type Lavamusic } from "../../structures/index.js"; -import { updateSetup } from "../../utils/SetupSystem.js"; +import type { TextChannel } from 'discord.js'; +import type { Player, Track, TrackStartEvent } from 'lavalink-client'; +import { Event, type Lavamusic } from '../../structures/index'; export default class QueueEnd extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "queueEnd", - }); - } + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'queueEnd', + }); + } - public async run(_player: Player, track: Song, dispatcher: Dispatcher): Promise { - const guild = this.client.guilds.cache.get(dispatcher.guildId); - if (!guild) return; - const locale = await this.client.db.getLanguage(guild.id); - switch (dispatcher.loop) { - case "repeat": - dispatcher.queue.unshift(track); - break; - case "queue": - dispatcher.queue.push(track); - break; - case "off": - dispatcher.previous = dispatcher.current; - dispatcher.current = null; - break; - } + public async run(player: Player, _track: Track | null, _payload: TrackStartEvent): Promise { + const guild = this.client.guilds.cache.get(player.guildId); + if (!guild) return; - if (dispatcher.autoplay) { - await dispatcher.Autoplay(track); - } else { - dispatcher.autoplay = false; - } + const messageId = player.get('messageId'); + if (!messageId) return; - await updateSetup(this.client, guild, locale); - this.client.utils.updateStatus(this.client, guild.id); - } + const channel = guild.channels.cache.get(player.textChannelId!) as TextChannel; + if (!channel) return; + + const message = await channel.messages.fetch(messageId).catch(() => { + null; + }); + if (!message) return; + + if (message.editable) { + await message.edit({ components: [] }).catch(() => { + null; + }); + } + } } /** diff --git a/src/events/player/TrackEnd.ts b/src/events/player/TrackEnd.ts index ccd09a034..048fc440b 100644 --- a/src/events/player/TrackEnd.ts +++ b/src/events/player/TrackEnd.ts @@ -1,39 +1,33 @@ -import type { Player } from "shoukaku"; -import type { Song } from "../../structures/Dispatcher.js"; -import { type Dispatcher, Event, type Lavamusic } from "../../structures/index.js"; +import type { TextChannel } from 'discord.js'; +import type { Player, Track, TrackStartEvent } from 'lavalink-client'; +import { Event, type Lavamusic } from '../../structures/index'; export default class TrackEnd extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "trackEnd", - }); - } - - public async run(_player: Player, track: Song, dispatcher: Dispatcher): Promise { - dispatcher.previous = dispatcher.current; - dispatcher.current = null; - - const nowPlayingMessage = await dispatcher.nowPlayingMessage?.fetch().catch(() => null); - - switch (dispatcher.loop) { - case "repeat": - dispatcher.queue.unshift(track); - break; - case "queue": - dispatcher.queue.push(track); - break; - } - - await dispatcher.play(); - - if (dispatcher.autoplay) { - await dispatcher.Autoplay(track); - } - - if (nowPlayingMessage?.deletable) { - await nowPlayingMessage.delete().catch(() => {}); - } - } + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'trackEnd', + }); + } + + public async run(player: Player, _track: Track | null, _payload: TrackStartEvent): Promise { + const guild = this.client.guilds.cache.get(player.guildId); + if (!guild) return; + + const messageId = player.get('messageId'); + if (!messageId) return; + + const channel = guild.channels.cache.get(player.textChannelId!) as TextChannel; + if (!channel) return; + + const message = await channel.messages.fetch(messageId).catch(() => { + null; + }); + if (!message) return; + + message.delete().catch(() => { + null; + }); + } } /** diff --git a/src/events/player/TrackStart.ts b/src/events/player/TrackStart.ts index 85f97715a..aa255d505 100644 --- a/src/events/player/TrackStart.ts +++ b/src/events/player/TrackStart.ts @@ -1,260 +1,288 @@ import { - ActionRowBuilder, - ButtonBuilder, - type ButtonInteraction, - ButtonStyle, - type ChannelSelectMenuInteraction, - GuildMember, - type MentionableSelectMenuInteraction, - PermissionFlagsBits, - type RoleSelectMenuInteraction, - type StringSelectMenuInteraction, - type TextChannel, - type UserSelectMenuInteraction, -} from "discord.js"; -import type { Player } from "shoukaku"; -import type { Song } from "../../structures/Dispatcher.js"; -import { T } from "../../structures/I18n.js"; -import { type Dispatcher, Event, type Lavamusic } from "../../structures/index.js"; -import { trackStart } from "../../utils/SetupSystem.js"; + ActionRowBuilder, + ButtonBuilder, + type ButtonInteraction, + ButtonStyle, + type ChannelSelectMenuInteraction, + GuildMember, + type MentionableSelectMenuInteraction, + PermissionFlagsBits, + type RoleSelectMenuInteraction, + type StringSelectMenuInteraction, + type TextChannel, + type UserSelectMenuInteraction, +} from 'discord.js'; +import type { Player, Track, TrackStartEvent } from 'lavalink-client'; +import { T } from '../../structures/I18n'; +import { Event, type Lavamusic } from '../../structures/index'; +import type { Requester } from '../../types'; +import { trackStart } from '../../utils/SetupSystem'; export default class TrackStart extends Event { - constructor(client: Lavamusic, file: string) { - super(client, file, { - name: "trackStart", - }); - } + constructor(client: Lavamusic, file: string) { + super(client, file, { + name: 'trackStart', + }); + } - public async run(player: Player, track: Song, dispatcher: Dispatcher): Promise { - if (!track?.info) return; + public async run(player: Player, track: Track | null, _payload: TrackStartEvent): Promise { + const guild = this.client.guilds.cache.get(player.guildId); + if (!guild) return; + if (!player.textChannelId) return; + if (!track) return; + const channel = guild.channels.cache.get(player.textChannelId) as TextChannel; + if (!channel) return; - const guild = this.client.guilds.cache.get(player.guildId); - if (!guild) return; + this.client.utils.updateStatus(this.client, guild.id); - const channel = guild.channels.cache.get(dispatcher.channelId) as TextChannel; - if (!channel) return; + const locale = await this.client.db.getLanguage(guild.id); - this.client.utils.updateStatus(this.client, guild.id); + const embed = this.client + .embed() + .setAuthor({ + name: T(locale, 'player.trackStart.now_playing'), + iconURL: + this.client.config.icons[track.info.sourceName] ?? this.client.user?.displayAvatarURL({ extension: 'png' }), + }) + .setColor(this.client.color.main) + .setDescription(`**[${track.info.title}](${track.info.uri})**`) + .setFooter({ + text: T(locale, 'player.trackStart.requested_by', { + user: (track.requester as Requester).username, + }), + iconURL: (track.requester as Requester).avatarURL, + }) + .setThumbnail(track.info.artworkUrl) + .addFields( + { + name: T(locale, 'player.trackStart.duration'), + value: track.info.isStream ? 'LIVE' : this.client.utils.formatTime(track.info.duration), + inline: true, + }, + { + name: T(locale, 'player.trackStart.author'), + value: track.info.author, + inline: true, + }, + ) + .setTimestamp(); - const locale = await this.client.db.getLanguage(guild.id); + const setup = await this.client.db.getSetup(guild.id); - const embed = this.client - .embed() - .setAuthor({ - name: T(locale, "player.trackStart.now_playing"), - iconURL: this.client.config.icons[track.info.sourceName] ?? this.client.user.displayAvatarURL({ extension: "png" }), - }) - .setColor(this.client.color.main) - .setDescription(`**[${track.info.title}](${track.info.uri})**`) - .setFooter({ - text: T(locale, "player.trackStart.requested_by", { - user: track.info.requester.tag, - }), - iconURL: track.info.requester.avatarURL({}), - }) - .setThumbnail(track.info.artworkUrl) - .addFields( - { - name: T(locale, "player.trackStart.duration"), - value: track.info.isStream ? "LIVE" : this.client.utils.formatTime(track.info.length), - inline: true, - }, - { - name: T(locale, "player.trackStart.author"), - value: track.info.author, - inline: true, - }, - ) - .setTimestamp(); + if (setup?.textId) { + const textChannel = guild.channels.cache.get(setup.textId) as TextChannel; + if (textChannel) { + await trackStart(setup.messageId, textChannel, player, track, this.client, locale); + } + } else { + const message = await channel.send({ + embeds: [embed], + components: [createButtonRow(player, this.client)], + }); - const setup = await this.client.db.getSetup(guild.id); - - if (setup?.textId) { - const textChannel = guild.channels.cache.get(setup.textId) as TextChannel; - const id = setup.messageId; - - if (textChannel) { - await trackStart(id, textChannel, dispatcher, track, this.client, locale); - } - } else { - const message = await channel.send({ - embeds: [embed], - components: [createButtonRow(dispatcher, this.client)], - }); - - dispatcher.nowPlayingMessage = message; - createCollector(message, dispatcher, track, embed, this.client, locale); - } - } + player.set('messageId', message.id); + createCollector(message, player, track, embed, this.client, locale); + } + } } -function createButtonRow(dispatcher: Dispatcher, client: Lavamusic): ActionRowBuilder { - const previousButton = new ButtonBuilder() +function createButtonRow(player: Player, client: Lavamusic): ActionRowBuilder { + const previousButton = new ButtonBuilder() - .setCustomId("previous") - .setEmoji(client.emoji.previous) - .setStyle(ButtonStyle.Secondary) - .setDisabled(!dispatcher.previous); + .setCustomId('previous') + .setEmoji(client.emoji.previous) + .setStyle(ButtonStyle.Secondary) + .setDisabled(!player.queue.previous); - const resumeButton = new ButtonBuilder() - .setCustomId("resume") - .setEmoji(dispatcher.paused ? client.emoji.resume : client.emoji.pause) - .setStyle(dispatcher.paused ? ButtonStyle.Success : ButtonStyle.Secondary); + const resumeButton = new ButtonBuilder() + .setCustomId('resume') + .setEmoji(player.paused ? client.emoji.resume : client.emoji.pause) + .setStyle(player.paused ? ButtonStyle.Success : ButtonStyle.Secondary); - const stopButton = new ButtonBuilder().setCustomId("stop").setEmoji(client.emoji.stop).setStyle(ButtonStyle.Danger); + const stopButton = new ButtonBuilder().setCustomId('stop').setEmoji(client.emoji.stop).setStyle(ButtonStyle.Danger); - const skipButton = new ButtonBuilder().setCustomId("skip").setEmoji(client.emoji.skip).setStyle(ButtonStyle.Secondary); + const skipButton = new ButtonBuilder() + .setCustomId('skip') + .setEmoji(client.emoji.skip) + .setStyle(ButtonStyle.Secondary); - const loopButton = new ButtonBuilder() - .setCustomId("loop") - .setEmoji(dispatcher.loop === "repeat" ? client.emoji.loop.track : client.emoji.loop.none) - .setStyle(dispatcher.loop !== "off" ? ButtonStyle.Success : ButtonStyle.Secondary); + const loopButton = new ButtonBuilder() + .setCustomId('loop') + .setEmoji(player.repeatMode === 'track' ? client.emoji.loop.track : client.emoji.loop.none) + .setStyle(player.repeatMode !== 'off' ? ButtonStyle.Success : ButtonStyle.Secondary); - return new ActionRowBuilder().addComponents(resumeButton, previousButton, stopButton, skipButton, loopButton); + return new ActionRowBuilder().addComponents( + resumeButton, + previousButton, + stopButton, + skipButton, + loopButton, + ); } -function createCollector(message: any, dispatcher: Dispatcher, _track: Song, embed: any, client: Lavamusic, locale: string): void { - const collector = message.createMessageComponentCollector({ - filter: async (b: ButtonInteraction) => { - if (b.member instanceof GuildMember) { - const isSameVoiceChannel = b.guild.members.me?.voice.channelId === b.member.voice.channelId; - if (isSameVoiceChannel) return true; - } - await b.reply({ - content: T(locale, "player.trackStart.not_connected_to_voice_channel", { - channel: b.guild.members.me?.voice.channelId ?? "None", - }), - ephemeral: true, - }); - return false; - }, - }); +function createCollector( + message: any, + player: Player, + _track: Track, + embed: any, + client: Lavamusic, + locale: string, +): void { + const collector = message.createMessageComponentCollector({ + filter: async (b: ButtonInteraction) => { + if (b.member instanceof GuildMember) { + const isSameVoiceChannel = b.guild?.members.me?.voice.channelId === b.member.voice.channelId; + if (isSameVoiceChannel) return true; + } + await b.reply({ + content: T(locale, 'player.trackStart.not_connected_to_voice_channel', { + channel: b.guild?.members.me?.voice.channelId ?? 'None', + }), + ephemeral: true, + }); + return false; + }, + }); - collector.on("collect", async (interaction) => { - if (!(await checkDj(client, interaction))) { - await interaction.reply({ - content: T(locale, "player.trackStart.need_dj_role"), - ephemeral: true, - }); - return; - } + collector.on('collect', async (interaction: ButtonInteraction<'cached'>) => { + if (!(await checkDj(client, interaction))) { + await interaction.reply({ + content: T(locale, 'player.trackStart.need_dj_role'), + ephemeral: true, + }); + return; + } - const editMessage = async (text: string): Promise => { - if (message) { - await message.edit({ - embeds: [ - embed.setFooter({ - text, - iconURL: interaction.user.avatarURL({}), - }), - ], - components: [createButtonRow(dispatcher, client)], - }); - } - }; - switch (interaction.customId) { - case "previous": - if (dispatcher.previous) { - await interaction.deferUpdate(); - dispatcher.previousTrack(); - await editMessage( - T(locale, "player.trackStart.previous_by", { - user: interaction.user.tag, - }), - ); - } else { - await interaction.reply({ - content: T(locale, "player.trackStart.no_previous_song"), - ephemeral: true, - }); - } - break; - case "resume": - dispatcher.pause(); - await interaction.deferUpdate(); - await editMessage( - dispatcher.paused - ? T(locale, "player.trackStart.paused_by", { - user: interaction.user.tag, - }) - : T(locale, "player.trackStart.resumed_by", { - user: interaction.user.tag, - }), - ); - break; - case "stop": - dispatcher.stop(); - await interaction.deferUpdate(); - break; - case "skip": - if (dispatcher.queue.length > 0) { - await interaction.deferUpdate(); - dispatcher.skip(); - await editMessage( - T(locale, "player.trackStart.skipped_by", { - user: interaction.user.tag, - }), - ); - } else { - await interaction.reply({ - content: T(locale, "player.trackStart.no_more_songs_in_queue"), - ephemeral: true, - }); - } - break; - case "loop": - await interaction.deferUpdate(); - switch (dispatcher.loop) { - case "off": - dispatcher.loop = "repeat"; - await editMessage( - T(locale, "player.trackStart.looping_by", { - user: interaction.user.tag, - }), - ); - break; - case "repeat": - dispatcher.loop = "queue"; - await editMessage( - T(locale, "player.trackStart.looping_queue_by", { - user: interaction.user.tag, - }), - ); - break; - case "queue": - dispatcher.loop = "off"; - await editMessage( - T(locale, "player.trackStart.looping_off_by", { - user: interaction.user.tag, - }), - ); - break; - } - break; - } - }); + const editMessage = async (text: string): Promise => { + if (message) { + await message.edit({ + embeds: [ + embed.setFooter({ + text, + iconURL: interaction.user.avatarURL({}), + }), + ], + components: [createButtonRow(player, client)], + }); + } + }; + switch (interaction.customId) { + case 'previous': + if (player.queue.previous) { + await interaction.deferUpdate(); + const previousTrack = player.queue.previous[0]; + player.play({ + track: previousTrack, + }); + await editMessage( + T(locale, 'player.trackStart.previous_by', { + user: interaction.user.tag, + }), + ); + } else { + await interaction.reply({ + content: T(locale, 'player.trackStart.no_previous_song'), + ephemeral: true, + }); + } + break; + case 'resume': + if (player.paused) { + player.resume(); + await interaction.deferUpdate(); + await editMessage( + T(locale, 'player.trackStart.resumed_by', { + user: interaction.user.tag, + }), + ); + } else { + player.pause(); + await interaction.deferUpdate(); + await editMessage( + T(locale, 'player.trackStart.paused_by', { + user: interaction.user.tag, + }), + ); + } + break; + case 'stop': { + player.stopPlaying(true, false); + await interaction.deferUpdate(); + break; + } + case 'skip': + if (player.queue.tracks.length > 0) { + await interaction.deferUpdate(); + player.skip(); + await editMessage( + T(locale, 'player.trackStart.skipped_by', { + user: interaction.user.tag, + }), + ); + } else { + await interaction.reply({ + content: T(locale, 'player.trackStart.no_more_songs_in_queue'), + ephemeral: true, + }); + } + break; + case 'loop': { + await interaction.deferUpdate(); + switch (player.repeatMode) { + case 'off': { + player.setRepeatMode('track'); + await editMessage( + T(locale, 'player.trackStart.looping_by', { + user: interaction.user.tag, + }), + ); + break; + } + case 'track': { + player.setRepeatMode('queue'); + await editMessage( + T(locale, 'player.trackStart.looping_queue_by', { + user: interaction.user.tag, + }), + ); + break; + } + case 'queue': { + player.setRepeatMode('off'); + await editMessage( + T(locale, 'player.trackStart.looping_off_by', { + user: interaction.user.tag, + }), + ); + break; + } + } + break; + } + } + }); } export async function checkDj( - client: Lavamusic, - interaction: - | ButtonInteraction<"cached"> - | StringSelectMenuInteraction<"cached"> - | UserSelectMenuInteraction<"cached"> - | RoleSelectMenuInteraction<"cached"> - | MentionableSelectMenuInteraction<"cached"> - | ChannelSelectMenuInteraction<"cached">, + client: Lavamusic, + interaction: + | ButtonInteraction<'cached'> + | StringSelectMenuInteraction<'cached'> + | UserSelectMenuInteraction<'cached'> + | RoleSelectMenuInteraction<'cached'> + | MentionableSelectMenuInteraction<'cached'> + | ChannelSelectMenuInteraction<'cached'>, ): Promise { - const dj = await client.db.getDj(interaction.guildId); - if (dj?.mode) { - const djRole = await client.db.getRoles(interaction.guildId); - if (!djRole) return false; - const hasDjRole = interaction.member.roles.cache.some((role) => djRole.map((r) => r.roleId).includes(role.id)); - if (!(hasDjRole || interaction.member.permissions.has(PermissionFlagsBits.ManageGuild))) { - return false; - } - } - return true; + const dj = await client.db.getDj(interaction.guildId); + if (dj?.mode) { + const djRole = await client.db.getRoles(interaction.guildId); + if (!djRole) return false; + const hasDjRole = interaction.member.roles.cache.some(role => djRole.map(r => r.roleId).includes(role.id)); + if (!(hasDjRole || interaction.member.permissions.has(PermissionFlagsBits.ManageGuild))) { + return false; + } + } + return true; } /** diff --git a/src/index.ts b/src/index.ts index 2fc907477..0debd514d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,7 @@ -import * as fs from "node:fs"; -import { ShardingManager } from "discord.js"; - -import config from "./config.js"; -import Logger from "./structures/Logger.js"; -import { ThemeSelector } from "./utils/ThemeSelector.js"; +import * as fs from 'node:fs'; +import { shardStart } from './shard'; +import Logger from './structures/Logger'; +import { ThemeSelector } from './utils/ThemeSelector'; const logger = new Logger(); @@ -14,44 +12,25 @@ const theme = new ThemeSelector(); * @param title - The new title for the console window. */ function setConsoleTitle(title: string): void { - // Write the escape sequence to change the console title - process.stdout.write(`\x1b]0;${title}\x07`); + // Write the escape sequence to change the console title + process.stdout.write(`\x1b]0;${title}\x07`); } -async function main(): Promise { - try { - if (!fs.existsSync("./src/utils/LavaLogo.txt")) { - logger.error("LavaLogo.txt file is missing"); - process.exit(1); - } - console.clear(); - // Set a custom title for the console window - setConsoleTitle("Lavamusic"); - const logFile = fs.readFileSync("./src/utils/LavaLogo.txt", "utf-8"); - console.log(theme.purpleNeon(logFile)); - const manager = new ShardingManager("./dist/LavaClient.js", { - respawn: true, - token: config.token, - totalShards: "auto", - shardList: "auto", - }); - - manager.on("shardCreate", (shard) => { - shard.on("ready", () => { - logger.start(`[CLIENT] Shard ${shard.id} connected to Discord's Gateway.`); - }); - }); - - await manager.spawn(); - - logger.start(`[CLIENT] ${manager.totalShards} shard(s) spawned.`); - } catch (err) { - logger.error("[CLIENT] An error has occurred:", err); - } +try { + if (!fs.existsSync('./src/utils/LavaLogo.txt')) { + logger.error('LavaLogo.txt file is missing'); + process.exit(1); + } + console.clear(); + // Set a custom title for the console window + setConsoleTitle('Lavamusic'); + const logFile = fs.readFileSync('./src/utils/LavaLogo.txt', 'utf-8'); + console.log(theme.purpleNeon(logFile)); + shardStart(logger); +} catch (err) { + logger.error('[CLIENT] An error has occurred:', err); } -main(); - /** * Project: lavamusic * Author: Appu diff --git a/src/plugin/index.ts b/src/plugin/index.ts index dd455b10f..37f4bebad 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -1,32 +1,30 @@ -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import type { Lavamusic } from "../structures/index.js"; +import fs from 'node:fs'; +import path from 'node:path'; +import type { Lavamusic } from '../structures/index'; -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const pluginsFolder = path.join(__dirname, "plugins"); +const pluginsFolder = path.join(__dirname, 'plugins'); export default async function loadPlugins(client: Lavamusic): Promise { - try { - const pluginFiles = fs.readdirSync(pluginsFolder).filter((file) => file.endsWith(".js")); - for (const file of pluginFiles) { - const pluginPath = path.join(pluginsFolder, file); - const { default: plugin } = await import(`file://${pluginPath}`); - if (plugin.initialize) plugin.initialize(client); - client.logger.info(`Loaded plugin: ${plugin.name} v${plugin.version}`); - } - } catch (error) { - client.logger.error("Error loading plugins:", error); - } + try { + const pluginFiles = fs.readdirSync(pluginsFolder).filter(file => file.endsWith('.js')); + for (const file of pluginFiles) { + const pluginPath = path.join(pluginsFolder, file); + const { default: plugin } = require(pluginPath); + if (plugin.initialize) plugin.initialize(client); + client.logger.info(`Loaded plugin: ${plugin.name} v${plugin.version}`); + } + } catch (error) { + client.logger.error('Error loading plugins:', error); + } } export interface BotPlugin { - name: string; - version: string; - author: string; - description?: string; - initialize: (client: Lavamusic) => void; - shutdown?: (client: Lavamusic) => void; + name: string; + version: string; + author: string; + description?: string; + initialize: (client: Lavamusic) => void; + shutdown?: (client: Lavamusic) => void; } /** diff --git a/src/plugin/plugins/antiCrash.ts b/src/plugin/plugins/antiCrash.ts index bd84eb4ff..352264fbd 100644 --- a/src/plugin/plugins/antiCrash.ts +++ b/src/plugin/plugins/antiCrash.ts @@ -1,29 +1,29 @@ -import type { Lavamusic } from "../../structures/index.js"; -import type { BotPlugin } from "../index.js"; +import type { Lavamusic } from '../../structures/index'; +import type { BotPlugin } from '../index'; const antiCrash: BotPlugin = { - name: "AntiCrash Plugin", - version: "1.0.0", - author: "Appu", - initialize: (client: Lavamusic) => { - const handleExit = async (): Promise => { - if (client) { - client.logger.star("Disconnecting from Discord..."); - await client.destroy(); - client.logger.success("Successfully disconnected from Discord!"); - process.exit(); - } - }; - process.on("unhandledRejection", (reason, promise) => { - client.logger.error("Unhandled Rejection at:", promise, "reason:", reason); - }); - process.on("uncaughtException", (err) => { - client.logger.error("Uncaught Exception thrown:", err); - }); - process.on("SIGINT", handleExit); - process.on("SIGTERM", handleExit); - process.on("SIGQUIT", handleExit); - }, + name: 'AntiCrash Plugin', + version: '1.0.0', + author: 'Appu', + initialize: (client: Lavamusic) => { + const handleExit = async (): Promise => { + if (client) { + client.logger.star('Disconnecting from Discord...'); + await client.destroy(); + client.logger.success('Successfully disconnected from Discord!'); + process.exit(); + } + }; + process.on('unhandledRejection', (reason, promise) => { + client.logger.error('Unhandled Rejection at:', promise, 'reason:', reason); + }); + process.on('uncaughtException', err => { + client.logger.error('Uncaught Exception thrown:', err); + }); + process.on('SIGINT', handleExit); + process.on('SIGTERM', handleExit); + process.on('SIGQUIT', handleExit); + }, }; export default antiCrash; diff --git a/src/plugin/plugins/keepAlive.ts b/src/plugin/plugins/keepAlive.ts index adf545818..7ff91949c 100644 --- a/src/plugin/plugins/keepAlive.ts +++ b/src/plugin/plugins/keepAlive.ts @@ -1,22 +1,23 @@ -import http from "node:http"; -import type { Lavamusic } from "../../structures/index.js"; -import type { BotPlugin } from "../index.js"; +import http from 'node:http'; +import { env } from '../../env'; +import type { Lavamusic } from '../../structures/index'; +import type { BotPlugin } from '../index'; const keepAlive: BotPlugin = { - name: "KeepAlive Plugin", - version: "1.0.0", - author: "Appu", - initialize: (client: Lavamusic) => { - if (client.config.keepAlive) { - const server = http.createServer((_req, res) => { - res.writeHead(200, { "Content-Type": "text/plain" }); - res.end(`I'm alive! Currently serving ${client.guilds.cache.size} guilds.`); - }); - server.listen(3000, () => { - client.logger.info("Keep-Alive server is running on port 3000"); - }); - } - }, + name: 'KeepAlive Plugin', + version: '1.0.0', + author: 'Appu', + initialize: (client: Lavamusic) => { + if (env.KEEP_ALIVE) { + const server = http.createServer((_req, res) => { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(`I'm alive! Currently serving ${client.guilds.cache.size} guilds.`); + }); + server.listen(3000, () => { + client.logger.info('Keep-Alive server is running on port 3000'); + }); + } + }, }; export default keepAlive; diff --git a/src/plugin/plugins/updateStatus.ts b/src/plugin/plugins/updateStatus.ts index e8487f547..7c07dba67 100644 --- a/src/plugin/plugins/updateStatus.ts +++ b/src/plugin/plugins/updateStatus.ts @@ -1,13 +1,13 @@ -import type { Lavamusic } from "../../structures/index.js"; -import type { BotPlugin } from "../index.js"; +import type { Lavamusic } from '../../structures/index'; +import type { BotPlugin } from '../index'; const updateStatusPlugin: BotPlugin = { - name: "Update Status Plugin", - version: "1.0.0", - author: "Appu", - initialize: (client: Lavamusic) => { - client.on("ready", () => client.utils.updateStatus(client)); - }, + name: 'Update Status Plugin', + version: '1.0.0', + author: 'Appu', + initialize: (client: Lavamusic) => { + client.on('ready', () => client.utils.updateStatus(client)); + }, }; export default updateStatusPlugin; diff --git a/src/shard.ts b/src/shard.ts new file mode 100644 index 000000000..432e28974 --- /dev/null +++ b/src/shard.ts @@ -0,0 +1,33 @@ +import { ShardingManager } from 'discord.js'; +import { env } from './env'; +import type Logger from './structures/Logger'; + +export async function shardStart(logger: Logger) { + const manager = new ShardingManager('./dist/LavaClient.js', { + respawn: true, + token: env.TOKEN, + totalShards: 'auto', + shardList: 'auto', + }); + + manager.on('shardCreate', shard => { + shard.on('ready', () => { + logger.start(`[CLIENT] Shard ${shard.id} connected to Discord's Gateway.`); + }); + }); + + await manager.spawn(); + + logger.start(`[CLIENT] ${manager.totalShards} shard(s) spawned.`); +} + +/** + * 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/structures/Command.ts b/src/structures/Command.ts index 2e2f5b990..bdcce0ff2 100644 --- a/src/structures/Command.ts +++ b/src/structures/Command.ts @@ -1,90 +1,90 @@ -import type { APIApplicationCommandOption, PermissionResolvable } from "discord.js"; -import type Lavamusic from "./Lavamusic.js"; +import type { APIApplicationCommandOption, PermissionResolvable } from 'discord.js'; +import type Lavamusic from './Lavamusic'; interface CommandDescription { - content: string; - usage: string; - examples: string[]; + content: string; + usage: string; + examples: string[]; } interface CommandPlayer { - voice: boolean; - dj: boolean; - active: boolean; - djPerm: string | null; + voice: boolean; + dj: boolean; + active: boolean; + djPerm: string | null; } interface CommandPermissions { - dev: boolean; - client: string[] | PermissionResolvable; - user: string[] | PermissionResolvable; + dev: boolean; + client: string[] | PermissionResolvable; + user: string[] | PermissionResolvable; } interface CommandOptions { - name: string; - name_localizations?: Record; - description?: Partial; - description_localizations?: Record; - aliases?: string[]; - cooldown?: number; - args?: boolean; - vote?: boolean; - player?: Partial; - permissions?: Partial; - slashCommand?: boolean; - options?: APIApplicationCommandOption[]; - category?: string; + name: string; + name_localizations?: Record; + description?: Partial; + description_localizations?: Record; + aliases?: string[]; + cooldown?: number; + args?: boolean; + vote?: boolean; + player?: Partial; + permissions?: Partial; + slashCommand?: boolean; + options?: APIApplicationCommandOption[]; + category?: string; } export default class Command { - public client: Lavamusic; - public name: string; - public name_localizations?: Record; - public description: CommandDescription; - public description_localizations?: Record; - public aliases: string[]; - public cooldown: number; - public args: boolean; - public vote: boolean; - public player: CommandPlayer; - public permissions: CommandPermissions; - public slashCommand: boolean; - public options: APIApplicationCommandOption[]; - public category: string; + public client: Lavamusic; + public name: string; + public name_localizations?: Record; + public description: CommandDescription; + public description_localizations?: Record; + public aliases: string[]; + public cooldown: number; + public args: boolean; + public vote: boolean; + public player: CommandPlayer; + public permissions: CommandPermissions; + public slashCommand: boolean; + public options: APIApplicationCommandOption[]; + public category: string; - constructor(client: Lavamusic, options: CommandOptions) { - this.client = client; - this.name = options.name; - this.name_localizations = options.name_localizations ?? {}; - this.description = { - content: options.description?.content ?? "No description provided", - usage: options.description?.usage ?? "No usage provided", - examples: options.description?.examples ?? ["No examples provided"], - }; - this.description_localizations = options.description_localizations ?? {}; - this.aliases = options.aliases ?? []; - this.cooldown = options.cooldown ?? 3; - this.args = options.args ?? false; - this.vote = options.vote ?? false; - this.player = { - voice: options.player?.voice ?? false, - dj: options.player?.dj ?? false, - active: options.player?.active ?? false, - djPerm: options.player?.djPerm ?? null, - }; - this.permissions = { - dev: options.permissions?.dev ?? false, - client: options.permissions?.client ?? ["SendMessages", "ViewChannel", "EmbedLinks"], - user: options.permissions?.user ?? [], - }; - this.slashCommand = options.slashCommand ?? false; - this.options = options.options ?? []; - this.category = options.category ?? "general"; - } + constructor(client: Lavamusic, options: CommandOptions) { + this.client = client; + this.name = options.name; + this.name_localizations = options.name_localizations ?? {}; + this.description = { + content: options.description?.content ?? 'No description provided', + usage: options.description?.usage ?? 'No usage provided', + examples: options.description?.examples ?? ['No examples provided'], + }; + this.description_localizations = options.description_localizations ?? {}; + this.aliases = options.aliases ?? []; + this.cooldown = options.cooldown ?? 3; + this.args = options.args ?? false; + this.vote = options.vote ?? false; + this.player = { + voice: options.player?.voice ?? false, + dj: options.player?.dj ?? false, + active: options.player?.active ?? false, + djPerm: options.player?.djPerm ?? null, + }; + this.permissions = { + dev: options.permissions?.dev ?? false, + client: options.permissions?.client ?? ['SendMessages', 'ViewChannel', 'EmbedLinks'], + user: options.permissions?.user ?? [], + }; + this.slashCommand = options.slashCommand ?? false; + this.options = options.options ?? []; + this.category = options.category ?? 'general'; + } - public async run(_client: Lavamusic, _message: any, _args: string[]): Promise { - return await Promise.resolve(); - } + public async run(_client: Lavamusic, _message: any, _args: string[]): Promise { + return await Promise.resolve(); + } } /** diff --git a/src/structures/Context.ts b/src/structures/Context.ts index da14bb27b..02bae690f 100644 --- a/src/structures/Context.ts +++ b/src/structures/Context.ts @@ -1,139 +1,154 @@ import { - type APIInteractionGuildMember, - ChannelType, - ChatInputCommandInteraction, - type CommandInteraction, - type DMChannel, - type Guild, - type GuildMember, - type GuildMemberResolvable, - type GuildTextBasedChannel, - type InteractionEditReplyOptions, - type InteractionReplyOptions, - Message, - type MessageCreateOptions, - type MessageEditOptions, - type MessagePayload, - type PartialDMChannel, - type TextChannel, - type User, -} from "discord.js"; -import { T } from "./I18n.js"; -import type { Lavamusic } from "./index.js"; + type APIInteractionGuildMember, + ChatInputCommandInteraction, + type CommandInteraction, + type Guild, + type GuildMember, + type GuildMemberResolvable, + type InteractionEditReplyOptions, + type InteractionReplyOptions, + Message, + type MessageCreateOptions, + type MessageEditOptions, + type MessagePayload, + type TextBasedChannel, + type TextChannel, + type User, +} from 'discord.js'; +import { T } from './I18n'; +import type { Lavamusic } from './index'; export default class Context { - public ctx: CommandInteraction | Message; - public interaction: CommandInteraction | null; - public message: Message | null; - public id: string; - public channelId: string; - public client: Lavamusic; - public author: User | null; - public channel: PartialDMChannel | GuildTextBasedChannel | TextChannel | DMChannel | null = null; - public guild: Guild | null; - public createdAt: Date; - public createdTimestamp: number; - public member: GuildMemberResolvable | GuildMember | APIInteractionGuildMember | null; - public args: any[]; - public msg: any; - public guildLocale: string; - - constructor(ctx: ChatInputCommandInteraction | Message, args: any[]) { - this.ctx = ctx; - this.interaction = ctx instanceof ChatInputCommandInteraction ? ctx : null; - this.message = ctx instanceof Message ? ctx : null; - - if (ctx.channel && ctx.channel.type !== ChannelType.GroupDM) { - this.channel = ctx.channel; - } else { - this.channel = null; - } - - this.id = ctx.id; - this.channelId = ctx.channelId; - this.client = ctx.client as Lavamusic; - this.author = ctx instanceof Message ? ctx.author : ctx.user; - this.guild = ctx.guild; - this.createdAt = ctx.createdAt; - this.createdTimestamp = ctx.createdTimestamp; - this.member = ctx.member; - this.args = args; - this.setArgs(args); - this.setUpLocale(); - } - - private async setUpLocale(): Promise { - this.guildLocale = this.guild ? await this.client.db.getLanguage(this.guild.id) : "en"; - } - - public get isInteraction(): boolean { - return this.ctx instanceof ChatInputCommandInteraction; - } - - public setArgs(args: any[]): void { - this.args = this.isInteraction ? args.map((arg: { value: any }) => arg.value) : args; - } - - public async sendMessage(content: string | MessagePayload | MessageCreateOptions | InteractionReplyOptions): Promise { - if (this.isInteraction) { - if (typeof content === "string" || isInteractionReplyOptions(content)) { - this.msg = await this.interaction.reply(content); - return this.msg; - } - } else if (typeof content === "string" || isMessagePayload(content)) { - this.msg = await (this.message.channel as TextChannel).send(content); - return this.msg; - } - return this.msg; - } - - public async editMessage(content: string | MessagePayload | InteractionEditReplyOptions | MessageEditOptions): Promise { - if (this.isInteraction && this.msg) { - this.msg = await this.interaction.editReply(content); - return this.msg; - } - if (this.msg) { - this.msg = await this.msg.edit(content); - return this.msg; - } - return this.msg; - } - - public async sendDeferMessage(content: string | MessagePayload | MessageCreateOptions): Promise { - if (this.isInteraction) { - this.msg = await this.interaction.deferReply({ fetchReply: true }); - return this.msg; - } - - this.msg = await (this.message.channel as TextChannel).send(content); - return this.msg; - } - - public locale(key: string, ...args: any) { - return T(this.guildLocale, key, ...args); - } - - public async sendFollowUp(content: string | MessagePayload | MessageCreateOptions | InteractionReplyOptions): Promise { - if (this.isInteraction) { - if (typeof content === "string" || isInteractionReplyOptions(content)) { - await this.interaction.followUp(content); - } - } else if (typeof content === "string" || isMessagePayload(content)) { - this.msg = await (this.message.channel as TextChannel).send(content); - } - } - - public get deferred(): boolean | Promise { - return this.isInteraction ? this.interaction.deferred : !!this.msg; - } + public ctx: CommandInteraction | Message; + public interaction: CommandInteraction | null; + public message: Message | null; + public id: string; + public channelId: string; + public client: Lavamusic; + public author: User | null; + public channel: TextBasedChannel; + public guild: Guild; + public createdAt: Date; + public createdTimestamp: number; + public member: GuildMemberResolvable | GuildMember | APIInteractionGuildMember | null; + public args: any[]; + public msg: any; + public guildLocale: string | undefined; + + constructor(ctx: ChatInputCommandInteraction | Message, args: any[]) { + this.ctx = ctx; + this.interaction = ctx instanceof ChatInputCommandInteraction ? ctx : null; + this.message = ctx instanceof Message ? ctx : null; + this.channel = ctx.channel!; + this.id = ctx.id; + this.channelId = ctx.channelId; + this.client = ctx.client as Lavamusic; + this.author = ctx instanceof Message ? ctx.author : ctx.user; + this.guild = ctx.guild!; + this.createdAt = ctx.createdAt; + this.createdTimestamp = ctx.createdTimestamp; + this.member = ctx.member; + this.args = args; + this.setArgs(args); + this.setUpLocale(); + } + + private async setUpLocale(): Promise { + this.guildLocale = this.guild ? await this.client.db.getLanguage(this.guild.id) : 'en'; + } + + public get isInteraction(): boolean { + return this.ctx instanceof ChatInputCommandInteraction; + } + + public setArgs(args: any[]): void { + this.args = this.isInteraction ? args.map((arg: { value: any }) => arg.value) : args; + } + + public async sendMessage( + content: string | MessagePayload | MessageCreateOptions | InteractionReplyOptions, + ): Promise { + if (this.isInteraction) { + if (typeof content === 'string' || isInteractionReplyOptions(content)) { + this.msg = await this.interaction?.reply(content); + return this.msg; + } + } else if (typeof content === 'string' || isMessagePayload(content)) { + this.msg = await (this.message?.channel as TextChannel).send(content); + return this.msg; + } + return this.msg; + } + + public async editMessage( + content: string | MessagePayload | InteractionEditReplyOptions | MessageEditOptions, + ): Promise { + if (this.isInteraction && this.msg) { + this.msg = await this.interaction?.editReply(content); + return this.msg; + } + if (this.msg) { + this.msg = await this.msg.edit(content); + return this.msg; + } + return this.msg; + } + + public async sendDeferMessage(content: string | MessagePayload | MessageCreateOptions): Promise { + if (this.isInteraction) { + this.msg = await this.interaction?.deferReply({ fetchReply: true }); + return this.msg; + } + + this.msg = await (this.message?.channel as TextChannel).send(content); + return this.msg; + } + + public locale(key: string, ...args: any) { + if (!this.guildLocale) this.guildLocale = 'EnglishUs'; + return T(this.guildLocale, key, ...args); + } + + public async sendFollowUp( + content: string | MessagePayload | MessageCreateOptions | InteractionReplyOptions, + ): Promise { + if (this.isInteraction) { + if (typeof content === 'string' || isInteractionReplyOptions(content)) { + await this.interaction?.followUp(content); + } + } else if (typeof content === 'string' || isMessagePayload(content)) { + this.msg = await (this.message?.channel as TextChannel).send(content); + } + } + + public get deferred(): boolean | undefined { + return this.isInteraction ? this.interaction?.deferred : !!this.msg; + } + options = { + getRole: (name: string, required = true) => { + return this.interaction?.options.get(name, required)?.role; + }, + getMember: (name: string, required = true) => { + return this.interaction?.options.get(name, required)?.member; + }, + get: (name: string, required = true) => { + return this.interaction?.options.get(name, required); + }, + getChannel: (name: string, required = true) => { + return this.interaction?.options.get(name, required)?.channel; + }, + getSubCommand: () => { + return this.interaction?.options.data[0].name; + }, + }; } function isInteractionReplyOptions(content: any): content is InteractionReplyOptions { - return content instanceof Object; + return content instanceof Object; } function isMessagePayload(content: any): content is MessagePayload { - return content instanceof Object; + return content instanceof Object; } /** diff --git a/src/structures/Dispatcher.ts b/src/structures/Dispatcher.ts deleted file mode 100644 index b2b7676c8..000000000 --- a/src/structures/Dispatcher.ts +++ /dev/null @@ -1,311 +0,0 @@ -import type { Message, User } from "discord.js"; -import { LoadType, type Node, type Player, type Track } from "shoukaku"; -import { SearchEngine } from "../types.js"; -import type { Lavamusic } from "./index.js"; - -export class Song implements Track { - encoded: string; - info: { - identifier: string; - isSeekable: boolean; - author: string; - length: number; - isStream: boolean; - position: number; - title: string; - uri?: string; - artworkUrl?: string; - isrc?: string; - sourceName: string; - requester: User; - }; - pluginInfo: unknown; - - constructor(track: Song | Track, user: User) { - if (!track) throw new Error("Track is not provided"); - this.encoded = track.encoded; - this.info = { - ...track.info, - requester: user, - }; - } -} - -interface DispatcherOptions { - client: Lavamusic; - guildId: string; - channelId: string; - player: Player; - node: Node; -} - -export default class Dispatcher { - private client: Lavamusic; - public guildId: string; - public channelId: string; - public player: Player; - public queue: Song[]; - public stopped: boolean; - public previous: Song | null; - public current: Song | null; - public loop: "off" | "repeat" | "queue"; - public requester: User; - public repeat: number; - public node: Node; - public paused: boolean; - public filters: string[]; - public autoplay: boolean; - public nowPlayingMessage: Message | null; - public history: Song[]; - - constructor(options: DispatcherOptions) { - this.client = options.client; - this.guildId = options.guildId; - this.channelId = options.channelId; - this.player = options.player; - this.queue = []; - this.stopped = false; - this.previous = null; - this.current = null; - this.loop = "off"; - this.repeat = 0; - this.node = options.node; - this.paused = false; - this.filters = []; - this.autoplay = false; - this.nowPlayingMessage = null; - this.history = []; - this.player - .on("start", () => this.client.shoukaku.emit("trackStart" as any, this.player, this.current, this)) - .on("end", () => { - if (this.queue.length === 0) { - this.client.shoukaku.emit("queueEnd" as any, this.player, this.current, this); - } - this.client.shoukaku.emit("trackEnd" as any, this.player, this.current, this); - }) - .on("stuck", () => this.client.shoukaku.emit("trackStuck" as any, this.player, this.current)) - .on("closed", (...args) => this.client.shoukaku.emit("socketClosed" as any, this.player, ...args)); - } - - get exists(): boolean { - return this.client.queue.has(this.guildId); - } - - get volume(): number { - return this.player.volume; - } - - public play(): Promise { - if (!(this.exists && (this.queue.length > 0 || this.current))) return; - this.current = this.queue.length > 0 ? this.queue.shift() : this.queue[0]; - if (this.current) { - this.player.playTrack({ track: { encoded: this.current.encoded } }); - this.history.push(this.current); - if (this.history.length > 100) this.history.shift(); - } - } - - public pause(): void { - if (this.player) { - this.paused = !this.paused; - this.player.setPaused(this.paused); - } - } - - public remove(index: number): void { - if (this.player && index <= this.queue.length) { - this.queue.splice(index, 1); - } - } - - public previousTrack(): void { - if (this.player && this.previous) { - this.queue.unshift(this.previous); - this.player.stopTrack(); - } - } - - public destroy(): void { - this.queue.length = 0; - this.history = []; - this.client.shoukaku.leaveVoiceChannel(this.guildId); - this.player.destroy(); - this.client.queue.delete(this.guildId); - if (!this.stopped) { - this.client.shoukaku.emit("playerDestroy" as any, this.player); - } - } - - public setShuffle(): void { - if (this.player) { - for (let i = this.queue.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [this.queue[i], this.queue[j]] = [this.queue[j], this.queue[i]]; - } - } - } - - public skip(skipto = 1): void { - if (this.player) { - if (skipto > this.queue.length) { - this.queue.length = 0; - } else { - this.queue.splice(0, skipto - 1); - } - this.repeat = this.repeat === 1 ? 0 : this.repeat; - this.player.stopTrack(); - } - } - - public seek(time: number): void { - if (this.player) { - this.player.seekTo(time); - } - } - - public stop(): void { - if (this.player) { - this.queue.length = 0; - this.history = []; - this.loop = "off"; - this.autoplay = false; - this.repeat = 0; - this.stopped = true; - this.player.stopTrack(); - } - } - - public setLoop(loop: "off" | "repeat" | "queue"): void { - this.loop = loop; - } - - public buildTrack(track: Song | Track, user: User): Song { - return new Song(track, user); - } - - public async isPlaying(): Promise { - if (this.queue.length > 0 && !this.current && !this.player.paused) { - await this.play(); - } - } - - public async Autoplay(song: Song): Promise { - if (!song?.info) return; - - try { - const node = this.client.shoukaku.options.nodeResolver(this.client.shoukaku.nodes); - if (!node) return; - switch (song.info.sourceName) { - case "youtube": { - const resolve = await node.rest.resolve(`${SearchEngine.YouTubeMusic}:${song.info.author}`); - this.addAutoplayTrack(resolve); - break; - } - case "soundcloud": - await node.rest.resolve(`${SearchEngine.SoundCloud}:${song.info.author}`); - break; - case "spotify": { - // need lavaSrc plugin in lavalink - const data = await node.rest.resolve(`sprec:seed_tracks=${song.info.identifier}`); - if (!data) return; - if (data.loadType === LoadType.PLAYLIST) { - const tracks = data.data.tracks; - const trackUrl = tracks[Math.floor(Math.random() * tracks.length)]?.info?.uri; - if (!trackUrl) return; - const resolve = await node.rest.resolve(trackUrl); - if (!resolve) return; - if (resolve.loadType === LoadType.TRACK) { - const song = new Song(resolve.data, this.client.user!); - this.queue.push(song); - return this.isPlaying(); - } - } - break; - } - // need jiosaavn plugin in lavalink (https://github.com/appujet/jiosaavn-plugin) - case "jiosaavn": { - const data = await node.rest.resolve(`jsrec:${song.info.identifier}`); - if (!data) return; - if (data.loadType === LoadType.PLAYLIST) { - const tracks = data.data.tracks; - const trackUrl = tracks[Math.floor(Math.random() * tracks.length)]?.info?.uri; - if (!trackUrl) return; - const resolve = await node.rest.resolve(trackUrl); - if (!resolve) return; - if (resolve.loadType === LoadType.TRACK) { - const song = new Song(resolve.data, this.client.user!); - this.queue.push(song); - return this.isPlaying(); - } - } - break; - } - case "deezer": { - const resolve = await node.rest.resolve(`${SearchEngine.Deezer}:${song.info.author}`); - this.addAutoplayTrack(resolve); - break; - } - case "applemusic": { - const resolve = await node.rest.resolve(`${SearchEngine.Apple}:${song.info.author}`); - this.addAutoplayTrack(resolve); - break; - } - default: { - const resolve = await node.rest.resolve(`${SearchEngine.YouTubeMusic}:${song.info.author}`); - this.addAutoplayTrack(resolve); - break; - } - } - } catch (_error) { - return this.destroy(); - } - } - private addAutoplayTrack(resolve: any) { - if (!(resolve?.data && Array.isArray(resolve.data))) { - console.error("Failed to fetch node resolve data."); - return this.destroy(); - } - - let choosed: Song | null = null; - const maxAttempts = 10; - let attempts = 0; - - const metadata = resolve.data as any[] as any; - - while (attempts < maxAttempts) { - const potentialChoice = new Song(metadata[Math.floor(Math.random() * metadata.length)], this.client.user!); - if ( - !( - this.queue.some((s) => s.encoded === potentialChoice.encoded) || - this.history.some((s) => s.encoded === potentialChoice.encoded) - ) - ) { - choosed = potentialChoice; - break; - } - attempts++; - } - - if (choosed) { - this.queue.push(choosed); - return this.isPlaying(); - } - } - public async setAutoplay(autoplay: boolean): Promise { - this.autoplay = autoplay; - if (autoplay) { - await this.Autoplay(this.current || this.queue[0]); - } - } -} - -/** - * 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/structures/Event.ts b/src/structures/Event.ts index d5e8edcf3..5dab8d62c 100644 --- a/src/structures/Event.ts +++ b/src/structures/Event.ts @@ -1,28 +1,37 @@ -import type Lavamusic from "./Lavamusic.js"; +import type { ButtonInteraction, ClientEvents, Message } from 'discord.js'; +import type { LavalinkManagerEvents, NodeManagerEvents } from 'lavalink-client'; +import type Lavamusic from './Lavamusic'; + +// custom client events setupSystem and setupButtons +interface CustomClientEvents { + setupSystem: (message: Message) => void; + setupButtons: (interaction: ButtonInteraction) => void; +} +export type AllEvents = LavalinkManagerEvents & NodeManagerEvents & ClientEvents & CustomClientEvents; interface EventOptions { - name: string; - one?: boolean; + name: keyof AllEvents; + one?: boolean; } export default class Event { - public client: Lavamusic; - public one: boolean; - public file: string; - public name: string; - public fileName: string; + public client: Lavamusic; + public one: boolean; + public file: string; + public name: keyof AllEvents; + public fileName: string; - constructor(client: Lavamusic, file: string, options: EventOptions) { - this.client = client; - this.file = file; - this.name = options.name; - this.one = options.one ?? false; - this.fileName = file.split(".")[0]; - } + constructor(client: Lavamusic, file: string, options: EventOptions) { + this.client = client; + this.file = file; + this.name = options.name; + this.one = options.one ?? false; + this.fileName = file.split('.')[0]; + } - public async run(..._args: any[]): Promise { - return await Promise.resolve(); - } + public async run(..._args: any): Promise { + return await Promise.resolve(); + } } /** diff --git a/src/structures/I18n.ts b/src/structures/I18n.ts index d9d286689..00a77cf48 100644 --- a/src/structures/I18n.ts +++ b/src/structures/I18n.ts @@ -1,50 +1,58 @@ -import i18n from "i18n"; +import i18n from 'i18n'; -import { Locale } from "discord.js"; -import defaultLanguage from "../config.js"; -import { Language } from "../types.js"; -import Logger from "./Logger.js"; +import { Locale } from 'discord.js'; +import defaultLanguage from '../config'; +import { Language } from '../types'; +import Logger from './Logger'; const logger = new Logger(); export function initI18n() { - i18n.configure({ - locales: Object.keys(Language), - defaultLocale: typeof defaultLanguage === "string" ? defaultLanguage : "EnglishUS", - directory: `${process.cwd()}/locales`, - retryInDefaultLocale: true, - objectNotation: true, - register: global, - logWarnFn: console.warn, - logErrorFn: console.error, - missingKeyFn: (_locale, value) => { - return value; - }, - mustacheConfig: { - tags: ["{", "}"], - disable: false, - }, - }); - - logger.info("I18n has been initialized"); + i18n.configure({ + locales: Object.keys(Language), + defaultLocale: typeof defaultLanguage === 'string' ? defaultLanguage : 'EnglishUS', + directory: `${process.cwd()}/locales`, + retryInDefaultLocale: true, + objectNotation: true, + register: global, + logWarnFn: console.warn, + logErrorFn: console.error, + missingKeyFn: (_locale, value) => { + return value; + }, + mustacheConfig: { + tags: ['{', '}'], + disable: false, + }, + }); + + logger.info('I18n has been initialized'); } export { i18n }; export function T(locale: string, text: string | i18n.TranslateOptions, ...params: any) { - i18n.setLocale(locale); - return i18n.__mf(text, ...params); + i18n.setLocale(locale); + return i18n.__mf(text, ...params); } -export function localization(lan: any, name: any, desc: any) { - return { - name: [Locale[lan], name], - description: [Locale[lan], T(lan, desc)], - }; +export function localization(lan: keyof typeof Locale, name: any, desc: any) { + return { + name: [Locale[lan], name], + description: [Locale[lan], T(lan, desc)], + }; } export function descriptionLocalization(name: any, text: any) { - return i18n.getLocales().map((locale) => localization(Locale[locale] || locale, name, text)); + return i18n.getLocales().map((locale: string) => { + // Check if the locale is a valid key of the Locale enum + if (locale in Locale) { + const localeValue = Locale[locale as keyof typeof Locale]; + return localization(localeValue as any, name, text); + } + // If locale is not in the enum, handle it accordingly + return localization(locale as any, name, text); // You can choose how to handle this case + }); } /** diff --git a/src/structures/LavalinkClient.ts b/src/structures/LavalinkClient.ts new file mode 100644 index 000000000..a05b537e5 --- /dev/null +++ b/src/structures/LavalinkClient.ts @@ -0,0 +1,52 @@ +import { LavalinkManager, type LavalinkNodeOptions, type SearchPlatform, type SearchResult } from 'lavalink-client'; +import { autoPlayFunction, requesterTransformer } from '../utils/functions/player'; +import type Lavamusic from './Lavamusic'; + +export default class LavalinkClient extends LavalinkManager { + public client: Lavamusic; + constructor(client: Lavamusic) { + super({ + nodes: client.env.NODES as LavalinkNodeOptions[], + sendToShard: (guildId, payload) => client.guilds.cache.get(guildId)?.shard?.send(payload), + queueOptions: { + maxPreviousTracks: 25, + }, + playerOptions: { + defaultSearchPlatform: client.env.SEARCH_ENGINE, + onDisconnect: { + autoReconnect: true, + destroyPlayer: false, + }, + requesterTransformer: requesterTransformer, + onEmptyQueue: { + autoPlayFunction, + }, + }, + }); + this.client = client; + } + /** + * Searches for a song and returns the tracks. + * @param query The query to search for. + * @param user The user who requested the search. + * @param source The source to search in. Defaults to youtube. + * @returns An array of tracks that match the query. + */ + public async search(query: string, user: unknown, source?: SearchPlatform): Promise { + const nodes = this.nodeManager.leastUsedNodes(); + const node = nodes[Math.floor(Math.random() * nodes.length)]; + const result = await node.search({ query, source }, user, false); + return result; + } +} + +/** + * 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/structures/Lavamusic.ts b/src/structures/Lavamusic.ts index 96a58dd27..211af8b16 100644 --- a/src/structures/Lavamusic.ts +++ b/src/structures/Lavamusic.ts @@ -1,220 +1,209 @@ -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { Api } from "@top-gg/sdk"; +import fs from 'node:fs'; +import path from 'node:path'; +import { Api } from '@top-gg/sdk'; import { - ApplicationCommandType, - Client, - Collection, - EmbedBuilder, - Events, - type Interaction, - PermissionsBitField, - REST, - type RESTPostAPIChatInputApplicationCommandsJSONBody, - Routes, -} from "discord.js"; -import { Locale } from "discord.js"; -import config from "../config.js"; -import ServerData from "../database/server.js"; -import loadPlugins from "../plugin/index.js"; -import { Utils } from "../utils/Utils.js"; -import { T, i18n, initI18n, localization } from "./I18n.js"; -import Logger from "./Logger.js"; -import { type Command, Queue, ShoukakuClient } from "./index.js"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); + ApplicationCommandType, + Client, + Collection, + EmbedBuilder, + Events, + type Interaction, + PermissionsBitField, + REST, + type RESTPostAPIChatInputApplicationCommandsJSONBody, + Routes, +} from 'discord.js'; +import { Locale } from 'discord.js'; +import config from '../config'; +import ServerData from '../database/server'; +import { env } from '../env'; +import loadPlugins from '../plugin/index'; +import { Utils } from '../utils/Utils'; +import { T, i18n, initI18n, localization } from './I18n'; +import LavalinkClient from './LavalinkClient'; +import Logger from './Logger'; +import type { Command } from './index'; export default class Lavamusic extends Client { - public commands: Collection = new Collection(); - public aliases: Collection = new Collection(); - public db = new ServerData(); - public cooldown: Collection = new Collection(); - public config = config; - public logger: Logger = new Logger(); - public readonly emoji = config.emoji; - public readonly color = config.color; - private body: RESTPostAPIChatInputApplicationCommandsJSONBody[] = []; - public shoukaku!: ShoukakuClient; - public topGG!: Api; - public utils = Utils; - public queue = new Queue(this); - - public embed(): EmbedBuilder { - return new EmbedBuilder(); - } - - public async start(token: string): Promise { - initI18n(); - const nodes = this.config.autoNode ? await this.getNodes() : this.config.lavalink; - this.shoukaku = new ShoukakuClient(this, nodes); - this.topGG = new Api(this.config.topGG); - await this.loadCommands(); - this.logger.info("Successfully loaded commands!"); - await this.loadEvents(); - this.logger.info("Successfully loaded events!"); - loadPlugins(this); - await this.login(token); - - this.on(Events.InteractionCreate, async (interaction: Interaction) => { - if (interaction.isButton() && interaction.guildId) { - const setup = await this.db.getSetup(interaction.guildId); - if (setup && interaction.channelId === setup.textId && interaction.message.id === setup.messageId) { - this.emit("setupButtons", interaction); - } - } - }); - } - - private async loadCommands(): Promise { - const commandsPath = path.join(__dirname, "../commands"); - const commandDirs = fs.readdirSync(commandsPath); - - for (const dir of commandDirs) { - const commandFiles = fs.readdirSync(path.join(commandsPath, dir)).filter((file) => file.endsWith(".js")); - - for (const file of commandFiles) { - const cmdModule = await import(`../commands/${dir}/${file}`); - const command: Command = new cmdModule.default(this); - command.category = dir; - - this.commands.set(command.name, command); - command.aliases.forEach((alias: string) => { - this.aliases.set(alias, command.name); - }); - - if (command.slashCommand) { - const data: RESTPostAPIChatInputApplicationCommandsJSONBody = { - name: command.name, - description: T(Locale.EnglishUS, command.description.content), - type: ApplicationCommandType.ChatInput, - options: command.options || [], - default_member_permissions: - Array.isArray(command.permissions.user) && command.permissions.user.length > 0 - ? PermissionsBitField.resolve(command.permissions.user as any).toString() - : null, - name_localizations: null, - description_localizations: null, - }; - - const localizations = []; - i18n.getLocales().map((locale) => { - localizations.push(localization(locale, command.name, command.description.content)); - }); - - for (const localization of localizations) { - const [language, name] = localization.name; - const [language2, description] = localization.description; - data.name_localizations = { - ...data.name_localizations, - [language]: name, - }; - data.description_localizations = { - ...data.description_localizations, - [language2]: description, - }; - } - - if (command.options.length > 0) { - command.options.map((option) => { - const optionsLocalizations = []; - i18n.getLocales().map((locale) => { - optionsLocalizations.push(localization(locale, option.name, option.description)); - }); - - for (const localization of optionsLocalizations) { - const [language, name] = localization.name; - const [language2, description] = localization.description; - option.name_localizations = { - ...option.name_localizations, - [language]: name, - }; - option.description_localizations = { - ...option.description_localizations, - [language2]: description, - }; - } - option.description = T(Locale.EnglishUS, option.description); - }); - - data.options.map((option) => { - if ("options" in option && option.options.length > 0) { - option.options.map((subOption) => { - const subOptionsLocalizations = []; - i18n.getLocales().map((locale) => { - subOptionsLocalizations.push(localization(locale, subOption.name, subOption.description)); - }); - - for (const localization of subOptionsLocalizations) { - const [language, name] = localization.name; - const [language2, description] = localization.description; - subOption.name_localizations = { - ...subOption.name_localizations, - [language]: name, - }; - subOption.description_localizations = { - ...subOption.description_localizations, - [language2]: description, - }; - } - subOption.description = T(Locale.EnglishUS, subOption.description); - }); - } - }); - } - this.body.push(data); - } - } - } - } - - public async deployCommands(guildId?: string): Promise { - const route = guildId - ? Routes.applicationGuildCommands(this.user?.id ?? "", guildId) - : Routes.applicationCommands(this.user?.id ?? ""); - - try { - const rest = new REST({ version: "10" }).setToken(this.config.token ?? ""); - await rest.put(route, { body: this.body }); - this.logger.info("Successfully deployed slash commands!"); - } catch (error) { - this.logger.error(error); - } - } - - private async getNodes(): Promise { - const params = new URLSearchParams({ - ssl: "false", - version: "v4", - format: "shoukaku", - }); - const res = await fetch(`https://lavainfo-api.deno.dev/nodes?${params.toString()}`, { - headers: { - "Content-Type": "application/json", - }, - }); - return await res.json(); - } - - private async loadEvents(): Promise { - const eventsPath = path.join(__dirname, "../events"); - const eventDirs = fs.readdirSync(eventsPath); - - for (const dir of eventDirs) { - const eventFiles = fs.readdirSync(path.join(eventsPath, dir)).filter((file) => file.endsWith(".js")); - - for (const file of eventFiles) { - const eventModule = await import(`../events/${dir}/${file}`); - const event = new eventModule.default(this, file); - - if (dir === "player") { - this.shoukaku.on(event.name, (...args) => event.run(...args)); - } else { - this.on(event.name, (...args) => event.run(...args)); - } - } - } - } + public commands: Collection = new Collection(); + public aliases: Collection = new Collection(); + public db = new ServerData(); + public cooldown: Collection = new Collection(); + public config = config; + public logger: Logger = new Logger(); + public readonly emoji = config.emoji; + public readonly color = config.color; + private body: RESTPostAPIChatInputApplicationCommandsJSONBody[] = []; + public topGG!: Api; + public utils = Utils; + public env: typeof env = env; + public manager!: LavalinkClient; + public embed(): EmbedBuilder { + return new EmbedBuilder(); + } + + public async start(token: string): Promise { + initI18n(); + if (env.TOPGG) { + this.topGG = new Api(env.TOPGG); + } else { + this.logger.warn('Top.gg token not found!'); + } + this.manager = new LavalinkClient(this); + await this.loadCommands(); + this.logger.info('Successfully loaded commands!'); + await this.loadEvents(); + this.logger.info('Successfully loaded events!'); + loadPlugins(this); + await this.login(token); + + this.on(Events.InteractionCreate, async (interaction: Interaction) => { + if (interaction.isButton() && interaction.guildId) { + const setup = await this.db.getSetup(interaction.guildId); + if (setup && interaction.channelId === setup.textId && interaction.message.id === setup.messageId) { + this.emit('setupButtons', interaction); + } + } + }); + } + + private async loadCommands(): Promise { + const commandsPath = fs.readdirSync(path.join(__dirname, '../commands')); + + for (const dir of commandsPath) { + const commandFiles = fs + .readdirSync(path.join(__dirname, '../commands', dir)) + .filter(file => file.endsWith('.js')); + + for (const file of commandFiles) { + const cmdModule = require(`../commands/${dir}/${file}`); + const command: Command = new cmdModule.default(this, file); + command.category = dir; + + this.commands.set(command.name, command); + command.aliases.forEach((alias: string) => { + this.aliases.set(alias, command.name); + }); + + if (command.slashCommand) { + const data: RESTPostAPIChatInputApplicationCommandsJSONBody = { + name: command.name, + description: T(Locale.EnglishUS, command.description.content), + type: ApplicationCommandType.ChatInput, + options: command.options || [], + default_member_permissions: + Array.isArray(command.permissions.user) && command.permissions.user.length > 0 + ? PermissionsBitField.resolve(command.permissions.user as any).toString() + : null, + name_localizations: null, + description_localizations: null, + }; + + const localizations: { name: any[]; description: string[] }[] = []; + i18n.getLocales().map((locale: any) => { + localizations.push(localization(locale, command.name, command.description.content)); + }); + + for (const localization of localizations) { + const [language, name] = localization.name; + const [language2, description] = localization.description; + data.name_localizations = { + ...data.name_localizations, + [language]: name, + }; + data.description_localizations = { + ...data.description_localizations, + [language2]: description, + }; + } + + if (command.options.length > 0) { + command.options.map(option => { + const optionsLocalizations: { name: any[]; description: string[] }[] = []; + i18n.getLocales().map((locale: any) => { + optionsLocalizations.push(localization(locale, option.name, option.description)); + }); + + for (const localization of optionsLocalizations) { + const [language, name] = localization.name; + const [language2, description] = localization.description; + option.name_localizations = { + ...option.name_localizations, + [language]: name, + }; + option.description_localizations = { + ...option.description_localizations, + [language2]: description, + }; + } + option.description = T(Locale.EnglishUS, option.description); + }); + + data.options?.map(option => { + if ('options' in option && option.options!.length > 0) { + option.options?.map(subOption => { + const subOptionsLocalizations: { name: any[]; description: string[] }[] = []; + i18n.getLocales().map((locale: any) => { + subOptionsLocalizations.push(localization(locale, subOption.name, subOption.description)); + }); + + for (const localization of subOptionsLocalizations) { + const [language, name] = localization.name; + const [language2, description] = localization.description; + subOption.name_localizations = { + ...subOption.name_localizations, + [language]: name, + }; + subOption.description_localizations = { + ...subOption.description_localizations, + [language2]: description, + }; + } + subOption.description = T(Locale.EnglishUS, subOption.description); + }); + } + }); + } + this.body.push(data); + } + } + } + } + + public async deployCommands(guildId?: string): Promise { + const route = guildId + ? Routes.applicationGuildCommands(this.user?.id ?? '', guildId) + : Routes.applicationCommands(this.user?.id ?? ''); + + try { + const rest = new REST({ version: '10' }).setToken(env.TOKEN ?? ''); + await rest.put(route, { body: this.body }); + this.logger.info('Successfully deployed slash commands!'); + } catch (error) { + this.logger.error(error); + } + } + + private async loadEvents(): Promise { + const eventsPath = fs.readdirSync(path.join(__dirname, '..', 'events')); + + for (const dir of eventsPath) { + const eventFiles = fs.readdirSync(path.join(__dirname, '..', 'events', dir)).filter(file => file.endsWith('.js')); + + for (const file of eventFiles) { + const eventModule = require(`../events/${dir}/${file}`); + const event = new eventModule.default(this, file); + + if (dir === 'player') { + this.manager.on(event.name, (...args: any) => event.run(...args)); + } else if (dir === 'node') { + this.manager.nodeManager.on(event.name, (...args: any) => event.run(...args)); + } else { + this.on(event.name, (...args) => event.run(...args)); + } + } + } + } } /** diff --git a/src/structures/Logger.ts b/src/structures/Logger.ts index b097b9cf9..e74f39a48 100644 --- a/src/structures/Logger.ts +++ b/src/structures/Logger.ts @@ -1,59 +1,59 @@ -import pkg, { type SignaleOptions } from "signale"; +import pkg, { type SignaleOptions } from 'signale'; const { Signale } = pkg; const options: SignaleOptions = { - disabled: false, - interactive: false, - logLevel: "info", - scope: "Lavamusic", - types: { - info: { - badge: "ℹ", - color: "blue", - label: "info", - }, - warn: { - badge: "⚠", - color: "yellow", - label: "warn", - }, - error: { - badge: "✖", - color: "red", - label: "error", - }, - debug: { - badge: "🐛", - color: "magenta", - label: "debug", - }, - success: { - badge: "✔", - color: "green", - label: "success", - }, - log: { - badge: "📝", - color: "white", - label: "log", - }, - pause: { - badge: "⏸", - color: "yellow", - label: "pause", - }, - start: { - badge: "▶", - color: "green", - label: "start", - }, - }, + disabled: false, + interactive: false, + logLevel: 'info', + scope: 'Lavamusic', + types: { + info: { + badge: 'ℹ', + color: 'blue', + label: 'info', + }, + warn: { + badge: '⚠', + color: 'yellow', + label: 'warn', + }, + error: { + badge: '✖', + color: 'red', + label: 'error', + }, + debug: { + badge: '🐛', + color: 'magenta', + label: 'debug', + }, + success: { + badge: '✔', + color: 'green', + label: 'success', + }, + log: { + badge: '📝', + color: 'white', + label: 'log', + }, + pause: { + badge: '⏸', + color: 'yellow', + label: 'pause', + }, + start: { + badge: '▶', + color: 'green', + label: 'start', + }, + }, }; export default class Logger extends Signale { - constructor() { - super(options); - } + constructor() { + super(options); + } } /** diff --git a/src/structures/Queue.ts b/src/structures/Queue.ts deleted file mode 100644 index 5223677b3..000000000 --- a/src/structures/Queue.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { Guild } from "discord.js"; -import type { LavalinkResponse, Node } from "shoukaku"; -import { Dispatcher, type Lavamusic } from "./index.js"; - -export class Queue extends Map { - public client: Lavamusic; - - constructor(client: Lavamusic) { - super(); - this.client = client; - } - - public override get(guildId: string): Dispatcher | undefined { - return super.get(guildId); - } - - public override set(guildId: string, dispatcher: Dispatcher): this { - return super.set(guildId, dispatcher); - } - - public override delete(guildId: string): boolean { - return super.delete(guildId); - } - - public override clear(): void { - super.clear(); - } - - public async create(guild: Guild, voice: any, channel: any, givenNode?: Node): Promise { - if (!voice) throw new Error("No voice channel was provided"); - if (!channel) throw new Error("No text channel was provided"); - if (!guild) throw new Error("No guild was provided"); - - let dispatcher = this.get(guild.id); - const connection = this.client.shoukaku.connections.get(guild.id); - let player = this.client.shoukaku.players.get(guild.id); - if (player && connection) { - if (!dispatcher) { - dispatcher = new Dispatcher({ - client: this.client, - guildId: guild.id, - channelId: channel.id, - player, - node: player.node, - }); - this.set(guild.id, dispatcher); - } - } else { - const node = givenNode ?? this.client.shoukaku.options.nodeResolver(this.client.shoukaku.nodes); - player = await this.client.shoukaku.joinVoiceChannel({ - guildId: guild.id, - channelId: voice.id, - shardId: guild.shardId, - deaf: true, - }); - - dispatcher = new Dispatcher({ - client: this.client, - guildId: guild.id, - channelId: channel.id, - player, - node, - }); - - this.set(guild.id, dispatcher); - this.client.shoukaku.emit("playerCreate" as any, dispatcher.player); - } - return dispatcher; - } - - public async search(query: string): Promise { - const node = this.client.shoukaku.options.nodeResolver(this.client.shoukaku.nodes); - const searchQuery = /^https?:\/\//.test(query) ? query : `${this.client.config.searchEngine}:${query}`; - try { - return await node.rest.resolve(searchQuery); - } catch (err) { - console.error("Error during search:", err); - return null; - } - } -} - -/** - * 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/structures/Shoukaku.ts b/src/structures/Shoukaku.ts deleted file mode 100644 index e74c4bd2e..000000000 --- a/src/structures/Shoukaku.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Connectors, type NodeOption, Shoukaku } from "shoukaku"; -import type { Lavamusic } from "./index.js"; - -export default class ShoukakuClient extends Shoukaku { - public client: Lavamusic; - constructor(client: Lavamusic, nodes: NodeOption[]) { - super(new Connectors.DiscordJS(client), nodes, { - resume: true, // Whether to resume a connection on disconnect to Lavalink (Server Side) (Note: DOES NOT RESUME WHEN THE LAVALINK SERVER DIES) - resumeTimeout: 30, - resumeByLibrary: true, // Whether to resume the players by doing it in the library side (Client Side) (Note: TRIES TO RESUME REGARDLESS OF WHAT HAPPENED ON A LAVALINK SERVER) - reconnectTries: 5, - reconnectInterval: 5, - restTimeout: 60, - moveOnDisconnect: false, // Whether to move players to a different Lavalink node when a node disconnects - //voiceConnectionTimeout: 15, - nodeResolver: (nodes) => - [...nodes.values()] - .filter((node) => node.state === 2) - .sort((a, b) => a.penalties - b.penalties) - .shift(), - }); - this.client = client; - this.on("ready", (name, reconnected) => { - this.client.shoukaku.emit(reconnected ? "nodeReconnect" : ("nodeConnect" as any), name); - }); - this.on("error", (name, error) => { - this.client.shoukaku.emit("nodeError" as any, name, error); - }); - this.on("close", (name, code, reason) => { - this.client.shoukaku.emit("nodeDestroy" as any, name, code, reason); - }); - this.on("disconnect", (name, count) => { - this.client.shoukaku.emit("nodeDisconnect" as any, name, count); - }); - this.on("debug", (name, reason) => { - this.client.shoukaku.emit("nodeRaw" as any, name, reason); - }); - } -} - -/** - * 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/structures/index.ts b/src/structures/index.ts index 2695994fd..10179f692 100644 --- a/src/structures/index.ts +++ b/src/structures/index.ts @@ -1,12 +1,9 @@ -import Command from "./Command.js"; -import Context from "./Context.js"; -import Dispatcher from "./Dispatcher.js"; -import Event from "./Event.js"; -import Lavamusic from "./Lavamusic.js"; -import { Queue } from "./Queue.js"; -import ShoukakuClient from "./Shoukaku.js"; +import Command from './Command'; +import Context from './Context'; +import Event from './Event'; +import Lavamusic from './Lavamusic'; -export { Event, Queue, Command, Lavamusic, Context, ShoukakuClient, Dispatcher }; +export { Event, Command, Lavamusic, Context }; /** * Project: lavamusic diff --git a/src/types.ts b/src/types.ts index 93ee54df5..f1959e169 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,81 +1,88 @@ export enum SearchEngine { - YouTube = "ytsearch", - YouTubeMusic = "ytmsearch", - Spotify = "spsearch", - Deezer = "dzsearch", - Apple = "amsearch", - SoundCloud = "scsearch", - Yandex = "ymsearch", - JioSaavn = "jssearch", + YouTube = 'ytsearch', + YouTubeMusic = 'ytmsearch', + Spotify = 'spsearch', + Deezer = 'dzsearch', + Apple = 'amsearch', + SoundCloud = 'scsearch', + Yandex = 'ymsearch', + JioSaavn = 'jssearch', } export enum Language { - // Bulgarian = "Bulgarian", - ChineseCN = "ChineseCN", - ChineseTW = "ChineseTW", - // Croatian = "Croatian", - // Czech = "Czech", - // Danish = "Danish", - // Dutch = "Dutch", - // EnglishGB = "EnglishGB", - EnglishUS = "EnglishUS", - // Finnish = "Finnish", - French = "French", - German = "German", - // Greek = "Greek", - Hindi = "Hindi", - // Hungarian = "Hungarian", - Indonesian = "Indonesian", - // Italian = "Italian", - Japanese = "Japanese", - Korean = "Korean", - // Lithuanian = "Lithuanian", - Norwegian = "Norwegian", - Polish = "Polish", - // PortugueseBR = "PortugueseBR", - // Romanian = "Romanian", - Russian = "Russian", - SpanishES = "SpanishES", - // Swedish = "Swedish", - // Thai = "Thai", - // Turkish = "Turkish", - // Ukrainian = "Ukrainian", - Vietnamese = "Vietnamese", + // Bulgarian = "Bulgarian", + ChineseCN = 'ChineseCN', + ChineseTW = 'ChineseTW', + // Croatian = "Croatian", + // Czech = "Czech", + // Danish = "Danish", + // Dutch = "Dutch", + // EnglishGB = "EnglishGB", + EnglishUS = 'EnglishUS', + // Finnish = "Finnish", + French = 'French', + German = 'German', + // Greek = "Greek", + Hindi = 'Hindi', + // Hungarian = "Hungarian", + Indonesian = 'Indonesian', + // Italian = "Italian", + Japanese = 'Japanese', + Korean = 'Korean', + // Lithuanian = "Lithuanian", + Norwegian = 'Norwegian', + Polish = 'Polish', + // PortugueseBR = "PortugueseBR", + // Romanian = "Romanian", + Russian = 'Russian', + SpanishES = 'SpanishES', + // Swedish = "Swedish", + // Thai = "Thai", + // Turkish = "Turkish", + // Ukrainian = "Ukrainian", + Vietnamese = 'Vietnamese', } export const LocaleFlags = { - // [Language.Bulgarian]: "🇧🇬", - [Language.ChineseCN]: "🇨🇳", - [Language.ChineseTW]: "🇹🇼", - // [Language.Croatian]: "🇭🇷", - // [Language.Czech]: "🇨🇿", - // [Language.Danish]: "🇩🇰", - // [Language.Dutch]: "🇳🇱", - // [Language.EnglishGB]: "🇬🇧", - [Language.EnglishUS]: "🇺🇸", - // [Language.Finnish]: "🇫🇮", - [Language.French]: "🇫🇷", - [Language.German]: "🇩🇪", - // [Language.Greek]: "🇬🇷", - [Language.Hindi]: "🇮🇳", - // [Language.Hungarian]: "🇭🇺", - [Language.Indonesian]: "🇮🇩", - // [Language.Italian]: "🇮🇹", - [Language.Japanese]: "🇯🇵", - [Language.Korean]: "🇰🇷", - // [Language.Lithuanian]: "🇱🇹", - [Language.Norwegian]: "🇳🇴", - [Language.Polish]: "🇵🇱", - // [Language.PortugueseBR]: "🇧🇷", - // [Language.Romanian]: "🇷🇴", - [Language.Russian]: "🇷🇺", - [Language.SpanishES]: "🇪🇸", - // [Language.Swedish]: "🇸🇪", - // [Language.Thai]: "🇹🇭", - // [Language.Turkish]: "🇹🇷", - // [Language.Ukrainian]: "🇺🇦", - [Language.Vietnamese]: "🇻🇳", + // [Language.Bulgarian]: "🇧🇬", + [Language.ChineseCN]: '🇨🇳', + [Language.ChineseTW]: '🇹🇼', + // [Language.Croatian]: "🇭🇷", + // [Language.Czech]: "🇨🇿", + // [Language.Danish]: "🇩🇰", + // [Language.Dutch]: "🇳🇱", + // [Language.EnglishGB]: "🇬🇧", + [Language.EnglishUS]: '🇺🇸', + // [Language.Finnish]: "🇫🇮", + [Language.French]: '🇫🇷', + [Language.German]: '🇩🇪', + // [Language.Greek]: "🇬🇷", + [Language.Hindi]: '🇮🇳', + // [Language.Hungarian]: "🇭🇺", + [Language.Indonesian]: '🇮🇩', + // [Language.Italian]: "🇮🇹", + [Language.Japanese]: '🇯🇵', + [Language.Korean]: '🇰🇷', + // [Language.Lithuanian]: "🇱🇹", + [Language.Norwegian]: '🇳🇴', + [Language.Polish]: '🇵🇱', + // [Language.PortugueseBR]: "🇧🇷", + // [Language.Romanian]: "🇷🇴", + [Language.Russian]: '🇷🇺', + [Language.SpanishES]: '🇪🇸', + // [Language.Swedish]: "🇸🇪", + // [Language.Thai]: "🇹🇭", + // [Language.Turkish]: "🇹🇷", + // [Language.Ukrainian]: "🇺🇦", + [Language.Vietnamese]: '🇻🇳', }; +export interface Requester { + id: string; + username: string; + discriminator?: string; + avatarURL?: string; +} + /** * Project: lavamusic * Author: Appu diff --git a/src/utils/BotLog.ts b/src/utils/BotLog.ts index 612d7db77..c5a1f1ae6 100644 --- a/src/utils/BotLog.ts +++ b/src/utils/BotLog.ts @@ -1,25 +1,27 @@ -import type { TextChannel } from "discord.js"; -import type { Lavamusic } from "../structures/index.js"; +import type { TextChannel } from 'discord.js'; +import type { Lavamusic } from '../structures/index'; export default class BotLog { - public static send(client: Lavamusic, message: string, type: "error" | "warn" | "info" | "success" = "info"): void { - if (!client?.channels.cache && client.config.logChannelId) return; + public static send(client: Lavamusic, message: string, type: 'error' | 'warn' | 'info' | 'success' = 'info'): void { + if (!client?.channels.cache && client.env.LOG_CHANNEL_ID) return; - const channel = client.channels.cache.get(client.config.logChannelId!) as TextChannel; - if (!channel) return; + const channel = client.channels.cache.get(client.env.LOG_CHANNEL_ID!) as TextChannel; + if (!channel) return; - const colors = { - error: 0xff0000, - warn: 0xffff00, - info: 0x00ff00, - success: 0x00ff00, - } as const; + const colors = { + error: 0xff0000, + warn: 0xffff00, + info: 0x00ff00, + success: 0x00ff00, + } as const; - const color = colors[type]; - const embed = client.embed().setColor(color).setDescription(message).setTimestamp(); + const color = colors[type]; + const embed = client.embed().setColor(color).setDescription(message).setTimestamp(); - channel.send({ embeds: [embed] }).catch(() => {}); - } + channel.send({ embeds: [embed] }).catch(() => { + null; + }); + } } /** diff --git a/src/utils/Buttons.ts b/src/utils/Buttons.ts index 444e44628..a8eb8335b 100644 --- a/src/utils/Buttons.ts +++ b/src/utils/Buttons.ts @@ -1,75 +1,87 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type EmojiIdentifierResolvable } from "discord.js"; -import type { Dispatcher, Lavamusic } from "../structures/index.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type EmojiIdentifierResolvable } from 'discord.js'; +import type { Player } from 'lavalink-client'; +import type { Lavamusic } from '../structures/index'; -function getButtons(player: Dispatcher, client: Lavamusic): ActionRowBuilder[] { - const buttonData = [ - { - customId: "PREV_BUT", - emoji: client.emoji.previous, - style: ButtonStyle.Secondary, - }, - { - customId: "REWIND_BUT", - emoji: client.emoji.rewind, - style: ButtonStyle.Secondary, - }, - { - customId: "PAUSE_BUT", - emoji: player?.paused ? client.emoji.resume : client.emoji.pause, - style: player?.paused ? ButtonStyle.Success : ButtonStyle.Secondary, - }, - { - customId: "FORWARD_BUT", - emoji: client.emoji.forward, - style: ButtonStyle.Secondary, - }, - { - customId: "SKIP_BUT", - emoji: client.emoji.skip, - style: ButtonStyle.Secondary, - }, - { - customId: "LOW_VOL_BUT", - emoji: client.emoji.voldown, - style: ButtonStyle.Secondary, - }, - { - customId: "LOOP_BUT", - emoji: client.emoji.loop.none, - style: ButtonStyle.Secondary, - }, - { - customId: "STOP_BUT", - emoji: client.emoji.stop, - style: ButtonStyle.Danger, - }, - { - customId: "SHUFFLE_BUT", - emoji: client.emoji.shuffle, - style: ButtonStyle.Secondary, - }, - { - customId: "HIGH_VOL_BUT", - emoji: client.emoji.volup, - style: ButtonStyle.Secondary, - }, - ]; +function getButtons(player: Player, client: Lavamusic): ActionRowBuilder[] { + const buttonData = [ + { + customId: 'PREV_BUT', + emoji: client.emoji.previous, + style: ButtonStyle.Secondary, + }, + { + customId: 'REWIND_BUT', + emoji: client.emoji.rewind, + style: ButtonStyle.Secondary, + }, + { + customId: 'PAUSE_BUT', + emoji: player?.paused ? client.emoji.resume : client.emoji.pause, + style: player?.paused ? ButtonStyle.Success : ButtonStyle.Secondary, + }, + { + customId: 'FORWARD_BUT', + emoji: client.emoji.forward, + style: ButtonStyle.Secondary, + }, + { + customId: 'SKIP_BUT', + emoji: client.emoji.skip, + style: ButtonStyle.Secondary, + }, + { + customId: 'LOW_VOL_BUT', + emoji: client.emoji.voldown, + style: ButtonStyle.Secondary, + }, + { + customId: 'LOOP_BUT', + emoji: client.emoji.loop.none, + style: ButtonStyle.Secondary, + }, + { + customId: 'STOP_BUT', + emoji: client.emoji.stop, + style: ButtonStyle.Danger, + }, + { + customId: 'SHUFFLE_BUT', + emoji: client.emoji.shuffle, + style: ButtonStyle.Secondary, + }, + { + customId: 'HIGH_VOL_BUT', + emoji: client.emoji.volup, + style: ButtonStyle.Secondary, + }, + ]; - return buttonData.reduce((rows, { customId, emoji, style }, index) => { - if (index % 5 === 0) rows.push(new ActionRowBuilder()); + return buttonData.reduce((rows, { customId, emoji, style }, index) => { + if (index % 5 === 0) rows.push(new ActionRowBuilder()); - let emojiFormat: EmojiIdentifierResolvable; - if (typeof emoji === "string" && emoji.startsWith("<:")) { - const match = emoji.match(/^<:\w+:(\d+)>$/); - emojiFormat = match ? match[1] : emoji; - } else { - emojiFormat = emoji; - } + let emojiFormat: EmojiIdentifierResolvable; + if (typeof emoji === 'string' && emoji.startsWith('<:')) { + const match = emoji.match(/^<:\w+:(\d+)>$/); + emojiFormat = match ? match[1] : emoji; + } else { + emojiFormat = emoji; + } - const button = new ButtonBuilder().setCustomId(customId).setEmoji(emojiFormat).setStyle(style); - rows[rows.length - 1].addComponents(button); - return rows; - }, [] as ActionRowBuilder[]); + const button = new ButtonBuilder().setCustomId(customId).setEmoji(emojiFormat).setStyle(style); + rows[rows.length - 1].addComponents(button); + return rows; + }, [] as ActionRowBuilder[]); } export { getButtons }; + +/** + * 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/utils/SetupSystem.ts b/src/utils/SetupSystem.ts index 452d2d0d4..8f0cb3907 100644 --- a/src/utils/SetupSystem.ts +++ b/src/utils/SetupSystem.ts @@ -1,345 +1,319 @@ -import { type ColorResolvable, EmbedBuilder, type Message, type TextChannel } from "discord.js"; -import { LoadType } from "shoukaku"; -import type { Song } from "../structures/Dispatcher.js"; -import { T } from "../structures/I18n.js"; -import type { Dispatcher, Lavamusic } from "../structures/index.js"; -import { getButtons } from "./Buttons.js"; +import { type ColorResolvable, EmbedBuilder, type Message, type TextChannel } from 'discord.js'; -function neb(embed: EmbedBuilder, player: Dispatcher, client: Lavamusic, locale: string): EmbedBuilder { - if (!player?.current?.info) return embed; - const iconUrl = client.config.icons[player.current.info.sourceName] || client.user.displayAvatarURL({ extension: "png" }); - const icon = player.current.info.artworkUrl || client.config.links.img; +import type { Player, Track } from 'lavalink-client'; +import { T } from '../structures/I18n'; +import type { Lavamusic } from '../structures/index'; +import type { Requester } from '../types'; +import { getButtons } from './Buttons'; - const description = T(locale, "player.setupStart.description", { - title: player.current.info.title, - uri: player.current.info.uri, - author: player.current.info.author, - length: client.utils.formatTime(player.current.info.length), - requester: player.current.info.requester, - }); - return embed - .setAuthor({ - name: T(locale, "player.setupStart.now_playing"), - iconURL: iconUrl, - }) - .setDescription(description) - .setImage(icon) - .setColor(client.color.main); +/** + * A function that will generate an embed based on the player's current track. + * @param embed The embed that will be modified. + * @param player The player to get the current track from. + * @param client The client to get the config from. + * @param locale The locale to translate the strings. + * @returns The modified embed. + */ +function neb(embed: EmbedBuilder, player: Player, client: Lavamusic, locale: string): EmbedBuilder { + if (!player?.queue.current?.info) return embed; + const iconUrl = + client.config.icons[player.queue.current.info.sourceName] || client.user!.displayAvatarURL({ extension: 'png' }); + const icon = player.queue.current.info.artworkUrl || client.config.links.img; + + const description = T(locale, 'player.setupStart.description', { + title: player.queue.current.info.title, + uri: player.queue.current.info.uri, + author: player.queue.current.info.author, + length: client.utils.formatTime(player.queue.current.info.duration), + requester: (player.queue.current.requester as Requester).id, + }); + return embed + .setAuthor({ + name: T(locale, 'player.setupStart.now_playing'), + iconURL: iconUrl, + }) + .setDescription(description) + .setImage(icon) + .setColor(client.color.main); } -async function setupStart(client: Lavamusic, query: string, player: Dispatcher, message: Message): Promise { - let m: Message; - const embed = client.embed(); - const n = client.embed().setColor(client.color.main); - const data = await client.db.getSetup(message.guild.id); - const locale = await client.db.getLanguage(message.guildId); - try { - if (data) - m = await message.channel.messages.fetch({ - message: data.messageId, - cache: true, - }); - } catch (error) { - client.logger.error(error); - } - if (m) { - try { - if (message.inGuild()) { - const res = await client.queue.search(query); - switch (res.loadType) { - case LoadType.ERROR: - await message.channel - .send({ - embeds: [embed.setColor(client.color.red).setDescription(T(locale, "player.setupStart.error_searching"))], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - break; - case LoadType.EMPTY: - await message.channel - .send({ - embeds: [embed.setColor(client.color.red).setDescription(T(locale, "player.setupStart.no_results"))], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - break; - case LoadType.TRACK: { - const track = player.buildTrack(res.data, message.author); - if (player.queue.length > client.config.maxQueueSize) { - await message.channel - .send({ - embeds: [ - embed.setColor(client.color.red).setDescription( - T(locale, "player.setupStart.queue_too_long", { - maxQueueSize: client.config.maxQueueSize, - }), - ), - ], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - return; - } - player.queue.push(track); - await player.isPlaying(); - await message.channel - .send({ - embeds: [ - embed.setColor(client.color.main).setDescription( - T(locale, "player.setupStart.added_to_queue", { - title: res.data.info.title, - uri: res.data.info.uri, - }), - ), - ], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - neb(n, player, client, locale); - await m.edit({ embeds: [n] }).catch(() => {}); - break; - } - case LoadType.PLAYLIST: - if (res.data.tracks.length > client.config.maxPlaylistSize) { - await message.channel - .send({ - embeds: [ - embed.setColor(client.color.red).setDescription( - T(locale, "player.setupStart.playlist_too_long", { - maxPlaylistSize: client.config.maxPlaylistSize, - }), - ), - ], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - return; - } - for (const track of res.data.tracks) { - const pl = player.buildTrack(track, message.author); - if (player.queue.length > client.config.maxQueueSize) { - await message.channel - .send({ - embeds: [ - embed.setColor(client.color.red).setDescription( - T(locale, "player.setupStart.queue_too_long", { - maxQueueSize: client.config.maxQueueSize, - }), - ), - ], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - return; - } - player.queue.push(pl); - } - await player.isPlaying(); - await message.channel - .send({ - embeds: [ - embed - .setColor(client.color.main) - .setDescription( - T(locale, "player.setupStart.added_playlist_to_queue", { length: res.data.tracks.length }), - ), - ], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - neb(n, player, client, locale); - await m.edit({ embeds: [n] }).catch(() => {}); - break; - case LoadType.SEARCH: { - const track = player.buildTrack(res.data[0], message.author); - if (player.queue.length > client.config.maxQueueSize) { - await message.channel - .send({ - embeds: [ - embed.setColor(client.color.red).setDescription( - T(locale, "player.setupStart.queue_too_long", { - maxQueueSize: client.config.maxQueueSize, - }), - ), - ], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - return; - } - player.queue.push(track); - await player.isPlaying(); - await message.channel - .send({ - embeds: [ - embed.setColor(client.color.main).setDescription( - T(locale, "player.setupStart.added_to_queue", { - title: res.data[0].info.title, - uri: res.data[0].info.uri, - }), - ), - ], - }) - .then((msg) => setTimeout(() => msg.delete(), 5000)); - neb(n, player, client, locale); - await m.edit({ embeds: [n] }).catch(() => {}); - break; - } - } - } - } catch (error) { - client.logger.error(error); - } - } +/** + * A function that will generate a setup message or edit an existing one + * with the current song playing. + * @param client The client to get the config from. + * @param query The query to search for. + * @param player The player to get the current track from. + * @param message The message to edit or send the setup message. + * @returns A promise that resolves when the function is done. + */ +async function setupStart(client: Lavamusic, query: string, player: Player, message: Message): Promise { + let m: Message | undefined; + const embed = client.embed(); + const n = client.embed().setColor(client.color.main); + const data = await client.db.getSetup(message.guild!.id); + const locale = await client.db.getLanguage(message.guildId!); + try { + if (data) + m = await message.channel.messages.fetch({ + message: data.messageId, + cache: true, + }); + } catch (error) { + client.logger.error(error); + } + if (m) { + try { + if (message.inGuild()) { + const res = await player.search(query, message.author); + + switch (res.loadType) { + case 'empty': + case 'error': + await message.channel + .send({ + embeds: [ + embed.setColor(client.color.red).setDescription(T(locale, 'player.setupStart.error_searching')), + ], + }) + .then(msg => setTimeout(() => msg.delete(), 5000)); + break; + case 'search': + case 'track': { + player.queue.add(res.tracks[0]); + await message.channel + .send({ + embeds: [ + embed.setColor(client.color.main).setDescription( + T(locale, 'player.setupStart.added_to_queue', { + title: res.tracks[0].info.title, + uri: res.tracks[0].info.uri, + }), + ), + ], + }) + .then(msg => setTimeout(() => msg.delete(), 5000)); + neb(n, player, client, locale); + await m.edit({ embeds: [n] }).catch(() => { + null; + }); + break; + } + case 'playlist': { + player.queue.add(res.tracks); + await message.channel + .send({ + embeds: [ + embed + .setColor(client.color.main) + .setDescription( + T(locale, 'player.setupStart.added_playlist_to_queue', { length: res.tracks.length }), + ), + ], + }) + .then(msg => setTimeout(() => msg.delete(), 5000)); + neb(n, player, client, locale); + await m.edit({ embeds: [n] }).catch(() => { + null; + }); + break; + } + } + if (!player.playing) await player.play(); + } + } catch (error) { + client.logger.error(error); + } + } } +/** + * A function that will generate an embed based on the player's current track. + * @param msgId The message ID of the setup message. + * @param channel The channel to send the message in. + * @param player The player to get the current track from. + * @param track The track to generate the embed for. + * @param client The client to get the config from. + * @param locale The locale to translate the strings. + * @returns A promise that resolves when the function is done. + */ async function trackStart( - msgId: any, - channel: TextChannel, - player: Dispatcher, - track: Song, - client: Lavamusic, - locale: string, + msgId: any, + channel: TextChannel, + player: Player, + track: Track, + client: Lavamusic, + locale: string, ): Promise { - const icon = player.current ? player.current.info.artworkUrl : client.config.links.img; - let m: Message; + const icon = player.queue.current ? player.queue.current.info.artworkUrl : client.config.links.img; + let m: Message | undefined; - try { - m = await channel.messages.fetch({ message: msgId, cache: true }); - } catch (error) { - client.logger.error(error); - } + try { + m = await channel.messages.fetch({ message: msgId, cache: true }); + } catch (error) { + client.logger.error(error); + } - const iconUrl = client.config.icons[player.current.info.sourceName] || client.user.displayAvatarURL({ extension: "png" }); - const description = T(locale, "player.setupStart.description", { - title: track.info.title, - uri: track.info.uri, - author: track.info.author, - length: client.utils.formatTime(track.info.length), - requester: track.info.requester, - }); + const iconUrl = + client.config.icons[player.queue.current!.info.sourceName] || client.user!.displayAvatarURL({ extension: 'png' }); + const description = T(locale, 'player.setupStart.description', { + title: track.info.title, + uri: track.info.uri, + author: track.info.author, + length: client.utils.formatTime(track.info.duration), + requester: (player.queue.current!.requester as Requester).id, + }); - const embed = client - .embed() - .setAuthor({ - name: T(locale, "player.setupStart.now_playing"), - iconURL: iconUrl, - }) - .setColor(client.color.main) - .setDescription(description) - .setImage(icon); + const embed = client + .embed() + .setAuthor({ + name: T(locale, 'player.setupStart.now_playing'), + iconURL: iconUrl, + }) + .setColor(client.color.main) + .setDescription(description) + .setImage(icon); - if (m) { - await m - .edit({ - embeds: [embed], - components: getButtons(player, client).map((b) => { - b.components.forEach((c) => c.setDisabled(!player?.current)); - return b; - }), - }) - .catch(() => {}); - } else { - await channel - .send({ - embeds: [embed], - components: getButtons(player, client).map((b) => { - b.components.forEach((c) => c.setDisabled(!player?.current)); - return b; - }), - }) - .then((msg) => { - client.db.setSetup(msg.guild.id, msg.id, msg.channel.id); - }) - .catch(() => {}); - } + if (m) { + await m + .edit({ + embeds: [embed], + components: getButtons(player, client).map(b => { + b.components.forEach(c => c.setDisabled(!player?.queue.current)); + return b; + }), + }) + .catch(() => { + null; + }); + } else { + await channel + .send({ + embeds: [embed], + components: getButtons(player, client).map(b => { + b.components.forEach(c => c.setDisabled(!player?.queue.current)); + return b; + }), + }) + .then(msg => { + client.db.setSetup(msg.guild.id, msg.id, msg.channel.id); + }) + .catch(() => { + null; + }); + } } async function updateSetup(client: Lavamusic, guild: any, locale: string): Promise { - const setup = await client.db.getSetup(guild.id); - let m: Message; - if (setup?.textId) { - const textChannel = guild.channels.cache.get(setup.textId) as TextChannel; - if (!textChannel) return; - try { - m = await textChannel.messages.fetch({ - message: setup.messageId, - cache: true, - }); - } catch (error) { - client.logger.error(error); - } - } - if (m) { - const player = client.queue.get(guild.id); - if (player?.current) { - const iconUrl = client.config.icons[player.current.info.sourceName] || client.user.displayAvatarURL({ extension: "png" }); - const description = T(locale, "player.setupStart.description", { - title: player.current.info.title, - uri: player.current.info.uri, - author: player.current.info.author, - length: client.utils.formatTime(player.current.info.length), - requester: player.current.info.requester, - }); + const setup = await client.db.getSetup(guild.id); + let m: Message | undefined; + if (setup?.textId) { + const textChannel = guild.channels.cache.get(setup.textId) as TextChannel; + if (!textChannel) return; + try { + m = await textChannel.messages.fetch({ + message: setup.messageId, + cache: true, + }); + } catch (error) { + client.logger.error(error); + } + } + if (m) { + const player = client.manager.getPlayer(guild.id); + if (player?.queue.current) { + const iconUrl = + client.config.icons[player.queue.current.info.sourceName] || + client.user!.displayAvatarURL({ extension: 'png' }); + const description = T(locale, 'player.setupStart.description', { + title: player.queue.current.info.title, + uri: player.queue.current.info.uri, + author: player.queue.current.info.author, + length: client.utils.formatTime(player.queue.current.info.duration), + requester: (player.queue.current.requester as Requester).id, + }); - const embed = client - .embed() - .setAuthor({ - name: T(locale, "player.setupStart.now_playing"), - iconURL: iconUrl, - }) - .setColor(client.color.main) - .setDescription(description) - .setImage(player.current.info.artworkUrl); - await m - .edit({ - embeds: [embed], - components: getButtons(player, client).map((b) => { - b.components.forEach((c) => c.setDisabled(!player?.current)); - return b; - }), - }) - .catch(() => {}); - } else { - const embed = client - .embed() - .setColor(client.color.main) - .setAuthor({ - name: client.user.username, - iconURL: client.user.displayAvatarURL({ extension: "png" }), - }) - .setDescription(T(locale, "player.setupStart.nothing_playing")) - .setImage(client.config.links.img); - await m - .edit({ - embeds: [embed], - components: getButtons(player, client).map((b) => { - b.components.forEach((c) => c.setDisabled(true)); - return b; - }), - }) - .catch(() => {}); - } - } + const embed = client + .embed() + .setAuthor({ + name: T(locale, 'player.setupStart.now_playing'), + iconURL: iconUrl, + }) + .setColor(client.color.main) + .setDescription(description) + .setImage(player.queue.current.info.artworkUrl); + await m + .edit({ + embeds: [embed], + components: getButtons(player, client).map(b => { + b.components.forEach(c => c.setDisabled(!player?.queue.current)); + return b; + }), + }) + .catch(() => { + null; + }); + } else { + const embed = client + .embed() + .setColor(client.color.main) + .setAuthor({ + name: client.user!.username, + iconURL: client.user!.displayAvatarURL({ extension: 'png' }), + }) + .setDescription(T(locale, 'player.setupStart.nothing_playing')) + .setImage(client.config.links.img); + await m + .edit({ + embeds: [embed], + components: getButtons(player, client).map(b => { + b.components.forEach(c => c.setDisabled(true)); + return b; + }), + }) + .catch(() => { + null; + }); + } + } } async function buttonReply(int: any, args: string, color: ColorResolvable): Promise { - const embed = new EmbedBuilder(); - let m: Message; - if (int.replied) { - m = await int.editReply({ embeds: [embed.setColor(color).setDescription(args)] }).catch(() => {}); - } else { - m = await int.followUp({ embeds: [embed.setColor(color).setDescription(args)] }).catch(() => {}); - } - setTimeout(async () => { - if (int && !int.ephemeral) { - await m.delete().catch(() => {}); - } - }, 2000); + const embed = new EmbedBuilder(); + let m: Message; + if (int.replied) { + m = await int.editReply({ embeds: [embed.setColor(color).setDescription(args)] }).catch(() => { + null; + }); + } else { + m = await int.followUp({ embeds: [embed.setColor(color).setDescription(args)] }).catch(() => { + null; + }); + } + setTimeout(async () => { + if (int && !int.ephemeral) { + await m.delete().catch(() => { + null; + }); + } + }, 2000); } async function oops(channel: TextChannel, args: string): Promise { - try { - const embed1 = new EmbedBuilder().setColor("Red").setDescription(`${args}`); - const m = await channel.send({ - embeds: [embed1], - }); - setTimeout(async () => await m.delete().catch(() => {}), 12000); - } catch (e) { - return console.error(e); - } + try { + const embed1 = new EmbedBuilder().setColor('Red').setDescription(`${args}`); + const m = await channel.send({ + embeds: [embed1], + }); + setTimeout( + async () => + await m.delete().catch(() => { + null; + }), + 12000, + ); + } catch (e) { + return console.error(e); + } } export { setupStart, trackStart, buttonReply, updateSetup, oops }; diff --git a/src/utils/ThemeSelector.ts b/src/utils/ThemeSelector.ts index eb00e3140..638020063 100644 --- a/src/utils/ThemeSelector.ts +++ b/src/utils/ThemeSelector.ts @@ -1,77 +1,88 @@ export class ThemeSelector { - /** - * Applies a yellow fire effect to the text. - * - * @param text - The input text to apply the effect to. - * @returns The processed text with the green fire effect. - */ - fire(text: string): string { - let fade = ""; - let green = 250; + /** + * Applies a yellow fire effect to the text. + * + * @param text - The input text to apply the effect to. + * @returns The processed text with the green fire effect. + */ + fire(text: string): string { + let fade = ''; + let green = 250; - for (const line of text.split("\n")) { - fade += `\x1b[38;2;255;${green};0m${line}\x1b[0m\n`; - green = Math.max(0, green - 25); - } + for (const line of text.split('\n')) { + fade += `\x1b[38;2;255;${green};0m${line}\x1b[0m\n`; + green = Math.max(0, green - 25); + } - return fade; - } + return fade; + } - /** - * Applies a purple neon effect to the text. - * - * @param text - The input text to apply the effect to. - * @returns The processed text with the purple neon effect. - */ - purpleNeon(text: string): string { - let fade = ""; - let purple = 255; + /** + * Applies a purple neon effect to the text. + * + * @param text - The input text to apply the effect to. + * @returns The processed text with the purple neon effect. + */ + purpleNeon(text: string): string { + let fade = ''; + let purple = 255; - for (const line of text.split("\n")) { - fade += `\x1b[38;2;255;0;${purple}m${line}\x1b[0m\n`; - purple = Math.max(0, purple - 25); - } + for (const line of text.split('\n')) { + fade += `\x1b[38;2;255;0;${purple}m${line}\x1b[0m\n`; + purple = Math.max(0, purple - 25); + } - return fade; - } + return fade; + } - /** - * Applies a cyan effect to the text. - * - * @param text - The input text to apply the effect to. - * @returns The processed text with the cyan effect. - */ - cyan(text: string): string { - let fade = ""; - let blue = 100; + /** + * Applies a cyan effect to the text. + * + * @param text - The input text to apply the effect to. + * @returns The processed text with the cyan effect. + */ + cyan(text: string): string { + let fade = ''; + let blue = 100; - for (const line of text.split("\n")) { - fade += `\x1b[38;2;0;255;${blue}m${line}\x1b[0m\n`; - if (blue < 255) { - blue = Math.min(255, blue + 15); - } - } + for (const line of text.split('\n')) { + fade += `\x1b[38;2;0;255;${blue}m${line}\x1b[0m\n`; + if (blue < 255) { + blue = Math.min(255, blue + 15); + } + } - return fade; - } + return fade; + } - /** - * Applies a water effect to the text. - * - * @param text - The input text to apply the effect to. - * @returns The processed text with the water effect. - */ - water(text: string): string { - let fade = ""; - let green = 255; + /** + * Applies a water effect to the text. + * + * @param text - The input text to apply the effect to. + * @returns The processed text with the water effect. + */ + water(text: string): string { + let fade = ''; + let green = 255; - for (const line of text.split("\n")) { - fade += `\x1b[38;2;0;${green};255m${line}\x1b[0m\n`; - if (green > 30) { - green = Math.max(30, green - 40); - } - } + for (const line of text.split('\n')) { + fade += `\x1b[38;2;0;${green};255m${line}\x1b[0m\n`; + if (green > 30) { + green = Math.max(30, green - 40); + } + } - return fade; - } + return fade; + } } + +/** + * 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/utils/Utils.ts b/src/utils/Utils.ts index 6b59296ce..a2ec572dc 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,167 +1,177 @@ -import { ActionRowBuilder, ActivityType, ButtonBuilder, ButtonStyle, CommandInteraction, type TextChannel } from "discord.js"; -import config from "../config.js"; -import type { Context, Lavamusic } from "../structures/index.js"; - +import { + ActionRowBuilder, + ActivityType, + ButtonBuilder, + ButtonStyle, + CommandInteraction, + type TextChannel, +} from 'discord.js'; +import type { Context, Lavamusic } from '../structures/index'; + +// biome-ignore lint/complexity/noStaticOnlyClass: export class Utils { - public static formatTime(ms: number): string { - const minuteMs = 60 * 1000; - const hourMs = 60 * minuteMs; - const dayMs = 24 * hourMs; - if (ms < minuteMs) return `${ms / 1000}s`; - if (ms < hourMs) return `${Math.floor(ms / minuteMs)}m ${Math.floor((ms % minuteMs) / 1000)}s`; - if (ms < dayMs) return `${Math.floor(ms / hourMs)}h ${Math.floor((ms % hourMs) / minuteMs)}m`; - return `${Math.floor(ms / dayMs)}d ${Math.floor((ms % dayMs) / hourMs)}h`; - } - - public static updateStatus(client: Lavamusic, guildId?: string): void { - const { user } = client; - if (user && guildId === config.guildId) { - const player = client.queue.get(config.guildId!); - user.setPresence({ - activities: [ - { - name: player?.current ? `🎶 | ${player.current.info.title}` : config.botActivity, - type: player?.current ? ActivityType.Listening : config.botActivityType, - }, - ], - status: config.botStatus as any, - }); - } - } - - public static chunk(array: any[], size: number): any[] { - const chunked_arr = []; - for (let index = 0; index < array.length; index += size) { - chunked_arr.push(array.slice(index, size + index)); - } - return chunked_arr; - } - - public static formatBytes(bytes: number, decimals = 2): string { - if (bytes === 0) return "0 Bytes"; - const k = 1024; - const dm = decimals < 0 ? 0 : decimals; - const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`; - } - - public static formatNumber(number: number): string { - return number.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,"); - } - - public static parseTime(string: string): number { - const time = string.match(/([0-9]+[d,h,m,s])/g); - if (!time) return 0; - let ms = 0; - for (const t of time) { - const unit = t[t.length - 1]; - const amount = Number(t.slice(0, -1)); - if (unit === "d") ms += amount * 24 * 60 * 60 * 1000; - else if (unit === "h") ms += amount * 60 * 60 * 1000; - else if (unit === "m") ms += amount * 60 * 1000; - else if (unit === "s") ms += amount * 1000; - } - return ms; - } - - public static progressBar(current: number, total: number, size = 20): string { - const percent = Math.round((current / total) * 100); - const filledSize = Math.round((size * current) / total); - const filledBar = "▓".repeat(filledSize); - const emptyBar = "░".repeat(size - filledSize); - return `${filledBar}${emptyBar} ${percent}%`; - } - - public static async paginate(client: Lavamusic, ctx: Context, embed: any[]): Promise { - if (embed.length < 2) { - if (ctx.isInteraction) { - ctx.deferred ? ctx.interaction.followUp({ embeds: embed }) : ctx.interaction.reply({ embeds: embed }); - return; - } - - (ctx.channel as TextChannel).send({ embeds: embed }); - return; - } - - let page = 0; - const getButton = (page: number): any => { - const firstEmbed = page === 0; - const lastEmbed = page === embed.length - 1; - const pageEmbed = embed[page]; - const first = new ButtonBuilder() - .setCustomId("first") - .setEmoji(client.emoji.page.first) - .setStyle(ButtonStyle.Primary) - .setDisabled(firstEmbed); - const back = new ButtonBuilder() - .setCustomId("back") - .setEmoji(client.emoji.page.back) - .setStyle(ButtonStyle.Primary) - .setDisabled(firstEmbed); - const next = new ButtonBuilder() - .setCustomId("next") - .setEmoji(client.emoji.page.next) - .setStyle(ButtonStyle.Primary) - .setDisabled(lastEmbed); - const last = new ButtonBuilder() - .setCustomId("last") - .setEmoji(client.emoji.page.last) - .setStyle(ButtonStyle.Primary) - .setDisabled(lastEmbed); - const stop = new ButtonBuilder().setCustomId("stop").setEmoji(client.emoji.page.cancel).setStyle(ButtonStyle.Danger); - const row = new ActionRowBuilder().addComponents(first, back, stop, next, last); - return { embeds: [pageEmbed], components: [row] }; - }; - - const msgOptions = getButton(0); - const msg = ctx.isInteraction - ? await (ctx.deferred - ? ctx.interaction!.followUp({ - ...msgOptions, - fetchReply: true as boolean, - }) - : ctx.interaction!.reply({ ...msgOptions, fetchReply: true })) - : await (ctx.channel as TextChannel).send({ - ...msgOptions, - fetchReply: true, - }); - - const author = ctx instanceof CommandInteraction ? ctx.user : ctx.author; - - const filter = (int: any): any => int.user.id === author.id; - const collector = msg.createMessageComponentCollector({ - filter, - time: 60000, - }); - - collector.on("collect", async (interaction) => { - if (interaction.user.id === author.id) { - await interaction.deferUpdate(); - if (interaction.customId === "first" && page !== 0) { - page = 0; - } else if (interaction.customId === "back" && page !== 0) { - page--; - } else if (interaction.customId === "stop") { - collector.stop(); - } else if (interaction.customId === "next" && page !== embed.length - 1) { - page++; - } else if (interaction.customId === "last" && page !== embed.length - 1) { - page = embed.length - 1; - } - await interaction.editReply(getButton(page)); - } else { - await interaction.reply({ - content: ctx.locale("buttons.errors.not_author"), - ephemeral: true, - }); - } - }); - - collector.on("end", async () => { - await msg.edit({ embeds: [embed[page]], components: [] }); - }); - } + public static formatTime(ms: number): string { + const minuteMs = 60 * 1000; + const hourMs = 60 * minuteMs; + const dayMs = 24 * hourMs; + if (ms < minuteMs) return `${ms / 1000}s`; + if (ms < hourMs) return `${Math.floor(ms / minuteMs)}m ${Math.floor((ms % minuteMs) / 1000)}s`; + if (ms < dayMs) return `${Math.floor(ms / hourMs)}h ${Math.floor((ms % hourMs) / minuteMs)}m`; + return `${Math.floor(ms / dayMs)}d ${Math.floor((ms % dayMs) / hourMs)}h`; + } + + public static updateStatus(client: Lavamusic, guildId?: string): void { + const { user } = client; + if (user && client.env.GUILD_ID && guildId === client.env.GUILD_ID) { + const player = client.manager.getPlayer(client.env.GUILD_ID); + user.setPresence({ + activities: [ + { + name: player?.queue?.current ? `🎶 | ${player.queue?.current.info.title}` : client.env.BOT_ACTIVITY, + type: player?.queue?.current ? ActivityType.Listening : client.env.BOT_ACTIVITY_TYPE, + }, + ], + status: client.env.BOT_STATUS as any, + }); + } + } + + public static chunk(array: any[], size: number) { + const chunked_arr: any[][] = []; + for (let index = 0; index < array.length; index += size) { + chunked_arr.push(array.slice(index, size + index)); + } + return chunked_arr; + } + + public static formatBytes(bytes: number, decimals = 2): string { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`; + } + + public static formatNumber(number: number): string { + return number.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); + } + + public static parseTime(string: string): number { + const time = string.match(/([0-9]+[d,h,m,s])/g); + if (!time) return 0; + let ms = 0; + for (const t of time) { + const unit = t[t.length - 1]; + const amount = Number(t.slice(0, -1)); + if (unit === 'd') ms += amount * 24 * 60 * 60 * 1000; + else if (unit === 'h') ms += amount * 60 * 60 * 1000; + else if (unit === 'm') ms += amount * 60 * 1000; + else if (unit === 's') ms += amount * 1000; + } + return ms; + } + + public static progressBar(current: number, total: number, size = 20): string { + const percent = Math.round((current / total) * 100); + const filledSize = Math.round((size * current) / total); + const filledBar = '▓'.repeat(filledSize); + const emptyBar = '░'.repeat(size - filledSize); + return `${filledBar}${emptyBar} ${percent}%`; + } + + public static async paginate(client: Lavamusic, ctx: Context, embed: any[]): Promise { + if (embed.length < 2) { + if (ctx.isInteraction) { + ctx.deferred ? ctx.interaction?.followUp({ embeds: embed }) : ctx.interaction?.reply({ embeds: embed }); + return; + } + + (ctx.channel as TextChannel).send({ embeds: embed }); + return; + } + + let page = 0; + const getButton = (page: number): any => { + const firstEmbed = page === 0; + const lastEmbed = page === embed.length - 1; + const pageEmbed = embed[page]; + const first = new ButtonBuilder() + .setCustomId('first') + .setEmoji(client.emoji.page.first) + .setStyle(ButtonStyle.Primary) + .setDisabled(firstEmbed); + const back = new ButtonBuilder() + .setCustomId('back') + .setEmoji(client.emoji.page.back) + .setStyle(ButtonStyle.Primary) + .setDisabled(firstEmbed); + const next = new ButtonBuilder() + .setCustomId('next') + .setEmoji(client.emoji.page.next) + .setStyle(ButtonStyle.Primary) + .setDisabled(lastEmbed); + const last = new ButtonBuilder() + .setCustomId('last') + .setEmoji(client.emoji.page.last) + .setStyle(ButtonStyle.Primary) + .setDisabled(lastEmbed); + const stop = new ButtonBuilder() + .setCustomId('stop') + .setEmoji(client.emoji.page.cancel) + .setStyle(ButtonStyle.Danger); + const row = new ActionRowBuilder().addComponents(first, back, stop, next, last); + return { embeds: [pageEmbed], components: [row] }; + }; + + const msgOptions = getButton(0); + const msg = ctx.isInteraction + ? await (ctx.deferred + ? ctx.interaction!.followUp({ + ...msgOptions, + fetchReply: true as boolean, + }) + : ctx.interaction!.reply({ ...msgOptions, fetchReply: true })) + : await (ctx.channel as TextChannel).send({ + ...msgOptions, + fetchReply: true, + }); + + const author = ctx instanceof CommandInteraction ? ctx.user : ctx.author; + + const filter = (int: any): any => int.user.id === author?.id; + const collector = msg.createMessageComponentCollector({ + filter, + time: 60000, + }); + + collector.on('collect', async interaction => { + if (interaction.user.id === author?.id) { + await interaction.deferUpdate(); + if (interaction.customId === 'first' && page !== 0) { + page = 0; + } else if (interaction.customId === 'back' && page !== 0) { + page--; + } else if (interaction.customId === 'stop') { + collector.stop(); + } else if (interaction.customId === 'next' && page !== embed.length - 1) { + page++; + } else if (interaction.customId === 'last' && page !== embed.length - 1) { + page = embed.length - 1; + } + await interaction.editReply(getButton(page)); + } else { + await interaction.reply({ + content: ctx.locale('buttons.errors.not_author'), + ephemeral: true, + }); + } + }); + + collector.on('end', async () => { + await msg.edit({ embeds: [embed[page]], components: [] }); + }); + } } /** diff --git a/src/utils/functions/player.ts b/src/utils/functions/player.ts new file mode 100644 index 000000000..7585cac65 --- /dev/null +++ b/src/utils/functions/player.ts @@ -0,0 +1,121 @@ +import type { Player, Track } from 'lavalink-client'; +import type { Requester } from '../../types'; + +/** + * Transforms a requester into a standardized requester object. + * + * @param {any} requester The requester to transform. Can be a string, a user, or an object with + * the keys `id`, `username`, and `avatarURL`. + * @returns {Requester} The transformed requester object. + */ +export const requesterTransformer = (requester: any): Requester => { + // if it's already the transformed requester + if (typeof requester === 'object' && 'avatar' in requester && Object.keys(requester).length === 3) + return requester as Requester; + // if it's still a string + if (typeof requester === 'object' && 'displayAvatarURL' in requester) { + // it's a user + return { + id: requester.id, + username: requester.username, + avatarURL: requester.displayAvatarURL({ extension: 'png' }), + discriminator: requester.discriminator, + }; + } + return { id: requester!.toString(), username: 'unknown' }; +}; + +/** + * Function that will be called when the autoplay feature is enabled and the queue + * is empty. It will search for tracks based on the last played track and add them + * to the queue. + * + * @param {Player} player The player instance. + * @param {Track} lastTrack The last played track. + * @returns {Promise} A promise that resolves when the function is done. + */ +export async function autoPlayFunction(player: Player, lastTrack?: Track): Promise { + if (!player.get('autoplay')) return; + if (!lastTrack) return; + + if (lastTrack.info.sourceName === 'spotify') { + const filtered = player.queue.previous.filter(v => v.info.sourceName === 'spotify').slice(0, 5); + const ids = filtered.map( + v => v.info.identifier || v.info.uri.split('/')?.reverse()?.[0] || v.info.uri.split('/')?.reverse()?.[1], + ); + if (ids.length >= 2) { + const res = await player + .search( + { + query: `seed_tracks=${ids.join(',')}`, //`seed_artists=${artistIds.join(",")}&seed_genres=${genre.join(",")}&seed_tracks=${trackIds.join(",")}`; + source: 'sprec', + }, + lastTrack.requester, + ) + .then((response: any) => { + response.tracks = response.tracks.filter( + (v: { info: { identifier: string } }) => v.info.identifier !== lastTrack.info.identifier, + ); // remove the lastPlayed track if it's in there.. + return response; + }) + .catch(console.warn); + if (res && res.tracks.length > 0) + await player.queue.add( + res.tracks.slice(0, 5).map((track: { pluginInfo: { clientData: any } }) => { + // transform the track plugininfo so you can figure out if the track is from autoplay or not. + track.pluginInfo.clientData = { ...(track.pluginInfo.clientData || {}), fromAutoplay: true }; + return track; + }), + ); + } + return; + } + if (lastTrack.info.sourceName === 'youtube' || lastTrack.info.sourceName === 'youtubemusic') { + const res = await player + .search( + { + query: `https://www.youtube.com/watch?v=${lastTrack.info.identifier}&list=RD${lastTrack.info.identifier}`, + source: 'youtube', + }, + lastTrack.requester, + ) + .then((response: any) => { + response.tracks = response.tracks.filter( + (v: { info: { identifier: string } }) => v.info.identifier !== lastTrack.info.identifier, + ); // remove the lastPlayed track if it's in there.. + return response; + }) + .catch(console.warn); + if (res && res.tracks.length > 0) + await player.queue.add( + res.tracks.slice(0, 5).map((track: { pluginInfo: { clientData: any } }) => { + // transform the track plugininfo so you can figure out if the track is from autoplay or not. + track.pluginInfo.clientData = { ...(track.pluginInfo.clientData || {}), fromAutoplay: true }; + return track; + }), + ); + return; + } + if (lastTrack.info.sourceName === 'jiosaavn') { + const res = await player.search( + { query: `jsrec:${lastTrack.info.identifier}`, source: 'jsrec' }, + lastTrack.requester, + ); + if (res.tracks.length > 0) { + const track = res.tracks.filter(v => v.info.identifier !== lastTrack.info.identifier)[0]; + await player.queue.add(track); + } + } + return; +} + +/** + * 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/tsconfig.json b/tsconfig.json index d3cbf1fd6..4a46aa3b0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,30 @@ { "compilerOptions": { - "target": "esnext", - "module": "esnext", - "lib": ["esnext"], - "declaration": true, - "sourceMap": true, - "newLine": "crlf", - "outDir": "dist", - "rootDir": "src", - "strict": false, + "module": "CommonJS", + "target": "ESNext", + "lib": ["ESNext", "WebWorker"], "moduleResolution": "node", + "declaration": true, + "sourceMap": false, + "strict": true, "esModuleInterop": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, + "preserveConstEnums": true, + /* Type Checking */ + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true + "noErrorTruncation": true, + "outDir": "./dist", + "stripInternal": true }, - "include": ["src/"], - "exclude": ["dist/", "node_modules/"] + "exclude": ["dist/", "node_modules/"], + "include": ["src/**/*"] }