From 16f3d3f191d8fa6b58b7e1d4c2845c06ff3c08e2 Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Sun, 5 Jan 2025 19:16:19 +0800 Subject: [PATCH] feat: demo discord bot --- .gitignore | 5 + cspell.config.yaml | 7 +- pnpm-lock.yaml | 447 +++++++++++++++++- services/discord-voice-bot/package.json | 47 ++ .../src/bots/discord/commands/index.ts | 18 + .../src/bots/discord/commands/ping.ts | 5 + .../src/bots/discord/commands/summon.ts | 100 ++++ services/discord-voice-bot/src/index.ts | 45 ++ .../src/pipelines/tts/transformers.ts | 39 ++ services/discord-voice-bot/tsconfig.json | 18 + 10 files changed, 727 insertions(+), 4 deletions(-) create mode 100644 services/discord-voice-bot/package.json create mode 100644 services/discord-voice-bot/src/bots/discord/commands/index.ts create mode 100644 services/discord-voice-bot/src/bots/discord/commands/ping.ts create mode 100644 services/discord-voice-bot/src/bots/discord/commands/summon.ts create mode 100644 services/discord-voice-bot/src/index.ts create mode 100644 services/discord-voice-bot/src/pipelines/tts/transformers.ts create mode 100644 services/discord-voice-bot/tsconfig.json diff --git a/.gitignore b/.gitignore index 4fa8b212..383d5afb 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,8 @@ coverage/ **/public/assets/js/* **/public/assets/live2d/models/* **/public/assets/vrm/models/* + +*.pcm +*.wav +*.ogg +*.mp3 diff --git a/cspell.config.yaml b/cspell.config.yaml index c0a6dbb0..92cc2d51 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -9,6 +9,7 @@ words: - airi-vtuber - Attributify - Ayaka + - Bitstream - bumpp - cientos - composables @@ -27,6 +28,7 @@ words: - cuteen - defu - demi + - dotenvx - dtype - elevenlabs - formkit @@ -66,6 +68,7 @@ words: - onnx - onnxruntime - openai + - opusscript - pgvector - picklist - pinia @@ -73,16 +76,16 @@ words: - pixiv - pretrained - rehype - - rushstack - remeda + - rushstack - Shadcn - shiki - shikijs - silero - sizecheck - Sniglet - - tamagotchi - supergroup + - tamagotchi - taze - tresjs - typeschema diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b76feef6..9c081004 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -474,6 +474,42 @@ importers: specifier: ^2.2.0 version: 2.2.0(typescript@5.7.2) + services/discord-voice-bot: + dependencies: + '@discordjs/voice': + specifier: ^0.18.0 + version: 0.18.0(ffmpeg-static@5.2.0)(opusscript@0.1.1) + '@dotenvx/dotenvx': + specifier: ^1.32.0 + version: 1.32.0 + '@guiiai/logg': + specifier: ^1.0.6 + version: 1.0.6 + '@huggingface/transformers': + specifier: ^3.2.4 + version: 3.2.4 + discord.js: + specifier: ^14.17.2 + version: 14.17.2 + ffmpeg-static: + specifier: ^5.2.0 + version: 5.2.0 + fluent-ffmpeg: + specifier: ^2.1.3 + version: 2.1.3 + libsodium-wrappers: + specifier: ^0.7.15 + version: 0.7.15 + opusscript: + specifier: ^0.1.1 + version: 0.1.1 + prism-media: + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 + tsx: + specifier: ^4.19.2 + version: 4.19.2 + packages: 7zip-bin@5.2.0: @@ -1112,10 +1148,50 @@ packages: '@cspell/dict-ru_ru@2.2.4': resolution: {integrity: sha512-Ub5Y318ZAaFJDAPgeImcLg8ksfthGhxMHsyHGkn9Uf3g9AZUlYsabs1HwgLmh9NtqDNjMlF52S9R11GFDdaWIw==} + '@derhuerst/http-basic@8.2.4': + resolution: {integrity: sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==} + engines: {node: '>=6.0.0'} + '@develar/schema-utils@2.6.5': resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} engines: {node: '>= 8.9.0'} + '@discordjs/builders@1.10.0': + resolution: {integrity: sha512-ikVZsZP+3shmVJ5S1oM+7SveUCK3L9fTyfA8aJ7uD9cNQlTqF+3Irbk2Y22KXTb3C3RNUahRkSInClJMkHrINg==} + engines: {node: '>=16.11.0'} + + '@discordjs/collection@1.5.3': + resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==} + engines: {node: '>=16.11.0'} + + '@discordjs/collection@2.1.1': + resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==} + engines: {node: '>=18'} + + '@discordjs/formatters@0.6.0': + resolution: {integrity: sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw==} + engines: {node: '>=16.11.0'} + + '@discordjs/rest@2.4.2': + resolution: {integrity: sha512-9bOvXYLQd5IBg/kKGuEFq3cstVxAMJ6wMxO2U3wjrgO+lHv8oNCT+BBRpuzVQh7BoXKvk/gpajceGvQUiRoJ8g==} + engines: {node: '>=18'} + + '@discordjs/util@1.1.1': + resolution: {integrity: sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==} + engines: {node: '>=18'} + + '@discordjs/voice@0.18.0': + resolution: {integrity: sha512-BvX6+VJE5/vhD9azV9vrZEt9hL1G+GlOdsQaVl5iv9n87fkXjf3cSwllhR3GdaUC8m6dqT8umXIWtn3yCu4afg==} + engines: {node: '>=18'} + + '@discordjs/ws@1.2.0': + resolution: {integrity: sha512-QH5CAFe3wHDiedbO+EI3OOiyipwWd+Q6BdoFZUw/Wf2fw5Cv2fgU/9UEtJRmJa9RecI+TAhdGPadMaEIur5yJg==} + engines: {node: '>=16.11.0'} + + '@dotenvx/dotenvx@1.32.0': + resolution: {integrity: sha512-oQaGYijYfQx6pY9D+FQ08gUOckF1R0RSVK7Jqk+Ma2RyeceoMIawQl1KoogRaJ12i0SmyVWhiGyQxDU01/k13g==} + hasBin: true + '@dprint/formatter@0.3.0': resolution: {integrity: sha512-N9fxCxbaBOrDkteSOzaCqwWjso5iAe+WJPsHC021JfHNj2ThInPNEF13ORDKta3llq5D1TlclODCvOvipH7bWQ==} @@ -1125,6 +1201,12 @@ packages: '@dprint/toml@0.6.3': resolution: {integrity: sha512-zQ42I53sb4WVHA+5yoY1t59Zk++Ot02AvUgtNKLzTT8mPyVqVChFcePa3on/xIoKEgH+RoepgPHzqfk9837YFw==} + '@ecies/ciphers@0.2.2': + resolution: {integrity: sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + peerDependencies: + '@noble/ciphers': ^1.0.0 + '@electron-toolkit/preload@3.0.1': resolution: {integrity: sha512-EzoQmpK8jqqU8YnM5jRe0GJjGVJPke2KtANqz8QtN2JPT96ViOvProBdK5C6riCm0j1T8jjAGVQCZLQy9OVoIA==} peerDependencies: @@ -1823,6 +1905,9 @@ packages: resolution: {integrity: sha512-4Et4AN6wmqeA0PfU5Clkv/IS27wiefsWf6TemAZrb75uzkClYEFavim7SboeKwbll9Nbsn2Iv0LT/HS5H7orZg==} hasBin: true + '@guiiai/logg@1.0.6': + resolution: {integrity: sha512-x2dibX7Nr3sTCCJTsA3wgFSrZCtFA2k0vbg2fy+eG+fCAHKXjiSUNLAD94PpXcM7Jqbo4cJqzkOeCAgFZSjT0w==} + '@huggingface/jinja@0.3.2': resolution: {integrity: sha512-F2FvuIc+w1blGsaqJI/OErRbWH6bVJDCBI8Rm5D86yZ2wlwrGERsfIaru7XUv9eYC3DMP3ixDRRtF0h6d8AZcQ==} engines: {node: '>=18'} @@ -2100,6 +2185,18 @@ packages: '@napi-rs/wasm-runtime@0.2.5': resolution: {integrity: sha512-kwUxR7J9WLutBbulqg1dfOrMTwhMdXLdcGUhcbCcGwnPLt3gz19uHVdwH1syKVDbE022ZS2vZxOWflFLS0YTjw==} + '@noble/ciphers@1.2.0': + resolution: {integrity: sha512-YGdEUzYEd+82jeaVbSKKVp1jFZb8LwaNMIIzHFkihGvYdd/KKAr7KaJHdEdSYGredE3ssSravXIa0Jxg28Sv5w==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.8.0': + resolution: {integrity: sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.7.0': + resolution: {integrity: sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==} + engines: {node: ^14.21.3 || >=16} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2766,6 +2863,18 @@ packages: cpu: [x64] os: [win32] + '@sapphire/async-queue@1.5.5': + resolution: {integrity: sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + + '@sapphire/shapeshift@4.0.0': + resolution: {integrity: sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==} + engines: {node: '>=v16'} + + '@sapphire/snowflake@3.5.3': + resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -2916,6 +3025,9 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + '@types/node@10.17.60': + resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} + '@types/node@20.17.11': resolution: {integrity: sha512-Ept5glCK35R8yeyIeYlRIZtX6SLRyqMhOFTgj5SOkMpLTdw3SEHI9fHx60xaUZ+V1aJxQJODE+7/j5ocZydYTg==} @@ -2961,6 +3073,9 @@ packages: '@types/webxr@0.5.20': resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} + '@types/ws@8.5.13': + resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} + '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} @@ -3259,6 +3374,10 @@ packages: '@vitest/utils@2.1.8': resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} + '@vladfrangu/async_event_emitter@2.4.6': + resolution: {integrity: sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + '@volar/language-core@2.4.10': resolution: {integrity: sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==} @@ -3733,6 +3852,9 @@ packages: resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} engines: {node: '>=0.12.0'} + async@0.2.10: + resolution: {integrity: sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==} + async@2.6.4: resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} @@ -3922,6 +4044,9 @@ packages: caniuse-lite@1.0.30001680: resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==} + caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -4037,6 +4162,10 @@ packages: command-exists@1.2.9: resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -4085,6 +4214,10 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -4225,6 +4358,9 @@ packages: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} @@ -4346,6 +4482,13 @@ packages: dir-compare@3.3.0: resolution: {integrity: sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==} + discord-api-types@0.37.115: + resolution: {integrity: sha512-ivPnJotSMrXW8HLjFu+0iCVs8zP6KSliMelhr7HgcB2ki1QzpORkb26m71l1pzSnnGfm7gb5n/VtRTtpw8kXFA==} + + discord.js@14.17.2: + resolution: {integrity: sha512-mrH6ziLVtNtId4bV4bsaUt5jE6NUaiHMPqO5VsSw1VVhFnjFi9duD8ctlo90/6cUH+8uyKBkoq9mSJ35SuuZ7Q==} + engines: {node: '>=18'} + dmg-builder@24.13.3: resolution: {integrity: sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==} @@ -4386,6 +4529,9 @@ packages: draco3d@1.5.7: resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==} + duplex-child-process@1.0.1: + resolution: {integrity: sha512-tWbt4tyioDjyK5nh+qicbdvBvNjSXsTUF5zKUwSauuKPg1mokjwn/HezwfvWhh6hXoLdgetY+ZlzU/sMwUMJkg==} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -4395,6 +4541,10 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + eciesjs@0.4.13: + resolution: {integrity: sha512-zBdtR4K+wbj10bWPpIOF9DW+eFYQu8miU5ypunh0t4Bvt83ZPlEWgT5Dq/0G6uwEXumZKjfb5BZxYUZQ2Hzn/Q==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -4869,6 +5019,10 @@ packages: fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + ffmpeg-static@5.2.0: + resolution: {integrity: sha512-WrM7kLW+do9HLr+H6tk7LzQ7kPqbAgLjdzNE32+u3Ff11gXt9Kkkd2nusGFrlWMIe+XaA97t+I8JS7sZIrvRgA==} + engines: {node: '>=16'} + figures@6.1.0: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} @@ -4925,6 +5079,10 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + fluent-ffmpeg@2.1.3: + resolution: {integrity: sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==} + engines: {node: '>=18'} + follow-redirects@1.15.2: resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} @@ -5241,6 +5399,9 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + http-response-object@3.0.2: + resolution: {integrity: sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==} + http2-wrapper@1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} engines: {node: '>=10.19.0'} @@ -5525,6 +5686,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -5667,6 +5832,12 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + libsodium-wrappers@0.7.15: + resolution: {integrity: sha512-E4anqJQwcfiC6+Yrl01C1m8p99wEhLmJSs0VQqST66SbQXXBoaJY0pF4BNjRYa/sOQAxx6lXAaAFIlx+15tXJQ==} + + libsodium@0.7.15: + resolution: {integrity: sha512-sZwRknt/tUpE2AwzHq3jEyUU5uvIZHtSssktXq7owd++3CSgn8RGrv6UZJJBpP7+iBghBqe7Z06/2M31rI2NKw==} + lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -5726,6 +5897,9 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} @@ -5772,6 +5946,9 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + magic-bytes.js@1.10.0: + resolution: {integrity: sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==} + magic-string-ast@0.6.2: resolution: {integrity: sha512-oN3Bcd7ZVt+0VGEs7402qR/tjgjbM7kPlH/z7ufJnzTLVBzXJITRHOJiwMmmYMgZfdoWQsfQcY+iKlxiBppnMA==} engines: {node: '>=16.14.0'} @@ -6198,6 +6375,10 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} + object-treeify@1.1.33: + resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} + engines: {node: '>= 10'} + object.assign@4.1.4: resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} engines: {node: '>= 0.4'} @@ -6270,6 +6451,9 @@ packages: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} + opusscript@0.1.1: + resolution: {integrity: sha512-mL0fZZOUnXdZ78woRXp18lApwpp0lF5tozJOD1Wut0dgrA9WuQTgSels/CSmFleaAZrJi/nci5KOVtbuxeWoQA==} + oxc-resolver@2.1.1: resolution: {integrity: sha512-xPkFYfaR5zJXoaGvYF8FAb1JY4mKoyhIyShj7nwWKeald5Cee03ktPzxdW77PubSl3w8Kle0bHsNm37+gxyY1g==} @@ -6310,6 +6494,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-cache-control@1.0.1: + resolution: {integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==} + parse-gitignore@2.0.0: resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} engines: {node: '>=14'} @@ -6670,6 +6857,26 @@ packages: resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==} engines: {node: '>=18'} + prism-media@1.3.5: + resolution: {integrity: sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA==} + peerDependencies: + '@discordjs/opus': '>=0.8.0 <1.0.0' + ffmpeg-static: ^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0 + node-opus: ^0.3.3 + opusscript: ^0.0.8 + peerDependenciesMeta: + '@discordjs/opus': + optional: true + ffmpeg-static: + optional: true + node-opus: + optional: true + opusscript: + optional: true + + prism-media@2.0.0-alpha.0: + resolution: {integrity: sha512-QL9rnO4xo0grgj7ptsA+AzSCYLirGWM4+ZcyboFmbkYHSgaXIESzHq/SXNizz2iHIfuM2og0cPhmSnTVMeFjKg==} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -7471,6 +7678,9 @@ packages: ts-macro@0.1.10: resolution: {integrity: sha512-jK3yom5TWGtZ8Lbq8YEPrhWK51gn//71W6mY8KApHAzWKQeWMUHG3TO6YpduJeippjsUsHjm/zUCC4Ps+pAZ2Q==} + ts-mixer@6.0.4: + resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + tslib@2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} @@ -7525,6 +7735,9 @@ packages: typed-array-length@1.0.4: resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + typescript@5.7.2: resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} @@ -7568,6 +7781,10 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici@6.19.8: + resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==} + engines: {node: '>=18.17'} + unhead@1.11.14: resolution: {integrity: sha512-XmXW0aZyX9kGk9ejCKCSvv/J4T3Rt4hoAe2EofM+nhG+zwZ7AArUMK/0F/fj6FTkfgY0u0/JryE00qUDULgygA==} @@ -8086,11 +8303,20 @@ packages: resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} engines: {node: '>= 0.4'} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -9061,17 +9287,102 @@ snapshots: '@cspell/dict-ru_ru@2.2.4': {} + '@derhuerst/http-basic@8.2.4': + dependencies: + caseless: 0.12.0 + concat-stream: 2.0.0 + http-response-object: 3.0.2 + parse-cache-control: 1.0.1 + '@develar/schema-utils@2.6.5': dependencies: ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) + '@discordjs/builders@1.10.0': + dependencies: + '@discordjs/formatters': 0.6.0 + '@discordjs/util': 1.1.1 + '@sapphire/shapeshift': 4.0.0 + discord-api-types: 0.37.115 + fast-deep-equal: 3.1.3 + ts-mixer: 6.0.4 + tslib: 2.8.1 + + '@discordjs/collection@1.5.3': {} + + '@discordjs/collection@2.1.1': {} + + '@discordjs/formatters@0.6.0': + dependencies: + discord-api-types: 0.37.115 + + '@discordjs/rest@2.4.2': + dependencies: + '@discordjs/collection': 2.1.1 + '@discordjs/util': 1.1.1 + '@sapphire/async-queue': 1.5.5 + '@sapphire/snowflake': 3.5.3 + '@vladfrangu/async_event_emitter': 2.4.6 + discord-api-types: 0.37.115 + magic-bytes.js: 1.10.0 + tslib: 2.8.1 + undici: 6.19.8 + + '@discordjs/util@1.1.1': {} + + '@discordjs/voice@0.18.0(ffmpeg-static@5.2.0)(opusscript@0.1.1)': + dependencies: + '@types/ws': 8.5.13 + discord-api-types: 0.37.115 + prism-media: 1.3.5(ffmpeg-static@5.2.0)(opusscript@0.1.1) + tslib: 2.8.1 + ws: 8.18.0 + transitivePeerDependencies: + - '@discordjs/opus' + - bufferutil + - ffmpeg-static + - node-opus + - opusscript + - utf-8-validate + + '@discordjs/ws@1.2.0': + dependencies: + '@discordjs/collection': 2.1.1 + '@discordjs/rest': 2.4.2 + '@discordjs/util': 1.1.1 + '@sapphire/async-queue': 1.5.5 + '@types/ws': 8.5.13 + '@vladfrangu/async_event_emitter': 2.4.6 + discord-api-types: 0.37.115 + tslib: 2.8.1 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@dotenvx/dotenvx@1.32.0': + dependencies: + commander: 11.1.0 + dotenv: 16.4.6 + eciesjs: 0.4.13 + execa: 5.1.1 + fdir: 6.4.2(picomatch@4.0.2) + ignore: 5.3.2 + object-treeify: 1.1.33 + picomatch: 4.0.2 + which: 4.0.0 + '@dprint/formatter@0.3.0': {} '@dprint/markdown@0.17.8': {} '@dprint/toml@0.6.3': {} + '@ecies/ciphers@0.2.2(@noble/ciphers@1.2.0)': + dependencies: + '@noble/ciphers': 1.2.0 + '@electron-toolkit/preload@3.0.1(electron@31.7.6)': dependencies: electron: 31.7.6 @@ -9535,6 +9846,11 @@ snapshots: - esbuild - typescript + '@guiiai/logg@1.0.6': + dependencies: + chalk: 5.4.1 + date-fns: 4.1.0 + '@huggingface/jinja@0.3.2': {} '@huggingface/transformers@3.2.4': @@ -9821,6 +10137,14 @@ snapshots: '@tybys/wasm-util': 0.9.0 optional: true + '@noble/ciphers@1.2.0': {} + + '@noble/curves@1.8.0': + dependencies: + '@noble/hashes': 1.7.0 + + '@noble/hashes@1.7.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -10494,6 +10818,15 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.29.1': optional: true + '@sapphire/async-queue@1.5.5': {} + + '@sapphire/shapeshift@4.0.0': + dependencies: + fast-deep-equal: 3.1.3 + lodash: 4.17.21 + + '@sapphire/snowflake@3.5.3': {} + '@sec-ant/readable-stream@0.4.1': {} '@shikijs/core@1.26.1': @@ -10675,6 +11008,8 @@ snapshots: '@types/ms@0.7.34': {} + '@types/node@10.17.60': {} + '@types/node@20.17.11': dependencies: undici-types: 6.19.8 @@ -10723,6 +11058,10 @@ snapshots: '@types/webxr@0.5.20': {} + '@types/ws@8.5.13': + dependencies: + '@types/node': 22.10.5 + '@types/yauzl@2.10.3': dependencies: '@types/node': 22.10.5 @@ -11222,6 +11561,8 @@ snapshots: loupe: 3.1.2 tinyrainbow: 1.2.0 + '@vladfrangu/async_event_emitter@2.4.6': {} + '@volar/language-core@2.4.10': dependencies: '@volar/source-map': 2.4.10 @@ -11987,6 +12328,8 @@ snapshots: async-exit-hook@2.0.1: {} + async@0.2.10: {} + async@2.6.4: dependencies: lodash: 4.17.21 @@ -12237,6 +12580,8 @@ snapshots: caniuse-lite@1.0.30001680: {} + caseless@0.12.0: {} + ccount@2.0.1: {} chai@5.1.2: @@ -12353,6 +12698,8 @@ snapshots: command-exists@1.2.9: {} + commander@11.1.0: {} + commander@12.1.0: {} commander@2.20.3: {} @@ -12397,6 +12744,13 @@ snapshots: concat-map@0.0.1: {} + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + confbox@0.1.8: {} config-file-ts@0.2.6: @@ -12559,6 +12913,8 @@ snapshots: whatwg-url: 14.0.0 optional: true + date-fns@4.1.0: {} + de-indent@1.0.2: {} debug@2.6.9: @@ -12643,6 +12999,26 @@ snapshots: buffer-equal: 1.0.1 minimatch: 3.1.2 + discord-api-types@0.37.115: {} + + discord.js@14.17.2: + dependencies: + '@discordjs/builders': 1.10.0 + '@discordjs/collection': 1.5.3 + '@discordjs/formatters': 0.6.0 + '@discordjs/rest': 2.4.2 + '@discordjs/util': 1.1.1 + '@discordjs/ws': 1.2.0 + '@sapphire/snowflake': 3.5.3 + discord-api-types: 0.37.115 + fast-deep-equal: 3.1.3 + lodash.snakecase: 4.1.1 + tslib: 2.8.1 + undici: 6.19.8 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3): dependencies: app-builder-lib: 24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3) @@ -12699,12 +13075,21 @@ snapshots: draco3d@1.5.7: {} + duplex-child-process@1.0.1: {} + duplexer@0.1.2: {} earcut@2.2.4: {} eastasianwidth@0.2.0: {} + eciesjs@0.4.13: + dependencies: + '@ecies/ciphers': 0.2.2(@noble/ciphers@1.2.0) + '@noble/ciphers': 1.2.0 + '@noble/curves': 1.8.0 + '@noble/hashes': 1.7.0 + ee-first@1.1.1: {} ejs@3.1.9: @@ -13481,6 +13866,15 @@ snapshots: fflate@0.8.2: {} + ffmpeg-static@5.2.0: + dependencies: + '@derhuerst/http-basic': 8.2.4 + env-paths: 2.2.1 + https-proxy-agent: 5.0.1 + progress: 2.0.3 + transitivePeerDependencies: + - supports-color + figures@6.1.0: dependencies: is-unicode-supported: 2.1.0 @@ -13550,6 +13944,11 @@ snapshots: flatted@3.3.1: {} + fluent-ffmpeg@2.1.3: + dependencies: + async: 0.2.10 + which: 1.3.1 + follow-redirects@1.15.2: {} for-each@0.3.3: @@ -13919,6 +14318,10 @@ snapshots: - supports-color optional: true + http-response-object@3.0.2: + dependencies: + '@types/node': 10.17.60 + http2-wrapper@1.0.3: dependencies: quick-lru: 5.1.1 @@ -14174,6 +14577,8 @@ snapshots: isexe@2.0.0: {} + isexe@3.1.1: {} + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -14332,6 +14737,12 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + libsodium-wrappers@0.7.15: + dependencies: + libsodium: 0.7.15 + + libsodium@0.7.15: {} + lie@3.3.0: dependencies: immediate: 3.0.6 @@ -14397,6 +14808,8 @@ snapshots: lodash.merge@4.6.2: {} + lodash.snakecase@4.1.1: {} + lodash.sortby@4.7.0: {} lodash.union@4.6.0: {} @@ -14438,6 +14851,8 @@ snapshots: dependencies: yallist: 4.0.0 + magic-bytes.js@1.10.0: {} + magic-string-ast@0.6.2: dependencies: magic-string: 0.30.17 @@ -14998,6 +15413,8 @@ snapshots: object-keys@1.1.1: {} + object-treeify@1.1.33: {} + object.assign@4.1.4: dependencies: call-bind: 1.0.7 @@ -15103,6 +15520,8 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + opusscript@0.1.1: {} + oxc-resolver@2.1.1: optionalDependencies: '@oxc-resolver/binding-darwin-arm64': 2.1.1 @@ -15147,6 +15566,8 @@ snapshots: dependencies: callsites: 3.1.0 + parse-cache-control@1.0.1: {} + parse-gitignore@2.0.0: {} parse-imports@2.1.1: @@ -15499,6 +15920,15 @@ snapshots: dependencies: parse-ms: 4.0.0 + prism-media@1.3.5(ffmpeg-static@5.2.0)(opusscript@0.1.1): + optionalDependencies: + ffmpeg-static: 5.2.0 + opusscript: 0.1.1 + + prism-media@2.0.0-alpha.0: + dependencies: + duplex-child-process: 1.0.1 + process-nextick-args@2.0.1: {} process@0.11.10: {} @@ -16487,6 +16917,8 @@ snapshots: - rollup - typescript + ts-mixer@6.0.4: {} + tslib@2.4.0: {} tslib@2.8.1: {} @@ -16545,6 +16977,8 @@ snapshots: for-each: 0.3.3 is-typed-array: 1.1.12 + typedarray@0.0.6: {} + typescript@5.7.2: {} uc.micro@2.1.0: {} @@ -16616,6 +17050,8 @@ snapshots: undici-types@6.20.0: {} + undici@6.19.8: {} + unhead@1.11.14: dependencies: '@unhead/dom': 1.11.14 @@ -17343,10 +17779,18 @@ snapshots: gopd: 1.0.1 has-tostringtag: 1.0.0 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 + which@4.0.0: + dependencies: + isexe: 3.1.1 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -17485,8 +17929,7 @@ snapshots: wrappy@1.0.2: {} - ws@8.18.0: - optional: true + ws@8.18.0: {} xml-name-validator@4.0.0: {} diff --git a/services/discord-voice-bot/package.json b/services/discord-voice-bot/package.json new file mode 100644 index 00000000..13387570 --- /dev/null +++ b/services/discord-voice-bot/package.json @@ -0,0 +1,47 @@ +{ + "name": "@proj-airi/discord-voice-bot", + "type": "module", + "version": "0.1.0", + "private": false, + "description": "Discord voice bot for Airi", + "author": { + "name": "Neko Ayaka", + "email": "neko@ayaka.moe", + "url": "https://github.com/nekomeowww" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/moeru-ai/airi.git", + "directory": "services/discord-voice-bot" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs" + } + }, + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "files": [ + "README.md", + "dist", + "package.json" + ], + "scripts": { + "start": "dotenvx run -f .env.local -f .env -- tsx src/index.ts" + }, + "dependencies": { + "@discordjs/voice": "^0.18.0", + "@dotenvx/dotenvx": "^1.32.0", + "@guiiai/logg": "^1.0.6", + "@huggingface/transformers": "^3.2.4", + "discord.js": "^14.17.2", + "ffmpeg-static": "^5.2.0", + "fluent-ffmpeg": "^2.1.3", + "libsodium-wrappers": "^0.7.15", + "opusscript": "^0.1.1", + "prism-media": "2.0.0-alpha.0", + "tsx": "^4.19.2" + } +} diff --git a/services/discord-voice-bot/src/bots/discord/commands/index.ts b/services/discord-voice-bot/src/bots/discord/commands/index.ts new file mode 100644 index 00000000..c34be40d --- /dev/null +++ b/services/discord-voice-bot/src/bots/discord/commands/index.ts @@ -0,0 +1,18 @@ +import { env } from 'node:process' +import { REST, Routes, SlashCommandBuilder } from 'discord.js' + +export * from './ping' +export * from './summon' + +export async function registerCommands() { + const rest = new REST() + + rest.setToken(env.DISCORD_TOKEN) + rest.put( + Routes.applicationCommands(env.DISCORD_BOT_CLIENT_ID), + { body: [ + new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!'), + new SlashCommandBuilder().setName('summon').setDescription('Summons the bot to your voice channel'), + ] }, + ) +} diff --git a/services/discord-voice-bot/src/bots/discord/commands/ping.ts b/services/discord-voice-bot/src/bots/discord/commands/ping.ts new file mode 100644 index 00000000..7c324157 --- /dev/null +++ b/services/discord-voice-bot/src/bots/discord/commands/ping.ts @@ -0,0 +1,5 @@ +import type { CacheType, ChatInputCommandInteraction } from 'discord.js' + +export async function handlePing(interaction: ChatInputCommandInteraction) { + await interaction.reply('Pong!') +} diff --git a/services/discord-voice-bot/src/bots/discord/commands/summon.ts b/services/discord-voice-bot/src/bots/discord/commands/summon.ts new file mode 100644 index 00000000..927ae961 --- /dev/null +++ b/services/discord-voice-bot/src/bots/discord/commands/summon.ts @@ -0,0 +1,100 @@ +import type { useLogg } from '@guiiai/logg' +import type { CacheType, ChatInputCommandInteraction, GuildMember } from 'discord.js' +import { createWriteStream } from 'node:fs' +import { EndBehaviorType, entersState, joinVoiceChannel, VoiceConnectionStatus } from '@discordjs/voice' +import ffmpeg from 'fluent-ffmpeg' +import OpusScript from 'opusscript' + +export async function handleSummon(log: ReturnType, interaction: ChatInputCommandInteraction) { + const currVoiceChannel = (interaction.member as GuildMember).voice.channel + + if (!currVoiceChannel) { + return await interaction.reply('Please join a voice channel first.') + } + + try { + const connection = joinVoiceChannel({ + channelId: currVoiceChannel.id, + guildId: interaction.guild.id, + adapterCreator: interaction.guild.voiceAdapterCreator, + }) + + connection.on(VoiceConnectionStatus.Signalling, async () => { + log.log('Connection is signalling') + }) + + connection.on(VoiceConnectionStatus.Connecting, async () => { + log.log('Connection is connecting') + }) + + connection.on(VoiceConnectionStatus.Ready, async () => { + await interaction.reply(`Joined: ${currVoiceChannel.name}.`) + }) + + connection.on(VoiceConnectionStatus.Disconnected, async (_oldState, _newState) => { + try { + await Promise.race([ + entersState(connection, VoiceConnectionStatus.Signalling, 5_000), + entersState(connection, VoiceConnectionStatus.Connecting, 5_000), + ]) + // Seems to be reconnecting to a new channel - ignore disconnect + } + catch (error) { + log.withError(error).log('Failed to reconnect to channel') + // Seems to be a real disconnect which SHOULDN'T be recovered from + connection.destroy() + } + }) + + connection.on(VoiceConnectionStatus.Destroyed, async () => { + log.log('Destroyed connection') + }) + + connection.receiver.speaking.on('start', async (userId) => { + log.log(`User ${userId} started speaking`) + + const listenStream = connection.receiver.subscribe(userId, { + end: { + behavior: EndBehaviorType.AfterSilence, + duration: 2000, // Max 2s of silence before ending the stream. + }, + }) + + // Generate a uid for the audio file. + // Create a stream that writes a new pcm file with the generated uid + const writeStream = createWriteStream(`audio.pcm`, { flags: 'a' }) + const decoder = new OpusScript(48000, 2) + + // Create the pipeline + listenStream.on('data', (chunk) => { + const pcm = decoder.decode(chunk) + writeStream.write(pcm) + }) + + // When user stops talking, stop the stream and generate an mp3 file. + listenStream.on('end', async () => { + writeStream.end() + + ffmpeg() + .input(`audio.pcm`) + .inputFormat('s32le') + .audioFrequency(60000) + .audioChannels(2) + .output(`audio.wav`) + .outputFormat('wav') + .on('error', (err) => { + log.error('Error:', err) + }) + .run() + }) + }) + + connection.receiver.speaking.on('end', (userId) => { + log.log(`User ${userId} stopped speaking`) + }) + } + catch (error) { + log.error(error) + await interaction.reply('Could not join voice channel.') + } +} diff --git a/services/discord-voice-bot/src/index.ts b/services/discord-voice-bot/src/index.ts new file mode 100644 index 00000000..8bfa17ca --- /dev/null +++ b/services/discord-voice-bot/src/index.ts @@ -0,0 +1,45 @@ +import { env } from 'node:process' +import { Format, LogLevel, setGlobalFormat, setGlobalLogLevel, useLogg } from '@guiiai/logg' +import { Client, Events, GatewayIntentBits } from 'discord.js' + +import { handlePing, handleSummon, registerCommands } from './bots/discord/commands' + +import 'dotenv/config' + +setGlobalFormat(Format.Pretty) +setGlobalLogLevel(LogLevel.Log) +const log = useLogg('Bot').useGlobalConfig() + +// Create a new client instance +async function main() { + const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates] }) + + // When the client is ready, run this code (only once). + // The distinction between `client: Client` and `readyClient: Client` is important for TypeScript developers. + // It makes some properties non-nullable. + client.once(Events.ClientReady, (readyClient) => { + log.withField('identity', readyClient.user.tag).log(`Ready!`) + }) + + client.on(Events.InteractionCreate, async (interaction) => { + if (!interaction.isChatInputCommand()) + return + + log.log(interaction) + + switch (interaction.commandName) { + case 'ping': + await handlePing(interaction) + break + case 'summon': + await handleSummon(log, interaction) + break + } + }) + + await registerCommands() + // Log in to Discord with your client's token + await client.login(env.DISCORD_TOKEN) +} + +main().catch(log.error) diff --git a/services/discord-voice-bot/src/pipelines/tts/transformers.ts b/services/discord-voice-bot/src/pipelines/tts/transformers.ts new file mode 100644 index 00000000..7c23beb8 --- /dev/null +++ b/services/discord-voice-bot/src/pipelines/tts/transformers.ts @@ -0,0 +1,39 @@ +import type { PreTrainedModel, PreTrainedTokenizer, Processor, ProgressCallback } from '@huggingface/transformers' +import { AutoProcessor, AutoTokenizer, WhisperForConditionalGeneration } from '@huggingface/transformers' + +/** + * This class uses the Singleton pattern to ensure that only one instance of the model is loaded. + */ +class WhisperAutomaticSpeechRecognitionPipeline { + static model_id: string | null = null + static tokenizer: Promise + static processor: Promise + static model: Promise + + static async getInstance(progress_callback?: ProgressCallback) { + this.model_id = 'onnx-community/whisper-base' + + this.tokenizer ??= AutoTokenizer.from_pretrained(this.model_id, { + progress_callback, + }) + + this.processor ??= AutoProcessor.from_pretrained(this.model_id, { + progress_callback, + }) + + this.model ??= WhisperForConditionalGeneration.from_pretrained(this.model_id, { + dtype: { + encoder_model: 'fp32', // 'fp16' works too + decoder_model_merged: 'q4', // or 'fp32' ('fp16' is broken) + }, + device: 'auto', + progress_callback, + }) + + return Promise.all([this.tokenizer, this.processor, this.model]) + } +} + +export async function useWhisperPipeline() { + return await WhisperAutomaticSpeechRecognitionPipeline.getInstance() +} diff --git a/services/discord-voice-bot/tsconfig.json b/services/discord-voice-bot/tsconfig.json new file mode 100644 index 00000000..00dcfd80 --- /dev/null +++ b/services/discord-voice-bot/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": [ + "ESNext" + ], + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": [ + "src/**/*.ts" + ] +}