mirror of
https://github.com/MinazukiAmane/Tinasha-Bot.git
synced 2025-03-15 07:35:57 +08:00
Merge branch 'Development' into dependabot/npm_and_yarn/multi-fb6576e9f1
This commit is contained in:
commit
67b237fd88
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
node_modules
|
||||
docs
|
||||
.env.asli
|
||||
config.js
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
@ -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
|
||||
},
|
||||
],
|
||||
},
|
@ -12,5 +12,6 @@
|
||||
"@structures/*": ["./src/structures/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "**/node_modules/*"]
|
||||
}
|
198
package-lock.json
generated
198
package-lock.json
generated
@ -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"
|
||||
},
|
||||
|
10
package.json
10
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",
|
||||
|
@ -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`;
|
||||
|
51
src/commands/music/autoplay.js
Normal file
51
src/commands/music/autoplay.js
Normal file
@ -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!";
|
||||
}
|
@ -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: "<none|low|medium|high>",
|
||||
},
|
||||
@ -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}\``;
|
||||
}
|
||||
|
61
src/commands/music/join.js
Normal file
61
src/commands/music/join.js
Normal file
@ -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}.`
|
||||
}
|
||||
}
|
41
src/commands/music/leave.js
Normal file
41
src/commands/music/leave.js
Normal file
@ -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";
|
||||
}
|
@ -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: "<queue|track>",
|
||||
usage: "<queue|track|off>",
|
||||
},
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
@ -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}`)
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
);
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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: "<song-name>",
|
||||
aliases: ["p"],
|
||||
usage: "[source:Default/Youtube Music/Soundcloud/Spotify/Deezer] <song-name>",
|
||||
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] };
|
||||
}
|
||||
}
|
||||
|
@ -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] };
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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: "<song-name>",
|
||||
aliases: ["sc"],
|
||||
usage: "[source:Default/Youtube Muisc/SoundCloud/Spotify/Deezer] <song-name>",
|
||||
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] };
|
||||
}
|
||||
|
@ -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)}**`;
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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.`;
|
||||
}
|
||||
await player.skip();
|
||||
return `⏯️ ${title} was skipped`;
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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}\``;
|
||||
}
|
||||
|
@ -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: "<script>",
|
||||
minArgsCount: 1,
|
||||
},
|
||||
slashCommand: {
|
||||
enabled: false,
|
||||
enabled: process.env.DEV === 'true',
|
||||
options: [
|
||||
{
|
||||
name: "expression",
|
||||
description: "content to evaluate",
|
||||
description: "Code to evaluate",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "attachment",
|
||||
description: "Code to evaluate",
|
||||
type: ApplicationCommandOptionType.Attachment,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
name: "async",
|
||||
description: "Use async - requires return to retreive value",
|
||||
type: ApplicationCommandOptionType.Boolean,
|
||||
required: false
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
async messageRun(message, args) {
|
||||
const input = args.join(" ");
|
||||
async messageRun(message, args, data) {
|
||||
let content = message.content.slice(data.prefix.length + data.invoke.length).trim()
|
||||
|
||||
if (!input) return message.safeReply("Please provide code to eval");
|
||||
|
||||
let response;
|
||||
try {
|
||||
const output = eval(input);
|
||||
response = buildSuccessResponse(output, message.client);
|
||||
} catch (ex) {
|
||||
response = buildErrorResponse(ex);
|
||||
if (content.startsWith('```')) {
|
||||
content = content.replace(/^```(js)?/, '')
|
||||
if (content.endsWith('```')) content = content.slice(0, -3)
|
||||
}
|
||||
await message.safeReply(response);
|
||||
else if (content.startsWith('``')) {
|
||||
content = content.slice(2, -2)
|
||||
}
|
||||
|
||||
const attachment = message.attachments.at(0)
|
||||
|
||||
await message.reply(await execute(false, attachment, content, { message, args, data }, message.client));
|
||||
},
|
||||
|
||||
async interactionRun(interaction) {
|
||||
const input = interaction.options.getString("expression");
|
||||
async interactionRun(interaction, data) {
|
||||
const attachment = interaction.options.getAttachment("attachment")
|
||||
const code = interaction.options.getString("expression")
|
||||
const useAsync = interaction.options.getBoolean("async") ?? false
|
||||
|
||||
let response;
|
||||
try {
|
||||
const output = eval(input);
|
||||
response = buildSuccessResponse(output, interaction.client);
|
||||
} catch (ex) {
|
||||
response = buildErrorResponse(ex);
|
||||
}
|
||||
await interaction.followUp(response);
|
||||
await interaction.followUp(await execute(useAsync, attachment, code, { interaction, data }, interaction.client));
|
||||
},
|
||||
};
|
||||
|
||||
const buildSuccessResponse = (output, client) => {
|
||||
async function execute(useAsync, attachment, code, context, client) {
|
||||
if (!code) {
|
||||
if (!attachment) return "Specify code or attachment to run"
|
||||
if (attachment.contentType !== 'text/javascript') return "Attachment type must be JavaScript"
|
||||
|
||||
const res = await fetch(attachment.url)
|
||||
if (!res.ok && !code) return "Failed to retreive attachment"
|
||||
code = await res.text()
|
||||
}
|
||||
|
||||
Object.assign(context, {
|
||||
client,
|
||||
store: store,
|
||||
_: prev
|
||||
})
|
||||
|
||||
try {
|
||||
const exec = useAsync
|
||||
? new AsyncFunctionConstructor('ctx', `with (ctx) {${code}}; return "return not set"`)
|
||||
: new Function('ctx', `with (ctx) return eval(${JSON.stringify(code)})`)
|
||||
const output = await exec.call(client, context)
|
||||
|
||||
prev = output
|
||||
return buildResponse(output, client);
|
||||
} catch (ex) {
|
||||
return buildResponse(ex, client, true);
|
||||
}
|
||||
}
|
||||
|
||||
const buildResponse = (output, client, isError) => {
|
||||
// Token protection
|
||||
output = require("util").inspect(output, { depth: 0 }).replaceAll(client.token, DUMMY_TOKEN);
|
||||
output = require("util").inspect(output, {
|
||||
maxArrayLength: 5,
|
||||
maxStringLength: 500,
|
||||
depth: 3
|
||||
}).replaceAll(client.token, DUMMY_TOKEN);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setAuthor({ name: "📤 Output" })
|
||||
.setDescription("```js\n" + (output.length > 4096 ? `${output.substr(0, 4000)}...` : output) + "\n```")
|
||||
.setColor("Random")
|
||||
.setTimestamp(Date.now());
|
||||
// use file
|
||||
if (output.length > 200 || (output.match(/\n/g)?.length ?? 0) > 10) {
|
||||
const file = new AttachmentBuilder(Buffer.from(output), { name: 'output.js' })
|
||||
|
||||
return { embeds: [embed] };
|
||||
};
|
||||
|
||||
const buildErrorResponse = (err) => {
|
||||
const embed = new EmbedBuilder();
|
||||
embed
|
||||
.setAuthor({ name: "📤 Error" })
|
||||
.setDescription("```js\n" + (err.length > 4096 ? `${err.substr(0, 4000)}...` : err) + "\n```")
|
||||
.setColor(EMBED_COLORS.ERROR)
|
||||
.setTimestamp(Date.now());
|
||||
|
||||
return { embeds: [embed] };
|
||||
return {
|
||||
content: isError ? 'Error!' : 'Output:',
|
||||
files: [file]
|
||||
}
|
||||
}
|
||||
// use embed
|
||||
else {
|
||||
const embed = new EmbedBuilder()
|
||||
.setAuthor({ name: isError ? "📤 Error" : "📤 Output" })
|
||||
.setDescription("```js\n" + output + "\n```")
|
||||
.setColor(isError ? EMBED_COLORS.ERROR : EMBED_COLORS.SUCCESS)
|
||||
.setTimestamp(Date.now());
|
||||
|
||||
return { embeds: [embed] }
|
||||
}
|
||||
};
|
||||
|
120
src/commands/owner/reloadcmd.js
Normal file
120
src/commands/owner/reloadcmd.js
Normal file
@ -0,0 +1,120 @@
|
||||
const path = require('path')
|
||||
const { ApplicationCommandOptionType, ApplicationCommandType } = require('discord.js')
|
||||
|
||||
/**
|
||||
* @type {import("@structures/Command")}
|
||||
*/
|
||||
module.exports = {
|
||||
name: "reloadcmd",
|
||||
description: "Reloads commands",
|
||||
category: "OWNER",
|
||||
command: {
|
||||
enabled: process.env.DEV === 'true',
|
||||
usage: "<filename> [guild]",
|
||||
minArgsCount: 1,
|
||||
},
|
||||
slashCommand: {
|
||||
enabled: process.env.DEV === 'true',
|
||||
options: [
|
||||
{
|
||||
name: "filename",
|
||||
description: "File name to load. Reloads all if not specified",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
}, {
|
||||
name: "guild",
|
||||
description: "guild",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: false,
|
||||
}, {
|
||||
name: "reregister",
|
||||
description: "Reregisters command to manager",
|
||||
type: ApplicationCommandOptionType.Boolean,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
async messageRun(message, args) {
|
||||
await message.safeReply(await runReload(message.client, args[0], args[1]))
|
||||
},
|
||||
|
||||
async interactionRun(interaction) {
|
||||
await interaction.followUp(await runReload(
|
||||
interaction.client,
|
||||
interaction.options.getString("filename"),
|
||||
interaction.options.getString("guild"),
|
||||
interaction.options.getBoolean("reregister"),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
async function runReload(client, filename, guildid, rereg = false) {
|
||||
// guild id
|
||||
let guild
|
||||
if (guildid) {
|
||||
guild = client.guilds.cache.get(guildid);
|
||||
if (!guild) return "No matching guild"
|
||||
}
|
||||
|
||||
// load
|
||||
if (filename[0] === '/') {
|
||||
const cmdname = filename.slice(1)
|
||||
const cmd = client.commands.get(cmdname)
|
||||
if (!cmd) return "Cannot find command with name " + cmd
|
||||
if (!cmd.category) return `Command category ${cmd} is undefined`
|
||||
|
||||
filename = cmd.category.toLowerCase() + '/' + cmdname
|
||||
}
|
||||
|
||||
if (!path.extname(filename)) filename += '.js'
|
||||
const target = path.resolve('src', 'commands', path.normalize(filename))
|
||||
const oldCmd = require.cache[target]?.exports
|
||||
const oldSlashEnable = oldCmd?.slashCommand?.enabled
|
||||
delete require.cache[target]
|
||||
const newCmd = require(target)
|
||||
const newSlashEnable = newCmd?.slashCommand?.enabled
|
||||
|
||||
if (rereg) {
|
||||
// register / edit
|
||||
const cmdManager = guild ? guild.commands : client.application.commands
|
||||
const slashCmd = {
|
||||
name: newCmd.name,
|
||||
description: newCmd.description,
|
||||
type: ApplicationCommandType.ChatInput,
|
||||
options: newCmd.slashCommand?.options
|
||||
}
|
||||
|
||||
// slash commands
|
||||
if (oldSlashEnable) {
|
||||
const oldSlash = client.application.commands.cache.find(v => v.name === oldCmd.name)
|
||||
if (oldSlash) {
|
||||
if (newSlashEnable) await cmdManager.edit(oldSlash, slashCmd)
|
||||
else await cmdManager.delete(oldSlash)
|
||||
}
|
||||
}
|
||||
else if (newSlashEnable) {
|
||||
await cmdManager.create(slashCmd)
|
||||
}
|
||||
}
|
||||
|
||||
// remove old
|
||||
if (oldCmd?.command?.enabled) {
|
||||
client.commands.delete(oldCmd.name.toLowerCase())
|
||||
for (const alias of oldCmd.command.aliases ?? []) client.commandAlias.delete(alias)
|
||||
}
|
||||
if (oldSlashEnable) {
|
||||
client.slashCommands.delete(oldCmd.name);
|
||||
}
|
||||
|
||||
// add new
|
||||
if (newCmd?.command?.enabled) {
|
||||
client.commands.set(newCmd.name.toLowerCase(), newCmd)
|
||||
for (const alias of newCmd.command.aliases ?? []) client.commandAlias.set(alias, newCmd)
|
||||
}
|
||||
if (newSlashEnable) {
|
||||
client.slashCommands.set(newCmd.name, newCmd);
|
||||
}
|
||||
|
||||
return `Reloaded ${filename}`
|
||||
}
|
@ -310,7 +310,7 @@ function getMsgCategoryEmbeds(client, category, prefix) {
|
||||
// For REMAINING Categories
|
||||
const commands = client.commands.filter((cmd) => cmd.category === category);
|
||||
|
||||
if (commands.length === 0) {
|
||||
if (commands.size === 0) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(EMBED_COLORS.BOT_EMBED)
|
||||
.setThumbnail(CommandCategory[category]?.image)
|
||||
@ -323,8 +323,9 @@ function getMsgCategoryEmbeds(client, category, prefix) {
|
||||
const arrSplitted = [];
|
||||
const arrEmbeds = [];
|
||||
|
||||
while (commands.length) {
|
||||
let toAdd = commands.splice(0, commands.length > CMDS_PER_PAGE ? CMDS_PER_PAGE : commands.length);
|
||||
const cmdArr = Array.from(commands.values())
|
||||
while (cmdArr.length) {
|
||||
let toAdd = cmdArr.splice(0, cmdArr.size > CMDS_PER_PAGE ? CMDS_PER_PAGE : cmdArr.size);
|
||||
toAdd = toAdd.map((cmd) => `\`${prefix}${cmd.name}\`\n ❯ ${cmd.description}\n`);
|
||||
arrSplitted.push(toAdd);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ const { byteUnit } = require('@root/src/helpers/Units');
|
||||
|
||||
module.exports = {
|
||||
name: "nodes",
|
||||
description: "Lists lavalink nodes",
|
||||
description: "Lists Audio nodes",
|
||||
category: "UTILITY",
|
||||
command: {
|
||||
enabled: true,
|
||||
@ -41,13 +41,13 @@ async function fetchNodesAndTurnItToTable(nodes) {
|
||||
const res = await fetchNodeStat(node).then(HttpUtil.assertStatusOk)
|
||||
stat = await res.json()
|
||||
} catch(e) {
|
||||
error(node.identifier, e)
|
||||
error(node.id, e)
|
||||
}
|
||||
|
||||
if (stat) {
|
||||
const { uptime, memory: { free, reservable }, cpu: { lavalinkLoad }, playingPlayers, players } = stat
|
||||
return {
|
||||
name: `:green_circle: ${node.identifier}`,
|
||||
name: `:green_circle: ${node.id}`,
|
||||
value: [
|
||||
`❯ **Uptime**: ${Utils.formatTime(uptime)}`,
|
||||
`❯ **Memory**: ${byteUnit(free)} (${(free / reservable * 100).toPrecision(3)}%) of ${byteUnit(reservable)}`,
|
||||
@ -57,27 +57,27 @@ async function fetchNodesAndTurnItToTable(nodes) {
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
name: `:red_circle: ${node.identifier}`,
|
||||
name: `:red_circle: ${node.id}`,
|
||||
value: 'Node is offline'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
return new EmbedBuilder({
|
||||
title: 'Lavalink Nodes',
|
||||
title: 'Audio Nodes',
|
||||
color: 0x0088ff,
|
||||
fields: fields
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchNodeStat(node, connTimeout = 5000) {
|
||||
const baseUrl = (node.info.port !== 80 && node.info.secure)
|
||||
? `https://${node.info.host}:${node.info.port}`
|
||||
: `http://${node.info.host}:${node.info.port}`;
|
||||
const baseUrl = (node.port !== 80 && node.secure)
|
||||
? `https://${node.host}:${node.port}`
|
||||
: `http://${node.host}:${node.port}`;
|
||||
|
||||
return HttpUtil.fetchWithConnTimeout(
|
||||
`${baseUrl}/v4/stats`,
|
||||
{ headers: { Authorization: node.info.auth } },
|
||||
{ headers: { Authorization: node.authorization } },
|
||||
connTimeout
|
||||
);
|
||||
}
|
||||
|
@ -1,123 +0,0 @@
|
||||
const { EmbedBuilder, ApplicationCommandOptionType } = require("discord.js");
|
||||
const { MESSAGES, EMBED_COLORS } = require("@root/config");
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const subscriptions = new Map(); // Store subscriptions in memory (consider using the mongodb or maybe gonna use redis)
|
||||
|
||||
/**
|
||||
* @type {import("@structures/Command")}
|
||||
*/
|
||||
module.exports = {
|
||||
name: "subscribe",
|
||||
description: "Subscribe to YouTube or TikTok notifications.",
|
||||
category: "UTILITY",
|
||||
botPermissions: ["SendMessages"],
|
||||
command: {
|
||||
enabled: true,
|
||||
usage: "<platform> <channel/user_id>",
|
||||
},
|
||||
slashCommand: {
|
||||
enabled: true,
|
||||
options: [
|
||||
{
|
||||
name: "platform",
|
||||
description: "The platform to subscribe to (youtube/tiktok)",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
choices: [
|
||||
{ name: "YouTube", value: "youtube" },
|
||||
{ name: "TikTok", value: "tiktok" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "channel_or_user_id",
|
||||
description: "The YouTube channel ID or TikTok user ID",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
async messageRun(message, args) {
|
||||
const platform = args[0].toLowerCase();
|
||||
const channelOrUserId = args[1];
|
||||
const response = await subscribe(message.channel, platform, channelOrUserId);
|
||||
await message.safeReply(response);
|
||||
},
|
||||
|
||||
async interactionRun(interaction) {
|
||||
const platform = interaction.options.getString("platform");
|
||||
const channelOrUserId = interaction.options.getString("channel_or_user_id");
|
||||
const response = await subscribe(interaction.channel, platform, channelOrUserId);
|
||||
await interaction.followUp(response);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Subscribe to notifications for a specific platform and channel/user.
|
||||
* @param {import("discord.js").TextChannel} channel
|
||||
* @param {string} platform
|
||||
* @param {string} channelOrUserId
|
||||
*/
|
||||
async function subscribe(channel, platform, channelOrUserId) {
|
||||
const key = `${platform}:${channelOrUserId}`;
|
||||
|
||||
if (subscriptions.has(key)) {
|
||||
return "🚫 You are already subscribed to this channel/user.";
|
||||
}
|
||||
|
||||
subscriptions.set(key, channel.id);
|
||||
return `✅ Successfully subscribed to ${platform} updates for ${channelOrUserId}.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check for updates (YouTube and TikTok)
|
||||
* This function should be called periodically (e.g., using setInterval)
|
||||
*/
|
||||
async function checkForUpdates() {
|
||||
for (const [key, channelId] of subscriptions.entries()) {
|
||||
const [platform, id] = key.split(":");
|
||||
|
||||
let updateMessage = "";
|
||||
|
||||
if (platform === "youtube") {
|
||||
const updates = await fetchYouTubeUpdates(id);
|
||||
if (updates) {
|
||||
updateMessage = `New YouTube update for ${id}: ${updates}`;
|
||||
}
|
||||
} else if (platform === "tiktok") {
|
||||
const updates = await fetchTikTokUpdates(id);
|
||||
if (updates) {
|
||||
updateMessage = `New TikTok update for ${id}: ${updates}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (updateMessage) {
|
||||
const channel = await channelId.fetch();
|
||||
channel.send(updateMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch updates from YouTube (placeholder function)
|
||||
* @param {string} channelId
|
||||
*/
|
||||
async function fetchYouTubeUpdates(channelId) {
|
||||
// Implement your logic to fetch updates from YouTube
|
||||
// Return the update message or null if no updates
|
||||
return null; // Placeholder
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch updates from TikTok (placeholder function)
|
||||
* @param {string} userId
|
||||
*/
|
||||
async function fetchTikTokUpdates(userId) {
|
||||
// Implement your logic to fetch updates from TikTok
|
||||
// Return the update message or null if no updates
|
||||
return null; // Placeholder
|
||||
}
|
||||
|
||||
// Set an interval to check for updates every 10 minutes (600000 ms)
|
||||
setInterval(checkForUpdates, 600000);
|
120
src/database/schemas/Queue.js
Normal file
120
src/database/schemas/Queue.js
Normal file
@ -0,0 +1,120 @@
|
||||
//@ts-check
|
||||
const queue = require("@root/src/commands/music/queue");
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const queueSchema = new mongoose.Schema({
|
||||
guildId: { type: String, required: true, unique: true },
|
||||
tracks: [
|
||||
{
|
||||
title: String,
|
||||
uri: String,
|
||||
duration: Number,
|
||||
requester: String,
|
||||
},
|
||||
],
|
||||
currentTrack: {
|
||||
title: String,
|
||||
uri: String,
|
||||
duration: Number,
|
||||
requester: String,
|
||||
},
|
||||
});
|
||||
|
||||
const QueueModel = mongoose.model("Queue", queueSchema)
|
||||
|
||||
module.exports = {
|
||||
model: QueueModel,
|
||||
|
||||
async save(player) {
|
||||
const queue = player.queue
|
||||
const currentQueue = queue.current
|
||||
|
||||
const queueData = {
|
||||
guildId: player.guildId,
|
||||
tracks: queue.tracks.map(track => ({
|
||||
title: track.info.title,
|
||||
uri: track.info.uri,
|
||||
duration: track.info.duration,
|
||||
requester: track.requester.id,
|
||||
})),
|
||||
currentTrack: currentQueue?.info?.title ? {
|
||||
title: currentQueue.info.title,
|
||||
uri: currentQueue.info.uri,
|
||||
duration: currentQueue.info.duration,
|
||||
requester: currentQueue.requester.id,
|
||||
} : null,
|
||||
};
|
||||
|
||||
await QueueModel.findOneAndUpdate(
|
||||
{ guildId: player.guildId },
|
||||
queueData,
|
||||
{ upsert: true, new: true }
|
||||
)
|
||||
},
|
||||
|
||||
async saveCurrentTrack(player, track) {
|
||||
const queueData = {
|
||||
guildId: player.guildId,
|
||||
currentTrack: {
|
||||
title: track.info.title,
|
||||
uri: track.info.uri,
|
||||
duration: track.info.duration,
|
||||
requester: track.requester.id,
|
||||
position: player.position || 0, // Save the current position
|
||||
},
|
||||
};
|
||||
|
||||
await QueueModel.findOneAndUpdate(
|
||||
{ guildId: player.guildId },
|
||||
queueData,
|
||||
{ upsert: true, new: true }
|
||||
);
|
||||
},
|
||||
|
||||
async findGuild(guildOrPlayer) {
|
||||
if ('guildId' in guildOrPlayer) guildOrPlayer = guildOrPlayer.guildId
|
||||
return QueueModel.findOne({ guildId: guildOrPlayer });
|
||||
},
|
||||
|
||||
async deleteGuild(guildOrPlayer) {
|
||||
if ('guildId' in guildOrPlayer) guildOrPlayer = guildOrPlayer.guildId
|
||||
return QueueModel.deleteOne({ guildId: guildOrPlayer });
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {import('lavalink-client').Player} player
|
||||
* @returns
|
||||
*/
|
||||
async restore(player) {
|
||||
const queueData = await this.findGuild(player)
|
||||
if (!queueData) return false
|
||||
const { tracks, currentTrack } = queueData
|
||||
|
||||
player.queue.utils.save()
|
||||
|
||||
player.queue.add(tracks.map(track => ({
|
||||
info: {
|
||||
title: track.title,
|
||||
uri: track.uri,
|
||||
duration: track.duration,
|
||||
},
|
||||
requester: { id: track.requester },
|
||||
})));
|
||||
|
||||
if (currentTrack?.uri) {
|
||||
player.queue.current = {
|
||||
info: {
|
||||
title: currentTrack.title,
|
||||
uri: currentTrack.uri,
|
||||
duration: currentTrack.duration,
|
||||
},
|
||||
requester: { id: currentTrack.requester },
|
||||
};
|
||||
|
||||
// Set the current position if available
|
||||
player.position = currentTrack.position || 0; // Default to 0 if not set
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
};
|
11
src/events/buttonInteraction.js
Normal file
11
src/events/buttonInteraction.js
Normal file
@ -0,0 +1,11 @@
|
||||
const joinCommand = require("@src/commands/music/join");
|
||||
|
||||
client.on("interactionCreate", async (interaction) => {
|
||||
if (!interaction.isButton()) return;
|
||||
|
||||
const { customId } = interaction;
|
||||
|
||||
if (customId === "resume") {
|
||||
await joinCommand.execute(interaction);
|
||||
}
|
||||
});
|
@ -2,6 +2,7 @@ const { inviteHandler, greetingHandler } = require("@src/handlers");
|
||||
const { getSettings } = require("@schemas/Guild");
|
||||
const { AuditLogEvent } = require("discord.js");
|
||||
const { EmbedBuilder } = require("discord.js");
|
||||
|
||||
/**
|
||||
* @param {import('@src/structures').BotClient} client
|
||||
* @param {import('discord.js').GuildMember|import('discord.js').PartialGuildMember} member
|
||||
@ -34,13 +35,21 @@ module.exports = async (client, member) => {
|
||||
if (possibleLog) {
|
||||
if (settings.logging?.members) return;
|
||||
const logChannel = client.channels.cache.get(settings.logging.members);
|
||||
|
||||
// Check if logChannel is defined
|
||||
if (!logChannel) {
|
||||
console.error(`Log channel not found for guild: ${guild.id}`);
|
||||
return; // Exit if the log channel is not found
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setAuthor({ name: "Member kicked" })
|
||||
.setColor("Red")
|
||||
.setTitle(`${member.displayName} (\`${member.id}\` was kicked.)`)
|
||||
.setTitle(`${member.displayName} (\`${member.id}\`) was kicked.`)
|
||||
.setDescription(`Reason: ${possibleLog.reason || "none"}`)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `ID: ${member.id} | Executor: ${possibleLog.executor.username}` })
|
||||
await logChannel.send({ embeds: [embed] })
|
||||
.setFooter({ text: `ID: ${member.id} | Executor: ${possibleLog.executor.username}` });
|
||||
|
||||
await logChannel.send({ embeds: [embed] });
|
||||
}
|
||||
};
|
||||
};
|
13
src/events/player/playerDestroy.js
Normal file
13
src/events/player/playerDestroy.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = async (client, player) => {
|
||||
const guild = client.guilds.cache.get(player.guildId);
|
||||
if (!guild) return;
|
||||
|
||||
if (player.voiceChannelId) {
|
||||
await client.utils.setVoiceStatus(client, player.voiceChannelId, "");
|
||||
}
|
||||
|
||||
const msg = player.get("message");
|
||||
if (msg && msg.deletable) {
|
||||
await msg.delete().catch(() => {});
|
||||
}
|
||||
};
|
38
src/events/player/playerDisconnect.js
Normal file
38
src/events/player/playerDisconnect.js
Normal file
@ -0,0 +1,38 @@
|
||||
const Queue = require("@src/database/schemas/Queue");
|
||||
const { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } = require("discord.js");
|
||||
|
||||
module.exports = async (client, player) => {
|
||||
const guild = client.guilds.cache.get(player.guildId);
|
||||
if (!guild) return;
|
||||
|
||||
await Queue.save(player)
|
||||
|
||||
if (player.voiceChannelId) {
|
||||
await client.utils.setVoiceStatus(client, player.voiceChannelId, "");
|
||||
}
|
||||
|
||||
const msg = player.get("message");
|
||||
if (msg && msg.deletable) {
|
||||
await msg.delete().catch(() => {});
|
||||
}
|
||||
|
||||
// Send a message with a button to resume playback
|
||||
const channel = guild.channels.cache.get(player.textChannelId);
|
||||
if (channel) {
|
||||
const row = new ActionRowBuilder().addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId("resume")
|
||||
.setLabel("Resume Music")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
);
|
||||
|
||||
await channel.safeSend({
|
||||
embeds: [
|
||||
new EmbedBuilder()
|
||||
.setColor(EMBED_COLORS.BOT_EMBED)
|
||||
.setDescription("The music has stopped due to a disconnection. You can resume it by clicking the button below.")
|
||||
],
|
||||
components: [row]
|
||||
});
|
||||
}
|
||||
};
|
34
src/events/player/queueEnd.js
Normal file
34
src/events/player/queueEnd.js
Normal file
@ -0,0 +1,34 @@
|
||||
const { MUSIC, EMBED_COLORS } = require("@root/config");
|
||||
const { EmbedBuilder } = require("discord.js");
|
||||
const Queue = require("@src/database/schemas/Queue");
|
||||
|
||||
module.exports = async (client, player) => {
|
||||
const guild = client.guilds.cache.get(player.guildId);
|
||||
if (!guild) return;
|
||||
|
||||
if (player.voiceChannelId) {
|
||||
await client.utils.setVoiceStatus(client, player.voiceChannelId, "Silence? Use /play to start the beat!");
|
||||
}
|
||||
|
||||
if (player.volume > 100) {
|
||||
await player.setVolume(MUSIC.DEFAULT_VOLUME);
|
||||
}
|
||||
|
||||
const msg = player.get("message");
|
||||
if (msg && msg.deletable) {
|
||||
await msg.delete().catch(() => {});
|
||||
}
|
||||
|
||||
const channel = guild.channels.cache.get(player.textChannelId);
|
||||
if (channel) {
|
||||
await channel.safeSend(
|
||||
{
|
||||
embeds: [new EmbedBuilder().setColor(EMBED_COLORS.BOT_EMBED).setDescription("Queue has ended.")],
|
||||
},
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
// Clean up the queue from the database if all tracks are played
|
||||
await Queue.deleteGuild(player)
|
||||
};
|
38
src/events/player/trackEnd.js
Normal file
38
src/events/player/trackEnd.js
Normal file
@ -0,0 +1,38 @@
|
||||
const { autoplayFunction } = require("@handlers/player");
|
||||
const { MUSIC } = require("@root/config.js");
|
||||
const Queue = require("@src/database/schemas/Queue");
|
||||
|
||||
module.exports = async (client, player, track) => {
|
||||
const guild = client.guilds.cache.get(player.guildId);
|
||||
if (!guild) return;
|
||||
|
||||
if (player.volume > 100) {
|
||||
await player.setVolume(MUSIC.DEFAULT_VOLUME);
|
||||
}
|
||||
|
||||
const msg = player.get("message");
|
||||
if (msg && msg.deletable) {
|
||||
await msg.delete().catch(() => {});
|
||||
}
|
||||
|
||||
// Check if the current track was saved in the database
|
||||
const queueData = await Queue.findGuild(player)
|
||||
|
||||
// Remove the current track from the database after it has been played
|
||||
if (queueData) {
|
||||
const updatedTracks = queueData.tracks.filter(t => t.uri !== track.info.uri);
|
||||
await Queue.model.updateOne(
|
||||
{ guildId: player.guildId },
|
||||
{ tracks: updatedTracks, currentTrack: null }
|
||||
);
|
||||
|
||||
// If there are no more tracks left, consider cleaning up the database entry
|
||||
if (updatedTracks.length === 0) {
|
||||
await Queue.deleteGuild(player)
|
||||
}
|
||||
}
|
||||
|
||||
if (player.get("autoplay") === true) {
|
||||
await autoplayFunction(client, track, player);
|
||||
}
|
||||
};
|
221
src/events/player/trackStart.js
Normal file
221
src/events/player/trackStart.js
Normal file
@ -0,0 +1,221 @@
|
||||
const { EmbedBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder } = require("discord.js");
|
||||
const { EMBED_COLORS } = require("@root/config");
|
||||
const Queue = require("@src/database/schemas/Queue");
|
||||
const joinCommand = require("@src/commands/music/join");
|
||||
|
||||
module.exports = async (client, player, track) => {
|
||||
const guild = client.guilds.cache.get(player.guildId);
|
||||
if (!guild) return;
|
||||
|
||||
// Check if the queue exists in the database
|
||||
await Queue.restore(player)
|
||||
|
||||
// Save the current track to the database
|
||||
await Queue.saveCurrentTrack(player, track);
|
||||
|
||||
if (!player.textChannelId || !track) return;
|
||||
|
||||
const channel = guild.channels.cache.get(player.textChannelId);
|
||||
if (!channel) return;
|
||||
|
||||
if (player.get("autoplay") === true) {
|
||||
await player.queue.previous.push(track);
|
||||
}
|
||||
|
||||
if (player.voiceChannelId) {
|
||||
await client.utils.setVoiceStatus(client, player.voiceChannelId, `Playing: **${track.info.title}**`);
|
||||
}
|
||||
|
||||
const previous = await player.queue.shiftPrevious();
|
||||
|
||||
const row1 = (player) =>
|
||||
new ActionRowBuilder().addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId("volumedown")
|
||||
.setEmoji("🔉")
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("previous")
|
||||
.setEmoji("⏮️")
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setDisabled(!previous),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("pause")
|
||||
.setEmoji(player.paused ? "▶️" : "⏸️")
|
||||
.setStyle(player.paused ? ButtonStyle.Success : ButtonStyle.Secondary),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("skip")
|
||||
.setEmoji("⏭️")
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("volumeup")
|
||||
.setEmoji("🔊")
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
);
|
||||
|
||||
const row2 = (player) =>
|
||||
new ActionRowBuilder().addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId("autoplay")
|
||||
.setEmoji("♾️")
|
||||
.setStyle(player.get("autoplay") ? ButtonStyle.Success : ButtonStyle.Secondary),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("loop")
|
||||
.setEmoji("🔁")
|
||||
.setStyle(player.repeatMode === "off" ? ButtonStyle.Secondary : ButtonStyle.Success),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("stop")
|
||||
.setEmoji("⏹️")
|
||||
.setStyle(ButtonStyle.Danger),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("shuffle")
|
||||
.setEmoji("🔀")
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("queue")
|
||||
.setEmoji("📑")
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
);
|
||||
|
||||
const msg = await channel.safeSend({
|
||||
embeds: [
|
||||
new EmbedBuilder()
|
||||
.setColor(EMBED_COLORS.BOT_EMBED)
|
||||
.setAuthor({ name: "Track Started" })
|
||||
.setDescription(`🎶 **Now playing [${track.info.title}](${track.info.uri})**`)
|
||||
.setThumbnail(track.info.artworkUrl)
|
||||
.setFooter({
|
||||
text: `Requested by: ${track.requester.displayName}`,
|
||||
})
|
||||
.addFields(
|
||||
{
|
||||
name: "Duration",
|
||||
value: track.info.isStream ? "Live" : client.utils.formatTime(track.info.duration),
|
||||
inline: true,
|
||||
},
|
||||
{ name: "Author", value: track.info.author || "Unknown", inline: true }
|
||||
),
|
||||
],
|
||||
components: [row1(player), row2(player)],
|
||||
});
|
||||
|
||||
if (msg) player.set("message", msg);
|
||||
|
||||
const collector = msg.createMessageComponentCollector({
|
||||
filter: async (int) => {
|
||||
const sameVc = int.guild.members.me.voice.channelId === int.member.voice.channelId;
|
||||
return sameVc;
|
||||
},
|
||||
});
|
||||
|
||||
collector.on("collect", async (int) => {
|
||||
if (!int.isButton()) return;
|
||||
|
||||
await int.deferReply({ ephemeral: true });
|
||||
let description;
|
||||
|
||||
switch (int.customId) {
|
||||
case "previous":
|
||||
description = previous ? "Playing the previous track..." : "No previous track available";
|
||||
if (previous) player.play({ clientTrack: previous });
|
||||
break;
|
||||
|
||||
case "pause":
|
||||
if (player.paused) {
|
||||
player.resume();
|
||||
description = "Track resumed";
|
||||
} else {
|
||||
player.pause();
|
||||
description = "Track paused";
|
||||
}
|
||||
await msg.edit({ components: [row1(player), row2(player)] });
|
||||
break;
|
||||
|
||||
case "stop":
|
||||
player.stopPlaying(true, false);
|
||||
description = "Playback stopped";
|
||||
break;
|
||||
|
||||
case "skip":
|
||||
description = player.queue.tracks.length > 0 ? "Skipped to the next track" : "The queue is empty!";
|
||||
if (player.queue.tracks.length > 0) player.skip();
|
||||
break;
|
||||
|
||||
case "shuffle":
|
||||
if (player.queue.tracks.length < 2) {
|
||||
description = "The queue is too short to shuffle!";
|
||||
} else {
|
||||
player.queue.shuffle();
|
||||
description = "The queue has been shuffled!";
|
||||
}
|
||||
break;
|
||||
|
||||
case "volumeup":
|
||||
const newVolUp = Math.min(player.volume + 10, 100);
|
||||
await player.setVolume(newVolUp);
|
||||
description = `Volume increased to \`${newVolUp}%\``;
|
||||
break;
|
||||
|
||||
case "volumedown":
|
||||
const newVolDown = Math.max(player.volume - 10, 0);
|
||||
await player.setVolume(newVolDown);
|
||||
description = `Volume decreased to \`${newVolDown}%\``;
|
||||
break;
|
||||
|
||||
case "loop":
|
||||
const currentMode = player.repeatMode;
|
||||
if (currentMode === "off") {
|
||||
player.setRepeatMode("track");
|
||||
description = "Loop mode set to `track`";
|
||||
} else if (currentMode === "track") {
|
||||
player.setRepeatMode("queue");
|
||||
description = "Loop mode set to `queue`";
|
||||
} else {
|
||||
player.setRepeatMode("off");
|
||||
description = "Loop mode `disabled`";
|
||||
}
|
||||
await msg.edit({ components: [row1(player), row2(player)] });
|
||||
break;
|
||||
|
||||
case "autoplay":
|
||||
const autoplay = !player.get("autoplay");
|
||||
player.set("autoplay", autoplay);
|
||||
description = autoplay ? "Autoplay activated!" : "Autoplay deactivated";
|
||||
await msg.edit({ components: [row1(player), row2(player)] });
|
||||
break;
|
||||
|
||||
case "queue":
|
||||
const queue = player.queue;
|
||||
const tracks = queue.tracks.slice(0, 10); // Get first 10 tracks
|
||||
|
||||
const queueEmbed = new EmbedBuilder()
|
||||
.setColor(EMBED_COLORS.BOT_EMBED)
|
||||
.setAuthor({ name: "Music Queue" })
|
||||
.setDescription(
|
||||
tracks.length > 0
|
||||
? tracks
|
||||
.map((track, i) => `${i + 1}. [${track.info.title}](${track.info.uri})`)
|
||||
.join("\n")
|
||||
: "No tracks in queue"
|
||||
)
|
||||
.addFields({
|
||||
name: "Now Playing",
|
||||
value: `[${queue.current.info.title}](${queue.current.info.uri})`,
|
||||
});
|
||||
|
||||
if (tracks.length > 0) {
|
||||
queueEmbed.setFooter({
|
||||
text: `${queue.tracks.length} track(s) in queue`,
|
||||
});
|
||||
}
|
||||
|
||||
description = "Queue sent as new message";
|
||||
await channel.send({ embeds: [queueEmbed] });
|
||||
break;
|
||||
}
|
||||
|
||||
await int.followUp({
|
||||
embeds: [new EmbedBuilder().setDescription(description).setColor(EMBED_COLORS.BOT_EMBED)],
|
||||
});
|
||||
});
|
||||
};
|
3
src/events/raw.js
Normal file
3
src/events/raw.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = async (client, data) => {
|
||||
client.musicManager.sendRawData(data);
|
||||
};
|
@ -10,7 +10,7 @@ module.exports = async (client) => {
|
||||
|
||||
// Initialize Music Manager
|
||||
if (client.config.MUSIC.ENABLED) {
|
||||
client.musicManager.connect({ userId: client.user.id });
|
||||
client.musicManager.init({ ...client.user, shards: "auto" });
|
||||
client.logger.success("Music Manager initialized");
|
||||
}
|
||||
|
||||
|
@ -22,9 +22,9 @@ module.exports = async (client, oldState, newState) => {
|
||||
if (oldState.channel.members.size === 1) {
|
||||
setTimeout(() => {
|
||||
// if 1 (you), wait 1 minute
|
||||
if (!oldState.channel.members.size - 1) {
|
||||
const player = client.musicManager.players.resolve(guild.id);
|
||||
if (player) client.musicManager.players.destroy(guild.id).then(player.voice.disconnect()); // destroy the player
|
||||
if (oldState.channel.members.size === 1) {
|
||||
const player = client.musicManager.getPlayer(guild.id);
|
||||
if (player) player.destroy(); // destroy the player
|
||||
}
|
||||
}, client.config.MUSIC.IDLE_TIME * 1000);
|
||||
}
|
||||
|
@ -11,4 +11,6 @@ module.exports = {
|
||||
suggestionHandler: require("./suggestion"),
|
||||
ticketHandler: require("./ticket"),
|
||||
translationHandler: require("./translation"),
|
||||
managerHandler: require("./manager"),
|
||||
playerHandler: require("./player"),
|
||||
};
|
||||
|
@ -1,75 +0,0 @@
|
||||
const { EmbedBuilder, GatewayDispatchEvents } = require("discord.js");
|
||||
const { Cluster } = require("lavaclient");
|
||||
const { formatTime } = require("@helpers/Utils");
|
||||
require("@lavaclient/plugin-queue").load();
|
||||
|
||||
/**
|
||||
* @param {import("@structures/BotClient")} client
|
||||
*/
|
||||
module.exports = (client) => {
|
||||
const lavaclient = new Cluster({
|
||||
nodes: client.config.MUSIC.LAVALINK_NODES,
|
||||
ws: client.config.MUSIC.LAVALINK_WS,
|
||||
discord: {
|
||||
sendGatewayCommand: (id, payload) => client.guilds.cache.get(id)?.shard?.send(payload),
|
||||
},
|
||||
});
|
||||
|
||||
client.ws.on(GatewayDispatchEvents.VoiceStateUpdate, (data) => lavaclient.players.handleVoiceUpdate(data));
|
||||
client.ws.on(GatewayDispatchEvents.VoiceServerUpdate, (data) => lavaclient.players.handleVoiceUpdate(data));
|
||||
|
||||
lavaclient.on("nodeConnected", (node, event) => {
|
||||
client.logger.log(`Node "${node.identifier}" connected`);
|
||||
});
|
||||
|
||||
lavaclient.on("nodeDisconnected", (node, event) => {
|
||||
client.logger.log(`Node "${node.identifier}" disconnected`);
|
||||
const reconnectInterval = 20000; // Time in MS, change as needed.
|
||||
setTimeout(() => {
|
||||
node.connect();
|
||||
}, reconnectInterval);
|
||||
});
|
||||
|
||||
lavaclient.on("nodeError", (node, error) => {
|
||||
client.logger.error(`Node "${node.identifier}" encountered an error: ${error.message}.`, error);
|
||||
});
|
||||
|
||||
lavaclient.on("nodeDebug", (node, event) => {
|
||||
client.logger.debug(`Node "${node.identifier}" debug: ${event.message}`);
|
||||
});
|
||||
|
||||
lavaclient.on("nodeTrackStart", async (_node, queue, track) => {
|
||||
const fields = [];
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setAuthor({ name: "Now Playing" })
|
||||
.setColor(client.config.EMBED_COLORS.BOT_EMBED)
|
||||
.setDescription(`[${track.info.title}](${track.info.uri})`)
|
||||
.setFooter({ text: `Requested By: ${track.requesterId}` })
|
||||
.setThumbnail(track.info.artworkUrl);
|
||||
|
||||
fields.push({
|
||||
name: "Song Duration",
|
||||
value: "`" + formatTime(track.info.length) + "`",
|
||||
inline: true,
|
||||
});
|
||||
|
||||
if (queue.tracks.length > 0) {
|
||||
fields.push({
|
||||
name: "Position in Queue",
|
||||
value: (queue.tracks.length + 1).toString(),
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
|
||||
embed.setFields(fields);
|
||||
queue.data.channel.safeSend({ embeds: [embed] });
|
||||
});
|
||||
|
||||
lavaclient.on("nodeQueueFinish", async (_node, queue) => {
|
||||
queue.data.channel.safeSend("Queue has ended.");
|
||||
await client.musicManager.players.destroy(queue.player.guildId).then(() => queue.player.voice.disconnect());
|
||||
});
|
||||
|
||||
return lavaclient;
|
||||
};
|
43
src/handlers/manager.js
Normal file
43
src/handlers/manager.js
Normal file
@ -0,0 +1,43 @@
|
||||
const { LavalinkManager } = require("lavalink-client");
|
||||
const { MUSIC } = require("@root/config.js");
|
||||
|
||||
class Manager extends LavalinkManager {
|
||||
constructor(client) {
|
||||
super({
|
||||
nodes: MUSIC.LAVALINK_NODES,
|
||||
sendToShard: (guildId, payload) => client.guilds.cache.get(guildId)?.shard?.send(payload),
|
||||
emitNewSongsOnly: false,
|
||||
queueOptions: {
|
||||
maxPreviousTracks: 30,
|
||||
},
|
||||
playerOptions: {
|
||||
defaultSearchPlatform: MUSIC.DEFAULT_SOURCE,
|
||||
onDisconnect: {
|
||||
autoReconnect: true,
|
||||
destroyPlayer: false,
|
||||
},
|
||||
},
|
||||
linksAllowed: true,
|
||||
linksBlacklist: ["porn"],
|
||||
linksWhitelist: [],
|
||||
});
|
||||
|
||||
this.nodeManager.on("connect", (node) => {
|
||||
client.logger.success(`Lavalink node ${node.id} connected!`);
|
||||
});
|
||||
|
||||
this.nodeManager.on("disconnect", (node, reason) => {
|
||||
client.logger.warn(`Lavalink node "${node.id}" disconnected. Reason: ${JSON.stringify(reason)}`);
|
||||
});
|
||||
|
||||
this.nodeManager.on("error", (node, error) => {
|
||||
client.logger.error(`Error occurred on Lavalink node "${node.id}": ${error.message}`);
|
||||
});
|
||||
|
||||
this.nodeManager.on("destroy", (node) => {
|
||||
client.logger.warn(`Lavalink node "${node.id}" destroyed`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Manager;
|
71
src/handlers/player.js
Normal file
71
src/handlers/player.js
Normal file
@ -0,0 +1,71 @@
|
||||
const { EMBED_COLORS, MUSIC } = require("@root/config");
|
||||
const { EmbedBuilder } = require("discord.js");
|
||||
|
||||
module.exports = {
|
||||
autoplayFunction: async (client, track, player) => {
|
||||
if (player.queue.tracks.length > 0) return;
|
||||
if (!track) return;
|
||||
|
||||
const channel = client.guilds.cache.get(player.guildId)?.channels.cache.get(player.textChannelId);
|
||||
|
||||
let url, source;
|
||||
|
||||
switch (track.info.sourceName) {
|
||||
case "spotify":
|
||||
url = `seed_tracks=${track.info.identifier ?? "11xbo8bGa5XTrXrGP77zwc"}&limit=20&min_popularity=30`;
|
||||
source = "sprec";
|
||||
break;
|
||||
|
||||
case "youtube":
|
||||
url = `https://youtube.com/watch?v=${track.info.identifier ?? "lpeuIu-ZYJY"}&list=RD${track.info.identifier ?? "lpeuIu-ZYJY"}`;
|
||||
source = "ytsearch";
|
||||
break;
|
||||
|
||||
case "jiosaavn":
|
||||
url = `${track.info.identifier ?? "Hvma-gqd"}`;
|
||||
source = "jsrec";
|
||||
break;
|
||||
|
||||
default:
|
||||
url = track.info.author;
|
||||
source = MUSIC.DEFAULT_SOURCE;
|
||||
break;
|
||||
}
|
||||
|
||||
const res = await player.search({ query: url, source: source }, track.requester);
|
||||
|
||||
if (!res || res.tracks.length === 0) {
|
||||
await channel.safeSend(
|
||||
{
|
||||
embeds: [new EmbedBuilder().setColor(EMBED_COLORS.WARNING).setDescription("> Autoplay, No results found")],
|
||||
},
|
||||
10
|
||||
);
|
||||
return player.destroy();
|
||||
}
|
||||
|
||||
for (let songs = 0; songs < 3; ) {
|
||||
const chosen = res.tracks[Math.floor(Math.random() * res.tracks.length)];
|
||||
|
||||
if (
|
||||
!player.queue.previous?.some((o) => o.info.identifier === chosen.info.identifier) &&
|
||||
!player.queue.tracks.some((o) => o.info.identifier === chosen.info.identifier)
|
||||
) {
|
||||
await player.queue.add(chosen);
|
||||
songs++;
|
||||
}
|
||||
}
|
||||
|
||||
if (player.queue.tracks.length === 0) {
|
||||
await channel?.safeSend(
|
||||
{
|
||||
embeds: [
|
||||
new EmbedBuilder().setColor(EMBED_COLORS.WARNING).setDescription("> Autoplay, No unique track found"),
|
||||
],
|
||||
},
|
||||
10
|
||||
);
|
||||
return player.destroy();
|
||||
}
|
||||
},
|
||||
};
|
@ -63,8 +63,8 @@ module.exports = class BotUtils {
|
||||
static get musicValidations() {
|
||||
return [
|
||||
{
|
||||
callback: ({ client, guildId }) => client.musicManager.players.resolve(guildId),
|
||||
message: "🚫 No music is being played!",
|
||||
callback: ({ client, guildId }) => client.musicManager.getPlayer(guildId),
|
||||
message: "🚫 I'm not in a voice channel.",
|
||||
},
|
||||
{
|
||||
callback: ({ member }) => member.voice?.channelId,
|
||||
@ -72,7 +72,7 @@ module.exports = class BotUtils {
|
||||
},
|
||||
{
|
||||
callback: ({ member, client, guildId }) =>
|
||||
member.voice?.channelId === client.musicManager.players.resolve(guildId)?.voice.channelId,
|
||||
member.voice?.channelId === client.musicManager.getPlayer(guildId)?.voiceChannelId,
|
||||
message: "🚫 You're not in the same voice channel.",
|
||||
},
|
||||
];
|
||||
|
@ -162,21 +162,43 @@ module.exports = class Utils {
|
||||
* @returns {string} - Formatted time string
|
||||
*/
|
||||
static formatTime(ms) {
|
||||
const minuteMs = 60 * 1000;
|
||||
const hourMs = 60 * minuteMs;
|
||||
const dayMs = 24 * hourMs;
|
||||
if (ms < minuteMs) {
|
||||
return `${ms / 1000}s`;
|
||||
} else if (ms < hourMs) {
|
||||
return `${Math.floor(ms / minuteMs)}m ${Math.floor(
|
||||
(ms % minuteMs) / 1000
|
||||
)}s`;
|
||||
} else if (ms < dayMs) {
|
||||
return `${Math.floor(ms / hourMs)}h ${Math.floor(
|
||||
(ms % hourMs) / minuteMs
|
||||
)}m`;
|
||||
} else {
|
||||
return `${Math.floor(ms / dayMs)}d ${Math.floor((ms % dayMs) / hourMs)}h`;
|
||||
}
|
||||
return ms < 1000
|
||||
? `${ms / 1000}s`
|
||||
: ["d", "h", "m", "s"]
|
||||
.map((unit, i) => {
|
||||
const value = [864e5, 36e5, 6e4, 1e3][i];
|
||||
const amount = Math.floor(ms / value);
|
||||
ms %= value;
|
||||
return amount ? `${amount}${unit}` : null;
|
||||
})
|
||||
.filter((x) => x !== null)
|
||||
.join(" ") || "0s";
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a time string into milliseconds
|
||||
* @param {string} string - The time string (e.g., "1d", "2h", "3m", "4s")
|
||||
* @returns {number} - The time in milliseconds
|
||||
*/
|
||||
static parseTime(string) {
|
||||
const time = string.match(/([0-9]+[dhms])/g);
|
||||
if (!time) return 0;
|
||||
return time.reduce((ms, t) => {
|
||||
const unit = t[t.length - 1];
|
||||
const amount = Number(t.slice(0, -1));
|
||||
return ms + amount * { d: 864e5, h: 36e5, m: 6e4, s: 1e3 }[unit];
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates voice channel status
|
||||
* @param {string} channel - The voice channel ID
|
||||
* @param {string} status - The status to update
|
||||
* @param {object} client - The bot client
|
||||
*/
|
||||
static async setVoiceStatus(client, channelId, message) {
|
||||
const url = `/channels/${channelId}/voice-status`;
|
||||
const payload = { status: message };
|
||||
await client.rest.put(url, { body: payload }).catch(() => {});
|
||||
}
|
||||
};
|
||||
|
@ -44,14 +44,15 @@ module.exports = class Validator {
|
||||
|
||||
// Music
|
||||
if (config.MUSIC.ENABLED) {
|
||||
if (!process.env.SPOTIFY_CLIENT_ID || !process.env.SPOTIFY_CLIENT_SECRET) {
|
||||
warn("env: SPOTIFY_CLIENT_ID or SPOTIFY_CLIENT_SECRET are missing. Spotify music links won't work");
|
||||
}
|
||||
if (config.MUSIC.LAVALINK_NODES.length == 0) {
|
||||
warn("config.js: There must be at least one node for Lavalink");
|
||||
}
|
||||
if (!["ytsearch", "ytmsearch", "spsearch", "scsearch"].includes(config.MUSIC.DEFAULT_SOURCE)) {
|
||||
warn("config.js: MUSIC.DEFAULT_SOURCE must be either ytsearch, ytmsearch, spsearch or scsearch");
|
||||
if (
|
||||
!["ytsearch", "ytmsearch", "scsearch", "spsearch", "dzsearch", "jssearch"].includes(config.MUSIC.DEFAULT_SOURCE)
|
||||
) {
|
||||
warn(
|
||||
"config.js: MUSIC.DEFAULT_SOURCE must be either ytsearch, ytmsearch, scsearch, spsearch, dzsearch or jssearch"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ const { recursiveReadDirSync } = require("../helpers/Utils");
|
||||
const { validateCommand, validateContext } = require("../helpers/Validator");
|
||||
const { schemas } = require("@src/database/mongoose");
|
||||
const CommandCategory = require("./CommandCategory");
|
||||
const lavaclient = require("../handlers/lavaclient");
|
||||
const Manager = require("../handlers/manager");
|
||||
const giveawaysHandler = require("../handlers/giveaway");
|
||||
const { DiscordTogether } = require("discord-together");
|
||||
|
||||
@ -43,10 +43,10 @@ module.exports = class BotClient extends Client {
|
||||
this.config = require("@root/config"); // load the config file
|
||||
|
||||
/**
|
||||
* @type {import('@structures/Command')[]}
|
||||
* @type {Collection<string, import('@structures/Command')>}
|
||||
*/
|
||||
this.commands = []; // store actual command
|
||||
this.commandIndex = new Collection(); // store (alias, arrayIndex) pair
|
||||
this.commands = new Collection(); // store actual command
|
||||
this.commandAlias = new Map(); // store (alias, command) pair
|
||||
|
||||
/**
|
||||
* @type {Collection<string, import('@structures/Command')>}
|
||||
@ -65,7 +65,7 @@ module.exports = class BotClient extends Client {
|
||||
: undefined;
|
||||
|
||||
// Music Player
|
||||
if (this.config.MUSIC.ENABLED) this.musicManager = lavaclient(this);
|
||||
if (this.config.MUSIC.ENABLED) this.musicManager = new Manager(this);
|
||||
|
||||
// Giveaways
|
||||
if (this.config.GIVEAWAYS.ENABLED) this.giveawaysManager = giveawaysHandler(this);
|
||||
@ -76,6 +76,9 @@ module.exports = class BotClient extends Client {
|
||||
// Database
|
||||
this.database = schemas;
|
||||
|
||||
// Utils
|
||||
this.utils = require("../helpers/Utils");
|
||||
|
||||
// Discord Together
|
||||
this.discordTogether = new DiscordTogether(this);
|
||||
}
|
||||
@ -92,18 +95,23 @@ module.exports = class BotClient extends Client {
|
||||
|
||||
recursiveReadDirSync(directory).forEach((filePath) => {
|
||||
const file = path.basename(filePath);
|
||||
const dir = path.basename(path.dirname(filePath));
|
||||
try {
|
||||
const eventName = path.basename(file, ".js");
|
||||
const event = require(filePath);
|
||||
|
||||
this.on(eventName, event.bind(null, this));
|
||||
if (dir === "player") {
|
||||
this.musicManager.on(eventName, event.bind(null, this));
|
||||
} else {
|
||||
this.on(eventName, event.bind(null, this));
|
||||
}
|
||||
clientEvents.push([file, "✓"]);
|
||||
|
||||
delete require.cache[require.resolve(filePath)];
|
||||
success += 1;
|
||||
} catch (ex) {
|
||||
failed += 1;
|
||||
this.logger.error(`loadEvent - ${file}`, ex);
|
||||
this.logger.error(`Failed to load event - ${file}`, ex);
|
||||
}
|
||||
});
|
||||
|
||||
@ -127,8 +135,8 @@ module.exports = class BotClient extends Client {
|
||||
* @returns {import('@structures/Command')|undefined}
|
||||
*/
|
||||
getCommand(invoke) {
|
||||
const index = this.commandIndex.get(invoke.toLowerCase());
|
||||
return index !== undefined ? this.commands[index] : undefined;
|
||||
const invokeLower = invoke.toLowerCase()
|
||||
return this.commands.get(invokeLower) ?? this.commandAlias.get(invokeLower);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -143,18 +151,17 @@ module.exports = class BotClient extends Client {
|
||||
}
|
||||
// Prefix Command
|
||||
if (cmd.command?.enabled) {
|
||||
const index = this.commands.length;
|
||||
if (this.commandIndex.has(cmd.name)) {
|
||||
const name = cmd.name.toLowerCase()
|
||||
if (this.commands.has(name)) {
|
||||
throw new Error(`Command ${cmd.name} already registered`);
|
||||
}
|
||||
if (Array.isArray(cmd.command.aliases)) {
|
||||
cmd.command.aliases.forEach((alias) => {
|
||||
if (this.commandIndex.has(alias)) throw new Error(`Alias ${alias} already registered`);
|
||||
this.commandIndex.set(alias.toLowerCase(), index);
|
||||
if (this.commandAlias.has(alias)) throw new Error(`Alias ${alias} already registered`);
|
||||
this.commandAlias.set(alias.toLowerCase(), cmd);
|
||||
});
|
||||
}
|
||||
this.commandIndex.set(cmd.name.toLowerCase(), index);
|
||||
this.commands.push(cmd);
|
||||
this.commands.set(name, cmd);
|
||||
} else {
|
||||
this.logger.debug(`Skipping command ${cmd.name}. Disabled!`);
|
||||
}
|
||||
@ -187,7 +194,7 @@ module.exports = class BotClient extends Client {
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.success(`Loaded ${this.commands.length} commands`);
|
||||
this.logger.success(`Loaded ${this.commands.size} commands`);
|
||||
this.logger.success(`Loaded ${this.slashCommands.size} slash commands`);
|
||||
if (this.slashCommands.size > 100) throw new Error("A maximum of 100 slash commands can be enabled");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user