From c18650e5c59ce8d22c53ff85557b327276345f2b Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:08:21 +0700 Subject: [PATCH 01/34] Add deps --- package.json | 12 +++- yarn.lock | 175 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 170 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 3c9dc64..38a0fb6 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,29 @@ { "dependencies": { "@discordjs/builders": "^0.7.0", + "@discordjs/opus": "^0.7.0", + "@discordjs/rest": "^0.3.0", + "@discordjs/voice": "^0.8.0", "@types/color-thief-node": "^1.0.1", "bluebird": "^3.7.2", + "bufferutil": "^4.0.6", "color-thief-node": "^1.0.4", "country-code-emoji": "^2.3.0", "country-code-lookup": "^0.0.19", + "discord-api-types": "^0.26.1", "discord.js": "^13.6.0", "dotenv": "^10.0.0", + "erlpack": "discord/erlpack", + "libsodium-wrappers": "^0.7.9", "node-osu": "^2.2.1", "ping": "^0.4.1", + "play-dl": "^1.8.2", "ts-custom-error": "^3.2.0", "typescript": "^4.4.4", + "utf-8-validate": "^5.0.8", "validator": "^13.7.0", - "winston": "^3.3.3" + "winston": "^3.3.3", + "zlib-sync": "^0.1.7" }, "devDependencies": { "@prisma/client": "^3.3.0", diff --git a/yarn.lock b/yarn.lock index 438db03..cad2e70 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,6 +38,54 @@ resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.4.0.tgz#b6488286a1cc7b41b644d7e6086f25a1c1e6f837" integrity sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw== +"@discordjs/node-pre-gyp@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@discordjs/node-pre-gyp/-/node-pre-gyp-0.4.2.tgz#37dd27f8d353eeb87372fcce059c5249550b37ab" + integrity sha512-V239Czn+DXFGLhhuccwEDBoTdgMGrRu30dOlzm1GzrSIjwFj01ZJerNX7x+CEX1NG1Q/1gGfOOkeZFNHjycrRA== + dependencies: + detect-libc "^1.0.3" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.5" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +"@discordjs/opus@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@discordjs/opus/-/opus-0.7.0.tgz#69e9611a6412ea470f8e640f54478da89326ca3b" + integrity sha512-3Xxa3dh7taSDwBAR5fLALZ/KTxvbMmHCMxYLYve6NlPO7Ms1CLmKqp/R4ZoVzkRGQVUVWEhaB1s0v9jfa2tfDg== + dependencies: + "@discordjs/node-pre-gyp" "^0.4.2" + node-addon-api "^4.2.0" + +"@discordjs/rest@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-0.3.0.tgz#89e06a42b168c91598891d6bf860353e28fba5d2" + integrity sha512-F9aeP3odlAlllM1ciBZLdd+adiAyBj4VaZBejj4UMj4afE2wfCkNTGvYYiRxrXUE9fN7e/BuDP2ePl0tVA2m7Q== + dependencies: + "@discordjs/collection" "^0.4.0" + "@sapphire/async-queue" "^1.1.9" + "@sapphire/snowflake" "^3.0.1" + discord-api-types "^0.26.1" + form-data "^4.0.0" + node-fetch "^2.6.5" + tslib "^2.3.1" + +"@discordjs/voice@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@discordjs/voice/-/voice-0.8.0.tgz#5d790fc25b883698f6eb7762efe1af00b6440947" + integrity sha512-o0JfVLMs3eLjUzPf6oxMydEeum40I7xzfUc66SLN+RrKpSAsTbngf5qnCF53nm+KDNSvrwg1AZqNm4LEAdxJIA== + dependencies: + "@types/ws" "^8.2.0" + discord-api-types "^0.26.1" + prism-media "^1.3.2" + tiny-typed-emitter "^2.1.0" + tslib "^2.3.1" + ws "^8.4.2" + "@mapbox/node-pre-gyp@^1.0.0": version "1.0.8" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz#32abc8a5c624bc4e46c43d84dfb8b26d33a96f58" @@ -71,14 +119,19 @@ integrity sha512-bHYubuItSN/DGYo36aDu7xJiJmK52JOSHs4MK+KbceAtwS20BCWadRgtpQ3iZ2EXfN/B1T0iCXlNraaNwnpU2w== "@sapphire/async-queue@^1.1.9": - version "1.1.9" - resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.1.9.tgz#ce69611c8753c4affd905a7ef43061c7eb95c01b" - integrity sha512-CbXaGwwlEMq+l1TRu01FJCvySJ1CEFKFclHT48nIfNeZXaAAmmwwy7scUKmYHPUa3GhoMp6Qr1B3eAJux6XgOQ== + version "1.2.0" + resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.2.0.tgz#7a56afd318101d338433d7180ebd6af349243268" + integrity sha512-O5ND5Ljpef86X5oy8zXorQ754TMjWALcPSAgPBu4+76HLtDTrNoDyzU2uGE2G4A8Wv51u0MXHzGQ0WZ4GMtpIw== + +"@sapphire/snowflake@^3.0.1": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.1.0.tgz#1c2d9781d3b1de0bcb02b7079898947ce754d394" + integrity sha512-K+OiqXSx4clIaXcoaghrCV56zsm3bZZ5SBpgJkgvAKegFFdETMntHviUfypjt8xVleIuDaNyQA4APOIl3BMcxg== "@sindresorhus/is@^4.0.1", "@sindresorhus/is@^4.2.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.3.0.tgz#344fd9bf808a84567ba563d00cc54b2f428dbab1" - integrity sha512-wwOvh0eO3PiTEivGJWiZ+b946SlMSb4pe+y+Ur/4S87cwo09pYi+FWHHnbrM3W9W7cBYKDqQXcrFYjYUCOJUEQ== + version "4.4.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.4.0.tgz#e277e5bdbdf7cb1e20d320f02f5e2ed113cd3185" + integrity sha512-QppPM/8l3Mawvh4rn9CNEYIU9bxpXUCRMaX9yUpvBk1nMKusLKpfXGDEKExKaPhLzcn3lzil7pR6rnJ11HgeRQ== "@types/bluebird@^3.5.36": version "3.5.36" @@ -99,9 +152,9 @@ form-data "^3.0.0" "@types/node@*": - version "17.0.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.12.tgz#f7aa331b27f08244888c47b7df126184bc2339c5" - integrity sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA== + version "17.0.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.13.tgz#5ed7ed7c662948335fcad6c412bb42d99ea754e3" + integrity sha512-Y86MAxASe25hNzlDbsviXl8jQHb0RDvKt4c40ZJQ1Don0AAL0STLZSs4N+6gLEO55pedy7r2cLwS+ZDxPm/2Bw== "@types/node@^16.11.6": version "16.11.21" @@ -128,7 +181,7 @@ resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.1.tgz#cdab1b4779f6b1718a08de89d92d2603b71950cb" integrity sha512-I6OUIZ5cYRk5lp14xSOAiXjWrfVoMZVjDuevBYgQDYzZIjsf2CAISpEcXOkFAtpAHbmWIDLcZObejqny/9xq5Q== -"@types/ws@^8.2.2": +"@types/ws@^8.2.0", "@types/ws@^8.2.2": version "8.2.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg== @@ -198,6 +251,13 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -223,6 +283,13 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +bufferutil@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.6.tgz#ebd6c67c7922a0e902f053e5d8be5ec850e48433" + integrity sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw== + dependencies: + node-gyp-build "^4.3.0" + call-bind@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -404,7 +471,7 @@ discord-api-types@^0.24.0: resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.24.0.tgz#9e429b8a1ddb4147134dfb3109093422de7ec549" integrity sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A== -discord-api-types@^0.26.0: +discord-api-types@^0.26.0, discord-api-types@^0.26.1: version "0.26.1" resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.26.1.tgz#726f766ddc37d60da95740991d22cb6ef2ed787b" integrity sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ== @@ -453,6 +520,13 @@ enabled@2.0.x: resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== +erlpack@discord/erlpack: + version "0.1.3" + resolved "https://codeload.github.com/discord/erlpack/tar.gz/3b793a333dd3f6a140b9168ea91e9fa9660753ce" + dependencies: + bindings "^1.5.0" + nan "^2.15.0" + fast-safe-stringify@^2.0.7: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" @@ -463,6 +537,11 @@ fecha@^4.2.0: resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.1.tgz#0a83ad8f86ef62a091e22bb5a039cd03d23eecce" integrity sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q== +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -657,6 +736,18 @@ kuler@^2.0.0: resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== +libsodium-wrappers@^0.7.9: + version "0.7.9" + resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.9.tgz#4ffc2b69b8f7c7c7c5594a93a4803f80f6d0f346" + integrity sha512-9HaAeBGk1nKTRFRHkt7nzxqCvnkWTjn1pdjKgcUnZxj0FyOP4CnhgFhMdrFfgNsukijBGyBLpP2m2uKT1vuWhQ== + dependencies: + libsodium "^0.7.0" + +libsodium@^0.7.0: + version "0.7.9" + resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.9.tgz#4bb7bcbf662ddd920d8795c227ae25bbbfa3821b" + integrity sha512-gfeADtR4D/CM0oRUviKBViMGXZDgnFdMKMzHsvBdqLBHd9ySi6EtYnmuhHVDDYgYpAO8eU8hEY+F8vIUAPh08A== + lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" @@ -761,11 +852,16 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nan@^2.15.0: +nan@^2.14.0, nan@^2.15.0: version "2.15.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== +node-addon-api@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" + integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== + node-fetch@^2.6.1, node-fetch@^2.6.5: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -773,6 +869,11 @@ node-fetch@^2.6.1, node-fetch@^2.6.5: dependencies: whatwg-url "^5.0.0" +node-gyp-build@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" + integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== + node-osu@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/node-osu/-/node-osu-2.2.1.tgz#2dcf9bef2e92490cab7a215842559f003d702ac1" @@ -861,6 +962,23 @@ ping@^0.4.1: q "1.x" underscore "^1.12.0" +play-audio@^0.4.3: + version "0.4.9" + resolved "https://registry.yarnpkg.com/play-audio/-/play-audio-0.4.9.tgz#74f3ad053d02e0464fce1a28887b2b99da75a76b" + integrity sha512-An88OAvbCcFZH00n9eq6+t2LmJUPJgBCz8ycKXdvUWQz3GcZ3U4o1ZsSHfVFIuoiKuovX+ue5gKNcfSs4ophew== + +play-dl@^1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/play-dl/-/play-dl-1.8.2.tgz#daeb59cb40399d45384885346ebce32285338ab8" + integrity sha512-W42wgAqTHC+LvVmUIMiSMKqh834XfI3xz+AjVUSGwOYWYQnMIL+OxEJfttRHQeDKI3KIPOAKFC/5i2AyEkVTKg== + dependencies: + play-audio "^0.4.3" + +prism-media@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-1.3.2.tgz#a1f04423ec15d22f3d62b1987b6a25dc49aad13b" + integrity sha512-L6UsGHcT6i4wrQhFF1aPK+MNYgjRqR2tUoIqEY+CG1NqVkMjPRKzS37j9f8GiYPlD6wG9ruBj+q5Ax+bH8Ik1g== + prisma@^3.3.0: version "3.8.1" resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.8.1.tgz#44395cef7cbb1ea86216cb84ee02f856c08a7873" @@ -929,6 +1047,11 @@ safe-stable-stringify@^1.1.0: resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz#c8a220ab525cd94e60ebf47ddc404d610dc5d84a" integrity sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw== +safe-stable-stringify@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz#ab67cbe1fe7d40603ca641c5e765cb942d04fc73" + integrity sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg== + semver@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -1071,6 +1194,11 @@ text-hex@1.0.x: resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== +tiny-typed-emitter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5" + integrity sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -1161,6 +1289,13 @@ underscore@^1.12.0: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.2.tgz#276cea1e8b9722a8dbed0100a407dda572125881" integrity sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g== +utf-8-validate@^5.0.8: + version "5.0.8" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.8.tgz#4a735a61661dbb1c59a0868c397d2fe263f14e58" + integrity sha512-k4dW/Qja1BYDl2qD4tOMB9PFVha/UJtxTc1cXYOe3WwA/2m0Yn4qB7wLMpJyLJ/7DR0XnTut3HsCSzDT4ZvKgA== + dependencies: + node-gyp-build "^4.3.0" + util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -1206,9 +1341,9 @@ winston-transport@^4.4.2: triple-beam "^1.2.0" winston@^3.3.3: - version "3.4.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.4.0.tgz#7080f24b02a0684f8a37f9d5c6afb1ac23e95b84" - integrity sha512-FqilVj+5HKwCfIHQzMxrrd5tBIH10JTS3koFGbLVWBODjiIYq7zir08rFyBT4rrTYG/eaTqDcfSIbcjSM78YSw== + version "3.5.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.5.0.tgz#b1ef4dbc6d1a7c1b462650070f171abc7ce9eac0" + integrity sha512-OQMbmLsIdVHvm2hSurrYZs+iZNIImXneYJ6pX7LseSMEq20HdTETXiNnNX3FDwN4LB/xDRZLF6JYOY+AI112Kw== dependencies: "@dabh/diagnostics" "^2.0.2" async "^3.2.3" @@ -1216,6 +1351,7 @@ winston@^3.3.3: logform "^2.3.2" one-time "^1.0.0" readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" stack-trace "0.0.x" triple-beam "^1.3.0" winston-transport "^4.4.2" @@ -1225,7 +1361,7 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@^8.4.0: +ws@^8.4.0, ws@^8.4.2: version "8.4.2" resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== @@ -1245,6 +1381,13 @@ yn@3.1.1: resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== +zlib-sync@^0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/zlib-sync/-/zlib-sync-0.1.7.tgz#b0b24f9428e434943e306ad586428ef8baf3ed60" + integrity sha512-UmciU6ZrIwtwPC8noMzq+kGMdiWwNRZ3wC0SbED4Ew5Ikqx14MqDPRs/Pbk+3rZPh5SzsOgUBs1WRE0iieddpg== + dependencies: + nan "^2.14.0" + zod@^3.11.6: version "3.11.6" resolved "https://registry.yarnpkg.com/zod/-/zod-3.11.6.tgz#e43a5e0c213ae2e02aefe7cb2b1a6fa3d7f1f483" From 6a802f028311dd3e4f3782defd8a25e3ad266376 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:09:19 +0700 Subject: [PATCH 02/34] Allow some more interaction type --- src/utils/DiscordMessage.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/DiscordMessage.ts b/src/utils/DiscordMessage.ts index d265efe..8363c00 100644 --- a/src/utils/DiscordMessage.ts +++ b/src/utils/DiscordMessage.ts @@ -65,7 +65,9 @@ export async function sendMessage(channel: TextChannel | DMChannel | BaseGuildTe try { message = await channel.send(options); } catch(error) { - if(typeof user === 'undefined') return; + if(typeof user === 'undefined') { + return Logger.error(`Cannot find available destinations to send the message CID: ${channel.id} C_ERR: ${error}`); + } try { message = await user.send(options); } catch(errorDM) { Logger.error(`Cannot find available destinations to send the message CID: ${channel.id} UID: ${user.id} C_ERR: ${error} DM_ERR: ${errorDM}`); @@ -99,7 +101,7 @@ export async function sendMessageOrInteractionResponse(data: Message | Interacti const isSlashCommand = data instanceof Interaction && data.isCommand(); const isMessage = data instanceof Message; - if(isSlashCommand) { + if(isSlashCommand || (data instanceof Interaction && data.isSelectMenu())) { if(!data.replied) { let message; try { From bc3f79128159dd52b61867fdb522414ca35778cb Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:09:54 +0700 Subject: [PATCH 03/34] Create DiscordMusicPlayer.ts --- src/providers/DiscordMusicPlayer.ts | 288 ++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 src/providers/DiscordMusicPlayer.ts diff --git a/src/providers/DiscordMusicPlayer.ts b/src/providers/DiscordMusicPlayer.ts new file mode 100644 index 0000000..0d59e23 --- /dev/null +++ b/src/providers/DiscordMusicPlayer.ts @@ -0,0 +1,288 @@ +import playdl, { YouTubeVideo, InfoData } from "play-dl"; +import { Message, VoiceChannel, Snowflake, TextChannel, StageChannel, Guild } from "discord.js"; +import { AudioPlayer, VoiceConnection, createAudioPlayer, joinVoiceChannel, createAudioResource, StreamType, VoiceConnectionStatus, AudioPlayerStatus, NoSubscriberBehavior, VoiceConnectionDestroyedState, VoiceConnectionState, DiscordGatewayAdapterCreator, AudioResource } from "@discordjs/voice"; +import { EventEmitter } from "stream"; + +import DiscordProvider from "./Discord"; + +export type ValidTracks = YouTubeVideo; + +export class Queue { + public track: (ValidTracks)[] = []; +} + +export class YouTubeLink { + public videoId: string; + public list?: string; + + constructor(videoId: string, list: string) { + this.videoId = videoId; + this.list = list; + } +} + +export class PlayerPlayingEvent { + public instance: DiscordMusicPlayerInstance; + + constructor(instance: DiscordMusicPlayerInstance) { + this.instance = instance; + } +} + +export class DiscordMusicPlayerInstance { + public queue: Queue; + public player: AudioPlayer; + public textChannel?: TextChannel; + public voiceChannel: (VoiceChannel | StageChannel); + public voiceConnection?: VoiceConnection; + + public readonly events: EventEmitter; + + constructor({ voiceChannel }: { voiceChannel: (VoiceChannel | StageChannel) }) { + this.queue = new Queue(); + this.player = createAudioPlayer({ + behaviors: { + noSubscriber: NoSubscriberBehavior.Pause, + maxMissedFrames: 1000 + } + }); + this.voiceChannel = voiceChannel; + this.events = new EventEmitter(); + + this.player.on(AudioPlayerStatus.Idle, async (oldStage, newStage) => { + //The player stopped + if (newStage.status === AudioPlayerStatus.Idle && oldStage.status !== AudioPlayerStatus.Idle) { + if(this.queue.track.length !== 0) { + this.queue.track.shift(); + if(this.queue.track.length > 0) { + this.playTrack(this.queue.track[0]); + } + } + } + }); + + this.player.on(AudioPlayerStatus.Playing, (oldState: any, newState: any) => { + this.events.emit('playing', new PlayerPlayingEvent(this)); + }); + } + + public isReady() { + if (!this.voiceConnection) return false; + return this.voiceConnection.state.status === VoiceConnectionStatus.Ready; + } + + public isConnected() { + if (!this.voiceConnection) return false; + + if(this.voiceConnection.state.status === VoiceConnectionStatus.Destroyed) return false; + if(this.voiceConnection.state.status === VoiceConnectionStatus.Disconnected) return false; + + return true; + } + + public joinVoiceChannel(voiceChannel: (VoiceChannel | StageChannel), textChannel?: TextChannel) { + const permissions = voiceChannel.permissionsFor(DiscordProvider.client.user!); + if (!permissions) + throw new Error("No permissions"); + + if (!voiceChannel.joinable || !permissions.has("CONNECT")) + throw new Error("No permissions"); + + if(textChannel) + this.textChannel = textChannel; + + this.voiceConnection = joinVoiceChannel({ + channelId: this.voiceChannel.id, + guildId: this.voiceChannel.guild.id, + adapterCreator: this.voiceChannel.guild.voiceAdapterCreator + }); + + this.voiceConnection.on(VoiceConnectionStatus.Ready, (oldState: VoiceConnectionState, newState: VoiceConnectionState) => { + let currentVC = DiscordProvider.client.guilds.cache.get(voiceChannel.guild.id)?.me?.voice.channel; + if(currentVC && currentVC.id !== this.voiceChannel.id) { + this.voiceChannel = currentVC; + } + }); + } + + public async leaveVoiceChannel() { + + if(this.player) + this.player.pause(); + + if(DiscordProvider.client.guilds.cache.get(this.voiceChannel.guild.id)?.me?.voice) { + await DiscordProvider.client.guilds.cache.get(this.voiceChannel.guild.id)?.me?.voice.disconnect(); + } + + } + + public addTrackToQueue(track: (ValidTracks)) { + + if(this.queue.track.length === 0) { + this.queue.track.push(track); + this.playTrack(this.queue.track[0]); + return; + } + + this.queue.track.push(track); + } + + public async playTrack(track: ValidTracks) { + + if(!this.voiceConnection) throw new Error("No voice connection"); + + const stream = await playdl.stream(track.url); + const resource = createAudioResource(stream.stream, { + inputType: stream.type + }); + + this.player.play(resource) + this.voiceConnection.subscribe(this.player) + } + + public async skipTrack() { + if(!this.voiceConnection) throw new Error("No voice connection"); + + if(this.queue.track.length > 1) { + this.queue.track.shift(); + this.playTrack(this.queue.track[0]); + } + } + + public async destroy() { + await this.leaveVoiceChannel(); + + if(this.voiceConnection) { + this.voiceConnection.removeAllListeners(); + if(this.voiceConnection.state.status !== 'destroyed') + this.voiceConnection.destroy(); + } + + if(this.player) { + this.player.removeAllListeners(); + this.player.stop(true); + } + + this.queue.track = []; + this.textChannel = undefined; + this.voiceConnection = undefined; + } + +} + +class DiscordMusicPlayer { + + public GuildQueue = new Map(); + + public getGuildInstance(guildId: Snowflake): (DiscordMusicPlayerInstance | null) { + if(!this.isGuildInstanceExists(guildId)) return null; + return this.GuildQueue.get(guildId); + } + + public isGuildInstanceExists(guildId: Snowflake) { + return this.GuildQueue.has(guildId); + } + + public createGuildInstance(guildId: Snowflake, voiceChannel: (VoiceChannel | StageChannel)) { + this.GuildQueue.set(guildId, new DiscordMusicPlayerInstance({ voiceChannel })); + } + + public async destoryGuildInstance(guild: (Guild | Snowflake)) { + + let guildId: Snowflake = guild instanceof Guild ? guild.id : guild; + + if(this.isGuildInstanceExists(guildId)) { + await this.GuildQueue.get(guildId).destroy(); + this.GuildQueue.delete(guildId); + } + } + + public async searchYouTubeByQuery(query: string) { + const searched: YouTubeVideo[] = await playdl.search(query, { source: { youtube: "video" } }); + if (searched.length == 0) return null; + return searched; + } + + public async searchYouTubeByYouTubeLink(youtubeLink: YouTubeLink) { + + // Search the url + const searched: YouTubeVideo[] = await playdl.search("https://www.youtube.com/watch?v=" + youtubeLink.videoId, { source: { youtube: "video" } }); + for (let video of searched) { + if (video.id === youtubeLink.videoId) + return video; + } + + // Serch the video Id + const searched2: YouTubeVideo[] = await playdl.search(youtubeLink.videoId, { source: { youtube: "video" } }); + for (let video of searched2) { + if (video.id === youtubeLink.videoId) + return video; + } + + // Last resort, search the title + const videoInfo = await playdl.video_basic_info("https://www.youtube.com/watch?v=" + youtubeLink.videoId); + if(videoInfo?.video_details?.title) { + const searched: YouTubeVideo[] = await playdl.search(videoInfo.video_details.title, { source: { youtube: "video" } }); + for (let video of searched) { + if (video.id === youtubeLink.videoId) + return video; + } + } + + // TODO: Construct our own YouTubeVideo for unlisted videos or somehow the search failed + + return null; + } + + public isYouTubeLink(link: string): boolean { + try { + this.parseYouTubeLink(link); + return true; + } catch (err) { + return false; + } + } + + public parseYouTubeLink(query: string): YouTubeLink { + if (query.startsWith('https://www.youtube.com/watch?v=') || query.startsWith('http://www.youtube.com/watch?v=')) { + let data = this.parseURLQuery(query); + if (!data.v) + throw new Error('YouTube link is invalid'); + return { + videoId: data.v, + list: (data.list ? data.list : undefined) + } + } + else if (query.startsWith('https://youtu.be/') || query.startsWith('http://youtu.be/')) { + //Get youtube video id after the url + let videoId = query.split('/')[3]; + + if (!videoId) throw new Error('YouTube link is invalid'); + if (query.split('/')[4]) throw new Error('YouTube link is invalid'); + + return { + videoId: videoId + } + } + else { + throw new Error('YouTube link is invalid'); + } + } + + private parseURLQuery(query: string) { + let queryObject: any = {}; + if (query.indexOf('?') >= 0) { + let queryString = query.split('?')[1]; + let queryArray = queryString.split('&'); + for (let i = 0; i < queryArray.length; i++) { + let queryPair = queryArray[i].split('='); + queryObject[queryPair[0]] = queryPair[1]; + } + } + return queryObject; + } + +} + +const DiscordMusicPlayer_Instance = new DiscordMusicPlayer(); +export default DiscordMusicPlayer_Instance; \ No newline at end of file From 8f7939b27a2489601ce5bc6745b2bf5fa09cd5cd Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:10:17 +0700 Subject: [PATCH 04/34] Add voice intents and voice modules --- src/providers/Discord.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/providers/Discord.ts b/src/providers/Discord.ts index 748f154..30ac372 100644 --- a/src/providers/Discord.ts +++ b/src/providers/Discord.ts @@ -13,6 +13,13 @@ import Discord_InteractionManager from "../discord/InteractionManager"; import Discord_MembershipScreening from "../discord/MembershipScreening"; import Discord_osu from "../discord/osu"; import Discord_UserInfo from "../discord/UserInfo"; +import Discord_MusicPlayer_Play from "../discord/MusicPlayer/Play"; +import Discord_MusicPlayer_Skip from "../discord/MusicPlayer/Skip"; +import Discord_MusicPlayer_Join from "../discord/MusicPlayer/Join"; +import Discord_MusicPlayer_Leave from "../discord/MusicPlayer/Leave"; +import Discord_MusicPlayer_Queue from "../discord/MusicPlayer/Queue"; +import Discord_MusicPlayer_Search from "../discord/MusicPlayer/Search"; + import Discord_Developer_ServiceAnnouncement from "../discord/developer/ServiceAnnouncement" import Cache from "./Cache"; @@ -24,8 +31,8 @@ class Discord { constructor () { this.client = new Client({ - partials: ['MESSAGE', 'CHANNEL', 'REACTION'], - intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MEMBERS, Intents.FLAGS.GUILD_PRESENCES] + //partials: ['MESSAGE', 'CHANNEL', 'REACTION'], + intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MEMBERS, Intents.FLAGS.GUILD_PRESENCES, Intents.FLAGS.GUILD_VOICE_STATES] }); this.loaded_module = {}; } @@ -46,6 +53,13 @@ class Discord { this.loaded_module["Discord_osu"] = new Discord_osu(); this.loaded_module["Discord_UserInfo"] = new Discord_UserInfo(); + this.loaded_module["Discord_MusicPlayer_Play"] = new Discord_MusicPlayer_Play(); + this.loaded_module["Discord_MusicPlayer_Skip"] = new Discord_MusicPlayer_Skip(); + this.loaded_module["Discord_MusicPlayer_Join"] = new Discord_MusicPlayer_Join(); + this.loaded_module["Discord_MusicPlayer_Leave"] = new Discord_MusicPlayer_Leave(); + this.loaded_module["Discord_MusicPlayer_Queue"] = new Discord_MusicPlayer_Queue(); + this.loaded_module["Discord_MusicPlayer_Search"] = new Discord_MusicPlayer_Search(); + this.loaded_module["MembershipScreening"] = new Discord_MembershipScreening(); this.loaded_module["Discord_Developer_ServiceAnnouncement"] = new Discord_Developer_ServiceAnnouncement(); From cc45ac8aafc4b1d4fe74068ac4a5ce2ccf7df2ec Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:10:28 +0700 Subject: [PATCH 05/34] Create Skip.ts --- src/discord/MusicPlayer/Skip.ts | 61 +++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/discord/MusicPlayer/Skip.ts diff --git a/src/discord/MusicPlayer/Skip.ts b/src/discord/MusicPlayer/Skip.ts new file mode 100644 index 0000000..fc1d5ad --- /dev/null +++ b/src/discord/MusicPlayer/Skip.ts @@ -0,0 +1,61 @@ +import { Message, CommandInteraction, Interaction, VoiceChannel } from "discord.js"; +import { getEmotes, makeSuccessEmbed, makeProcessingEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; +import DiscordProvider from "../../providers/Discord"; +import Users from "../../services/Users" +import Environment from "../../providers/Environment"; +import DiscordMusicPlayer from "../../providers/DiscordMusicPlayer"; + +const EMBEDS = { + SKIPPED: (data: Message | Interaction) => { + return makeSuccessEmbed({ + title: `Skipped`, + user: (data instanceof Interaction) ? data.user : data.author + }); + } +} + +export default class Skip { + + async onCommand(command: string, args: any, message: Message) { + if (command.toLowerCase() !== 'skip') return; + await this.process(message, args); + } + + async interactionCreate(interaction: CommandInteraction) { + if (interaction.isCommand()) { + if (typeof interaction.commandName === 'undefined') return; + if ((interaction.commandName).toLowerCase() !== 'skip') return; + await this.process(interaction, interaction.options); + } + } + + async process(data: Interaction | Message, args: any) { + const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); + const isMessage = data instanceof Message; + + if (!isSlashCommand && !isMessage) return; + + if (!data.member) return; + if (!data.guildId) return; + + const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel; + if (!channel) return; + + + // TODO: User must be in vc error msg + if (!data.member.voice.channel) return; + let voiceChannel = data.member.voice.channel; + + // TODO: No queue error + if(!DiscordMusicPlayer.isGuildInstanceExists(data.guildId)) { + return; + } + + const instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + instance!.skipTrack(); + + //await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SKIPPED(data)] }); + + return; + } +} \ No newline at end of file From 4f247eaa5a1b3a2a3ea657c509b3a2edc52b5a79 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:10:32 +0700 Subject: [PATCH 06/34] Create Search.ts --- src/discord/MusicPlayer/Search.ts | 141 ++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 src/discord/MusicPlayer/Search.ts diff --git a/src/discord/MusicPlayer/Search.ts b/src/discord/MusicPlayer/Search.ts new file mode 100644 index 0000000..8ad8607 --- /dev/null +++ b/src/discord/MusicPlayer/Search.ts @@ -0,0 +1,141 @@ +import { Message, CommandInteraction, Interaction, VoiceChannel, MessageActionRow, Permissions, DMChannel, TextChannel, MessageSelectMenu, MessageSelectOptionData } from "discord.js"; +import { makeErrorEmbed, makeSuccessEmbed, makeProcessingEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; +import DiscordProvider from "../../providers/Discord"; +import Environment from "../../providers/Environment"; +import DiscordMusicPlayer, { ValidTracks } from "../../providers/DiscordMusicPlayer"; +import Prisma from "../../providers/Prisma"; +import { joinVoiceChannelProcedure } from "./Join"; + +const EMBEDS = { + SEARCH_RESULT: (data: Message | Interaction, result: ValidTracks[]) => { + return makeInfoEmbed({ + title: `Search result`, + description: `${result.length} results found\n\n${result.map(track => `- [${track.title}](${track.url})`).join('\n')}`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + INVALID_LINK: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `Invalid Link`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + ADDED_QUEUE: (data: Message | Interaction, track: ValidTracks) => { + let embed = makeSuccessEmbed({ + title: `Song added to queue!`, + description: `${track.title}`, + user: (data instanceof Interaction) ? data.user : data.author + }); + embed.setImage(track.thumbnails[0].url); + return embed; + } +} + + +export default class Search { + + async onCommand(command: string, args: any, message: Message) { + if (command.toLowerCase() !== 'search') return; + await this.process(message, args); + } + + async interactionCreate(interaction: CommandInteraction) { + if (interaction.isCommand()) { + if (typeof interaction.commandName === 'undefined') return; + if ((interaction.commandName).toLowerCase() !== 'search') return; + await this.process(interaction, interaction.options); + } + } + + async process(data: Interaction | Message, args: any) { + const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); + const isMessage = data instanceof Message; + + if (!isSlashCommand && !isMessage) return; + + if (!data.member) return; + if (!data.guild) return; + if (!data.guildId) return; + + let query; + + if (isMessage) { + if (data === null || !data.guildId || data.member === null || data.guild === null) return; + + if (args.length === 0) { + // TODO: Show info + //return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.MSINFO(data)] }); + return; + } + query = args[0]; + + } + else if (isSlashCommand) { + query = args.getSubcommand(); + } + + if (!query) return; + if (!(data.channel instanceof TextChannel)) return; + + // TODO: User must be in vc error msg + if (!data.member.voice.channel) return; + let voiceChannel = data.member.voice.channel; + + if (!DiscordMusicPlayer.isGuildInstanceExists(data.guildId)) { + await joinVoiceChannelProcedure(data, null, voiceChannel); + } + + let instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + + if (!instance!.isConnected()) { + await joinVoiceChannelProcedure(data, instance!, voiceChannel); + instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + } + + if (!instance) return; + + let result = await DiscordMusicPlayer.searchYouTubeByQuery(query); + + if (!result) return; + + const menuOptions: MessageSelectOptionData[] = []; + + const messageSelectMenu = new MessageSelectMenu(); + /* + Discord have 100 char custom id char limit + So we need to shorten our json. + MP_SM stands for MusicPlayer_SearchMenu + data.r stands for data.requester + data.v stands for data.voiceChannel + */ + messageSelectMenu.setCustomId(JSON.stringify({ + module: 'MP_SM', + action: 'play', + data: { + r: data.member.id, + v: voiceChannel.id + } + })) + messageSelectMenu.setPlaceholder('Nothing selected'); + + for(let song of result) { + if(!song.title) continue; + menuOptions.push({ + label: song.title, + description: `${song.durationInSec} sec | ${song.views} views`, + value: song.url + }); + } + + messageSelectMenu.addOptions(menuOptions); + + const row = new MessageActionRow(); + row.addComponents(messageSelectMenu); + + await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SEARCH_RESULT(data, result)], components: [row] }); + + return; + } + + +} \ No newline at end of file From 997716383e3c8d5d1a4edbc8c67233a61a303d46 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:10:36 +0700 Subject: [PATCH 07/34] Create Queue.ts --- src/discord/MusicPlayer/Queue.ts | 67 ++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/discord/MusicPlayer/Queue.ts diff --git a/src/discord/MusicPlayer/Queue.ts b/src/discord/MusicPlayer/Queue.ts new file mode 100644 index 0000000..82f936a --- /dev/null +++ b/src/discord/MusicPlayer/Queue.ts @@ -0,0 +1,67 @@ +import { Message, CommandInteraction, Interaction, VoiceChannel, TextChannel } from "discord.js"; +import { getEmotes, makeSuccessEmbed, makeProcessingEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; +import DiscordProvider from "../../providers/Discord"; +import Users from "../../services/Users" +import Environment from "../../providers/Environment"; +import DiscordMusicPlayer, { DiscordMusicPlayerInstance, ValidTracks, Queue } from "../../providers/DiscordMusicPlayer"; + +const EMBEDS = { + QUEUE: (data: Message | Interaction, queue: Queue) => { + if(!queue.track[0]) + return makeInfoEmbed({ + title: `Queue`, + description: `The queue is empty!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + else if(queue.track.length == 1) + return makeInfoEmbed({ + title: `Queue`, + description: `Now playing: ${queue.track[0].title}\n\nThe queue is empty!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + else + return makeInfoEmbed({ + title: `Queue`, + description: `Now playing: ${queue.track[0].title}\n\nUpcoming song: ${queue.track[1].title}\n\nThere are ${Object.keys(queue).length} songs in the queue!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + } +} + +export default class QueueCommand { + + async onCommand(command: string, args: any, message: Message) { + if(command.toLowerCase() !== 'queue') return; + await this.process(message, args); + } + + async interactionCreate(interaction: CommandInteraction) { + if(interaction.isCommand()) { + if(typeof interaction.commandName === 'undefined') return; + if((interaction.commandName).toLowerCase() !== 'queue') return; + await this.process(interaction, interaction.options); + } + } + + async process(data: Interaction | Message, args: any) { + const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); + const isMessage = data instanceof Message; + + if(!isSlashCommand && !isMessage) return; + + if(!data.member) return; + if(!data.guildId) return; + if(!(data.channel instanceof TextChannel)) return; + + const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel; + if(!channel) return; + + + const instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + if(!instance) return; + + await sendMessageOrInteractionResponse(data, { embeds:[EMBEDS.QUEUE(data, instance.queue)] }); + + return; + } +} \ No newline at end of file From 2f2649f435cade24faf372e6064b859baef854e4 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:10:44 +0700 Subject: [PATCH 08/34] Create Play.ts --- src/discord/MusicPlayer/Play.ts | 181 ++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 src/discord/MusicPlayer/Play.ts diff --git a/src/discord/MusicPlayer/Play.ts b/src/discord/MusicPlayer/Play.ts new file mode 100644 index 0000000..336503c --- /dev/null +++ b/src/discord/MusicPlayer/Play.ts @@ -0,0 +1,181 @@ +import { Message, CommandInteraction, Interaction, VoiceChannel, MessageActionRow, Permissions, DMChannel, TextChannel } from "discord.js"; +import { makeErrorEmbed, makeSuccessEmbed, makeProcessingEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; +import DiscordProvider from "../../providers/Discord"; +import Environment from "../../providers/Environment"; +import DiscordMusicPlayer, { ValidTracks } from "../../providers/DiscordMusicPlayer"; +import Prisma from "../../providers/Prisma"; +import { joinVoiceChannelProcedure } from "./Join"; + +const EMBEDS = { + YOUTUBE_ONLY: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `Only youtube.com link is supported for now`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + INVALID_LINK: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `Invalid Link`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + ADDED_QUEUE: (data: Message | Interaction, track: ValidTracks) => { + let embed = makeSuccessEmbed({ + title: `Song added to queue!`, + description: `${track.title}`, + user: (data instanceof Interaction) ? data.user : data.author + }); + embed.setImage(track.thumbnails[0].url); + return embed; + } +} + + +export default class Play { + + async onCommand(command: string, args: any, message: Message) { + if (command.toLowerCase() !== 'play' && command.toLowerCase() !== 'p') return; + await this.process(message, args); + } + + async interactionCreate(interaction: Interaction) { + if (interaction.isCommand()) { + if (typeof interaction.commandName === 'undefined') return; + if ((interaction.commandName).toLowerCase() !== 'play') return; + await this.process(interaction, interaction.options); + } + else if (interaction.isSelectMenu()) { + if(!interaction.guild || !interaction.guildId) return; + if (!this.tryParseJSONObject(interaction.customId)) return; + + let payload = JSON.parse(interaction.customId); + + /* + Discord have 100 char custom id char limit + So we need to shorten our json. + MP_SM stands for MusicPlayer_SearchMenu + data.r stands for data.requester + data.v stands for data.voiceChannel + */ + if (typeof payload.module === 'undefined' || + typeof payload.action === 'undefined' || + payload.module !== 'MP_SM' || + payload.action !== 'play') return; + + interaction.deferReply(); + + let voiceChannel = interaction.guild.channels.cache.get(payload.data.v); + let member = interaction.guild.members.cache.get(payload.data.r); + + if(!member) return; + if (!voiceChannel || !(voiceChannel instanceof VoiceChannel)) return; + if (!member.voice.channel) return; + + if (!DiscordMusicPlayer.isGuildInstanceExists(interaction.guildId)) { + await joinVoiceChannelProcedure(interaction, null, voiceChannel); + } + + let instance = DiscordMusicPlayer.getGuildInstance(interaction.guildId); + + if (!instance!.isConnected()) { + await joinVoiceChannelProcedure(interaction, instance!, voiceChannel); + instance = DiscordMusicPlayer.getGuildInstance(interaction.guildId); + } + + if (!instance) return; + + let result = await DiscordMusicPlayer.searchYouTubeByYouTubeLink(DiscordMusicPlayer.parseYouTubeLink(interaction.values[0])); + if (!result) return; + + instance.addTrackToQueue(result); + //interaction.editReply({ embeds: [EMBEDS.ADDED_QUEUE(interaction, result)] }); + return await sendMessageOrInteractionResponse(interaction, { embeds: [EMBEDS.ADDED_QUEUE(interaction, result)] }, true); + } + } + + async process(data: Interaction | Message, args: any) { + const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); + const isMessage = data instanceof Message; + + if (!isSlashCommand && !isMessage) return; + + if (!data.member) return; + if (!data.guild) return; + if (!data.guildId) return; + + let query; + + if (isMessage) { + if (data === null || !data.guildId || data.member === null || data.guild === null) return; + + if (args.length === 0) { + // TODO: Show info + //return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.MSINFO(data)] }); + return; + } + query = args[0]; + + } + else if (isSlashCommand) { + query = args.getSubcommand(); + } + + if (!query) return; + if (!(data.channel instanceof TextChannel)) return; + + // TODO: User must be in vc error msg + if (!data.member.voice.channel) return; + let voiceChannel = data.member.voice.channel; + + if (!DiscordMusicPlayer.isGuildInstanceExists(data.guildId)) { + await joinVoiceChannelProcedure(data, null, voiceChannel); + } + + let instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + + if (!instance!.isConnected()) { + await joinVoiceChannelProcedure(data, instance!, voiceChannel); + instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + } + + if (!instance) return; + + if (DiscordMusicPlayer.isYouTubeLink(query)) { + let result = await DiscordMusicPlayer.searchYouTubeByYouTubeLink(DiscordMusicPlayer.parseYouTubeLink(query)); + + if (!result) return; + + if (isMessage && (data as Message).embeds.length > 0) { + (data as Message).suppressEmbeds(true); + } + + instance.addTrackToQueue(result); + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.ADDED_QUEUE(data, result)] }); + } else { + let result = await DiscordMusicPlayer.searchYouTubeByQuery(query); + + if (!result) return; + instance.addTrackToQueue(result[0]); + + // TODO: Let user choose the video + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.ADDED_QUEUE(data, result[0])] }); + } + + return; + } + + tryParseJSONObject(jsonString: string) { + try { + let o = JSON.parse(jsonString); + if (o && typeof o === "object") { + return true; + //return o; + } + } + catch (e) { } + + return false; + } + + +} \ No newline at end of file From 24b78c193a32fc14dac19751c4957a2f7d57b706 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:10:47 +0700 Subject: [PATCH 09/34] Create Leave.ts --- src/discord/MusicPlayer/Leave.ts | 55 ++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/discord/MusicPlayer/Leave.ts diff --git a/src/discord/MusicPlayer/Leave.ts b/src/discord/MusicPlayer/Leave.ts new file mode 100644 index 0000000..64a4138 --- /dev/null +++ b/src/discord/MusicPlayer/Leave.ts @@ -0,0 +1,55 @@ +import { Message, CommandInteraction, Interaction, VoiceChannel } from "discord.js"; +import { getEmotes, makeSuccessEmbed, makeProcessingEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; +import DiscordProvider from "../../providers/Discord"; +import Users from "../../services/Users" +import Environment from "../../providers/Environment"; +import DiscordMusicPlayer, { DiscordMusicPlayerInstance } from "../../providers/DiscordMusicPlayer"; + +const EMBEDS = { + VOICECHANNEL_LEFT: (data: Message | Interaction) => { + return makeSuccessEmbed({ + title: `Success`, + description: `Left the voice channel`, + user: (data instanceof Interaction) ? data.user : data.author + }); + } +} + +export default class Leave { + + async onCommand(command: string, args: any, message: Message) { + if(command.toLowerCase() !== 'leave') return; + await this.process(message, args); + } + + async interactionCreate(interaction: CommandInteraction) { + if(interaction.isCommand()) { + if(typeof interaction.commandName === 'undefined') return; + if((interaction.commandName).toLowerCase() !== 'leave') return; + await this.process(interaction, interaction.options); + } + } + + async process(data: Interaction | Message, args: any) { + const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); + const isMessage = data instanceof Message; + + if(!isSlashCommand && !isMessage) return; + + if(!data.member) return; + if(!data.guildId) return; + + const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel; + if(!channel) return; + + + let instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + if(!instance) return; + + + DiscordMusicPlayer.destoryGuildInstance(data.guild?.id!); + await sendMessageOrInteractionResponse(data, { embeds:[EMBEDS.VOICECHANNEL_LEFT(data)] }); + + return; + } +} \ No newline at end of file From f2063efa0b8268b6ec6bf037c9233bdccfa1d5c3 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:10:52 +0700 Subject: [PATCH 10/34] Create Join.ts --- src/discord/MusicPlayer/Join.ts | 219 ++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 src/discord/MusicPlayer/Join.ts diff --git a/src/discord/MusicPlayer/Join.ts b/src/discord/MusicPlayer/Join.ts new file mode 100644 index 0000000..e025457 --- /dev/null +++ b/src/discord/MusicPlayer/Join.ts @@ -0,0 +1,219 @@ +import { Message, CommandInteraction, Interaction, VoiceChannel, Permissions, GuildMember, DMChannel, StageChannel, MessageActionRow, MessageButton, MessageAttachment, TextChannel } from "discord.js"; +import { getEmotes, makeSuccessEmbed, makeErrorEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; +import DiscordProvider from "../../providers/Discord"; +import Users from "../../services/Users" +import Environment from "../../providers/Environment"; +import DiscordMusicPlayer, { PlayerPlayingEvent, ValidTracks, DiscordMusicPlayerInstance } from "../../providers/DiscordMusicPlayer"; +import Discord from "../../providers/Discord"; + +const EMBEDS = { + VOICECHANNEL_JOINED: (data: Message | Interaction) => { + return makeSuccessEmbed({ + title: `Success`, + description: `Joined voice channel`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + VOICECHANNEL_INUSE: (data: Message | Interaction) => { + + let haveForceMove = (data instanceof Message) ? (data as Message).member!.permissions.has([Permissions.FLAGS.MOVE_MEMBERS]) : ((data as Interaction).member! as GuildMember).permissions.has([Permissions.FLAGS.MOVE_MEMBERS]); + + return makeErrorEmbed({ + title: `I can't open portal to parallel universe`, + description: `I can't join mutiple voice channel on the same guild. + I wish I had a superpower, maybe... one day I will. + + Want to move me to your voice channel? You can use the button below (soon)${!haveForceMove ? ` or use move command (soon)` : ", use move command (soon) or just (ab)use me with your admin permissions hehe"}`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + VOICECHANNEL_ALREADY_JOINED: (data: Message | Interaction) => { + return makeInfoEmbed({ + title: `I'm already here`, + description: `Already in the voice channel you are currently connected to`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + NOW_PLAYING: (data: Message | Interaction, track: ValidTracks) => { + const embed = makeInfoEmbed({ + title: ' Now playing', + icon: '🎵', + fields: [ + { + name: `${track.title}`, + value: `\u200B`, + inline: false + } + ], + user: DiscordProvider.client.user + }); + + embed.setImage(track.thumbnails[0].url); + return embed; + } +} + +export async function joinVoiceChannelProcedure (data: Interaction | Message, instance: (DiscordMusicPlayerInstance | null), voiceChannel: (VoiceChannel | StageChannel)) { + + const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); + const isAcceptableInteraction = data instanceof Interaction && data.isSelectMenu(); + const isMessage = data instanceof Message; + if ((!isSlashCommand && !isAcceptableInteraction) && !isMessage) return; + + if (!data.member) return; + if (!data.channel) return; + if (!data.guildId) return; + + if (data.channel instanceof DMChannel) return; + if (!(data.channel instanceof TextChannel)) return; + + const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel; + if (!channel) return; + + + // If already in VoiceChannel + if (DiscordProvider.client.guilds.cache.get(data.guildId!)!.me!.voice.channelId) { + // But, no music instance yet (The bot might just restarted) + if (!instance) { + // User is in different VoiceChannel + if (channel.id !== DiscordProvider.client.guilds.cache.get(data.guildId)?.me?.voice) { + //Disconnect it + await DiscordProvider.client.guilds.cache.get(data.guildId)?.me?.voice.disconnect(); + } + + //Create instance for a new one + DiscordMusicPlayer.createGuildInstance(data.guildId, voiceChannel); + instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + + instance!.joinVoiceChannel(voiceChannel, data.channel); + if(!isAcceptableInteraction) + await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.VOICECHANNEL_JOINED(data)] }); + } + // And, already have instance on the guild + else { + // And, User VoiceChannel is same as the instance + if (channel.id === instance.voiceChannel.id) { + if(!isAcceptableInteraction) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.VOICECHANNEL_ALREADY_JOINED(data)] }); + else return; + } + // But, not the same VoiceChannel + else { + let activeMembers = instance.voiceChannel.members.filter(member => member.user.bot === false); + // And someone else is using the bot + if(activeMembers.size > 0) { + if(!isAcceptableInteraction) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.VOICECHANNEL_INUSE(data)] }); + else return; + } + // And alone in the VoiceChannel + else { + // Move to the new voice channel + await DiscordProvider.client.guilds.cache.get(data.guildId)?.me?.voice.setChannel(voiceChannel); + if(!isAcceptableInteraction) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.VOICECHANNEL_JOINED(data)] }); + else return; + } + } + } + + } + // Not in any VoiceChannel + else { + + if (instance) + await DiscordMusicPlayer.destoryGuildInstance(data.guildId!); + + DiscordMusicPlayer.createGuildInstance(data.guildId, voiceChannel); + instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + + instance!.joinVoiceChannel(voiceChannel, data.channel); + await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.VOICECHANNEL_JOINED(data)] }); + } + + if (!instance) return; + + let nowPlayingMessage: any; + + // Register Event Listeners + instance.events.on('playing', async (event: PlayerPlayingEvent) => { + + const row = new MessageActionRow() + .addComponents( + new MessageButton() + .setEmoji('▶️') + .setLabel(' Open on YouTube') + .setURL(encodeURI(`https://www.youtube.com/watch?v=${event.instance.queue.track[0].id}`)) + .setStyle('LINK'), + ) + + if (event.instance.textChannel) { + await sendMessage(event.instance.textChannel, undefined, { embeds: [EMBEDS.NOW_PLAYING(data, event.instance.queue.track[0])], components: [row] }); + } + }) +} + +export default class Join { + + + async onCommand(command: string, args: any, message: Message) { + if (command.toLowerCase() !== 'join') return; + await this.process(message, args); + } + + async interactionCreate(interaction: CommandInteraction) { + if (interaction.isCommand()) { + if (typeof interaction.commandName === 'undefined') return; + if ((interaction.commandName).toLowerCase() !== 'join') return; + await this.process(interaction, interaction.options); + } + } + + async process(data: Interaction | Message, args: any) { + const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); + const isMessage = data instanceof Message; + + if (!isSlashCommand && !isMessage) return; + + if (!data.member) return; + + this.joinCommands(data, args); + + } + + public async joinCommands(data: Interaction | Message, args: any) { + + const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); + const isMessage = data instanceof Message; + if (!isSlashCommand && !isMessage) return; + + if (!data.member) return; + if (!data.channel) return; + if (!data.guildId) return; + + if (data.channel instanceof DMChannel) return; + if (!(data.channel instanceof TextChannel)) return; + + const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel; + if (!channel) return; + + // TODO: User must be in vc error msg + if (!data.member.voice.channel) return; + let voiceChannel = data.member.voice.channel; + + + + //if(!DiscordMusicPlayer.isGuildInstanceExists(data.guildId)) { + // DiscordMusicPlayer.createGuildInstance(data.guildId, voiceChannel); + //} + + let instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + + await joinVoiceChannelProcedure(data, instance!, voiceChannel); + + //if(!instance.isConnected()) + // instance.joinVoiceChannel(voiceChannel, data.channel ? data.channel : undefined); + + + } +} \ No newline at end of file From 346207fe9ef154a816c65018eb339ceed8bc0945 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:23:46 +0700 Subject: [PATCH 11/34] Downgrade winston, ts compile error --- package.json | 2 +- yarn.lock | 26 ++++++++++---------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 38a0fb6..e860f25 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "typescript": "^4.4.4", "utf-8-validate": "^5.0.8", "validator": "^13.7.0", - "winston": "^3.3.3", + "winston": "3.3.3", "zlib-sync": "^0.1.7" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index cad2e70..eb3fea3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -231,7 +231,7 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -async@^3.2.3: +async@^3.1.0: version "3.2.3" resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== @@ -753,7 +753,7 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= -logform@^2.3.2: +logform@^2.2.0, logform@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/logform/-/logform-2.3.2.tgz#68babe6a74ab09a1fd15a9b1e6cbc7713d41cb5b" integrity sha512-V6JiPThZzTsbVRspNO6TmHkR99oqYTs8fivMBYQkjZj6rxW92KxtDCPE6IkAk1DNBnYKNkjm4jYBm6JDUcyhOA== @@ -1047,11 +1047,6 @@ safe-stable-stringify@^1.1.0: resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz#c8a220ab525cd94e60ebf47ddc404d610dc5d84a" integrity sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw== -safe-stable-stringify@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz#ab67cbe1fe7d40603ca641c5e765cb942d04fc73" - integrity sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg== - semver@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -1331,7 +1326,7 @@ wide-align@^1.1.2: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -winston-transport@^4.4.2: +winston-transport@^4.4.0: version "4.4.2" resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.2.tgz#554efe3fce229d046df006e0e3c411d240652e51" integrity sha512-9jmhltAr5ygt5usgUTQbEiw/7RYXpyUbEAFRCSicIacpUzPkrnQsQZSPGEI12aLK9Jth4zNcYJx3Cvznwrl8pw== @@ -1340,21 +1335,20 @@ winston-transport@^4.4.2: readable-stream "^3.4.0" triple-beam "^1.2.0" -winston@^3.3.3: - version "3.5.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.5.0.tgz#b1ef4dbc6d1a7c1b462650070f171abc7ce9eac0" - integrity sha512-OQMbmLsIdVHvm2hSurrYZs+iZNIImXneYJ6pX7LseSMEq20HdTETXiNnNX3FDwN4LB/xDRZLF6JYOY+AI112Kw== +winston@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170" + integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw== dependencies: "@dabh/diagnostics" "^2.0.2" - async "^3.2.3" + async "^3.1.0" is-stream "^2.0.0" - logform "^2.3.2" + logform "^2.2.0" one-time "^1.0.0" readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" stack-trace "0.0.x" triple-beam "^1.3.0" - winston-transport "^4.4.2" + winston-transport "^4.4.0" wrappy@1: version "1.0.2" From 9875dbdbffa238b89753702d71e25762e25cf11a Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:25:34 +0700 Subject: [PATCH 12/34] Add missing message check --- src/discord/MusicPlayer/Join.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/discord/MusicPlayer/Join.ts b/src/discord/MusicPlayer/Join.ts index e025457..7c3995a 100644 --- a/src/discord/MusicPlayer/Join.ts +++ b/src/discord/MusicPlayer/Join.ts @@ -128,7 +128,8 @@ export async function joinVoiceChannelProcedure (data: Interaction | Message, in instance = DiscordMusicPlayer.getGuildInstance(data.guildId); instance!.joinVoiceChannel(voiceChannel, data.channel); - await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.VOICECHANNEL_JOINED(data)] }); + if(!isAcceptableInteraction) + await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.VOICECHANNEL_JOINED(data)] }); } if (!instance) return; From dffadd6ef39dd4be15a8c3fb2942198608a5a767 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:25:47 +0700 Subject: [PATCH 13/34] Version bump --- src/providers/App.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/App.ts b/src/providers/App.ts index 7fe79e1..4abe42f 100644 --- a/src/providers/App.ts +++ b/src/providers/App.ts @@ -8,7 +8,7 @@ import Configuration from './Configuration'; class App { - public readonly version = `0.05${Environment.get().NODE_ENV === "development" ? ' / Development Build' : ''}`; + public readonly version = `0.07${Environment.get().NODE_ENV === "development" ? ' / Development Build' : ''}`; public loadConfig(): void { Logger.log('info', 'Loading configuration'); From c3d86ffdf0870ba1d173e3c6da38d6aa4b218850 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 20:55:31 +0700 Subject: [PATCH 14/34] Create .env.example --- .env.example | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a73c170 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +NODE_ENV=development +#development +#production + +DATABASE_URL=postgresql://user:password@host:port/dbName?schema=public + +DISCORD_TOKEN=SuperSecretDiscordToken +DEVELOPER_IDS=DEV1_ID,DEV2_ID +PRIVATE_BOT=true + +OSU_API_KEY=SuperSecretosu!Key +YOUTUBE_COOKIE_BASE64=SuperSecretCookie_EncodeItInBase64_AndThen_RemoveTheLastEqualToo \ No newline at end of file From 0759290306a90e5ecd224cf2726e97ddc3840ee0 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 21:35:19 +0700 Subject: [PATCH 15/34] Add YouTube Cookies --- src/providers/DiscordMusicPlayer.ts | 8 ++++++++ src/providers/Environment.ts | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/providers/DiscordMusicPlayer.ts b/src/providers/DiscordMusicPlayer.ts index 0d59e23..95a9701 100644 --- a/src/providers/DiscordMusicPlayer.ts +++ b/src/providers/DiscordMusicPlayer.ts @@ -4,9 +4,17 @@ import { AudioPlayer, VoiceConnection, createAudioPlayer, joinVoiceChannel, crea import { EventEmitter } from "stream"; import DiscordProvider from "./Discord"; +import Environment from "./Environment"; export type ValidTracks = YouTubeVideo; +if (Environment.get().YOUTUBE_COOKIE_BASE64) { + playdl.setToken({ + youtube: { + cookie: Buffer.from(Environment.get().YOUTUBE_COOKIE_BASE64, 'base64').toString() + } + }) +} export class Queue { public track: (ValidTracks)[] = []; } diff --git a/src/providers/Environment.ts b/src/providers/Environment.ts index edfee12..a9e1c11 100644 --- a/src/providers/Environment.ts +++ b/src/providers/Environment.ts @@ -41,6 +41,7 @@ class Environment { const PRIVATE_BOT = process.env.PRIVATE_BOT; const OSU_API_KEY = process.env.OSU_API_KEY; + const YOUTUBE_COOKIE_BASE64 = process.env.YOUTUBE_COOKIE_BASE64; return { NODE_ENV, @@ -49,7 +50,8 @@ class Environment { DEVELOPER_IDS, PRIVATE_BOT, - OSU_API_KEY + OSU_API_KEY, + YOUTUBE_COOKIE_BASE64 }; } From 040eab96efa65fc848bd8270a697ca579019dc6d Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 21:35:56 +0700 Subject: [PATCH 16/34] Support unlisted and direct url for YouTube --- src/providers/DiscordMusicPlayer.ts | 77 +++++++++++++++++++---------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/src/providers/DiscordMusicPlayer.ts b/src/providers/DiscordMusicPlayer.ts index 95a9701..b5e7716 100644 --- a/src/providers/DiscordMusicPlayer.ts +++ b/src/providers/DiscordMusicPlayer.ts @@ -30,7 +30,7 @@ export class YouTubeLink { } export class PlayerPlayingEvent { - public instance: DiscordMusicPlayerInstance; + public instance: DiscordMusicPlayerInstance; constructor(instance: DiscordMusicPlayerInstance) { this.instance = instance; @@ -60,9 +60,9 @@ export class DiscordMusicPlayerInstance { this.player.on(AudioPlayerStatus.Idle, async (oldStage, newStage) => { //The player stopped if (newStage.status === AudioPlayerStatus.Idle && oldStage.status !== AudioPlayerStatus.Idle) { - if(this.queue.track.length !== 0) { + if (this.queue.track.length !== 0) { this.queue.track.shift(); - if(this.queue.track.length > 0) { + if (this.queue.track.length > 0) { this.playTrack(this.queue.track[0]); } } @@ -82,8 +82,8 @@ export class DiscordMusicPlayerInstance { public isConnected() { if (!this.voiceConnection) return false; - if(this.voiceConnection.state.status === VoiceConnectionStatus.Destroyed) return false; - if(this.voiceConnection.state.status === VoiceConnectionStatus.Disconnected) return false; + if (this.voiceConnection.state.status === VoiceConnectionStatus.Destroyed) return false; + if (this.voiceConnection.state.status === VoiceConnectionStatus.Disconnected) return false; return true; } @@ -96,7 +96,7 @@ export class DiscordMusicPlayerInstance { if (!voiceChannel.joinable || !permissions.has("CONNECT")) throw new Error("No permissions"); - if(textChannel) + if (textChannel) this.textChannel = textChannel; this.voiceConnection = joinVoiceChannel({ @@ -107,7 +107,7 @@ export class DiscordMusicPlayerInstance { this.voiceConnection.on(VoiceConnectionStatus.Ready, (oldState: VoiceConnectionState, newState: VoiceConnectionState) => { let currentVC = DiscordProvider.client.guilds.cache.get(voiceChannel.guild.id)?.me?.voice.channel; - if(currentVC && currentVC.id !== this.voiceChannel.id) { + if (currentVC && currentVC.id !== this.voiceChannel.id) { this.voiceChannel = currentVC; } }); @@ -115,18 +115,18 @@ export class DiscordMusicPlayerInstance { public async leaveVoiceChannel() { - if(this.player) - this.player.pause(); + if (this.player) + this.player.pause(); - if(DiscordProvider.client.guilds.cache.get(this.voiceChannel.guild.id)?.me?.voice) { + if (DiscordProvider.client.guilds.cache.get(this.voiceChannel.guild.id)?.me?.voice) { await DiscordProvider.client.guilds.cache.get(this.voiceChannel.guild.id)?.me?.voice.disconnect(); } - + } public addTrackToQueue(track: (ValidTracks)) { - if(this.queue.track.length === 0) { + if (this.queue.track.length === 0) { this.queue.track.push(track); this.playTrack(this.queue.track[0]); return; @@ -137,21 +137,21 @@ export class DiscordMusicPlayerInstance { public async playTrack(track: ValidTracks) { - if(!this.voiceConnection) throw new Error("No voice connection"); + if (!this.voiceConnection) throw new Error("No voice connection"); const stream = await playdl.stream(track.url); const resource = createAudioResource(stream.stream, { inputType: stream.type }); - + this.player.play(resource) this.voiceConnection.subscribe(this.player) } public async skipTrack() { - if(!this.voiceConnection) throw new Error("No voice connection"); + if (!this.voiceConnection) throw new Error("No voice connection"); - if(this.queue.track.length > 1) { + if (this.queue.track.length > 1) { this.queue.track.shift(); this.playTrack(this.queue.track[0]); } @@ -160,13 +160,13 @@ export class DiscordMusicPlayerInstance { public async destroy() { await this.leaveVoiceChannel(); - if(this.voiceConnection) { + if (this.voiceConnection) { this.voiceConnection.removeAllListeners(); - if(this.voiceConnection.state.status !== 'destroyed') + if (this.voiceConnection.state.status !== 'destroyed') this.voiceConnection.destroy(); } - - if(this.player) { + + if (this.player) { this.player.removeAllListeners(); this.player.stop(true); } @@ -183,12 +183,12 @@ class DiscordMusicPlayer { public GuildQueue = new Map(); public getGuildInstance(guildId: Snowflake): (DiscordMusicPlayerInstance | null) { - if(!this.isGuildInstanceExists(guildId)) return null; + if (!this.isGuildInstanceExists(guildId)) return null; return this.GuildQueue.get(guildId); } public isGuildInstanceExists(guildId: Snowflake) { - return this.GuildQueue.has(guildId); + return this.GuildQueue.has(guildId); } public createGuildInstance(guildId: Snowflake, voiceChannel: (VoiceChannel | StageChannel)) { @@ -199,7 +199,7 @@ class DiscordMusicPlayer { let guildId: Snowflake = guild instanceof Guild ? guild.id : guild; - if(this.isGuildInstanceExists(guildId)) { + if (this.isGuildInstanceExists(guildId)) { await this.GuildQueue.get(guildId).destroy(); this.GuildQueue.delete(guildId); } @@ -229,16 +229,39 @@ class DiscordMusicPlayer { // Last resort, search the title const videoInfo = await playdl.video_basic_info("https://www.youtube.com/watch?v=" + youtubeLink.videoId); - if(videoInfo?.video_details?.title) { + if (videoInfo?.video_details?.title) { const searched: YouTubeVideo[] = await playdl.search(videoInfo.video_details.title, { source: { youtube: "video" } }); for (let video of searched) { if (video.id === youtubeLink.videoId) return video; } } - - // TODO: Construct our own YouTubeVideo for unlisted videos or somehow the search failed - + + let yt_info = await playdl.video_info("https://www.youtube.com/watch?v=" + youtubeLink.videoId); + if(yt_info) { + return new YouTubeVideo({ + id: yt_info.video_details.id, + url: yt_info.video_details.url, + type: yt_info.video_details.type, + title: yt_info.video_details.title, + description: yt_info.video_details.description, + durationRaw: yt_info.video_details.durationRaw, + durationInSec: yt_info.video_details.durationInSec, + uploadedAt: yt_info.video_details.uploadedAt, + upcoming: yt_info.video_details.upcoming, + views: yt_info.video_details.views, + thumbnails: yt_info.video_details.thumbnails, + channel: yt_info.video_details.channel, + likes: yt_info.video_details.likes, + live: yt_info.video_details.live, + private: yt_info.video_details.private, + tags: yt_info.video_details.tags, + discretionAdvised: yt_info.video_details.discretionAdvised, + music: yt_info.video_details.music + }); + } + + return null; } From da56d51ba0b6640a86e2f987de7225e52da06e60 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 21:50:18 +0700 Subject: [PATCH 17/34] Better help message --- src/discord/Help.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/discord/Help.ts b/src/discord/Help.ts index b16ce26..5336c9b 100644 --- a/src/discord/Help.ts +++ b/src/discord/Help.ts @@ -17,22 +17,32 @@ const EMBEDS = { let _e = makeInfoEmbed({ icon: "💌", title: `Help - ${DiscordProvider.client.user?.username}`, + description: `\u200b\n📰 **Info**\nYumi is currently undergoing complete rewrite, as expected, losing many of her functionality. All of the features will be re-implemented.\n\n🤔 **Why?**\nYumi was specifically written for use in a only 1 private server, now it's time to extend the border and globalize it (just kidding lol)\n\n🏷️ **Prefix**\nYou can call me using \`\`${prefix.replaceAll('`','`​')}\`\`, <@${DiscordProvider.client.user?.id}> or \`\`/slash command\`\`\n\n💻 **Available commands**`, fields: [ { - name: 'Info', - value: `Yumi is currently undergoing complete rewrite, as expected, losing many of her functionality. All of the features will be re-implemented.` + name: '☕ General', + value: `help, ping, invite, userinfo`, + inline: true }, { - name: 'Why?', - value: `Yumi was specifically written for use in a only 1 private server, now it's time to extend the border and globalize it (just kidding lol)` + name: '🎵 Music', + value: `play, search, skip, queue, join, leave`, + inline: true }, { - name: 'Prefix', - value: `You can call me using \`\`${prefix.replaceAll('`','`​')}\`\`, <@${DiscordProvider.client.user?.id}> or \`\`/slash command\`\`` + name: '🎮 Games', + value: `osu`, + inline: true }, { - name: 'Available commands', - value: '``help``\n``ping``\n``invite``\n``osu``\n``membershipscreening (ms)``\n``interaction`' + name: '🔐 Admin', + value: `settings, say, membershipscreening (ms)`, + inline: true + }, + { + name: '🔧 Developer', + value: 'interactions, serviceannouncement', + inline: true } ], user: (data instanceof Interaction) ? data.user : data.author From 16c03c3f4f42cebd69e9b69c8d288db5210da432 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 30 Jan 2022 22:42:00 +0700 Subject: [PATCH 18/34] Add user voice channel checks --- src/discord/MusicPlayer/Join.ts | 13 ++++++++--- src/discord/MusicPlayer/Leave.ts | 29 ++++++++++++++++++++++-- src/discord/MusicPlayer/Play.ts | 37 +++++++++++++++++++++++++------ src/discord/MusicPlayer/Queue.ts | 15 ++++++++----- src/discord/MusicPlayer/Search.ts | 21 ++++++++++++++++-- src/discord/MusicPlayer/Skip.ts | 37 ++++++++++++++++++++++++------- 6 files changed, 125 insertions(+), 27 deletions(-) diff --git a/src/discord/MusicPlayer/Join.ts b/src/discord/MusicPlayer/Join.ts index 7c3995a..da346e0 100644 --- a/src/discord/MusicPlayer/Join.ts +++ b/src/discord/MusicPlayer/Join.ts @@ -23,7 +23,7 @@ const EMBEDS = { description: `I can't join mutiple voice channel on the same guild. I wish I had a superpower, maybe... one day I will. - Want to move me to your voice channel? You can use the button below (soon)${!haveForceMove ? ` or use move command (soon)` : ", use move command (soon) or just (ab)use me with your admin permissions hehe"}`, + There are also somebody listening to the music in the voice channel I'm currently in.${!haveForceMove ? ` Wait until I finish playing there or I'm alone there. Better yet, join them!` : " Wait until I finish playing there or I'm alone there. Better yet, join them! \n\n**Or... just (ab)use your admin permissions and move me to where you want!**"}`, user: (data instanceof Interaction) ? data.user : data.author }); }, @@ -34,6 +34,12 @@ const EMBEDS = { user: (data instanceof Interaction) ? data.user : data.author }); }, + USER_NOT_IN_VOICECHANNEL: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `You need to be in the voice channel first!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, NOW_PLAYING: (data: Message | Interaction, track: ValidTracks) => { const embed = makeInfoEmbed({ title: ' Now playing', @@ -198,8 +204,9 @@ export default class Join { const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel; if (!channel) return; - // TODO: User must be in vc error msg - if (!data.member.voice.channel) return; + if (!data.member.voice.channel) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(data)] }); + let voiceChannel = data.member.voice.channel; diff --git a/src/discord/MusicPlayer/Leave.ts b/src/discord/MusicPlayer/Leave.ts index 64a4138..be8645f 100644 --- a/src/discord/MusicPlayer/Leave.ts +++ b/src/discord/MusicPlayer/Leave.ts @@ -1,5 +1,5 @@ import { Message, CommandInteraction, Interaction, VoiceChannel } from "discord.js"; -import { getEmotes, makeSuccessEmbed, makeProcessingEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; +import { getEmotes, makeSuccessEmbed, makeErrorEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; import DiscordProvider from "../../providers/Discord"; import Users from "../../services/Users" import Environment from "../../providers/Environment"; @@ -12,6 +12,24 @@ const EMBEDS = { description: `Left the voice channel`, user: (data instanceof Interaction) ? data.user : data.author }); + }, + USER_NOT_IN_VOICECHANNEL: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `You need to be in the voice channel first!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + NO_MUSIC_PLAYING: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `There are no music playing`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + USER_NOT_IN_SAME_VOICECHANNEL: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `You are not in the same voice channel!`, + user: (data instanceof Interaction) ? data.user : data.author + }); } } @@ -40,11 +58,18 @@ export default class Leave { if(!data.guildId) return; const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel; + if (!data.member.voice.channel) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(data)] }); + if(!channel) return; let instance = DiscordMusicPlayer.getGuildInstance(data.guildId); - if(!instance) return; + if(!instance) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data)] }); + + if(instance.voiceChannel.id !== data.member.voice.channel.id) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(data)] }); DiscordMusicPlayer.destoryGuildInstance(data.guild?.id!); diff --git a/src/discord/MusicPlayer/Play.ts b/src/discord/MusicPlayer/Play.ts index 336503c..1a50b53 100644 --- a/src/discord/MusicPlayer/Play.ts +++ b/src/discord/MusicPlayer/Play.ts @@ -27,7 +27,19 @@ const EMBEDS = { }); embed.setImage(track.thumbnails[0].url); return embed; - } + }, + USER_NOT_IN_VOICECHANNEL: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `You need to be in the voice channel first!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + USER_NOT_IN_SAME_VOICECHANNEL: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `You are not in the same voice channel!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, } @@ -49,6 +61,7 @@ export default class Play { if (!this.tryParseJSONObject(interaction.customId)) return; let payload = JSON.parse(interaction.customId); + if(!interaction.member?.user?.id) return; /* Discord have 100 char custom id char limit @@ -62,14 +75,17 @@ export default class Play { payload.module !== 'MP_SM' || payload.action !== 'play') return; - interaction.deferReply(); + await interaction.deferReply(); - let voiceChannel = interaction.guild.channels.cache.get(payload.data.v); - let member = interaction.guild.members.cache.get(payload.data.r); + let voiceChannel = DiscordProvider.client.guilds.cache.get(interaction.guildId)?.channels.cache.get(payload.data.v); + let member = DiscordProvider.client.guilds.cache.get(interaction.guildId)?.members.cache.get(interaction.member?.user?.id); if(!member) return; if (!voiceChannel || !(voiceChannel instanceof VoiceChannel)) return; - if (!member.voice.channel) return; + + + if (!member.voice.channel) + return await sendMessageOrInteractionResponse(interaction, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(interaction)] }, true); if (!DiscordMusicPlayer.isGuildInstanceExists(interaction.guildId)) { await joinVoiceChannelProcedure(interaction, null, voiceChannel); @@ -77,6 +93,9 @@ export default class Play { let instance = DiscordMusicPlayer.getGuildInstance(interaction.guildId); + if(instance!.voiceChannel.id !== member.voice.channel.id) + return await sendMessageOrInteractionResponse(interaction, { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(interaction)] }, true); + if (!instance!.isConnected()) { await joinVoiceChannelProcedure(interaction, instance!, voiceChannel); instance = DiscordMusicPlayer.getGuildInstance(interaction.guildId); @@ -123,8 +142,9 @@ export default class Play { if (!query) return; if (!(data.channel instanceof TextChannel)) return; - // TODO: User must be in vc error msg - if (!data.member.voice.channel) return; + if (!data.member.voice.channel) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(data)] }); + let voiceChannel = data.member.voice.channel; if (!DiscordMusicPlayer.isGuildInstanceExists(data.guildId)) { @@ -133,6 +153,9 @@ export default class Play { let instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + if(instance!.voiceChannel.id !== data.member.voice.channel.id) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(data)] }, true); + if (!instance!.isConnected()) { await joinVoiceChannelProcedure(data, instance!, voiceChannel); instance = DiscordMusicPlayer.getGuildInstance(data.guildId); diff --git a/src/discord/MusicPlayer/Queue.ts b/src/discord/MusicPlayer/Queue.ts index 82f936a..496927d 100644 --- a/src/discord/MusicPlayer/Queue.ts +++ b/src/discord/MusicPlayer/Queue.ts @@ -1,5 +1,5 @@ import { Message, CommandInteraction, Interaction, VoiceChannel, TextChannel } from "discord.js"; -import { getEmotes, makeSuccessEmbed, makeProcessingEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; +import { getEmotes, makeSuccessEmbed, makeErrorEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; import DiscordProvider from "../../providers/Discord"; import Users from "../../services/Users" import Environment from "../../providers/Environment"; @@ -25,6 +25,12 @@ const EMBEDS = { description: `Now playing: ${queue.track[0].title}\n\nUpcoming song: ${queue.track[1].title}\n\nThere are ${Object.keys(queue).length} songs in the queue!`, user: (data instanceof Interaction) ? data.user : data.author }); + }, + NO_MUSIC_PLAYING: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `There are no music playing`, + user: (data instanceof Interaction) ? data.user : data.author + }); } } @@ -53,12 +59,11 @@ export default class QueueCommand { if(!data.guildId) return; if(!(data.channel instanceof TextChannel)) return; - const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel; - if(!channel) return; - + //const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel; + //if(!channel) return; const instance = DiscordMusicPlayer.getGuildInstance(data.guildId); - if(!instance) return; + if(!instance) return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data)] }); await sendMessageOrInteractionResponse(data, { embeds:[EMBEDS.QUEUE(data, instance.queue)] }); diff --git a/src/discord/MusicPlayer/Search.ts b/src/discord/MusicPlayer/Search.ts index 8ad8607..54ea4d1 100644 --- a/src/discord/MusicPlayer/Search.ts +++ b/src/discord/MusicPlayer/Search.ts @@ -28,6 +28,18 @@ const EMBEDS = { }); embed.setImage(track.thumbnails[0].url); return embed; + }, + USER_NOT_IN_VOICECHANNEL: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `You need to be in the voice channel first!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + USER_NOT_IN_SAME_VOICECHANNEL: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `You are not in the same voice channel!`, + user: (data instanceof Interaction) ? data.user : data.author + }); } } @@ -77,8 +89,10 @@ export default class Search { if (!query) return; if (!(data.channel instanceof TextChannel)) return; - // TODO: User must be in vc error msg - if (!data.member.voice.channel) return; + + if (!data.member.voice.channel) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(data)] }); + let voiceChannel = data.member.voice.channel; if (!DiscordMusicPlayer.isGuildInstanceExists(data.guildId)) { @@ -86,6 +100,9 @@ export default class Search { } let instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + + if (instance!.voiceChannel.id !== voiceChannel.id) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(data)] }); if (!instance!.isConnected()) { await joinVoiceChannelProcedure(data, instance!, voiceChannel); diff --git a/src/discord/MusicPlayer/Skip.ts b/src/discord/MusicPlayer/Skip.ts index fc1d5ad..8cf3c83 100644 --- a/src/discord/MusicPlayer/Skip.ts +++ b/src/discord/MusicPlayer/Skip.ts @@ -1,5 +1,5 @@ import { Message, CommandInteraction, Interaction, VoiceChannel } from "discord.js"; -import { getEmotes, makeSuccessEmbed, makeProcessingEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; +import { getEmotes, makeSuccessEmbed, makeErrorEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; import DiscordProvider from "../../providers/Discord"; import Users from "../../services/Users" import Environment from "../../providers/Environment"; @@ -11,6 +11,24 @@ const EMBEDS = { title: `Skipped`, user: (data instanceof Interaction) ? data.user : data.author }); + }, + NO_MUSIC_PLAYING: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `There are no music playing`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + USER_NOT_IN_VOICECHANNEL: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `You need to be in the voice channel first!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + USER_NOT_IN_SAME_VOICECHANNEL: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `You are not in the same voice channel!`, + user: (data instanceof Interaction) ? data.user : data.author + }); } } @@ -39,19 +57,22 @@ export default class Skip { if (!data.guildId) return; const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel; + + if (!data.member.voice.channel) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(data)] }); + if (!channel) return; - - // TODO: User must be in vc error msg - if (!data.member.voice.channel) return; let voiceChannel = data.member.voice.channel; - // TODO: No queue error - if(!DiscordMusicPlayer.isGuildInstanceExists(data.guildId)) { - return; - } + if(!DiscordMusicPlayer.isGuildInstanceExists(data.guildId)) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data)] }); const instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + + if(instance!.voiceChannel.id !== data.member.voice.channel.id) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(data)] }, true); + instance!.skipTrack(); //await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SKIPPED(data)] }); From 9c5a39454178706fb4441c625664a871d53a6f08 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Wed, 2 Feb 2022 21:16:28 +0700 Subject: [PATCH 19/34] Added config example This file is also used as template if normal .json file is not found --- configs/Ping.example.json | 15 +++++++ configs/ServiceAnnouncement.example.json | 55 ++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 configs/Ping.example.json create mode 100644 configs/ServiceAnnouncement.example.json diff --git a/configs/Ping.example.json b/configs/Ping.example.json new file mode 100644 index 0000000..f22d237 --- /dev/null +++ b/configs/Ping.example.json @@ -0,0 +1,15 @@ +[ + { + "type": "ping", + "title": "☁️ Internet | ", + "host": "1.1.1.1" + }, + { + "type": "discordwebsocket", + "title": "🚀 Discord Websocket | " + }, + { + "type": "discordhttp", + "title": "🏓 Discord HTTP | " + } +] diff --git a/configs/ServiceAnnouncement.example.json b/configs/ServiceAnnouncement.example.json new file mode 100644 index 0000000..f28b9c8 --- /dev/null +++ b/configs/ServiceAnnouncement.example.json @@ -0,0 +1,55 @@ +{ + "News": { + "color": "#FAEDF0", + "title": "📰 Newsletter", + "description": "\u200B\n**{bot_username} have been super-charged and upgraded!**\nThe long-awaited update is now here, running latest and greatest version!\n\n**What's new?**\n\u200B\n", + "fields": [ + { + "name": "🔥 Feature 1!", + "value": "\u200B\nSuch wow! New feature so cool 10/10!!!!\n\u200B\n", + "inline": false + }, + { + "name": "✨ Shiny and new feature!", + "value": "\u200B\nNow brighter than ever!\n\u200B\n", + "inline": false + }, + { + "name": "🪲 Bugs squashed", + "value": "\u200B\nBe gone, bugs!\n\u200B\n", + "inline": false + } + ], + "thumbnail": { + "url": "bot_avatar" + } + }, + "Maintenance": { + "color": "#383b80", + "title": "🔧 Maintenance", + "description": "{bot_username} will be down for maintenance\nThe maintenance includes feature improvement to bug fixes\n\nDuring the maintenance, {bot_username}'s commands and features will be unavailable", + "fields": [ + { "name": "Start", "value": "", "inline": true}, + { "name": "End", "value": "", "inline": true} + ], + "thumbnail": { + "url": "bot_avatar" + } + }, + "Message": { + "color": "#A1DE93", + "title": "✉️ Message", + "description": "Hello everyone!", + "thumbnail": { + "url": "bot_avatar" + } + }, + "Alert": { + "color": "#EC255A", + "title": "🚨 Alert", + "description": "{bot_username} currently has an issue with lack of food, {bot_username} need more FOOD! MORE AND MORE!.\nWe're fixing it ASAP", + "thumbnail": { + "url": "bot_avatar" + } + } +} \ No newline at end of file From 16082cc21585659f13524eeea1f22fc911fd924d Mon Sep 17 00:00:00 2001 From: Yuzu Date: Wed, 2 Feb 2022 21:16:31 +0700 Subject: [PATCH 20/34] Create .dockerignore --- .dockerignore | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d731f3c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,98 @@ +Dockerfile +.dockerignore + +# See http://help.github.com/ignore-files/ for more about ignoring files. + +/build +/configs/* +!/configs/*.example.json + +# compiled output +/dist +/tmp +/out-tsc + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# IDEs and editors +.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +#!.vscode/settings.json Not needed for now +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +.sass-cache +connect.lock +typings + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + + +# Dependency directories +node_modules/ +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +# Lerna +lerna-debug.log + +# System Files +.DS_Store +Thumbs.db \ No newline at end of file From 2a8007e2f4d3a434fb126a5ae17e3d6c59f71960 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Wed, 2 Feb 2022 21:16:40 +0700 Subject: [PATCH 21/34] Create Dockerfile --- Dockerfile | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fadd0ba --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM ubuntu + +RUN mkdir -p /home/node/app/node_modules +WORKDIR /home/node/app + +COPY package*.json ./ +COPY prisma ./prisma/ + +RUN apt-get update -y +RUN apt-get -y install curl gnupg git python2 make g++ iputils-ping +RUN curl -sL https://deb.nodesource.com/setup_17.x | bash - +RUN apt-get -y install nodejs + +RUN npm i -g yarn + +RUN yarn +RUN yarn prisma generate +COPY . . + +RUN yarn build +CMD [ "yarn", "start"] \ No newline at end of file From 61cf5a4a8c35902dfc0c9064c4e55eaa981bc8c5 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Wed, 2 Feb 2022 21:16:44 +0700 Subject: [PATCH 22/34] Update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 80d3a0c..11a898b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ # See http://help.github.com/ignore-files/ for more about ignoring files. /build -/configs +/configs/* +!/configs/*.example.json # compiled output /dist From 20bb133e6b86ab43a955285e7c979329930aebfb Mon Sep 17 00:00:00 2001 From: Yuzu Date: Wed, 2 Feb 2022 21:17:20 +0700 Subject: [PATCH 23/34] Removed erlpack Docker won't make image because of this compile error --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e860f25..d9c88cb 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "discord-api-types": "^0.26.1", "discord.js": "^13.6.0", "dotenv": "^10.0.0", - "erlpack": "discord/erlpack", + "libsodium-wrappers": "^0.7.9", "node-osu": "^2.2.1", "ping": "^0.4.1", @@ -37,6 +37,8 @@ "scripts": { "tsc": "tsc", "development": "ts-node-dev ./src/index.ts --respawn --transpileOnly", + "build": "tsc -b", + "start": "node ./build/index.js", "production": "tsc && node ./build/index.js", "test": "mocha -r ts-node/register tests/**/*.ts" }, From 12704f947124e1747d42da5a688ec259514579d3 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Wed, 2 Feb 2022 21:17:51 +0700 Subject: [PATCH 24/34] Changed to use init function instead --- src/discord/Ping.ts | 76 +++++++++----------- src/discord/developer/ServiceAnnouncement.ts | 47 ++++++------ 2 files changed, 57 insertions(+), 66 deletions(-) diff --git a/src/discord/Ping.ts b/src/discord/Ping.ts index e4d899e..fb29cd7 100644 --- a/src/discord/Ping.ts +++ b/src/discord/Ping.ts @@ -14,31 +14,7 @@ enum MeasureType { DiscordWebsocket = "discordwebsocket" } -let measureList = [ - { - type: MeasureType.Ping, - title: "☁️ Internet | ", - host: "1.1.1.1" - }, - { - type: MeasureType.DiscordWebsocket, - title: "🚀 Discord Websocket | " - }, - { - type: MeasureType.DiscordHTTPPing, - title: "🏓 Discord HTTP | " - } -]; - -if(fs.existsSync(path.join(process.cwd(), 'configs/Ping.json'))) { - try { - const rawData = fs.readFileSync(path.join(process.cwd(), 'configs/Ping.json')); - const jsonData = JSON.parse(rawData.toString()); - measureList = jsonData; - } catch (err) { - Logger.error("Unable to load custom Ping config: " + err); - } -} +let measureList: any = []; const EMBEDS = { PING_INFO: (data: Message | Interaction, description: string) => { @@ -53,15 +29,29 @@ const EMBEDS = { export default class Ping { + async init() { + + if (fs.existsSync(path.join(process.cwd(), 'configs/Ping.json'))) { + try { + const rawData = fs.readFileSync(path.join(process.cwd(), 'configs/Ping.json')); + const jsonData = JSON.parse(rawData.toString()); + measureList = jsonData; + } catch (err) { + Logger.error("Unable to load custom Ping config: " + err); + } + } + + } + async onCommand(command: string, args: any, message: Message) { - if(command.toLowerCase() !== 'ping') return; + if (command.toLowerCase() !== 'ping') return; await this.process(message, args); } - async interactionCreate(interaction: CommandInteraction) { - if(interaction.isCommand()) { - if(typeof interaction.commandName === 'undefined') return; - if((interaction.commandName).toLowerCase() !== 'ping') return; + async interactionCreate(interaction: CommandInteraction) { + if (interaction.isCommand()) { + if (typeof interaction.commandName === 'undefined') return; + if ((interaction.commandName).toLowerCase() !== 'ping') return; await this.process(interaction, interaction.options); } } @@ -70,7 +60,7 @@ export default class Ping { const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); const isMessage = data instanceof Message; - if(!isSlashCommand && !isMessage) return; + if (!isSlashCommand && !isMessage) return; let placeholder; @@ -81,7 +71,7 @@ export default class Ping { }); let beforeEditDate = Date.now(); - placeholder = await sendMessageOrInteractionResponse(data, { embeds:[loadingEmbed]} ); + placeholder = await sendMessageOrInteractionResponse(data, { embeds: [loadingEmbed] }); let afterEditDate = Date.now(); @@ -89,23 +79,23 @@ export default class Ping { // TODO: Optimize speed of this - for(let entry of measureList) { + for (let entry of measureList) { let stringCurrent = `${entry.title}`; - if(entry.type === MeasureType.Ping) { + if (entry.type === MeasureType.Ping) { const res = await NodePing.promise.probe(entry.host!, { min_reply: 4 }); - - if(!res.alive) { + + if (!res.alive) { stringCurrent += "Failed"; return desString.push(stringCurrent); } - - if(res.avg === 'unknown' || res.min === 'unknown' || res.max === 'unknown') { + + if (res.avg === 'unknown' || res.min === 'unknown' || res.max === 'unknown') { stringCurrent += "Failed"; return desString.push(stringCurrent); } - + stringCurrent += `${parseFloat(res.avg).toFixed(1)}ms (${parseFloat(res.min).toFixed(1)}ms - ${parseFloat(res.max).toFixed(1)}ms)`; desString.push(stringCurrent); } else if (entry.type === MeasureType.DiscordWebsocket) { @@ -114,15 +104,15 @@ export default class Ping { } else if (entry.type === MeasureType.DiscordHTTPPing) { stringCurrent += `${Math.abs(afterEditDate - beforeEditDate).toFixed(1)}ms`; desString.push(stringCurrent); - } - + } + } desString.push(""); desString.push("💻 Running on " + `${os.hostname()}${Environment.get().NODE_ENV === "development" ? ' / Development Environment' : ''}`); - if(isSlashCommand) return await data.editReply({ embeds: [EMBEDS.PING_INFO(data, desString.join('\n'))] }); - else if(typeof placeholder !== "undefined" && isMessage && placeholder instanceof Message) return await placeholder.edit({ embeds: [EMBEDS.PING_INFO(data, desString.join('\n'))] }); + if (isSlashCommand) return await data.editReply({ embeds: [EMBEDS.PING_INFO(data, desString.join('\n'))] }); + else if (typeof placeholder !== "undefined" && isMessage && placeholder instanceof Message) return await placeholder.edit({ embeds: [EMBEDS.PING_INFO(data, desString.join('\n'))] }); } } \ No newline at end of file diff --git a/src/discord/developer/ServiceAnnouncement.ts b/src/discord/developer/ServiceAnnouncement.ts index 91d95b1..31ea1e2 100644 --- a/src/discord/developer/ServiceAnnouncement.ts +++ b/src/discord/developer/ServiceAnnouncement.ts @@ -5,7 +5,7 @@ import { registerAllGuildsCommands, unregisterAllGuildsCommands } from "../../ut import Prisma from "../../providers/Prisma"; import Users from "../../services/Users"; -import {Promise, reject} from "bluebird"; +import { Promise, reject } from "bluebird"; import fs from "fs"; import path from "path"; import Logger from "../../libs/Logger"; @@ -124,20 +124,21 @@ let Announcements = { } } - -if (fs.existsSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json'))) { - try { - const rawData = fs.readFileSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json')); - const jsonData = JSON.parse(rawData.toString()); - Announcements = jsonData; - } catch (err) { - Logger.error("Unable to load custom ServiceAnnouncement config: " + err); - } -} else { - fs.writeFileSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json'), JSON.stringify(Announcements, null, 4), 'utf8'); -} - export default class InteractionManager { + + async init() { + + if (fs.existsSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json'))) { + try { + const rawData = fs.readFileSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json')); + const jsonData = JSON.parse(rawData.toString()); + Announcements = jsonData; + } catch (err) { + Logger.error("Unable to load custom ServiceAnnouncement config: " + err); + } + } + } + async onCommand(command: string, args: any, message: Message) { if (command.toLowerCase() !== 'serviceannouncement') return; await this.process(message, args); @@ -215,7 +216,7 @@ export default class InteractionManager { toSend.set(Guild, channel); } - let placeholder = await sendMessage(data.channel!, undefined, { embeds:[EMBEDS.SENDING_SERVICE_ANNOUNCEMENT(data)] }); + let placeholder = await sendMessage(data.channel!, undefined, { embeds: [EMBEDS.SENDING_SERVICE_ANNOUNCEMENT(data)] }); await Promise.map(toSend, element => { return new Promise(async (resolve, reject) => { @@ -223,14 +224,14 @@ export default class InteractionManager { let Channel: TextChannel = element[1]; try { const guildObject = DiscordProvider.client.guilds.cache.get(Guild.id); - if(!guildObject) + if (!guildObject) throw new Error('Guild not found'); Logger.info(`Sending Service Announcement to ${guildObject?.name} (${guildObject.id}) #${Channel.name} (${Channel.id})`); - await sendMessage(Channel, undefined, { embeds:[EMBEDS.MAKE_PAYLOAD(data, embed)] }) + await sendMessage(Channel, undefined, { embeds: [EMBEDS.MAKE_PAYLOAD(data, embed)] }) } catch (err) { withError = true; const guildObject = DiscordProvider.client.guilds.cache.get(Guild.id); - if(guildObject) + if (guildObject) Logger.info(`Unable to send Service Announcement to ${guildObject?.name} (${guildObject.id}) #${Channel.name} (${Channel.id})`); reject(err); @@ -241,16 +242,16 @@ export default class InteractionManager { }); }, { concurrency: 2 }); - if(withError) { - if(isMessage) + if (withError) { + if (isMessage) (placeholder as Message).edit({ embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_SENT_WITH_ERRORS(data)] }); - else if(isSlashCommand) + else if (isSlashCommand) return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_SENT_WITH_ERRORS(data)] }, true); } else { - if(isMessage) + if (isMessage) (placeholder as Message).edit({ embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_SENT(data)] }); - else if(isSlashCommand) + else if (isSlashCommand) return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_SENT(data)] }, true); } } From e6f09403a037784ea5450c1faf73ae3189219522 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Wed, 2 Feb 2022 21:18:05 +0700 Subject: [PATCH 25/34] Copy from example if not exist --- src/providers/Configuration.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/providers/Configuration.ts b/src/providers/Configuration.ts index d61c738..91c1a79 100644 --- a/src/providers/Configuration.ts +++ b/src/providers/Configuration.ts @@ -10,6 +10,15 @@ class Configuration { if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } + this.copyExampleIfNotExists("Ping.json"); + this.copyExampleIfNotExists("ServiceAnnouncement.json"); + } + + private copyExampleIfNotExists(file: string): void { + const dir = path.join(process.cwd(), 'configs/'); + if (!fs.existsSync(path.join(dir, file))) { + fs.copyFileSync(path.join(process.cwd(), 'configs/', `${file.replace('.json', '.example.json')}`), path.join(dir, file)); + } } // TODO: Make a real providers like Environment From 7f6e80f9e79ecc2264b5c317898dc8f5b3c6ec8e Mon Sep 17 00:00:00 2001 From: Yuzu Date: Wed, 2 Feb 2022 22:07:33 +0700 Subject: [PATCH 26/34] Switched to debian --- Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index fadd0ba..92ea1f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,15 @@ -FROM ubuntu +FROM debian RUN mkdir -p /home/node/app/node_modules WORKDIR /home/node/app COPY package*.json ./ COPY prisma ./prisma/ + +RUN DEBIAN_FRONTEND=noninteractive RUN apt-get update -y -RUN apt-get -y install curl gnupg git python2 make g++ iputils-ping +RUN apt-get -y install curl gnupg git python2 make g++ iputils-ping ffmpeg RUN curl -sL https://deb.nodesource.com/setup_17.x | bash - RUN apt-get -y install nodejs From b87779d65edbe3443cf6ec5579e56dea5e91c195 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Wed, 2 Feb 2022 22:13:27 +0700 Subject: [PATCH 27/34] Update Help.ts --- src/discord/Help.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/discord/Help.ts b/src/discord/Help.ts index 5336c9b..0ce68f7 100644 --- a/src/discord/Help.ts +++ b/src/discord/Help.ts @@ -43,6 +43,11 @@ const EMBEDS = { name: '🔧 Developer', value: 'interactions, serviceannouncement', inline: true + }, + { + name: '\u200b', + value: '**Made with 💖 and [open source](https://github.com/YuzuZensai/Yumi)**', + inline: false } ], user: (data instanceof Interaction) ? data.user : data.author From 31e15aef2dd569921aa783bbeefe167f74b03e26 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Fri, 4 Feb 2022 22:42:44 +0700 Subject: [PATCH 28/34] Bump play-dl --- package.json | 2 +- yarn.lock | 37 +++++++++---------------------------- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index d9c88cb..0acf66e 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "libsodium-wrappers": "^0.7.9", "node-osu": "^2.2.1", "ping": "^0.4.1", - "play-dl": "^1.8.2", + "play-dl": "^1.8.6", "ts-custom-error": "^3.2.0", "typescript": "^4.4.4", "utf-8-validate": "^5.0.8", diff --git a/yarn.lock b/yarn.lock index eb3fea3..c202746 100644 --- a/yarn.lock +++ b/yarn.lock @@ -251,13 +251,6 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -520,13 +513,6 @@ enabled@2.0.x: resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== -erlpack@discord/erlpack: - version "0.1.3" - resolved "https://codeload.github.com/discord/erlpack/tar.gz/3b793a333dd3f6a140b9168ea91e9fa9660753ce" - dependencies: - bindings "^1.5.0" - nan "^2.15.0" - fast-safe-stringify@^2.0.7: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" @@ -537,11 +523,6 @@ fecha@^4.2.0: resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.1.tgz#0a83ad8f86ef62a091e22bb5a039cd03d23eecce" integrity sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q== -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -962,17 +943,17 @@ ping@^0.4.1: q "1.x" underscore "^1.12.0" -play-audio@^0.4.3: - version "0.4.9" - resolved "https://registry.yarnpkg.com/play-audio/-/play-audio-0.4.9.tgz#74f3ad053d02e0464fce1a28887b2b99da75a76b" - integrity sha512-An88OAvbCcFZH00n9eq6+t2LmJUPJgBCz8ycKXdvUWQz3GcZ3U4o1ZsSHfVFIuoiKuovX+ue5gKNcfSs4ophew== +play-audio@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/play-audio/-/play-audio-0.5.2.tgz#191dd45b95ff64ae20bbd718671da4e987d4edd1" + integrity sha512-ZAqHUKkQLix2Iga7pPbsf1LpUoBjcpwU93F1l3qBIfxYddQLhxS6GKmS0d3jV8kSVaUbr6NnOEcEMFvuX93SWQ== -play-dl@^1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/play-dl/-/play-dl-1.8.2.tgz#daeb59cb40399d45384885346ebce32285338ab8" - integrity sha512-W42wgAqTHC+LvVmUIMiSMKqh834XfI3xz+AjVUSGwOYWYQnMIL+OxEJfttRHQeDKI3KIPOAKFC/5i2AyEkVTKg== +play-dl@^1.8.6: + version "1.8.6" + resolved "https://registry.yarnpkg.com/play-dl/-/play-dl-1.8.6.tgz#483420c0273ad38f0a07346dcd78fb76ce31c2c9" + integrity sha512-NOnSmg6ikdrf6x6R4ML5QY6AJS4h4KkIIynlKuDx7RVDPE7C2kowgtS+e6RRMuuAXXXESkmlWrpUb+bnZFWy1w== dependencies: - play-audio "^0.4.3" + play-audio "^0.5.2" prism-media@^1.3.2: version "1.3.2" From e0c8a327abbc617e3498a397a6e035fd644f3090 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Fri, 4 Feb 2022 22:42:59 +0700 Subject: [PATCH 29/34] Support skip on last song --- src/discord/MusicPlayer/Skip.ts | 16 +++++++++++++--- src/providers/DiscordMusicPlayer.ts | 4 ++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/discord/MusicPlayer/Skip.ts b/src/discord/MusicPlayer/Skip.ts index 8cf3c83..d72a94a 100644 --- a/src/discord/MusicPlayer/Skip.ts +++ b/src/discord/MusicPlayer/Skip.ts @@ -12,6 +12,12 @@ const EMBEDS = { user: (data instanceof Interaction) ? data.user : data.author }); }, + SKIPPED_LASTSONG: (data: Message | Interaction) => { + return makeSuccessEmbed({ + title: `Skipped, that was the last song`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, NO_MUSIC_PLAYING: (data: Message | Interaction) => { return makeErrorEmbed({ title: `There are no music playing`, @@ -73,10 +79,14 @@ export default class Skip { if(instance!.voiceChannel.id !== data.member.voice.channel.id) return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(data)] }, true); + if(instance?.queue.track.length === 0) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data)] }); + instance!.skipTrack(); - //await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SKIPPED(data)] }); - - return; + if(instance?.queue.track.length === 0) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SKIPPED_LASTSONG(data)] }); + else + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SKIPPED(data)] }); } } \ No newline at end of file diff --git a/src/providers/DiscordMusicPlayer.ts b/src/providers/DiscordMusicPlayer.ts index b5e7716..fa5fc94 100644 --- a/src/providers/DiscordMusicPlayer.ts +++ b/src/providers/DiscordMusicPlayer.ts @@ -155,6 +155,10 @@ export class DiscordMusicPlayerInstance { this.queue.track.shift(); this.playTrack(this.queue.track[0]); } + else { + this.queue.track.shift(); + this.player.stop(); + } } public async destroy() { From c941c228504ac2dab075f006954e01fd40ca568b Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sat, 5 Feb 2022 00:00:01 +0700 Subject: [PATCH 30/34] Better queue command --- src/discord/MusicPlayer/Queue.ts | 38 ++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/discord/MusicPlayer/Queue.ts b/src/discord/MusicPlayer/Queue.ts index 496927d..069e85c 100644 --- a/src/discord/MusicPlayer/Queue.ts +++ b/src/discord/MusicPlayer/Queue.ts @@ -13,18 +13,32 @@ const EMBEDS = { description: `The queue is empty!`, user: (data instanceof Interaction) ? data.user : data.author }); - else if(queue.track.length == 1) - return makeInfoEmbed({ - title: `Queue`, - description: `Now playing: ${queue.track[0].title}\n\nThe queue is empty!`, - user: (data instanceof Interaction) ? data.user : data.author - }); - else - return makeInfoEmbed({ - title: `Queue`, - description: `Now playing: ${queue.track[0].title}\n\nUpcoming song: ${queue.track[1].title}\n\nThere are ${Object.keys(queue).length} songs in the queue!`, - user: (data instanceof Interaction) ? data.user : data.author - }); + else { + if(queue.track.length == 1) { + let queueString = `1. [${queue.track[0].title}](${queue.track[0].url})` + return makeInfoEmbed({ + title: `Queue`, + description: `Now playing: ${queue.track[0].title}\n\nThere are ${queue.track.length} song in the queue!\n${queueString}`, + user: (data instanceof Interaction) ? data.user : data.author + }); + } + else if(queue.track.length >= 10) { + let first10 = queue.track.slice(0, 10); + let queueString = first10.map((track, index) => `${index + 1}. [${track.title}](${track.url})`).join("\n"); + return makeInfoEmbed({ + title: `Queue`, + description: `Now playing: ${queue.track[0].title}\nUpcoming song: ${queue.track[1].title}\n\nThere are ${queue.track.length} songs in the queue!\n${queueString}\n${queue.track.length > 10 ? `...${queue.track.length - 10} more songs` : ""}`, + user: (data instanceof Interaction) ? data.user : data.author + }); + } else { + let queueString = queue.track.map((track, index) => `${index + 1}. [${track.title}](${track.url})`).join("\n"); + return makeInfoEmbed({ + title: `Queue`, + description: `Now playing: ${queue.track[0].title}\nUpcoming song: ${queue.track[1].title}\n\nThere are ${queue.track.length} songs in the queue!\n${queueString}`, + user: (data instanceof Interaction) ? data.user : data.author + }); + } + } }, NO_MUSIC_PLAYING: (data: Message | Interaction) => { return makeErrorEmbed({ From 3853f08ed231f0fc06c4b06c977158fef3e2471b Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sat, 5 Feb 2022 00:03:23 +0700 Subject: [PATCH 31/34] Add alias for play command in help --- src/discord/Help.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/discord/Help.ts b/src/discord/Help.ts index 0ce68f7..3c2181c 100644 --- a/src/discord/Help.ts +++ b/src/discord/Help.ts @@ -26,7 +26,7 @@ const EMBEDS = { }, { name: '🎵 Music', - value: `play, search, skip, queue, join, leave`, + value: `play (p), search, skip, queue, join, leave`, inline: true }, { From d31642f4d8a4ac09b905ad0614a203835306a300 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sat, 5 Feb 2022 00:23:53 +0700 Subject: [PATCH 32/34] Update message --- src/discord/MusicPlayer/Join.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/discord/MusicPlayer/Join.ts b/src/discord/MusicPlayer/Join.ts index da346e0..870f23e 100644 --- a/src/discord/MusicPlayer/Join.ts +++ b/src/discord/MusicPlayer/Join.ts @@ -44,13 +44,7 @@ const EMBEDS = { const embed = makeInfoEmbed({ title: ' Now playing', icon: '🎵', - fields: [ - { - name: `${track.title}`, - value: `\u200B`, - inline: false - } - ], + description: `${track.title}`, user: DiscordProvider.client.user }); From 8bf97e443853a381356f7d201223848ea3ebe013 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sat, 5 Feb 2022 00:32:02 +0700 Subject: [PATCH 33/34] Add NowPlaying command --- src/discord/Help.ts | 2 +- src/discord/MusicPlayer/NowPlaying.ts | 70 +++++++++++++++++++++++++++ src/providers/Discord.ts | 2 + src/providers/DiscordMusicPlayer.ts | 4 ++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/discord/MusicPlayer/NowPlaying.ts diff --git a/src/discord/Help.ts b/src/discord/Help.ts index 3c2181c..6596cf3 100644 --- a/src/discord/Help.ts +++ b/src/discord/Help.ts @@ -26,7 +26,7 @@ const EMBEDS = { }, { name: '🎵 Music', - value: `play (p), search, skip, queue, join, leave`, + value: `play (p), search, skip, queue, nowplaying (np), join, leave`, inline: true }, { diff --git a/src/discord/MusicPlayer/NowPlaying.ts b/src/discord/MusicPlayer/NowPlaying.ts new file mode 100644 index 0000000..7e8a3fb --- /dev/null +++ b/src/discord/MusicPlayer/NowPlaying.ts @@ -0,0 +1,70 @@ +import { Message, CommandInteraction, Interaction, MessageActionRow, TextChannel, MessageButton } from "discord.js"; +import { getEmotes, makeSuccessEmbed, makeErrorEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; +import DiscordProvider from "../../providers/Discord"; +import Users from "../../services/Users" +import Environment from "../../providers/Environment"; +import DiscordMusicPlayer, { DiscordMusicPlayerInstance, ValidTracks, Queue } from "../../providers/DiscordMusicPlayer"; + +const EMBEDS = { + NOW_PLAYING: (data: Message | Interaction, track: ValidTracks) => { + const embed = makeInfoEmbed({ + title: ' Now playing', + icon: '🎵', + description: `${track.title}`, + user: DiscordProvider.client.user + }); + + embed.setImage(track.thumbnails[0].url); + return embed; + }, + NO_MUSIC_PLAYING: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `There are no music playing`, + user: (data instanceof Interaction) ? data.user : data.author + }); + } +} + +export default class NowPlaying { + + async onCommand(command: string, args: any, message: Message) { + if(command.toLowerCase() !== 'nowplaying' && command.toLowerCase() !== 'np') return; + await this.process(message, args); + } + + async interactionCreate(interaction: CommandInteraction) { + if(interaction.isCommand()) { + if(typeof interaction.commandName === 'undefined') return; + if((interaction.commandName).toLowerCase() !== 'nowplaying') return; + await this.process(interaction, interaction.options); + } + } + + async process(data: Interaction | Message, args: any) { + const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); + const isMessage = data instanceof Message; + + if(!isSlashCommand && !isMessage) return; + + if(!data.member) return; + if(!data.guildId) return; + if(!(data.channel instanceof TextChannel)) return; + + //const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel; + //if(!channel) return; + + const instance = DiscordMusicPlayer.getGuildInstance(data.guildId); + if(!instance || !instance.queue.track[0]) return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data)] }); + + const row = new MessageActionRow() + .addComponents( + new MessageButton() + .setEmoji('▶️') + .setLabel(' Open on YouTube') + .setURL(encodeURI(`https://www.youtube.com/watch?v=${instance.queue.track[0].id}`)) + .setStyle('LINK'), + ) + + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NOW_PLAYING(data, instance.queue.track[0])], components: [row] }); + } +} \ No newline at end of file diff --git a/src/providers/Discord.ts b/src/providers/Discord.ts index 30ac372..8bd69e9 100644 --- a/src/providers/Discord.ts +++ b/src/providers/Discord.ts @@ -19,6 +19,7 @@ import Discord_MusicPlayer_Join from "../discord/MusicPlayer/Join"; import Discord_MusicPlayer_Leave from "../discord/MusicPlayer/Leave"; import Discord_MusicPlayer_Queue from "../discord/MusicPlayer/Queue"; import Discord_MusicPlayer_Search from "../discord/MusicPlayer/Search"; +import Discord_MusicPlayer_Search_NowPlaying from "../discord/MusicPlayer/NowPlaying"; import Discord_Developer_ServiceAnnouncement from "../discord/developer/ServiceAnnouncement" @@ -59,6 +60,7 @@ class Discord { this.loaded_module["Discord_MusicPlayer_Leave"] = new Discord_MusicPlayer_Leave(); this.loaded_module["Discord_MusicPlayer_Queue"] = new Discord_MusicPlayer_Queue(); this.loaded_module["Discord_MusicPlayer_Search"] = new Discord_MusicPlayer_Search(); + this.loaded_module["Discord_MusicPlayer_Search_NowPlaying"] = new Discord_MusicPlayer_Search_NowPlaying(); this.loaded_module["MembershipScreening"] = new Discord_MembershipScreening(); diff --git a/src/providers/DiscordMusicPlayer.ts b/src/providers/DiscordMusicPlayer.ts index fa5fc94..7fb24fa 100644 --- a/src/providers/DiscordMusicPlayer.ts +++ b/src/providers/DiscordMusicPlayer.ts @@ -111,6 +111,10 @@ export class DiscordMusicPlayerInstance { this.voiceChannel = currentVC; } }); + + this.voiceConnection.on(VoiceConnectionStatus.Disconnected, (oldState: VoiceConnectionState, newState: VoiceConnectionState) => { + // TODO: Handle on Disconnect by user (Right-click > Disconnect) + }); } public async leaveVoiceChannel() { From cc9db41813f98436ce953e6b2b32f50e1966b184 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sat, 5 Feb 2022 00:46:35 +0700 Subject: [PATCH 34/34] Added Stats Command --- src/discord/Stats.ts | 43 ++++++++++++++++++++++++++++++++++++++++ src/providers/Discord.ts | 3 +++ 2 files changed, 46 insertions(+) create mode 100644 src/discord/Stats.ts diff --git a/src/discord/Stats.ts b/src/discord/Stats.ts new file mode 100644 index 0000000..bdf3044 --- /dev/null +++ b/src/discord/Stats.ts @@ -0,0 +1,43 @@ +import { Message, CommandInteraction, Interaction } from "discord.js"; +import { getEmotes, makeSuccessEmbed, makeProcessingEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../utils/DiscordMessage"; +import DiscordProvider from "../providers/Discord"; +import Users from "../services/Users" +import Environment from "../providers/Environment"; + +const EMBEDS = { + STATS_INFO: (data: Message | Interaction) => { + let message; + + return makeInfoEmbed({ + title: `Stats`, + icon: '📊', + description: `Serving in ${DiscordProvider.client.guilds.cache.size} servers\n${DiscordProvider.client.guilds.cache.map((g) => g.memberCount).reduce((a, c) => a + c)} users`, + user: (data instanceof Interaction) ? data.user : data.author + }); + } +} + +export default class Stats { + + async onCommand(command: string, args: any, message: Message) { + if(command.toLowerCase() !== 'stats') return; + await this.process(message, args); + } + + async interactionCreate(interaction: CommandInteraction) { + if(interaction.isCommand()) { + if(typeof interaction.commandName === 'undefined') return; + if((interaction.commandName).toLowerCase() !== 'stats') return; + await this.process(interaction, interaction.options); + } + } + + async process(data: Interaction | Message, args: any) { + const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); + const isMessage = data instanceof Message; + + if(!isSlashCommand && !isMessage) return; + + return await sendMessageOrInteractionResponse(data, { embeds:[EMBEDS.STATS_INFO(data)] }); + } +} \ No newline at end of file diff --git a/src/providers/Discord.ts b/src/providers/Discord.ts index 8bd69e9..0335945 100644 --- a/src/providers/Discord.ts +++ b/src/providers/Discord.ts @@ -13,6 +13,8 @@ import Discord_InteractionManager from "../discord/InteractionManager"; import Discord_MembershipScreening from "../discord/MembershipScreening"; import Discord_osu from "../discord/osu"; import Discord_UserInfo from "../discord/UserInfo"; +import Discord_Stats from "../discord/Stats"; + import Discord_MusicPlayer_Play from "../discord/MusicPlayer/Play"; import Discord_MusicPlayer_Skip from "../discord/MusicPlayer/Skip"; import Discord_MusicPlayer_Join from "../discord/MusicPlayer/Join"; @@ -53,6 +55,7 @@ class Discord { this.loaded_module["Discord_Say"] = new Discord_Say(); this.loaded_module["Discord_osu"] = new Discord_osu(); this.loaded_module["Discord_UserInfo"] = new Discord_UserInfo(); + this.loaded_module["Discord_Stats"] = new Discord_Stats(); this.loaded_module["Discord_MusicPlayer_Play"] = new Discord_MusicPlayer_Play(); this.loaded_module["Discord_MusicPlayer_Skip"] = new Discord_MusicPlayer_Skip();