diff --git a/.gitignore b/.gitignore index e9cd46f..1b50942 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules docs .env.asli +config.js # Logs logs diff --git a/config.js b/config.js.example similarity index 81% rename from config.js rename to config.js.example index 6a57949..b7b35f9 100644 --- a/config.js +++ b/config.js.example @@ -49,32 +49,24 @@ module.exports = { MIN_BEG_AMOUNT: 1000, // minimum coins to be received when beg command is used MAX_BEG_AMOUNT: 25000, // maximum coins to be received when beg command is used }, - + MUSIC: { ENABLED: true, IDLE_TIME: 120, // Time in seconds before the bot disconnects from an idle voice channel + DEFAULT_VOLUME: 60, // Default player volume 1-100 MAX_SEARCH_RESULTS: 100, - DEFAULT_VOLUME: 100, // Default volume for the music player (0-100) - DEFAULT_SOURCE: "ytsearch", // ytsearch = Youtube, ytmsearch = Youtube Music, spsearch = Spotify, scsearch = SoundCloud - // Lavalink Websocket configuration - LAVALINK_WS: { - clientName: "Tinasha-Bot", // The name of the lavalink client. - resuming: true, // Whether Lavalink should attempt to resume existing sessions when reconnecting. - reconnecting: { - tries: Infinity, // Number of times to attempt reconnecting. - delay: 20000 // Delay - } - }, + DEFAULT_SOURCE: "ytsearch", // ytsearch = Youtube, ytmsearch = Youtube Music, scsearch = SoundCloud, spsearch = Spotify // Add any number of lavalink nodes here + // Refer to https://github.com/lavalink-devs/Lavalink to host your own lavalink server LAVALINK_NODES: [ { - info: { - host: "localhost", - port: 2333, - auth: "youshallnotpass", - secure: false, - }, - identifier: "Lavalink", + id: "Local Node", + host: "localhost", + port: 2333, + authorization: "youshallnotpass", + secure: false, + retryAmount: 20, // Number of reconnection attempts + retryDelay: 30000 // Delay (in ms) between reconnection attempts }, ], }, diff --git a/jsconfig.json b/jsconfig.json index a7062f3..d5bdae2 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -12,5 +12,6 @@ "@structures/*": ["./src/structures/*"] } }, + "include": ["src"], "exclude": ["node_modules", "**/node_modules/*"] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6be6dbd..9e27823 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,16 @@ { "name": "TinashaBot", - "version": "5.6.0", + "version": "5.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "TinashaBot", - "version": "5.6.0", + "version": "5.8.0", "license": "ISC", "dependencies": { "@google/generative-ai": "^0.14.0", "@lavaclient/plugin-queue": "^0.0.1", - "@lavaclient/queue": "^2.1.1", - "@lavaclient/spotify": "^3.1.0", "@lavaclient/types": "^2.1.1", "@vitalets/google-translate-api": "^9.2.0", "common-tags": "^1.8.2", @@ -24,14 +22,14 @@ "dotenv": "^16.3.1", "ejs": "^3.1.9", "enhanced-ms": "^2.3.0", - "express": "^4.21.1", + "express": "^4.21.2", "express-session": "^1.18.1", "fixedsize-map": "^1.0.1", "iso-639-1": "^3.1.0", - "lavaclient": "^5.0.0-rc.3", + "lavalink-client": "^2.4.0", "module-alias": "^2.2.3", "moment": "^2.30.1", - "mongoose": "^8.1.1", + "mongoose": "^8.9.5", "nekos.life": "^3.0.0", "pino": "^8.18.0", "pino-pretty": "^10.3.1", @@ -192,13 +190,15 @@ "version": "0.17.6", "resolved": "https://registry.npmjs.org/@effect/data/-/data-0.17.6.tgz", "integrity": "sha512-/vwz7Jh05eS0qY8kczR/YyJd18d0C+PMtUkAealh4f6gwvhABLGCnktNJTcq/+UHxY0Cbv18r5uaJ4+7PPC+WQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@effect/schema": { "version": "0.49.4", "resolved": "https://registry.npmjs.org/@effect/schema/-/schema-0.49.4.tgz", "integrity": "sha512-Em5qFV7kXfHpt6n89B2Zwd0ccGgfFpZbBAfQuGPdw/zY18k01Tl3ufKfBA6fFphKQiWrU6JS9btTlq1+/WRRIg==", "license": "MIT", + "peer": true, "peerDependencies": { "effect": "2.0.0-next.56", "fast-check": "^3.13.2" @@ -343,30 +343,6 @@ "lavalink-protocol": "1.0.1" } }, - "node_modules/@lavaclient/queue": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@lavaclient/queue/-/queue-2.1.1.tgz", - "integrity": "sha512-2I+T+4xpb6I4xh4GjCCY/F2zazoHw/RectbKWlBZYSEMGoIGbHfOu81yi4fModigSVG0+4br8NnwHLiQX+XNcg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@lavaclient/spotify": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@lavaclient/spotify/-/spotify-3.1.0.tgz", - "integrity": "sha512-B9AwZVyxScjJnJWJa4zMylF2i2/UOvDKL7lHWMxcezBMvOqjM+rZMr4ZTJj179qdpOaQ7zc8mBfRYSXGWJIWmA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/@lavaclient/types": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@lavaclient/types/-/types-2.1.1.tgz", @@ -374,10 +350,9 @@ "license": "MIT" }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz", - "integrity": "sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==", - "license": "MIT", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", "dependencies": { "sparse-bitfield": "^3.0.3" } @@ -821,10 +796,9 @@ } }, "node_modules/bson": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.7.0.tgz", - "integrity": "sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==", - "license": "Apache-2.0", + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.1.tgz", + "integrity": "sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==", "engines": { "node": ">=16.20.1" } @@ -1082,11 +1056,10 @@ "license": "ISC" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, - "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1598,9 +1571,10 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -1621,7 +1595,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -1636,6 +1610,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-session": { @@ -1685,6 +1663,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2475,6 +2462,7 @@ "resolved": "https://registry.npmjs.org/lavaclient/-/lavaclient-5.0.0-rc.3.tgz", "integrity": "sha512-zSB0wK9SEhXwWd918ije4UjjqMeDlP7Nry8mo5UFvQlD2YMFKxJJuKTJKc9VwgBhBjRVB0cCujAlru7NsTRdDg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@effect/schema": "^0.49.4", "lavalink-api-client": "1.0.1", @@ -2492,6 +2480,7 @@ "resolved": "https://registry.npmjs.org/lavalink-api-client/-/lavalink-api-client-1.0.1.tgz", "integrity": "sha512-ur4rrOf68mEKQQ6SwDdzG/8W+ew6zpguLPxny14DjEGxDz/mXvDtILovXpFRR5S4d8Ko9xZygIJ9YqEQfgKC/w==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.6.0" }, @@ -2507,6 +2496,7 @@ "resolved": "https://registry.npmjs.org/lavalink-protocol/-/lavalink-protocol-1.0.2.tgz", "integrity": "sha512-OLd4ZDrYeT35UxJY1K/Pu6PjqPnHN0X5HutcQ7E/UNz+3YBvieaki9z0gk08gPwsxkBhYjRKDNIVNTAT0sH0fA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@effect/data": "0.17.6", "@effect/schema": "^0.49.4", @@ -2521,6 +2511,7 @@ "resolved": "https://registry.npmjs.org/lavalink-ws-client/-/lavalink-ws-client-1.0.1.tgz", "integrity": "sha512-jXntoMJe/lk4vuO3RVfSVQPNx28CwammqP4I3+pi5uIF/WHBAJxzwyIzDCmUPIIXcuEveUH53bs2aE8uCq9Sig==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.6.0", "typed-emitter": "^2.1.0", @@ -2534,6 +2525,20 @@ "lavalink-protocol": "1.0.2" } }, + "node_modules/lavalink-client": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lavalink-client/-/lavalink-client-2.4.1.tgz", + "integrity": "sha512-wrRkbzILdjRzadAbdGM9O3bgLEabAebGci2YOUCcQoCumEjAMPz+rj7Xepa+y0oydnodyYZvBrB9ZeSdy7rzuw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.7.0", + "ws": "^8.18.0" + }, + "engines": { + "bun": ">=1.0.0", + "node": ">=18.0.0" + } + }, "node_modules/lavalink-protocol": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lavalink-protocol/-/lavalink-protocol-1.0.1.tgz", @@ -2621,8 +2626,7 @@ "node_modules/memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "license": "MIT" + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "node_modules/merge-descriptors": { "version": "1.0.3", @@ -2716,14 +2720,12 @@ } }, "node_modules/mongodb": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.7.0.tgz", - "integrity": "sha512-TMKyHdtMcO0fYBNORiYdmM25ijsHs+Njs963r4Tro4OQZzqYigAzYQouwWRg4OIaiLRUEGUh/1UAcH5lxdSLIA==", - "license": "Apache-2.0", - "peer": true, + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", + "integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==", "dependencies": { - "@mongodb-js/saslprep": "^1.1.5", - "bson": "^6.7.0", + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", "mongodb-connection-string-url": "^3.0.0" }, "engines": { @@ -2731,7 +2733,7 @@ }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", "mongodb-client-encryption": ">=6.0.0 <7", @@ -2773,14 +2775,13 @@ } }, "node_modules/mongoose": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.4.1.tgz", - "integrity": "sha512-odQ2WEWGL3hb0Qex+QMN4eH6D34WdMEw7F1If2MGABApSDmG9cMmqv/G1H6WsXmuaH9mkuuadW/WbLE5+tHJwA==", - "license": "MIT", + "version": "8.9.5", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.5.tgz", + "integrity": "sha512-SPhOrgBm0nKV3b+IIHGqpUTOmgVL5Z3OO9AwkFEmvOZznXTvplbomstCnPOGAyungtRXE5pJTgKpKcZTdjeESg==", "dependencies": { - "bson": "^6.7.0", + "bson": "^6.10.1", "kareem": "2.6.3", - "mongodb": "6.6.2", + "mongodb": "~6.12.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -2794,52 +2795,6 @@ "url": "https://opencollective.com/mongoose" } }, - "node_modules/mongoose/node_modules/mongodb": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.6.2.tgz", - "integrity": "sha512-ZF9Ugo2JCG/GfR7DEb4ypfyJJyiKbg5qBYKRintebj8+DNS33CyGMkWbrS9lara+u+h+yEOGSRiLhFO/g1s1aw==", - "license": "Apache-2.0", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.5", - "bson": "^6.7.0", - "mongodb-connection-string-url": "^3.0.0" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, "node_modules/mongoose/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3203,9 +3158,10 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/phin": { "version": "3.7.1", @@ -3603,6 +3559,7 @@ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "license": "Apache-2.0", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -3927,7 +3884,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "license": "MIT", "dependencies": { "memory-pager": "^1.0.2" } @@ -4156,9 +4112,9 @@ "license": "MIT" }, "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/twemoji-parser": { @@ -4210,6 +4166,7 @@ "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", "license": "MIT", + "peer": true, "optionalDependencies": { "rxjs": "*" } @@ -4362,9 +4319,10 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 218db51..6455aee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "TinashaBot", - "version": "5.6.0", + "version": "5.8.0", "description": "multipurpose discord bot built using discord-js", "main": "bot.js", "author": "Amane", @@ -24,8 +24,6 @@ "dependencies": { "@google/generative-ai": "^0.14.0", "@lavaclient/plugin-queue": "^0.0.1", - "@lavaclient/queue": "^2.1.1", - "@lavaclient/spotify": "^3.1.0", "@lavaclient/types": "^2.1.1", "@vitalets/google-translate-api": "^9.2.0", "common-tags": "^1.8.2", @@ -37,14 +35,14 @@ "dotenv": "^16.3.1", "ejs": "^3.1.9", "enhanced-ms": "^2.3.0", - "express": "^4.21.1", + "express": "^4.21.2", "express-session": "^1.18.1", "fixedsize-map": "^1.0.1", "iso-639-1": "^3.1.0", - "lavaclient": "^5.0.0-rc.3", + "lavalink-client": "^2.4.0", "module-alias": "^2.2.3", "moment": "^2.30.1", - "mongoose": "^8.1.1", + "mongoose": "^8.9.5", "nekos.life": "^3.0.0", "pino": "^8.18.0", "pino-pretty": "^10.3.1", diff --git a/src/commands/information/shared/botstats.js b/src/commands/information/shared/botstats.js index 3a5a418..43b0b2e 100644 --- a/src/commands/information/shared/botstats.js +++ b/src/commands/information/shared/botstats.js @@ -3,6 +3,7 @@ const { EMBED_COLORS, SUPPORT_SERVER, DASHBOARD } = require("@root/config"); const { timeformat } = require("@helpers/Utils"); const os = require("os"); const { stripIndent } = require("common-tags"); +const packageJson = require("@root/package.json"); /** * @param {import('@structures/BotClient')} client @@ -29,7 +30,7 @@ module.exports = (client) => { const overallUsage = `${Math.floor(((os.totalmem() - os.freemem()) / os.totalmem()) * 100)}%`; let desc = ""; - desc += `❒ Bot Version: 5.6.0\n`; + desc += `❒ Bot Version: ${packageJson.version}\n`; desc += `❒ Bot Maker: <@334307216926703616>\n`; desc += `❒ Bot Manager: Tinasha Bot Development Team\n`; desc += `❒ Total guilds: ${guilds}\n`; diff --git a/src/commands/music/autoplay.js b/src/commands/music/autoplay.js new file mode 100644 index 0000000..a1327b7 --- /dev/null +++ b/src/commands/music/autoplay.js @@ -0,0 +1,51 @@ +const { musicValidations } = require("@helpers/BotUtils"); +const { autoplayFunction } = require("@handlers/player"); + +/** + * @type {import("@structures/Command")} + */ +module.exports = { + name: "autoplay", + description: "Toggle autoplay feature for music player", + category: "MUSIC", + validations: musicValidations, + command: { + enabled: true, + aliases: ["ap"], + usage: "", + }, + slashCommand: { + enabled: true, + options: [], + }, + + async messageRun(message, args) { + const response = await toggleAutoplay(message); + await message.safeReply(response); + }, + + async interactionRun(interaction) { + const response = await toggleAutoplay(interaction); + await interaction.followUp(response); + }, +}; + +async function toggleAutoplay({ client, guildId }) { + const player = client.musicManager.getPlayer(guildId); + + if (!player || !player.queue.current) { + return "🚫 No song is currently playing"; + } + + const autoplayState = player.get("autoplay"); + + if (autoplayState) { + player.set("autoplay", false); + return "Autoplay deactivated"; + } + + player.set("autoplay", true); + autoplayFunction(client, player.queue.current, player); + + return "Autoplay activated!"; +} diff --git a/src/commands/music/bassboost.js b/src/commands/music/bassboost.js index d384756..8194656 100644 --- a/src/commands/music/bassboost.js +++ b/src/commands/music/bassboost.js @@ -1,23 +1,18 @@ const { musicValidations } = require("@helpers/BotUtils"); const { ApplicationCommandOptionType } = require("discord.js"); - -const levels = { - none: 0.0, - low: 0.1, - medium: 0.15, - high: 0.25, -}; +const { EQList } = require("lavalink-client"); /** * @type {import("@structures/Command")} */ module.exports = { name: "bassboost", - description: "set bassboost level", + description: "Set bassboost level", category: "MUSIC", validations: musicValidations, command: { enabled: true, + aliases: ["bb"], minArgsCount: 1, usage: "", }, @@ -30,48 +25,64 @@ module.exports = { type: ApplicationCommandOptionType.String, required: true, choices: [ - { - name: "none", - value: "none", - }, - { - name: "low", - value: "low", - }, - { - name: "medium", - value: "medium", - }, - { - name: "high", - value: "high", - }, + { name: "High", value: "high" }, + { name: "Medium", value: "medium" }, + { name: "Low", value: "low" }, + { name: "Off", value: "off" }, ], }, ], }, async messageRun(message, args) { - let level = "none"; - if (args.length && args[0].toLowerCase() in levels) level = args[0].toLowerCase(); - const response = setBassBoost(message, level); + let level = "off"; + if (args.length) { + const input = args[0].toLowerCase(); + if (["high", "medium", "low", "off"].includes(input)) { + level = input; + } + } + const response = await setBassBoost(message, level); await message.safeReply(response); }, async interactionRun(interaction) { let level = interaction.options.getString("level"); - const response = setBassBoost(interaction, level); + const response = await setBassBoost(interaction, level); await interaction.followUp(response); }, }; /** * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 - * @param {number} level + * @param {string} level */ -function setBassBoost({ client, guildId }, level) { - const player = client.musicManager.players.resolve(guildId); - const bands = new Array(3).fill(null).map((_, i) => ({ band: i, gain: levels[level] })); - player.setFilters(...bands); +async function setBassBoost({ client, guildId }, level) { + const player = client.musicManager.getPlayer(guildId); + + if (!player || !player.queue.current) { + return "🚫 No song is currently playing"; + } + + // Clear any existing EQ + await player.filterManager.clearEQ(); + + switch (level) { + case "high": + await player.filterManager.setEQ(EQList.BassboostHigh); + break; + case "medium": + await player.filterManager.setEQ(EQList.BassboostMedium); + break; + case "low": + await player.filterManager.setEQ(EQList.BassboostLow); + break; + case "off": + await player.filterManager.clearEQ(); + break; + default: + return "Invalid bassboost level"; + } + return `> Set the bassboost level to \`${level}\``; } diff --git a/src/commands/music/join.js b/src/commands/music/join.js new file mode 100644 index 0000000..e1e9fcc --- /dev/null +++ b/src/commands/music/join.js @@ -0,0 +1,61 @@ +const { EmbedBuilder } = require("discord.js"); +const Queue = require("@src/database/schemas/Queue"); +const { MUSIC } = require("@root/config"); + +module.exports = { + name: "join", + description: "Join the voice channel and resume playback if there is a saved queue.", + category: "MUSIC", + validations: [], // Add any validations if necessary + command: { + enabled: true, + }, + slashCommand: { + enabled: true, + }, + + async messageRun(message, args) { + const response = await executeJoinCommand(message); + await message.safeReply(response); + }, + + async interactionRun(interaction) { + const response = await executeJoinCommand(interaction); + await interaction.followUp(response); + }, +}; + +async function executeJoinCommand(interaction) { + const { guild, member } = interaction; + const voiceChannel = member.voice.channel; + + if (!voiceChannel) { + return "You need to be in a voice channel to use this command."; + } + + // Get or create the player for the guild + let player = guild.client.musicManager.getPlayer(guild.id); + if (!player) { + player = await guild.client.musicManager.createPlayer({ + guildId: guild.id, + voiceChannelId: voiceChannel.id, + textChannelId: interaction.channel.id, + selfMute: false, + selfDeaf: true, + volume: MUSIC.DEFAULT_VOLUME, + }); + } + + if (!player.connected) await player.connect() + + // Check for saved queue in the database + const restore = await Queue.restore(player) + + if (restore) { + await player.play({ paused: false }); + + return `Joined ${voiceChannel.name} and resumed playback of **${player.queue.current.info.title}**.`; + } else { + return `Joined ${voiceChannel.name}.` + } +} \ No newline at end of file diff --git a/src/commands/music/leave.js b/src/commands/music/leave.js new file mode 100644 index 0000000..61e3c15 --- /dev/null +++ b/src/commands/music/leave.js @@ -0,0 +1,41 @@ +const { musicValidations } = require("@helpers/BotUtils"); + +/** + * @type {import("@structures/Command")} + */ +module.exports = { + name: "leave", + description: "Disconnects the bot from the voice channel", + category: "MUSIC", + validations: musicValidations, + command: { + enabled: true, + aliases: ["dc"], + minArgsCount: 0, + usage: "", + }, + slashCommand: { + enabled: true, + options: [], + }, + + async messageRun(message, args) { + const response = await leave(message); + await message.safeReply(response); + }, + + async interactionRun(interaction) { + const response = await leave(interaction); + await interaction.followUp(response); + }, +}; + +/** + * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 + */ +async function leave({ client, guildId, member }) { + const player = client.musicManager.getPlayer(guildId); + + player.destroy(); + return "👋 Disconnected from the voice channel"; +} diff --git a/src/commands/music/loop.js b/src/commands/music/loop.js index 5e99fd2..61e3a1e 100644 --- a/src/commands/music/loop.js +++ b/src/commands/music/loop.js @@ -1,5 +1,4 @@ const { musicValidations } = require("@helpers/BotUtils"); -const { LoopType } = require("@lavaclient/plugin-queue"); const { ApplicationCommandOptionType } = require("discord.js"); /** @@ -12,8 +11,9 @@ module.exports = { validations: musicValidations, command: { enabled: true, + aliases: ["lp"], minArgsCount: 1, - usage: "", + usage: "", }, slashCommand: { enabled: true, @@ -21,17 +21,12 @@ module.exports = { { name: "type", type: ApplicationCommandOptionType.String, - description: "The entity you want to loop", + description: "Select loop type", required: false, choices: [ - { - name: "queue", - value: "queue", - }, - { - name: "track", - value: "track", - }, + { name: "Track", value: "track" }, + { name: "Queue", value: "queue" }, + { name: "Off", value: "off" }, ], }, ], @@ -39,34 +34,47 @@ module.exports = { async messageRun(message, args) { const input = args[0].toLowerCase(); - const type = input === "queue" ? "queue" : "track"; - const response = toggleLoop(message, type); + const type = input === "queue" ? "queue" : input === "track" ? "track" : "off"; + const response = await toggleLoop(message, type); await message.safeReply(response); }, async interactionRun(interaction) { const type = interaction.options.getString("type") || "track"; - const response = toggleLoop(interaction, type); + const response = await toggleLoop(interaction, type); await interaction.followUp(response); }, }; /** * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 - * @param {"queue"|"track"} type + * @param {"queue"|"track"|"off"} type */ -function toggleLoop({ client, guildId }, type) { - const player = client.musicManager.players.resolve(guildId); +async function toggleLoop({ client, guildId }, type) { + const player = client.musicManager.getPlayer(guildId); - // track - if (type === "track") { - player.queue.setLoop(LoopType.Song); - return "Loop mode is set to `track`"; + if (!player || !player.queue.current) { + return "🚫 No song is currently playing"; } - // queue - else if (type === "queue") { - player.queue.setLoop(LoopType.Queue); - return "Loop mode is set to `queue`"; + switch (type) { + case "track": + player.setRepeatMode("track"); + return "Loop mode is set to `track`"; + + case "queue": + if (player.queue.tracks.length > 1) { + player.setRepeatMode("queue"); + return "Loop mode is set to `queue`"; + } else { + return "🚫 Queue is too short to be looped"; + } + + case "off": + player.setRepeatMode("off"); + return "Loop mode is disabled"; + + default: + return "Invalid loop type"; } } diff --git a/src/commands/music/lyric.js b/src/commands/music/lyric.js index ae1274c..4bdc508 100644 --- a/src/commands/music/lyric.js +++ b/src/commands/music/lyric.js @@ -1,5 +1,5 @@ const { EmbedBuilder, ApplicationCommandOptionType } = require("discord.js"); -const { MESSAGES, EMBED_COLORS } = require("@root/config"); +const { EMBED_COLORS } = require("@root/config"); /** * @type {import("@structures/Command")} @@ -38,116 +38,94 @@ module.exports = { }, }; -/** - * @param {import('discord.js').Message} param0 - * @param {string} query ☺ - */ async function getLyric({ client, guild, member }, query) { - /** @type {Response} */ - let res; + /** @type {import('../../handlers/manager')} */ + const manager = client.musicManager + const player = manager.getPlayer(guild.id) + let node = player?.node + if (!node) [node] = manager.nodeManager.nodes.values() + let track; + + if (!query) { + // query not specified - use currently playing music + track = player?.queue.current; + if (!track) { + return "🚫 No music is currently playing!"; + } + } else { + // query specified -- search + const result = await node.search({ query }, member.user); + if (!result || result.loadType === "error" || result.loadType === "empty") { + return "Failed to find any tracks for the query."; + } + track = result.tracks[0]; + } + + // lavalyrics try { + const lyrics = await node.request(`/lyrics?track=${encodeURIComponent(track.encoded)}&skipTrackSource=${true}`) + if (lyrics && 'text' in lyrics) return createLyricsEmbed(lyrics, member, track) + else console.log(`Failed search LavaLyrics track ${track.info.title}`, lyrics) + } catch (e) { + console.error(`Error search LavaLyrics track ${track.info.title}`, e) + } + + // javatimed + if (node.info.plugins.includes('java-timed-lyrics')) { + // java-timed (current) if (!query) { - /** @type { import('lavaclient').Player } */ - const player = client.musicManager.players.resolve(guild.id); - - if (!player?.track) return "🚫 There's no active music player in this server."; - - // Fetch lyrics for the current track - const api = player.node.api; - res = await api.client.execute({ path: `/v4/sessions/${player.api.session.id}/players/${guild.id}/lyrics`, method: 'GET' }) - } else { - /** @type {import('lavaclient').ClusterNode} */ - const [[, node]] = client.musicManager.nodes - const api = node.api - - // Search for lyrics - res = await api.client.execute({ path: `/v4/lyrics/search?query=${encodeURIComponent(query)}&source=genius`, method: 'GET' }) + try { + const lyrics = await getCurrentLyricJavaTimed(node, guild.id) + if (lyrics && 'text' in lyrics) return createLyricsEmbed(lyrics, member, track) + else console.log(`Failed search JavaTimed (current) track ${track.info.title}`, lyrics) + } catch (e) { + console.error(`Error search JavaTimed (current) track ${track.info.title}`, e) + } } - // throws error anyway - if (res.status === 404) return "Lyric not found" - if (!res.ok) throw new Error(`Failed to search lyrics: ${res.status} ${res.statusText}`); - - const lyricData = await res.json() - return createLyricsEmbed(lyricData, member); - } catch (error) { - client.logger.error("Lyric Command Error:", error); - return "An error occurred while fetching the lyrics. Please try again later."; + // java-timed (search) + try { + const lyrics = await searchLyricJavaTimed(node, guild.id) + if (lyrics.lines) { + for (const line of lyrics.lines) { + line.timestamp = line.range.start + line.duration = line.range.end - line.range.start + } + } + if (lyrics && 'text' in lyrics) return createLyricsEmbed(lyrics, member, track) + else console.log(`Failed search JavaTimed (search) track ${track.info.title}`, lyrics) + } catch (e) { + console.error(`Error search JavaTimed (search) track ${track.info.title}`, e) + } } + + return "No lyrics found" } -/** - * @param {LyricResult} lyrics - * @param {import('discord.js').GuildMember} member - * @returns - */ -function createLyricsEmbed(lyrics, member) { - if (!lyrics) { - return "No lyrics found for this song."; - } - - const track = lyrics.track - +function createLyricsEmbed(lyrics, member, track) { const embed = new EmbedBuilder() .setColor(EMBED_COLORS.BOT_EMBED) - .setTitle(`${track.author} - ${track.title}`) - .setThumbnail(track.albumArt[0].url) - .setFooter({ text: `Requested by: ${member.user.displayName} | Source: ${lyrics.source || 'Unknown'}` }); + .setTitle(`${track.info.author} - ${track.info.title}`) + .setThumbnail(track.info.artworkUrl) + .setFooter({ text: `Requested by: ${member.user.displayName} | Source: ${lyrics.provider || lyrics.source || lyrics.sourceName || '-'}` }); - const ltext = lyrics.type === 'text' ? lyrics.text : lyrics.lines.map(v => v.line).join('\n') - embed.setDescription(ltext.length > 4096 ? ltext.slice(0, 4093) + "..." : ltext); + const lines = !lyrics.lines ? lyrics.text ?? 'No lyrics found' : lyrics.lines + .filter(Boolean) + .map(line => line.line) + .join("\n"); + + const truncatedLyrics = lines.length > 4096 ? `${lines.slice(0, 4093)}...` : lines; + + embed.setDescription(truncatedLyrics); return { embeds: [embed] }; } -/** - * @typedef LyricResult - * @type {LyricResultBase & (LyricResultText | LyricResultTimed)} - */ +function getCurrentLyricJavaTimed(node, guildId) { + return node.request(`/sessions/${node.sessionId}/players/${guildId}/lyrics`) +} -/** - * @typedef LyricResultBase - * @type {object} - * @property {LyricResultTrack} track - * @property {string} source - */ - -/** - * @typedef LyricResultText - * @type {object} - * @property {'text'} type - * @property {string} text - */ - -/** - * @typedef LyricResultTimed - * @type {object} - * @property {'time'} type - * @property {LyricResultLinePart[]} lines - */ - -/** - * @typedef LyricResultLinePart - * @type {object} - * @property {string} line - * @property {number} start - * @property {number} end - */ - -/** - * @typedef LyricResultTrack - * @type {object} - * @property {string} title - * @property {string} author - * @property {string | null} album - * @property {LyricResultTrackAlbumArt[]} albumArt - */ - -/** - * @typedef LyricResultTrackAlbumArt - * @type {object} - * @property {string} url - * @property {number} width - * @property {number} height - */ +function searchLyricJavaTimed(node, track, source = "genius") { + return node.request(`/lyrics/search?query=${encodeURIComponent(track.info.title + " " + track.info.author)}&source=${source}`) +} diff --git a/src/commands/music/np.js b/src/commands/music/np.js index 4e2620d..b4fdd16 100644 --- a/src/commands/music/np.js +++ b/src/commands/music/np.js @@ -1,13 +1,13 @@ const { EMBED_COLORS } = require("@root/config"); const { EmbedBuilder } = require("discord.js"); -const { formatTime } = require("@helpers/Utils"); +const { splitBar } = require("string-progressbar"); /** * @type {import("@structures/Command")} */ module.exports = { name: "np", - description: "show's what track is currently being played", + description: "Shows what track is currently being played", category: "MUSIC", botPermissions: ["EmbedLinks"], command: { @@ -18,7 +18,7 @@ module.exports = { enabled: true, }, - async messageRun(message) { + async messageRun(message, args) { const response = nowPlaying(message); await message.safeReply(response); }, @@ -32,35 +32,38 @@ module.exports = { /** * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 */ -function nowPlaying({ client, guildId, member }) { - const player = client.musicManager.players.resolve(guildId); - if (!player || !player.queue.current) return "🚫 No music is being played!"; +function nowPlaying({ client, guildId }) { + const player = client.musicManager.getPlayer(guildId); + if (!player || !player.queue.current) { + return "🚫 No music is being played!"; + } const track = player.queue.current; - const totalLength = track.info.length; - const position = player.position; - const progress = Math.round((position / totalLength) * 15); - const progressBar = `${formatTime(position)} [${"▬".repeat( - progress)}🔘${"▬".repeat(15 - progress)}] ${formatTime(totalLength)}`; + const end = track.info.isStream ? "Live" : client.utils.formatTime(track.info.duration); const embed = new EmbedBuilder() .setColor(EMBED_COLORS.BOT_EMBED) - .setAuthor({ name: "Now playing" }) + .setAuthor({ name: "Now Playing" }) .setDescription(`[${track.info.title}](${track.info.uri})`) .addFields( { name: "Song Duration", - value: `\`${formatTime(track.info.length)}\``, + value: client.utils.formatTime(track.info.duration), inline: true, }, { name: "Requested By", - value: track.requesterId ? track.requesterId : member.user.displayName, + value: track.requester.displayName || "Unknown", inline: true, }, { name: "\u200b", - value: progressBar, + value: + client.utils.formatTime(player.position) + + " [" + + splitBar(track.info.isStream ? player.position : track.info.duration, player.position, 15)[0] + + "] " + + end, inline: false, } ); diff --git a/src/commands/music/pause.js b/src/commands/music/pause.js index 79416a7..3e90987 100644 --- a/src/commands/music/pause.js +++ b/src/commands/music/pause.js @@ -5,7 +5,7 @@ const { musicValidations } = require("@helpers/BotUtils"); */ module.exports = { name: "pause", - description: "pause the music player", + description: "Pause the music player", category: "MUSIC", validations: musicValidations, command: { @@ -16,12 +16,12 @@ module.exports = { }, async messageRun(message, args) { - const response = pause(message); + const response = await pause(message); await message.safeReply(response); }, async interactionRun(interaction) { - const response = pause(interaction); + const response = await pause(interaction); await interaction.followUp(response); }, }; @@ -29,10 +29,17 @@ module.exports = { /** * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 */ -function pause({ client, guildId }) { - const player = client.musicManager.players.resolve(guildId); - if (player.paused) return "The player is already paused."; +async function pause({ client, guildId }) { + const player = client.musicManager.getPlayer(guildId); - player.pause(true); - return "⏸️ Paused the music player."; + if (!player || !player.queue.current) { + return "🚫 No song is currently playing"; + } + + if (player.paused) { + return "The player is already paused"; + } + + player.pause(); + return "⏸️ Paused the music player"; } diff --git a/src/commands/music/play.js b/src/commands/music/play.js index 176b88d..328cef2 100644 --- a/src/commands/music/play.js +++ b/src/commands/music/play.js @@ -1,24 +1,52 @@ const { EmbedBuilder, ApplicationCommandOptionType } = require("discord.js"); -const { formatTime } = require("@helpers/Utils"); -require("@lavaclient/plugin-queue") const { EMBED_COLORS, MUSIC } = require("@root/config"); +const Queue = require("@src/database/schemas/Queue"); /** * @type {import("@structures/Command")} */ module.exports = { name: "play", - description: "play a song", + description: "Play or queue your favorite song!", category: "MUSIC", botPermissions: ["EmbedLinks"], command: { enabled: true, - usage: "", + aliases: ["p"], + usage: "[source:Default/Youtube Music/Soundcloud/Spotify/Deezer] ", minArgsCount: 1, }, slashCommand: { enabled: true, options: [ + { + name: "source", + description: "select the source to search from", + type: ApplicationCommandOptionType.String, + required: true, + choices: [ + { + name: "Default", + value: "ytsearch", + }, + { + name: "Youtube Music", + value: "ytmsearch", + }, + { + name: "Soundcloud", + value: "scsearch", + }, + { + name: "Spotify", + value: "spsearch", + }, + { + name: "Deezer", + value: "dzsearch", + }, + ], + }, { name: "query", description: "song name or url", @@ -29,14 +57,23 @@ module.exports = { }, async messageRun(message, args) { - const query = args.join(" "); - const response = await play(message, query); + let source = MUSIC.DEFAULT_SOURCE; + + // Check if the first argument is a valid source + const validSources = ["ytsearch", "ytmsearch", "scsearch", "spsearch", "dzsearch"]; + if (validSources.includes(args[0].toLowerCase())) { + source = args.shift().toLowerCase(); + } + + const searchQuery = args.join(" "); + const response = await play(message, searchQuery, source); await message.safeReply(response); }, async interactionRun(interaction) { - const query = interaction.options.getString("query"); - const response = await play(interaction, query); + const source = interaction.options.getString("source") || MUSIC.DEFAULT_SOURCE; + const searchQuery = interaction.options.getString("query"); + const response = await play(interaction, searchQuery, source); await interaction.followUp(response); }, }; @@ -45,134 +82,110 @@ module.exports = { * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 * @param {string} query */ -async function play({ member, guild, channel }, query) { +async function play({ member, guild, channel }, searchQuery, source) { if (!member.voice.channel) return "🚫 You need to join a voice channel first"; - let player = guild.client.musicManager.players.resolve(guild.id); - if (player && !guild.members.me.voice.channel) { - player.voice.disconnect(); - await guild.client.musicManager.players.destroy(guild.id); - } + let player = guild.client.musicManager.getPlayer(guild.id); if (player && member.voice.channel !== guild.members.me.voice.channel) { return "🚫 You must be in the same voice channel as mine"; } - let embed = new EmbedBuilder().setColor(EMBED_COLORS.BOT_EMBED); - let tracks; - let description = ""; - let thumbnail; + if (!player) { + player = await guild.client.musicManager.createPlayer({ + guildId: guild.id, + voiceChannelId: member.voice.channel.id, + textChannelId: channel.id, + selfMute: false, + selfDeaf: true, + volume: MUSIC.DEFAULT_VOLUME, + }); + } + + if (!player.connected) await player.connect(); try { - const res = await guild.client.musicManager.api.loadTracks( - /^https?:\/\//.test(query) ? query : `${MUSIC.DEFAULT_SOURCE}:${query}` - ); + const res = await player.search({ query: searchQuery, source: source }, member.user); - let track; + if (!res || res.loadType === "empty") { + return `🚫 No results found for "${searchQuery}"`; + } - switch (res.loadType) { + switch (res?.loadType) { case "error": - guild.client.logger.error("Search Exception", res.data); + guild.client.logger.error("Search Exception", res.exception); return "🚫 There was an error while searching"; - case "empty": - return `No results found matching ${query}`; + case "playlist": { + player.queue.add(res.tracks); - case "playlist": - tracks = res.data.tracks; - description = res.data.info.name; - thumbnail = res.data.pluginInfo.artworkUrl; - break; + await Queue.save(player); + + const playlistEmbed = new EmbedBuilder() + .setAuthor({ name: "Added Playlist to queue" }) + .setThumbnail(res.playlist.thumbnail) + .setColor(EMBED_COLORS.BOT_EMBED) + .setDescription(`[${res.playlist.name}](${res.playlist.uri})`) + .addFields( + { name: "Enqueued", value: `${res.tracks.length} songs`, inline: true }, + { + name: "Playlist duration", + value: + "`" + + guild.client.utils.formatTime(res.tracks.map((t) => t.info.duration).reduce((a, b) => a + b, 0)) + + "`", + inline: true, + } + ) + .setFooter({ text: `Requested By: ${member.user.displayName}` }); + + if (!player.playing && player.queue.tracks.length > 0) { + await player.play({ paused: false }); + } + + return { embeds: [playlistEmbed] }; + } case "track": - track = res.data; - tracks = [track]; - break; + case "search": { + const track = res.tracks[0]; + player.queue.add(track); - case "search": - track = res.data[0]; - tracks = [track]; - break; + await Queue.save(player); + + const trackEmbed = new EmbedBuilder() + .setAuthor({ name: "Added Track to queue" }) + .setColor(EMBED_COLORS.BOT_EMBED) + .setDescription(`[${track.info.title}](${track.info.uri})`) + .setThumbnail(track.info.artworkUrl) + .addFields({ + name: "Song Duration", + value: "`" + guild.client.utils.formatTime(track.info.duration) + "`", + inline: true, + }) + .setFooter({ text: `Requested By: ${track.requester.displayName}` }); + + if (player.queue?.tracks?.length > 1) { + trackEmbed.addFields({ + name: "Position in Queue", + value: player.queue.tracks.length.toString(), + inline: true, + }); + } + + if (!player.playing && player.queue.tracks.length > 0) { + await player.play({ paused: false }); + } + + return { embeds: [trackEmbed] }; + } default: guild.client.logger.debug("Unknown loadType", res); return "🚫 An error occurred while searching for the song"; } - - if (!tracks) guild.client.logger.debug({ query, res }); } catch (error) { - guild.client.logger.error("Search Exception", typeof error === "object" ? JSON.stringify(error) : error); + guild.client.logger.error("Search Exception", JSON.stringify(error)); return "🚫 An error occurred while searching for the song"; } - - if (!tracks) return "🚫 An error occurred while searching for the song"; - - if (tracks.length === 1) { - const track = tracks[0]; - if (!player?.playing && !player?.paused && !player?.queue.tracks.length) { - embed.setAuthor({ name: "Added Track to queue" }); - } else { - const fields = []; - embed - .setAuthor({ name: "Added Track to queue" }) - .setDescription(`[${track.info.title}](${track.info.uri})`) - .setThumbnail(track.info.artworkUrl) - .setFooter({ text: `Requested By: ${member.user.displayName}` }); - - fields.push({ - name: "Song Duration", - value: "`" + formatTime(track.info.length) + "`", - inline: true, - }); - - if (player?.queue?.tracks?.length > 0) { - fields.push({ - name: "Position in Queue", - value: (player.queue.tracks.length + 1).toString(), - inline: true, - }); - } - embed.addFields(fields); - } - } else { - embed - .setAuthor({ name: "Added Playlist to queue" }) - .setThumbnail(thumbnail) - .setDescription(description) - .addFields( - { - name: "Enqueued", - value: `${tracks.length} songs`, - inline: true, - }, - { - name: "Playlist duration", - value: - "`" + - formatTime( - tracks.map((t) => t.info.length).reduce((a, b) => a + b, 0), - ) + - "`", - inline: true, - } - ) - .setFooter({ text: `Requested By: ${member.user.displayName}` }); - } - - // create a player and/or join the member's vc - if (!player?.connected) { - player = guild.client.musicManager.players.create(guild.id); - player.queue.data.channel = channel; - player.voice.connect(member.voice.channel.id, { deafened: true }); - player.setVolume(MUSIC.DEFAULT_VOLUME); - } - - // do queue things - const started = player.playing || player.paused; - player.queue.add(tracks, { requester: member.user.displayName, next: false }); - if (!started) { - await player.queue.start(); - } - - return { embeds: [embed] }; -} \ No newline at end of file +} diff --git a/src/commands/music/queue.js b/src/commands/music/queue.js index 7cefaf0..ca310e1 100644 --- a/src/commands/music/queue.js +++ b/src/commands/music/queue.js @@ -1,6 +1,5 @@ const { EMBED_COLORS } = require("@root/config"); const { EmbedBuilder, ApplicationCommandOptionType } = require("discord.js"); -const { formatTime } = require("@helpers/Utils"); /** * @type {import("@structures/Command")} @@ -12,6 +11,7 @@ module.exports = { botPermissions: ["EmbedLinks"], command: { enabled: true, + aliases: ["q"], usage: "[page]", }, slashCommand: { @@ -44,46 +44,39 @@ module.exports = { * @param {number} pgNo */ async function getQueue({ client, guild }, pgNo) { - const player = client.musicManager.players.resolve(guild.id); - if (!player) return "🚫 There is no music playing in this guild."; - - const queue = player.queue; - const embed = new EmbedBuilder() - .setColor(EMBED_COLORS.BOT_EMBED) - .setAuthor({ name: `Queue for ${guild.name}` }); - - // change for the amount of tracks per page - const multiple = 10; - const page = pgNo || 1; - - const end = page * multiple; - const start = end - multiple; - - const tracks = queue.tracks.slice(start, end); - - if (queue.current) { - const currentTrack = queue.current; - embed.addFields({ - name: "Current", - value: `[${currentTrack.info.title}](${currentTrack.info.uri}) \`[${formatTime(currentTrack.info.length)}]\`` - }); + const player = client.musicManager.getPlayer(guild.id); + if (!player || !player.queue.current) { + return "🚫 No song is currently playing"; } - const queueList = tracks.map((track, index) => { - const title = track.info.title; - const uri = track.info.uri; - const duration = formatTime(track.info.length); - return `${start + index + 1}. [${title}](${uri}) \`[${duration}]\``; - }); + const embed = new EmbedBuilder().setColor(EMBED_COLORS.BOT_EMBED).setAuthor({ name: `Queue for ${guild.name}` }); - if (!queueList.length) { - embed.setDescription(`No tracks in ${page > 1 ? `page ${page}` : "the queue"}.`); - } else { - embed.setDescription(queueList.join("\n")); + const end = (pgNo || 1) * 10; + const start = end - 10; + + const tracks = player.queue.tracks.slice(start, end); + + if (player.queue.current) { + const current = player.queue.current; + embed + .addFields({ + name: "Current", + value: `[${current.info.title}](${current.info.uri}) \`[${client.utils.formatTime(current.info.duration)}]\``, + }) + .setThumbnail(current.info.artworkUrl); } - const maxPages = Math.ceil(queue.tracks.length / multiple); - embed.setFooter({ text: `Page ${page > maxPages ? maxPages : page} of ${maxPages}` }); + const queueList = tracks.map( + (track, index) => + `${start + index + 1}. [${track.info.title}](${track.info.uri}) \`[${client.utils.formatTime(track.info.duration)}]\`` + ); + + embed.setDescription( + queueList.length ? queueList.join("\n") : `No tracks in ${pgNo > 1 ? `page ${pgNo}` : "the queue"}.` + ); + + const maxPages = Math.ceil(player.queue.tracks.length / 10); + embed.setFooter({ text: `Page ${pgNo > maxPages ? maxPages : pgNo} of ${maxPages}` }); return { embeds: [embed] }; } diff --git a/src/commands/music/resume.js b/src/commands/music/resume.js index 702ec82..8c9a513 100644 --- a/src/commands/music/resume.js +++ b/src/commands/music/resume.js @@ -5,7 +5,7 @@ const { musicValidations } = require("@helpers/BotUtils"); */ module.exports = { name: "resume", - description: "resumes the music player", + description: "Resumes the music player", category: "MUSIC", validations: musicValidations, command: { @@ -16,12 +16,12 @@ module.exports = { }, async messageRun(message, args) { - const response = resumePlayer(message); + const response = await resumePlayer(message); await message.safeReply(response); }, async interactionRun(interaction) { - const response = resumePlayer(interaction); + const response = await resumePlayer(interaction); await interaction.followUp(response); }, }; @@ -29,9 +29,15 @@ module.exports = { /** * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 */ -function resumePlayer({ client, guildId }) { - const player = client.musicManager.players.resolve(guildId); +async function resumePlayer({ client, guildId }) { + const player = client.musicManager.getPlayer(guildId); + + if (!player || !player.queue.current) { + return "🚫 No song is currently playing"; + } + if (!player.paused) return "The player is already resumed"; + player.resume(); return "▶️ Resumed the music player"; } diff --git a/src/commands/music/search.js b/src/commands/music/search.js index 47d8f8a..fa527eb 100644 --- a/src/commands/music/search.js +++ b/src/commands/music/search.js @@ -5,8 +5,8 @@ const { ApplicationCommandOptionType, ComponentType, } = require("discord.js"); -const { formatTime } = require("@helpers/Utils"); const { EMBED_COLORS, MUSIC } = require("@root/config"); +const Queue = require("@src/database/schemas/Queue"); /** * @type {import("@structures/Command")} @@ -18,12 +18,41 @@ module.exports = { botPermissions: ["EmbedLinks"], command: { enabled: true, - usage: "", + aliases: ["sc"], + usage: "[source:Default/Youtube Muisc/SoundCloud/Spotify/Deezer] ", minArgsCount: 1, }, slashCommand: { enabled: true, options: [ + { + name: "source", + description: "select the source to search from", + type: ApplicationCommandOptionType.String, + required: true, + choices: [ + { + name: "Default", + value: "ytsearch", + }, + { + name: "Youtube Music", + value: "ytmsearch", + }, + { + name: "Soundcloud", + value: "scsearch", + }, + { + name: "Spotify", + value: "spsearch", + }, + { + name: "Deezer", + value: "dzsearch", + }, + ], + }, { name: "query", description: "song to search", @@ -34,185 +63,127 @@ module.exports = { }, async messageRun(message, args) { + let source = MUSIC.DEFAULT_SOURCE; + + // Check if the first argument is a valid source + const validSources = ["ytsearch", "ytmsearch", "scsearch", "spsearch", "dzsearch"]; + if (validSources.includes(args[0].toLowerCase())) { + source = args.shift().toLowerCase(); + } + const query = args.join(" "); - const response = await search(message, query); + const response = await search(message, query, source); if (response) await message.safeReply(response); }, async interactionRun(interaction) { + const source = interaction.options.getString("source") || MUSIC.DEFAULT_SOURCE; const query = interaction.options.getString("query"); - const response = await search(interaction, query); + const response = await search(interaction, query, source); if (response) await interaction.followUp(response); else interaction.deleteReply(); }, }; /** - * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 + * @param {import("discord.js").CommandInteraction|import("discord.js").Message} interaction * @param {string} query + * @param {string} source */ -async function search({ member, guild, channel }, query) { +async function search({ member, guild, channel }, query, source) { if (!member.voice.channel) return "🚫 You need to join a voice channel first"; - let player = guild.client.musicManager.players.resolve(guild.id); - if (player && !guild.members.me.voice.channel) { - player.voice.disconnect(); - await guild.client.musicManager.players.destroy(guild.id); - } + let player = guild.client.musicManager.getPlayer(guild.id); + if (player && member.voice.channel !== guild.members.me.voice.channel) { - return "🚫 You must be in the same voice channel as mine"; + return "🚫 You must be in the same voice channel as me."; } - let res; + if (!player) { + player = await guild.client.musicManager.createPlayer({ + guildId: guild.id, + voiceChannelId: member.voice.channel.id, + textChannelId: channel.id, + selfMute: false, + selfDeaf: true, + volume: MUSIC.DEFAULT_VOLUME, + }); + } + + if (!player.connected) await player.connect(); + + const res = await player.search({ query, source }, member.user); + + if (!res || !res.tracks?.length) { + return { + embeds: [new EmbedBuilder().setColor(EMBED_COLORS.ERROR).setDescription(`No results found for \`${query}\``)], + }; + } + + let maxResults = MUSIC.MAX_SEARCH_RESULTS; + if (res.tracks.length < maxResults) maxResults = res.tracks.length; + + const results = res.tracks.slice(0, maxResults); + const options = results.map((track, index) => ({ + label: track.info.title.slice(0, 100), + value: index.toString(), + })); + + const menuRow = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId("search-results") + .setPlaceholder("Choose Search Results") + .setMaxValues(1) + .addOptions(options) + ); + + const searchEmbed = new EmbedBuilder() + .setColor(EMBED_COLORS.BOT_EMBED) + .setAuthor({ name: "Search Results" }) + .setDescription(`Select the song you wish to add to the queue`); + + const searchMessage = await channel.send({ + embeds: [searchEmbed], + components: [menuRow], + }); + try { - res = await guild.client.musicManager.api.loadTracks( - /^https?:\/\//.test(query) ? query : `${MUSIC.DEFAULT_SOURCE}:${query}` - ); - } catch (err) { - return "🚫 There was an error while searching"; - } + const response = await channel.awaitMessageComponent({ + filter: (i) => i.user.id === member.id && i.message.id === searchMessage.id, + componentType: ComponentType.StringSelect, + idle: 30 * 1000, + }); - let embed = new EmbedBuilder().setColor(EMBED_COLORS.BOT_EMBED); - let tracks; + if (response.customId !== "search-results") return; - switch (res.loadType) { - case "error": - guild.client.logger.error("Search Exception", res.data); - return "🚫 There was an error while searching"; + await searchMessage.delete(); + if (!response) return "🚫 You took too long to select the songs"; - case "empty": - return `No results found matching ${query}`; + const selectedTrack = results[response.values[0]]; + player.queue.add(selectedTrack); - case "track": { - const [track] = res.data[0]; - tracks = [track]; - if (!player?.playing && !player?.paused && !player?.queue.tracks.length) { - embed.setAuthor({ name: "Added Song to queue" }); - break; - } + // Save the current state to the database + await Queue.save(player); - const fields = []; - embed - .setAuthor({ name: "Added Song to queue" }) - .setDescription(`[${track.info.title}](${track.info.uri})`) - .setFooter({ text: `Requested By: ${member.user.displayName}` }); - - fields.push({ + const trackEmbed = new EmbedBuilder() + .setAuthor({ name: "Added Track to queue" }) + .setDescription(`[${selectedTrack.info.title}](${selectedTrack.info.uri})`) + .setThumbnail(selectedTrack.info.artworkUrl) + .addFields({ name: "Song Duration", - value: "`" + formatTime(track.info.length) + "`", + value: "`" + guild.client.utils.formatTime(selectedTrack.info.duration) + "`", inline: true, - }); + }) + .setFooter({ text: `Requested By: ${member.user.displayName}` }); - if (player?.queue?.tracks?.length > 0) { - fields.push({ - name: "Position in Queue", - value: (player.queue.tracks.length + 1).toString(), - inline: true, - }); - } - embed.addFields(fields); - break; + if (!player.playing && player.queue.tracks.length > 0) { + await player.play({ paused: false }); } - case "playlist": - tracks = res.data.tracks; - embed - .setAuthor({ name: "Added Playlist to queue" }) - .setDescription(res.data.info.name) - .addFields( - { - name: "Enqueued", - value: `${res.data.tracks.length} songs`, - inline: true, - }, - { - name: "Playlist duration", - value: - "`" + - formatTime( - res.data.tracks.map((t) => t.info.length).reduce((a, b) => a + b, 0), - ) + - "`", - inline: true, - } - ) - .setFooter({ text: `Requested By: ${member.user.displayName}` }); - break; - - case "search": { - let max = guild.client.config.MUSIC.MAX_SEARCH_RESULTS; - if (res.data.length < max) max = res.data.length; - - const results = res.data.slice(0, max); - const options = results.map((result, index) => ({ - label: result.info.title, - value: index.toString(), - })); - - const menuRow = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId("search-results") - .setPlaceholder("Choose Search Results") - .setMaxValues(max) - .addOptions(options) - ); - - const tempEmbed = new EmbedBuilder() - .setColor(EMBED_COLORS.BOT_EMBED) - .setAuthor({ name: "Search Results" }) - .setDescription(`Please select the songs you wish to add to queue`); - - const sentMsg = await channel.send({ - embeds: [tempEmbed], - components: [menuRow], - }); - - try { - const response = await channel.awaitMessageComponent({ - filter: (reactor) => reactor.message.id === sentMsg.id && reactor.user.id === member.id, - idle: 30 * 1000, - componentType: ComponentType.StringSelect, - }); - - await sentMsg.delete(); - if (!response) return "🚫 You took too long to select the songs"; - - if (response.customId !== "search-results") return; - const toAdd = []; - response.values.forEach((v) => toAdd.push(results[v])); - - // Only 1 song is selected - if (toAdd.length === 1) { - tracks = [toAdd[0]]; - embed.setAuthor({ name: "Added Song to queue" }); - } else { - tracks = toAdd; - embed - .setDescription(`🎶 Added ${toAdd.length} songs to queue`) - .setFooter({ text: `Requested By: ${member.user.displayName}` }); - } - } catch (err) { - console.log(err); - await sentMsg.delete(); - return "🚫 Failed to register your response"; - } - break; - } + return { embeds: [trackEmbed] }; + } catch (err) { + console.error("Error handling response:", err); + await searchMessage.delete(); + return "🚫 Failed to register your response"; } - - // create a player and/or join the member's vc - if (!player?.connected) { - player = guild.client.musicManager.players.create(guild.id); - player.queue.data.channel = channel; - player.voice.connect(member.voice.channel.id, { deafened: true }); - } - - // do queue things - const started = player.playing || player.paused; - player.queue.add(tracks, { requester: member.user.username, next: false }); - if (!started) { - await player.queue.start(); - } - - return { embeds: [embed] }; } diff --git a/src/commands/music/seek.js b/src/commands/music/seek.js index 7d23bdc..5947df0 100644 --- a/src/commands/music/seek.js +++ b/src/commands/music/seek.js @@ -1,6 +1,4 @@ const { musicValidations } = require("@helpers/BotUtils"); -const prettyMs = require("pretty-ms"); -const { durationToMillis } = require("@helpers/Utils"); const { ApplicationCommandOptionType } = require("discord.js"); /** @@ -8,7 +6,7 @@ const { ApplicationCommandOptionType } = require("discord.js"); */ module.exports = { name: "seek", - description: "sets the playing track's position to the specified position", + description: "Sets the position of the current track", category: "MUSIC", validations: musicValidations, command: { @@ -20,7 +18,7 @@ module.exports = { options: [ { name: "time", - description: "The time you want to seek to.", + description: "The time you want to seek to", type: ApplicationCommandOptionType.String, required: true, }, @@ -28,14 +26,20 @@ module.exports = { }, async messageRun(message, args) { - const time = args.join(" "); - const response = seekTo(message, time); + const time = message.client.utils.parseTime(args.join(" ")); + if (!time) { + return await message.safeReply("Invalid time format. Use 10s, 1m 50s, 1h"); + } + const response = await seekTo(message, time); await message.safeReply(response); }, async interactionRun(interaction) { - const time = interaction.options.getString("time"); - const response = seekTo(interaction, time); + const time = interaction.client.utils.parseTime(interaction.options.getString("time")); + if (!time) { + return await interaction.followUp("Invalid time format. Use 10s, 1m 50s, 1h"); + } + const response = await seekTo(interaction, time); await interaction.followUp(response); }, }; @@ -44,14 +48,17 @@ module.exports = { * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 * @param {number} time */ -function seekTo({ client, guildId }, time) { - const player = client.musicManager?.players.resolve(guildId); - const seekTo = durationToMillis(time); +async function seekTo({ client, guildId }, time) { + const player = client.musicManager.getPlayer(guildId); - if (seekTo > player.queue.current.info.length) { - return "The duration you provide exceeds the duration of the current track"; + if (!player || !player.queue.current) { + return "🚫 There's no music currently playing"; } - player.seek(seekTo); - return `Seeked to ${prettyMs(seekTo, { colonNotation: true, secondsDecimalDigits: 0 })}`; + if (time > player.queue.current.length) { + return "The duration you provided exceeds the duration of the current track"; + } + + player.seek(time); + return `Seeked song duration to **${client.utils.formatTime(time)}**`; } diff --git a/src/commands/music/shuffle.js b/src/commands/music/shuffle.js index 02c7b7c..dfa8a8a 100644 --- a/src/commands/music/shuffle.js +++ b/src/commands/music/shuffle.js @@ -30,7 +30,16 @@ module.exports = { * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 */ function shuffle({ client, guildId }) { - const player = client.musicManager.players.resolve(guildId); + const player = client.musicManager.getPlayer(guildId); + + if (!player || !player.queue.current) { + return "🚫 There's no music currently playing"; + } + + if (player.queue.tracks.length < 2) { + return "🚫 Not enough tracks to shuffle"; + } + player.queue.shuffle(); return "🎶 Queue has been shuffled"; } diff --git a/src/commands/music/skip.js b/src/commands/music/skip.js index 8bb0640..c4c1396 100644 --- a/src/commands/music/skip.js +++ b/src/commands/music/skip.js @@ -5,12 +5,12 @@ const { musicValidations } = require("@helpers/BotUtils"); */ module.exports = { name: "skip", - description: "skip the current song", + description: "Skip the current song", category: "MUSIC", validations: musicValidations, command: { enabled: true, - aliases: ["next"], + aliases: ["next", "s"], }, slashCommand: { enabled: true, @@ -31,20 +31,18 @@ module.exports = { * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 */ async function skip({ client, guildId }) { - const player = client.musicManager.players.resolve(guildId); + const player = client.musicManager.getPlayer(guildId); if (!player || !player.queue.current) { - return "There is no song currently being played."; + return "🚫 There's no music currently playing"; } - + const title = player.queue.current.info.title; - // Check if there is a next song in the queue - if (player.queue.tracks.length === 0) { - return "There is no next song to skip to."; -} + if (player.queue.tracks.length === 0) { + return "There is no next song to skip to"; + } -// Skip to the next song -player.queue.next(); -return `⏯️ ${title} was skipped successfully.`; -} \ No newline at end of file + await player.skip(); + return `⏯️ ${title} was skipped`; +} diff --git a/src/commands/music/stop.js b/src/commands/music/stop.js index 679430c..509626a 100644 --- a/src/commands/music/stop.js +++ b/src/commands/music/stop.js @@ -5,24 +5,24 @@ const { musicValidations } = require("@helpers/BotUtils"); */ module.exports = { name: "stop", - description: "stop the music player", + description: "Stop the music player", category: "MUSIC", validations: musicValidations, command: { enabled: true, - aliases: ["leave"], + aliases: [""], }, slashCommand: { enabled: true, }, - async messageRun(message, args) { - const response = await stop(message); + async messageRun(message, args, data) { + const response = await stop(message, data.settings); await message.safeReply(response); }, - async interactionRun(interaction) { - const response = await stop(interaction); + async interactionRun(interaction, data) { + const response = await stop(interaction, data.settings); await interaction.followUp(response); }, }; @@ -31,8 +31,17 @@ module.exports = { * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 */ async function stop({ client, guildId }) { - const player = client.musicManager.players.resolve(guildId); - player.voice.disconnect(); - await client.musicManager.players.destroy(guildId); - return "🎶 The music player is stopped and queue has been cleared"; + const player = client.musicManager.getPlayer(guildId); + + if (!player || !player.queue.current) { + return "🚫 There's no music currently playing"; + } + + if (player.get("autoplay") === true) { + player.set("autoplay", false); + } + + player.stopPlaying(true, false); + + return "🎶 The music player is stopped, and the queue has been cleared"; } diff --git a/src/commands/music/volume.js b/src/commands/music/volume.js index f383443..a062b42 100644 --- a/src/commands/music/volume.js +++ b/src/commands/music/volume.js @@ -11,14 +11,15 @@ module.exports = { validations: musicValidations, command: { enabled: true, - usage: "<1-100>", + aliases: ["vol"], + usage: "<0-100>", }, slashCommand: { enabled: true, options: [ { name: "amount", - description: "Enter a value to set [1 to 100]", + description: "Enter a value to set [0 to 100]", type: ApplicationCommandOptionType.Integer, required: false, }, @@ -42,15 +43,19 @@ module.exports = { * @param {import("discord.js").CommandInteraction|import("discord.js").Message} arg0 */ async function getVolume({ client, guildId }, amount) { - const player = client.musicManager.players.resolve(guildId); + const player = client.musicManager.getPlayer(guildId); - if (!amount) return `> The player volume is \`${player.volume}\`.`; + if (!player || !player.queue.current) { + return "🚫 There's no music currently playing"; + } + + if (!amount) return `> The player volume is \`${player.volume}\``; if (isNaN(amount) || amount < 0 || amount > 100) { - return "You need to give me a volume between 1 and 100."; + return "You need to give me a volume between 0 and 100"; } // Set the player volume await player.setVolume(amount); - return `🎶 Music player volume is set to \`${amount}\`.`; + return `🎶 Music player volume is set to \`${amount}\``; } diff --git a/src/commands/owner/eval.js b/src/commands/owner/eval.js index 47b8c0f..99fac35 100644 --- a/src/commands/owner/eval.js +++ b/src/commands/owner/eval.js @@ -1,9 +1,13 @@ -const { EmbedBuilder, ApplicationCommandOptionType } = require("discord.js"); +const { EmbedBuilder, ApplicationCommandOptionType, AttachmentBuilder } = require("discord.js"); const { EMBED_COLORS } = require("@root/config"); // This dummy token will be replaced by the actual token const DUMMY_TOKEN = "MY_TOKEN_IS_SECRET"; +const AsyncFunctionConstructor = (async() => {}).constructor +let prev = null +let store = Object.create(null) + /** * @type {import("@structures/Command")} */ @@ -13,71 +17,113 @@ module.exports = { category: "OWNER", botPermissions: ["EmbedLinks"], command: { - enabled: true, + enabled: process.env.DEV === 'true', usage: "