From f95447f94cfd0f0f8871504a5aaf4c3ef8c1460a Mon Sep 17 00:00:00 2001 From: drptbl Date: Wed, 12 Feb 2025 23:49:59 +0000 Subject: [PATCH 1/9] feat: add support bot Signed-off-by: drptbl --- pnpm-lock.yaml | 567 ++++++++++++++++++++++++++++++++++---- pnpm-workspace.yaml | 1 + support-bot/.env.example | 5 + support-bot/README.md | 75 +++++ support-bot/package.json | 24 ++ support-bot/src/index.ts | 178 ++++++++++++ support-bot/tsconfig.json | 15 + 7 files changed, 813 insertions(+), 52 deletions(-) create mode 100644 support-bot/.env.example create mode 100644 support-bot/README.md create mode 100644 support-bot/package.json create mode 100644 support-bot/src/index.ts create mode 100644 support-bot/tsconfig.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6779519e..18688dafa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,16 +46,16 @@ importers: version: link:../packages/tsconfig typedoc: specifier: 0.25.8 - version: 0.25.8(typescript@5.6.3) + version: 0.25.8(typescript@5.7.3) typedoc-plugin-markdown: specifier: 4.0.1 - version: 4.0.1(typedoc@0.25.8(typescript@5.6.3)) + version: 4.0.1(typedoc@0.25.8(typescript@5.7.3)) typedoc-vitepress-theme: specifier: 1.0.0 - version: 1.0.0(typedoc-plugin-markdown@4.0.1(typedoc@0.25.8(typescript@5.6.3))) + version: 1.0.0(typedoc-plugin-markdown@4.0.1(typedoc@0.25.8(typescript@5.7.3))) vitepress: specifier: 1.1.4 - version: 1.1.4(@algolia/client-search@4.24.0)(@types/node@22.9.1)(axios@1.7.7)(postcss@8.4.41)(search-insights@2.17.0)(typescript@5.6.3) + version: 1.1.4(@algolia/client-search@4.24.0)(@types/node@22.13.1)(axios@1.7.7)(postcss@8.4.41)(search-insights@2.17.0)(typescript@5.7.3) examples/ethereum-wallet-mock: dependencies: @@ -256,6 +256,28 @@ importers: specifier: 5.3.3 version: 5.3.3 + support-bot: + dependencies: + '@google/generative-ai': + specifier: 0.21.0 + version: 0.21.0 + discord.js: + specifier: 14.18.0 + version: 14.18.0 + dotenv: + specifier: 16.4.7 + version: 16.4.7 + devDependencies: + '@types/node': + specifier: 22.13.1 + version: 22.13.1 + tsx: + specifier: 4.19.2 + version: 4.19.2 + typescript: + specifier: 5.7.3 + version: 5.7.3 + wallets/ethereum-wallet-mock: dependencies: '@depay/web3-client': @@ -600,6 +622,34 @@ packages: resolution: {integrity: sha512-bBM1J0EWDWXJKVPtzo8YrX7fbGwUATYWN8kaJniQU2z5V+UK3kVhjQi+en0JMF9cCjinkERK7MqoZLaYR+cb+Q==} engines: {node: '>=16'} + '@discordjs/builders@1.10.1': + resolution: {integrity: sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==} + 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.3': + resolution: {integrity: sha512-+SO4RKvWsM+y8uFHgYQrcTl/3+cY02uQOH7/7bKbVZsTfrfpoE62o5p+mmV+s7FVhTX82/kQUGGbu4YlV60RtA==} + engines: {node: '>=18'} + + '@discordjs/util@1.1.1': + resolution: {integrity: sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==} + engines: {node: '>=18'} + + '@discordjs/ws@1.2.1': + resolution: {integrity: sha512-PBvenhZG56a6tMWF/f4P6f4GxZKJTBG95n7aiGSPTnodmz4N5g60t79rSIAq7ywMbv8A4jFtexMruH+oe51aQQ==} + engines: {node: '>=16.11.0'} + '@docsearch/css@3.6.1': resolution: {integrity: sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==} @@ -641,6 +691,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.19.12': resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} @@ -659,6 +715,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.19.12': resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} @@ -677,6 +739,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.19.12': resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} @@ -695,6 +763,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.19.12': resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} @@ -713,6 +787,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.19.12': resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} @@ -731,6 +811,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.19.12': resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} @@ -749,6 +835,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.19.12': resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} @@ -767,6 +859,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.19.12': resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} @@ -785,6 +883,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.19.12': resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} @@ -803,6 +907,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.19.12': resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} @@ -821,6 +931,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.19.12': resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} @@ -839,6 +955,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.19.12': resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} @@ -857,6 +979,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.19.12': resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} @@ -875,6 +1003,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.19.12': resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} @@ -893,6 +1027,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.19.12': resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} @@ -911,6 +1051,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.19.12': resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} @@ -929,6 +1075,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-x64@0.19.12': resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} @@ -947,6 +1099,18 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.19.12': resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} @@ -965,6 +1129,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.19.12': resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} @@ -983,6 +1153,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.19.12': resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} @@ -1001,6 +1177,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.19.12': resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} @@ -1019,6 +1201,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.19.12': resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} @@ -1037,6 +1225,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@ethersproject/abi@5.7.0': resolution: {integrity: sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==} @@ -1130,6 +1324,10 @@ packages: '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + '@google/generative-ai@0.21.0': + resolution: {integrity: sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg==} + engines: {node: '>=18.0.0'} + '@inquirer/confirm@3.2.0': resolution: {integrity: sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw==} engines: {node: '>=18'} @@ -1471,6 +1669,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'} + '@scure/base@1.1.7': resolution: {integrity: sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==} @@ -1584,8 +1794,8 @@ packages: '@types/node@20.11.17': resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==} - '@types/node@22.9.1': - resolution: {integrity: sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==} + '@types/node@22.13.1': + resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1626,6 +1836,9 @@ packages: '@types/wrap-ansi@3.0.0': resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} + '@types/ws@8.5.14': + resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==} + '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} @@ -1688,6 +1901,10 @@ packages: '@vitest/utils@2.1.5': resolution: {integrity: sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==} + '@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'} + '@vue/compiler-core@3.4.38': resolution: {integrity: sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==} @@ -2485,6 +2702,13 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + discord-api-types@0.37.119: + resolution: {integrity: sha512-WasbGFXEB+VQWXlo6IpW3oUv73Yuau1Ig4AZF/m13tXcTKnMpc/mHjpztIlz4+BM9FG9BHQkEXiPto3bKduQUg==} + + discord.js@14.18.0: + resolution: {integrity: sha512-SvU5kVUvwunQhN2/+0t55QW/1EHfB1lp0TtLZUSXVHDmyHTrdOj5LRKdR0zLcybaA15F+NtdWuWmGOX9lE+CAw==} + engines: {node: '>=18'} + dot-prop@6.0.1: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} @@ -2493,6 +2717,10 @@ packages: resolution: {integrity: sha512-rZSSFxke7d9nYQ5NeMIwp5PP+f8wXgKNljpOb7KtH6SKW1cEqcXAz9VSJYVLKe7Jhup/gUYOkaeSVyK8GJ+nBg==} engines: {node: '>=12'} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} @@ -2587,6 +2815,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -2846,6 +3079,9 @@ packages: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} + get-tsconfig@4.10.0: + resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + getos@3.2.1: resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} @@ -3466,6 +3702,9 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} @@ -3510,6 +3749,9 @@ packages: lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + magic-bytes.js@1.10.0: + resolution: {integrity: sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==} + magic-string@0.30.11: resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} @@ -4259,6 +4501,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -4759,6 +5004,9 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-mixer@6.0.4: + resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + tslib@2.7.0: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} @@ -4781,6 +5029,11 @@ packages: typescript: optional: true + tsx@4.19.2: + resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} + engines: {node: '>=18.0.0'} + hasBin: true + tty-table@4.2.3: resolution: {integrity: sha512-Fs15mu0vGzCrj8fmJNP7Ynxt5J7praPXqFN0leZeZBXJwkMxv9cb2D454k1ltrtUSJbZ4yH4e0CynsHLxmUfFA==} engines: {node: '>=8.0.0'} @@ -4903,8 +5156,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.6.3: - resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} engines: {node: '>=14.17'} hasBin: true @@ -4917,8 +5170,12 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + undici@6.21.1: + resolution: {integrity: sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==} + engines: {node: '>=18.17'} unique-filename@2.0.1: resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} @@ -5702,6 +5959,53 @@ snapshots: - bufferutil - utf-8-validate + '@discordjs/builders@1.10.1': + dependencies: + '@discordjs/formatters': 0.6.0 + '@discordjs/util': 1.1.1 + '@sapphire/shapeshift': 4.0.0 + discord-api-types: 0.37.119 + fast-deep-equal: 3.1.3 + ts-mixer: 6.0.4 + tslib: 2.7.0 + + '@discordjs/collection@1.5.3': {} + + '@discordjs/collection@2.1.1': {} + + '@discordjs/formatters@0.6.0': + dependencies: + discord-api-types: 0.37.119 + + '@discordjs/rest@2.4.3': + 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.119 + magic-bytes.js: 1.10.0 + tslib: 2.7.0 + undici: 6.21.1 + + '@discordjs/util@1.1.1': {} + + '@discordjs/ws@1.2.1': + dependencies: + '@discordjs/collection': 2.1.1 + '@discordjs/rest': 2.4.3 + '@discordjs/util': 1.1.1 + '@sapphire/async-queue': 1.5.5 + '@types/ws': 8.5.14 + '@vladfrangu/async_event_emitter': 2.4.6 + discord-api-types: 0.37.119 + tslib: 2.7.0 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@docsearch/css@3.6.1': {} '@docsearch/js@3.6.1(@algolia/client-search@4.24.0)(search-insights@2.17.0)': @@ -5735,6 +6039,9 @@ snapshots: '@esbuild/aix-ppc64@0.21.5': optional: true + '@esbuild/aix-ppc64@0.23.1': + optional: true + '@esbuild/android-arm64@0.19.12': optional: true @@ -5744,6 +6051,9 @@ snapshots: '@esbuild/android-arm64@0.21.5': optional: true + '@esbuild/android-arm64@0.23.1': + optional: true + '@esbuild/android-arm@0.19.12': optional: true @@ -5753,6 +6063,9 @@ snapshots: '@esbuild/android-arm@0.21.5': optional: true + '@esbuild/android-arm@0.23.1': + optional: true + '@esbuild/android-x64@0.19.12': optional: true @@ -5762,6 +6075,9 @@ snapshots: '@esbuild/android-x64@0.21.5': optional: true + '@esbuild/android-x64@0.23.1': + optional: true + '@esbuild/darwin-arm64@0.19.12': optional: true @@ -5771,6 +6087,9 @@ snapshots: '@esbuild/darwin-arm64@0.21.5': optional: true + '@esbuild/darwin-arm64@0.23.1': + optional: true + '@esbuild/darwin-x64@0.19.12': optional: true @@ -5780,6 +6099,9 @@ snapshots: '@esbuild/darwin-x64@0.21.5': optional: true + '@esbuild/darwin-x64@0.23.1': + optional: true + '@esbuild/freebsd-arm64@0.19.12': optional: true @@ -5789,6 +6111,9 @@ snapshots: '@esbuild/freebsd-arm64@0.21.5': optional: true + '@esbuild/freebsd-arm64@0.23.1': + optional: true + '@esbuild/freebsd-x64@0.19.12': optional: true @@ -5798,6 +6123,9 @@ snapshots: '@esbuild/freebsd-x64@0.21.5': optional: true + '@esbuild/freebsd-x64@0.23.1': + optional: true + '@esbuild/linux-arm64@0.19.12': optional: true @@ -5807,6 +6135,9 @@ snapshots: '@esbuild/linux-arm64@0.21.5': optional: true + '@esbuild/linux-arm64@0.23.1': + optional: true + '@esbuild/linux-arm@0.19.12': optional: true @@ -5816,6 +6147,9 @@ snapshots: '@esbuild/linux-arm@0.21.5': optional: true + '@esbuild/linux-arm@0.23.1': + optional: true + '@esbuild/linux-ia32@0.19.12': optional: true @@ -5825,6 +6159,9 @@ snapshots: '@esbuild/linux-ia32@0.21.5': optional: true + '@esbuild/linux-ia32@0.23.1': + optional: true + '@esbuild/linux-loong64@0.19.12': optional: true @@ -5834,6 +6171,9 @@ snapshots: '@esbuild/linux-loong64@0.21.5': optional: true + '@esbuild/linux-loong64@0.23.1': + optional: true + '@esbuild/linux-mips64el@0.19.12': optional: true @@ -5843,6 +6183,9 @@ snapshots: '@esbuild/linux-mips64el@0.21.5': optional: true + '@esbuild/linux-mips64el@0.23.1': + optional: true + '@esbuild/linux-ppc64@0.19.12': optional: true @@ -5852,6 +6195,9 @@ snapshots: '@esbuild/linux-ppc64@0.21.5': optional: true + '@esbuild/linux-ppc64@0.23.1': + optional: true + '@esbuild/linux-riscv64@0.19.12': optional: true @@ -5861,6 +6207,9 @@ snapshots: '@esbuild/linux-riscv64@0.21.5': optional: true + '@esbuild/linux-riscv64@0.23.1': + optional: true + '@esbuild/linux-s390x@0.19.12': optional: true @@ -5870,6 +6219,9 @@ snapshots: '@esbuild/linux-s390x@0.21.5': optional: true + '@esbuild/linux-s390x@0.23.1': + optional: true + '@esbuild/linux-x64@0.19.12': optional: true @@ -5879,6 +6231,9 @@ snapshots: '@esbuild/linux-x64@0.21.5': optional: true + '@esbuild/linux-x64@0.23.1': + optional: true + '@esbuild/netbsd-x64@0.19.12': optional: true @@ -5888,6 +6243,12 @@ snapshots: '@esbuild/netbsd-x64@0.21.5': optional: true + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + '@esbuild/openbsd-x64@0.19.12': optional: true @@ -5897,6 +6258,9 @@ snapshots: '@esbuild/openbsd-x64@0.21.5': optional: true + '@esbuild/openbsd-x64@0.23.1': + optional: true + '@esbuild/sunos-x64@0.19.12': optional: true @@ -5906,6 +6270,9 @@ snapshots: '@esbuild/sunos-x64@0.21.5': optional: true + '@esbuild/sunos-x64@0.23.1': + optional: true + '@esbuild/win32-arm64@0.19.12': optional: true @@ -5915,6 +6282,9 @@ snapshots: '@esbuild/win32-arm64@0.21.5': optional: true + '@esbuild/win32-arm64@0.23.1': + optional: true + '@esbuild/win32-ia32@0.19.12': optional: true @@ -5924,6 +6294,9 @@ snapshots: '@esbuild/win32-ia32@0.21.5': optional: true + '@esbuild/win32-ia32@0.23.1': + optional: true + '@esbuild/win32-x64@0.19.12': optional: true @@ -5933,6 +6306,9 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true + '@esbuild/win32-x64@0.23.1': + optional: true + '@ethersproject/abi@5.7.0': dependencies: '@ethersproject/address': 5.7.0 @@ -6190,6 +6566,8 @@ snapshots: '@gar/promisify@1.1.3': {} + '@google/generative-ai@0.21.0': {} + '@inquirer/confirm@3.2.0': dependencies: '@inquirer/core': 9.2.1 @@ -6222,7 +6600,7 @@ snapshots: '@inquirer/figures': 1.0.8 '@inquirer/type': 2.0.0 '@types/mute-stream': 0.0.4 - '@types/node': 22.9.1 + '@types/node': 22.13.1 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 cli-width: 4.1.0 @@ -6515,6 +6893,15 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.27.3': 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': {} + '@scure/base@1.1.7': {} '@scure/bip32@1.3.2': @@ -6609,7 +6996,7 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 20.11.17 + '@types/node': 22.13.1 '@types/linkify-it@5.0.0': {} @@ -6624,7 +7011,7 @@ snapshots: '@types/mute-stream@0.0.4': dependencies: - '@types/node': 20.11.17 + '@types/node': 22.13.1 '@types/node@12.20.55': {} @@ -6632,9 +7019,9 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@22.9.1': + '@types/node@22.13.1': dependencies: - undici-types: 6.19.8 + undici-types: 6.20.0 '@types/normalize-package-data@2.4.4': {} @@ -6644,7 +7031,7 @@ snapshots: '@types/readdir-glob@1.1.5': dependencies: - '@types/node': 20.11.17 + '@types/node': 22.13.1 '@types/semver@7.5.8': {} @@ -6669,9 +7056,13 @@ snapshots: '@types/wrap-ansi@3.0.0': {} + '@types/ws@8.5.14': + dependencies: + '@types/node': 22.13.1 + '@types/yauzl@2.10.3': dependencies: - '@types/node': 20.11.17 + '@types/node': 22.13.1 optional: true '@viem/anvil@0.0.7': @@ -6685,10 +7076,10 @@ snapshots: - debug - utf-8-validate - '@vitejs/plugin-vue@5.1.2(vite@5.4.2(@types/node@22.9.1))(vue@3.4.38(typescript@5.6.3))': + '@vitejs/plugin-vue@5.1.2(vite@5.4.2(@types/node@22.13.1))(vue@3.4.38(typescript@5.7.3))': dependencies: - vite: 5.4.2(@types/node@22.9.1) - vue: 3.4.38(typescript@5.6.3) + vite: 5.4.2(@types/node@22.13.1) + vue: 3.4.38(typescript@5.7.3) '@vitest/coverage-v8@1.2.2(vitest@1.2.2(@types/node@20.11.17))': dependencies: @@ -6798,6 +7189,8 @@ snapshots: loupe: 3.1.2 tinyrainbow: 1.2.0 + '@vladfrangu/async_event_emitter@2.4.6': {} + '@vue/compiler-core@3.4.38': dependencies: '@babel/parser': 7.25.4 @@ -6862,29 +7255,29 @@ snapshots: '@vue/shared': 3.4.38 csstype: 3.1.3 - '@vue/server-renderer@3.4.38(vue@3.4.38(typescript@5.6.3))': + '@vue/server-renderer@3.4.38(vue@3.4.38(typescript@5.7.3))': dependencies: '@vue/compiler-ssr': 3.4.38 '@vue/shared': 3.4.38 - vue: 3.4.38(typescript@5.6.3) + vue: 3.4.38(typescript@5.7.3) '@vue/shared@3.4.38': {} - '@vueuse/core@10.11.1(vue@3.4.38(typescript@5.6.3))': + '@vueuse/core@10.11.1(vue@3.4.38(typescript@5.7.3))': dependencies: '@types/web-bluetooth': 0.0.20 '@vueuse/metadata': 10.11.1 - '@vueuse/shared': 10.11.1(vue@3.4.38(typescript@5.6.3)) - vue-demi: 0.14.10(vue@3.4.38(typescript@5.6.3)) + '@vueuse/shared': 10.11.1(vue@3.4.38(typescript@5.7.3)) + vue-demi: 0.14.10(vue@3.4.38(typescript@5.7.3)) transitivePeerDependencies: - '@vue/composition-api' - vue - '@vueuse/integrations@10.11.1(axios@1.7.7)(focus-trap@7.5.4)(vue@3.4.38(typescript@5.6.3))': + '@vueuse/integrations@10.11.1(axios@1.7.7)(focus-trap@7.5.4)(vue@3.4.38(typescript@5.7.3))': dependencies: - '@vueuse/core': 10.11.1(vue@3.4.38(typescript@5.6.3)) - '@vueuse/shared': 10.11.1(vue@3.4.38(typescript@5.6.3)) - vue-demi: 0.14.10(vue@3.4.38(typescript@5.6.3)) + '@vueuse/core': 10.11.1(vue@3.4.38(typescript@5.7.3)) + '@vueuse/shared': 10.11.1(vue@3.4.38(typescript@5.7.3)) + vue-demi: 0.14.10(vue@3.4.38(typescript@5.7.3)) optionalDependencies: axios: 1.7.7 focus-trap: 7.5.4 @@ -6894,9 +7287,9 @@ snapshots: '@vueuse/metadata@10.11.1': {} - '@vueuse/shared@10.11.1(vue@3.4.38(typescript@5.6.3))': + '@vueuse/shared@10.11.1(vue@3.4.38(typescript@5.7.3))': dependencies: - vue-demi: 0.14.10(vue@3.4.38(typescript@5.6.3)) + vue-demi: 0.14.10(vue@3.4.38(typescript@5.7.3)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -7668,12 +8061,34 @@ snapshots: dependencies: path-type: 4.0.0 + discord-api-types@0.37.119: {} + + discord.js@14.18.0: + dependencies: + '@discordjs/builders': 1.10.1 + '@discordjs/collection': 1.5.3 + '@discordjs/formatters': 0.6.0 + '@discordjs/rest': 2.4.3 + '@discordjs/util': 1.1.1 + '@discordjs/ws': 1.2.1 + '@sapphire/snowflake': 3.5.3 + discord-api-types: 0.37.119 + fast-deep-equal: 3.1.3 + lodash.snakecase: 4.1.1 + tslib: 2.7.0 + undici: 6.21.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dot-prop@6.0.1: dependencies: is-obj: 2.0.0 dotenv@16.4.2: {} + dotenv@16.4.7: {} + duplexer2@0.1.4: dependencies: readable-stream: 2.3.8 @@ -7882,6 +8297,33 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + escalade@3.1.2: {} escape-goat@4.0.0: {} @@ -8198,6 +8640,10 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.4 + get-tsconfig@4.10.0: + dependencies: + resolve-pkg-maps: 1.0.0 + getos@3.2.1: dependencies: async: 3.2.6 @@ -8782,6 +9228,8 @@ snapshots: lodash.once@4.1.1: {} + lodash.snakecase@4.1.1: {} + lodash.sortby@4.7.0: {} lodash.startcase@4.4.0: {} @@ -8827,6 +9275,8 @@ snapshots: lunr@2.3.9: {} + magic-bytes.js@1.10.0: {} + magic-string@0.30.11: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -9658,6 +10108,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.8: dependencies: is-core-module: 2.15.1 @@ -10232,6 +10684,8 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-mixer@6.0.4: {} + tslib@2.7.0: {} tsup@8.0.2(postcss@8.4.41)(typescript@5.3.3): @@ -10257,6 +10711,13 @@ snapshots: - supports-color - ts-node + tsx@4.19.2: + dependencies: + esbuild: 0.23.1 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + tty-table@4.2.3: dependencies: chalk: 4.1.2 @@ -10360,25 +10821,25 @@ snapshots: dependencies: is-typedarray: 1.0.0 - typedoc-plugin-markdown@4.0.1(typedoc@0.25.8(typescript@5.6.3)): + typedoc-plugin-markdown@4.0.1(typedoc@0.25.8(typescript@5.7.3)): dependencies: - typedoc: 0.25.8(typescript@5.6.3) + typedoc: 0.25.8(typescript@5.7.3) - typedoc-vitepress-theme@1.0.0(typedoc-plugin-markdown@4.0.1(typedoc@0.25.8(typescript@5.6.3))): + typedoc-vitepress-theme@1.0.0(typedoc-plugin-markdown@4.0.1(typedoc@0.25.8(typescript@5.7.3))): dependencies: - typedoc-plugin-markdown: 4.0.1(typedoc@0.25.8(typescript@5.6.3)) + typedoc-plugin-markdown: 4.0.1(typedoc@0.25.8(typescript@5.7.3)) - typedoc@0.25.8(typescript@5.6.3): + typedoc@0.25.8(typescript@5.7.3): dependencies: lunr: 2.3.9 marked: 4.3.0 minimatch: 9.0.5 shiki: 0.14.7 - typescript: 5.6.3 + typescript: 5.7.3 typescript@5.3.3: {} - typescript@5.6.3: {} + typescript@5.7.3: {} ufo@1.5.4: {} @@ -10391,7 +10852,9 @@ snapshots: undici-types@5.26.5: {} - undici-types@6.19.8: {} + undici-types@6.20.0: {} + + undici@6.21.1: {} unique-filename@2.0.1: dependencies: @@ -10554,32 +11017,32 @@ snapshots: '@types/node': 20.11.17 fsevents: 2.3.3 - vite@5.4.2(@types/node@22.9.1): + vite@5.4.2(@types/node@22.13.1): dependencies: esbuild: 0.21.5 postcss: 8.4.41 rollup: 4.21.0 optionalDependencies: - '@types/node': 22.9.1 + '@types/node': 22.13.1 fsevents: 2.3.3 - vitepress@1.1.4(@algolia/client-search@4.24.0)(@types/node@22.9.1)(axios@1.7.7)(postcss@8.4.41)(search-insights@2.17.0)(typescript@5.6.3): + vitepress@1.1.4(@algolia/client-search@4.24.0)(@types/node@22.13.1)(axios@1.7.7)(postcss@8.4.41)(search-insights@2.17.0)(typescript@5.7.3): dependencies: '@docsearch/css': 3.6.1 '@docsearch/js': 3.6.1(@algolia/client-search@4.24.0)(search-insights@2.17.0) '@shikijs/core': 1.14.1 '@shikijs/transformers': 1.14.1 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.1.2(vite@5.4.2(@types/node@22.9.1))(vue@3.4.38(typescript@5.6.3)) + '@vitejs/plugin-vue': 5.1.2(vite@5.4.2(@types/node@22.13.1))(vue@3.4.38(typescript@5.7.3)) '@vue/devtools-api': 7.3.9 - '@vueuse/core': 10.11.1(vue@3.4.38(typescript@5.6.3)) - '@vueuse/integrations': 10.11.1(axios@1.7.7)(focus-trap@7.5.4)(vue@3.4.38(typescript@5.6.3)) + '@vueuse/core': 10.11.1(vue@3.4.38(typescript@5.7.3)) + '@vueuse/integrations': 10.11.1(axios@1.7.7)(focus-trap@7.5.4)(vue@3.4.38(typescript@5.7.3)) focus-trap: 7.5.4 mark.js: 8.11.1 minisearch: 6.3.0 shiki: 1.14.1 - vite: 5.4.2(@types/node@22.9.1) - vue: 3.4.38(typescript@5.6.3) + vite: 5.4.2(@types/node@22.13.1) + vue: 3.4.38(typescript@5.7.3) optionalDependencies: postcss: 8.4.41 transitivePeerDependencies: @@ -10684,19 +11147,19 @@ snapshots: vscode-textmate@8.0.0: {} - vue-demi@0.14.10(vue@3.4.38(typescript@5.6.3)): + vue-demi@0.14.10(vue@3.4.38(typescript@5.7.3)): dependencies: - vue: 3.4.38(typescript@5.6.3) + vue: 3.4.38(typescript@5.7.3) - vue@3.4.38(typescript@5.6.3): + vue@3.4.38(typescript@5.7.3): dependencies: '@vue/compiler-dom': 3.4.38 '@vue/compiler-sfc': 3.4.38 '@vue/runtime-dom': 3.4.38 - '@vue/server-renderer': 3.4.38(vue@3.4.38(typescript@5.6.3)) + '@vue/server-renderer': 3.4.38(vue@3.4.38(typescript@5.7.3)) '@vue/shared': 3.4.38 optionalDependencies: - typescript: 5.6.3 + typescript: 5.7.3 wcwidth@1.0.1: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 52b1faac5..cd848a867 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,3 +4,4 @@ packages: - "examples/*" - "release" - "docs" + - "support-bot" diff --git a/support-bot/.env.example b/support-bot/.env.example new file mode 100644 index 000000000..92261efb5 --- /dev/null +++ b/support-bot/.env.example @@ -0,0 +1,5 @@ +# Discord Bot Token from Discord Developer Portal +DISCORD_BOT_TOKEN=your_discord_bot_token_here + +# Google Cloud API Key for Gemini AI +CLOUD_API_KEY=your_google_cloud_api_key_here diff --git a/support-bot/README.md b/support-bot/README.md new file mode 100644 index 000000000..cf5eeb552 --- /dev/null +++ b/support-bot/README.md @@ -0,0 +1,75 @@ +# Synpress Support Bot + +A Discord bot powered by Google's Gemini AI that provides support for Synpress-related questions. The bot analyzes the Synpress codebase and can answer questions about its functionality, usage, and implementation details. + +## Features + +- Responds to mentions in the designated support channel +- Uses Gemini AI to provide accurate responses about Synpress +- Handles long responses by splitting them into multiple messages +- Prevents overlapping requests with a processing lock +- TypeScript implementation for better type safety + +## Prerequisites + +- Node.js 18 or higher +- pnpm package manager +- Discord Bot Token +- Google Cloud API Key with Gemini AI access + +## Setup + +1. Copy the `.env.example` file to `.env`: + + ```bash + cp .env.example .env + ``` + +2. Fill in your environment variables in the `.env` file: + + - `DISCORD_BOT_TOKEN`: Your Discord bot token from the Discord Developer Portal + - `CLOUD_API_KEY`: Your Google Cloud API key with Gemini AI access + +3. Install dependencies: + + ```bash + pnpm install + ``` + +4. Build the project: + ```bash + pnpm build + ``` + +## Usage + +1. Start the bot in development mode: + + ```bash + pnpm dev + ``` + + Or in production mode: + + ```bash + pnpm start + ``` + +2. Create a channel named `support-bot` in your Discord server + +3. Mention the bot (@BotName) in the support-bot channel with your Synpress-related question + +## Development + +- `pnpm build` - Builds the TypeScript code +- `pnpm start` - Starts the bot in production mode +- `pnpm dev` - Starts the bot in development mode with hot reload +- `pnpm lint` - Runs the linter +- `pnpm format` - Formats the code + +## Important Notes + +- The bot only responds in the channel named `support-bot` +- It requires the `synpress-source.txt` file in the root directory containing the Synpress codebase +- Responses are limited to 1900 characters and will be split into multiple messages if needed +- The bot processes one request at a time to maintain response quality diff --git a/support-bot/package.json b/support-bot/package.json new file mode 100644 index 000000000..4eae3f844 --- /dev/null +++ b/support-bot/package.json @@ -0,0 +1,24 @@ +{ + "name": "@synpress/support-bot", + "version": "0.1.0", + "description": "Discord support bot using Gemini AI for Synpress", + "type": "module", + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "dev": "tsx watch src/index.ts", + "format": "biome format . --write", + "lint": "biome check .", + "start": "node dist/index.js" + }, + "dependencies": { + "@google/generative-ai": "0.21.0", + "discord.js": "14.18.0", + "dotenv": "16.4.7" + }, + "devDependencies": { + "@types/node": "22.13.1", + "tsx": "4.19.2", + "typescript": "5.7.3" + } +} diff --git a/support-bot/src/index.ts b/support-bot/src/index.ts new file mode 100644 index 000000000..cc0bebaa6 --- /dev/null +++ b/support-bot/src/index.ts @@ -0,0 +1,178 @@ +import { promises as fs } from 'fs' +import { + ChatSession, + GenerativeModel, + GoogleGenerativeAI, + HarmBlockThreshold, + HarmCategory +} from '@google/generative-ai' +import { Client, GatewayIntentBits, Message, TextChannel } from 'discord.js' +import dotenv from 'dotenv' + +dotenv.config() + +const apiKey = process.env.CLOUD_API_KEY +if (!apiKey) { + throw new Error('CLOUD_API_KEY environment variable is not set') +} + +const discordToken = process.env.DISCORD_BOT_TOKEN +if (!discordToken) { + throw new Error('DISCORD_BOT_TOKEN environment variable is not set') +} + +const genAI = new GoogleGenerativeAI(apiKey) + +const modelConfig = { + model: 'gemini-1.5-pro-latest' +} + +const generationConfig = { + temperature: 1, + topP: 0.95, + topK: 64, + maxOutputTokens: 1900, + responseMimeType: 'text/plain' +} + +const safetyConfig = [ + { + category: HarmCategory.HARM_CATEGORY_HARASSMENT, + threshold: HarmBlockThreshold.BLOCK_NONE + }, + { + category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, + threshold: HarmBlockThreshold.BLOCK_NONE + }, + { + category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, + threshold: HarmBlockThreshold.BLOCK_NONE + }, + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + threshold: HarmBlockThreshold.BLOCK_NONE + } +] + +async function readFileContent(filePath: string): Promise { + try { + return await fs.readFile(filePath, 'utf-8') + } catch (err) { + console.error('Error reading file:', err) + throw err + } +} + +let chatSession: ChatSession | null = null +let isProcessing = false + +const client = new Client({ + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] +}) + +client.once('ready', () => { + console.log(`Bot is ready! Logged in as ${client.user?.tag}`) +}) + +async function sendLongMessage(channel: TextChannel, messageText: string, author: string) { + const maxMessageLength = 1900 + if (messageText.length <= maxMessageLength) { + await channel.send(`${author}, here's the response:\n${messageText}`) + return + } + + const lines = messageText.split('\n') + let currentMessage = '' + + for (const line of lines) { + if (currentMessage.length + line.length + 1 > maxMessageLength) { + await channel.send(`${author}, here's part of the response:\n${currentMessage}`) + currentMessage = line + } else { + currentMessage += (currentMessage.length > 0 ? '\n' : '') + line + } + } + + if (currentMessage.length > 0) { + await channel.send(`${author}, here's part of the response:\n${currentMessage}`) + } +} + +client.on('messageCreate', async (message: Message) => { + if ( + !(message.channel instanceof TextChannel) || + message.channel.name !== 'support-bot' || + message.author.bot || + !client.user || + !message.mentions.has(client.user.id) + ) { + return + } + + if (isProcessing) { + await message.channel.send(`${message.author}, I'm currently busy. Please wait for the response.`) + return + } + + isProcessing = true + await message.channel.send( + `${message.author}, I'm processing your question. Please wait up to 1 minute for the response.` + ) + + try { + if (!chatSession) { + const model = genAI.getGenerativeModel(modelConfig) + const fileContent = await readFileContent('synpress-source.txt') + + chatSession = model.startChat({ + generationConfig, + safetySettings: safetyConfig, + history: [ + { + role: 'user', + parts: [ + { + text: "synpress-source.txt file that represents an entire repository of synpress. The repository's individual files are separated by the sequence '''// File: \", followed by the file path. Each file's content begins immediately after its file path and extends until the next sequence of ''// File: \". Analyse this file and learn from it, so that all my next questions will be referenced to this file." + }, + { + text: fileContent + }, + { + text: 'When responding, make sure that response is not longer than 1900 characters. Also make sure to respond only to synpress-related questions.' + } + ] + }, + { + role: 'model', + parts: [ + { + text: 'I have now analyzed and learned from the provided `synpress-source.txt` file. You can ask me questions about the code, the functionalities, or anything related to Synpress, and I will do my best to answer them based on my understanding of the repository.' + } + ] + } + ] + }) + } + + const result = await chatSession.sendMessage(message.content) + let responseText = result.response.text() + const botMention = new RegExp(`<@!?${client.user.id}>`, 'g') + responseText = responseText.replace(botMention, '') + + await sendLongMessage(message.channel, responseText, message.author.toString()) + } catch (error) { + console.error('Failed to process the message:', error) + await message.channel.send( + `${message.author}, there was an error processing your request: ${ + error instanceof Error ? error.message : 'Unknown error' + }` + ) + } finally { + isProcessing = false + } +}) + +client.login(discordToken).catch((error) => { + console.error('Failed to login:', error) + process.exit(1) +}) diff --git a/support-bot/tsconfig.json b/support-bot/tsconfig.json new file mode 100644 index 000000000..9aa1c7c32 --- /dev/null +++ b/support-bot/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} From 62b667a1430d05accaf1a3da8f4cfa683f0db626 Mon Sep 17 00:00:00 2001 From: drptbl Date: Thu, 13 Feb 2025 00:14:44 +0000 Subject: [PATCH 2/9] chore: make sure that synpress-source.txt is present Signed-off-by: drptbl --- support-bot/src/index.ts | 310 ++++++++++++++++++++++----------------- 1 file changed, 173 insertions(+), 137 deletions(-) diff --git a/support-bot/src/index.ts b/support-bot/src/index.ts index cc0bebaa6..99c9b3a90 100644 --- a/support-bot/src/index.ts +++ b/support-bot/src/index.ts @@ -1,14 +1,13 @@ +import { exec } from 'child_process' import { promises as fs } from 'fs' -import { - ChatSession, - GenerativeModel, - GoogleGenerativeAI, - HarmBlockThreshold, - HarmCategory -} from '@google/generative-ai' +import path from 'path' +import { promisify } from 'util' +import { ChatSession, GoogleGenerativeAI, HarmBlockThreshold, HarmCategory } from '@google/generative-ai' import { Client, GatewayIntentBits, Message, TextChannel } from 'discord.js' import dotenv from 'dotenv' +const execAsync = promisify(exec) + dotenv.config() const apiKey = process.env.CLOUD_API_KEY @@ -23,156 +22,193 @@ if (!discordToken) { const genAI = new GoogleGenerativeAI(apiKey) -const modelConfig = { - model: 'gemini-1.5-pro-latest' -} +async function ensureSynpressSourceFile(): Promise { + try { + // Check if synpress-source.txt exists + try { + await fs.access('synpress-source.txt') + console.log('synpress-source.txt already exists') + return + } catch { + console.log('synpress-source.txt not found, generating...') + } -const generationConfig = { - temperature: 1, - topP: 0.95, - topK: 64, - maxOutputTokens: 1900, - responseMimeType: 'text/plain' + // Run flatten.cjs to generate the file + const workspaceRoot = path.resolve(process.cwd(), '..') + console.log('Workspace root:', workspaceRoot) + const flattenScript = path.join(workspaceRoot, 'flatten.cjs') + console.log('Flatten script path:', flattenScript) + await execAsync(`node ${flattenScript}`) + + // Move the generated file from output/output1.txt to synpress-source.txt + const outputFile = path.join(workspaceRoot, 'output', 'output1.txt') + console.log('Output file path:', outputFile) + await fs.rename(outputFile, 'synpress-source.txt') + console.log('Successfully generated synpress-source.txt') + } catch (error) { + console.error('Failed to generate synpress-source.txt:', error) + throw error + } } -const safetyConfig = [ - { - category: HarmCategory.HARM_CATEGORY_HARASSMENT, - threshold: HarmBlockThreshold.BLOCK_NONE - }, - { - category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, - threshold: HarmBlockThreshold.BLOCK_NONE - }, - { - category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, - threshold: HarmBlockThreshold.BLOCK_NONE - }, - { - category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, - threshold: HarmBlockThreshold.BLOCK_NONE - } -] +// Initialize bot after ensuring synpress-source.txt exists +async function initializeBot() { + await ensureSynpressSourceFile() -async function readFileContent(filePath: string): Promise { - try { - return await fs.readFile(filePath, 'utf-8') - } catch (err) { - console.error('Error reading file:', err) - throw err + const modelConfig = { + // model: 'gemini-2.0-pro-exp-02-05' + model: 'gemini-1.5-pro-latest' } -} -let chatSession: ChatSession | null = null -let isProcessing = false + const generationConfig = { + temperature: 1, + topP: 0.95, + topK: 64, + maxOutputTokens: 1900, + responseMimeType: 'text/plain' + } -const client = new Client({ - intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] -}) + const safetyConfig = [ + { + category: HarmCategory.HARM_CATEGORY_HARASSMENT, + threshold: HarmBlockThreshold.BLOCK_NONE + }, + { + category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, + threshold: HarmBlockThreshold.BLOCK_NONE + }, + { + category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, + threshold: HarmBlockThreshold.BLOCK_NONE + }, + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + threshold: HarmBlockThreshold.BLOCK_NONE + } + ] + + async function readFileContent(filePath: string): Promise { + try { + return await fs.readFile(filePath, 'utf-8') + } catch (err) { + console.error('Error reading file:', err) + throw err + } + } -client.once('ready', () => { - console.log(`Bot is ready! Logged in as ${client.user?.tag}`) -}) + let chatSession: ChatSession | null = null + let isProcessing = false -async function sendLongMessage(channel: TextChannel, messageText: string, author: string) { - const maxMessageLength = 1900 - if (messageText.length <= maxMessageLength) { - await channel.send(`${author}, here's the response:\n${messageText}`) - return - } + const client = new Client({ + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] + }) - const lines = messageText.split('\n') - let currentMessage = '' + client.once('ready', () => { + console.log(`Bot is ready! Logged in as ${client.user?.tag}`) + }) - for (const line of lines) { - if (currentMessage.length + line.length + 1 > maxMessageLength) { - await channel.send(`${author}, here's part of the response:\n${currentMessage}`) - currentMessage = line - } else { - currentMessage += (currentMessage.length > 0 ? '\n' : '') + line + async function sendLongMessage(channel: TextChannel, messageText: string, author: string) { + const maxMessageLength = 1900 + if (messageText.length <= maxMessageLength) { + await channel.send(`${author}, here's the response:\n${messageText}`) + return } - } - if (currentMessage.length > 0) { - await channel.send(`${author}, here's part of the response:\n${currentMessage}`) - } -} + const lines = messageText.split('\n') + let currentMessage = '' -client.on('messageCreate', async (message: Message) => { - if ( - !(message.channel instanceof TextChannel) || - message.channel.name !== 'support-bot' || - message.author.bot || - !client.user || - !message.mentions.has(client.user.id) - ) { - return - } + for (const line of lines) { + if (currentMessage.length + line.length + 1 > maxMessageLength) { + await channel.send(`${author}, here's part of the response:\n${currentMessage}`) + currentMessage = line + } else { + currentMessage += (currentMessage.length > 0 ? '\n' : '') + line + } + } - if (isProcessing) { - await message.channel.send(`${message.author}, I'm currently busy. Please wait for the response.`) - return + if (currentMessage.length > 0) { + await channel.send(`${author}, here's part of the response:\n${currentMessage}`) + } } - isProcessing = true - await message.channel.send( - `${message.author}, I'm processing your question. Please wait up to 1 minute for the response.` - ) - - try { - if (!chatSession) { - const model = genAI.getGenerativeModel(modelConfig) - const fileContent = await readFileContent('synpress-source.txt') - - chatSession = model.startChat({ - generationConfig, - safetySettings: safetyConfig, - history: [ - { - role: 'user', - parts: [ - { - text: "synpress-source.txt file that represents an entire repository of synpress. The repository's individual files are separated by the sequence '''// File: \", followed by the file path. Each file's content begins immediately after its file path and extends until the next sequence of ''// File: \". Analyse this file and learn from it, so that all my next questions will be referenced to this file." - }, - { - text: fileContent - }, - { - text: 'When responding, make sure that response is not longer than 1900 characters. Also make sure to respond only to synpress-related questions.' - } - ] - }, - { - role: 'model', - parts: [ - { - text: 'I have now analyzed and learned from the provided `synpress-source.txt` file. You can ask me questions about the code, the functionalities, or anything related to Synpress, and I will do my best to answer them based on my understanding of the repository.' - } - ] - } - ] - }) + client.on('messageCreate', async (message: Message) => { + if ( + !(message.channel instanceof TextChannel) || + message.channel.name !== 'support-bot' || + message.author.bot || + !client.user || + !message.mentions.has(client.user.id) + ) { + return } - const result = await chatSession.sendMessage(message.content) - let responseText = result.response.text() - const botMention = new RegExp(`<@!?${client.user.id}>`, 'g') - responseText = responseText.replace(botMention, '') + if (isProcessing) { + await message.channel.send(`${message.author}, I'm currently busy. Please wait for the response.`) + return + } - await sendLongMessage(message.channel, responseText, message.author.toString()) - } catch (error) { - console.error('Failed to process the message:', error) + isProcessing = true await message.channel.send( - `${message.author}, there was an error processing your request: ${ - error instanceof Error ? error.message : 'Unknown error' - }` + `${message.author}, I'm processing your question. Please wait up to 1 minute for the response.` ) - } finally { - isProcessing = false - } -}) -client.login(discordToken).catch((error) => { - console.error('Failed to login:', error) - process.exit(1) -}) + try { + if (!chatSession) { + const model = genAI.getGenerativeModel(modelConfig) + const fileContent = await readFileContent('synpress-source.txt') + + chatSession = model.startChat({ + generationConfig, + safetySettings: safetyConfig, + history: [ + { + role: 'user', + parts: [ + { + text: "synpress-source.txt file that represents an entire repository of synpress. The repository's individual files are separated by the sequence '''// File: \", followed by the file path. Each file's content begins immediately after its file path and extends until the next sequence of ''// File: \". Analyse this file and learn from it, so that all my next questions will be referenced to this file." + }, + { + text: fileContent + }, + { + text: 'When responding, make sure that response is not longer than 1900 characters. Also make sure to respond only to synpress-related questions.' + } + ] + }, + { + role: 'model', + parts: [ + { + text: 'I have now analyzed and learned from the provided `synpress-source.txt` file. You can ask me questions about the code, the functionalities, or anything related to Synpress, and I will do my best to answer them based on my understanding of the repository.' + } + ] + } + ] + }) + } + + const result = await chatSession.sendMessage(message.content) + let responseText = result.response.text() + const botMention = new RegExp(`<@!?${client.user.id}>`, 'g') + responseText = responseText.replace(botMention, '') + + await sendLongMessage(message.channel, responseText, message.author.toString()) + } catch (error) { + console.error('Failed to process the message:', error) + await message.channel.send( + `${message.author}, there was an error processing your request: ${ + error instanceof Error ? error.message : 'Unknown error' + }` + ) + } finally { + isProcessing = false + } + }) + + client.login(discordToken).catch((error) => { + console.error('Failed to login:', error) + process.exit(1) + }) +} + +initializeBot() From 83be7cfa2a53442a6ada1b4a933585983ff4f2f3 Mon Sep 17 00:00:00 2001 From: drptbl Date: Thu, 13 Feb 2025 00:14:59 +0000 Subject: [PATCH 3/9] chore: ignore synpress-source Signed-off-by: drptbl --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d4d936285..d4a4b71df 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ playwright/.cache **/metamask/cypress output + +synpress-source.txt From b88d1c640c2f8da6961dab71fbe8b11262475faa Mon Sep 17 00:00:00 2001 From: drptbl Date: Thu, 13 Feb 2025 00:15:16 +0000 Subject: [PATCH 4/9] chore: move flatten script Signed-off-by: drptbl --- flatten.cjs => support-bot/flatten.cjs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename flatten.cjs => support-bot/flatten.cjs (100%) diff --git a/flatten.cjs b/support-bot/flatten.cjs similarity index 100% rename from flatten.cjs rename to support-bot/flatten.cjs From b4b4ec09ec9b7a76cc06d934563ddf177fab21af Mon Sep 17 00:00:00 2001 From: drptbl Date: Thu, 13 Feb 2025 00:17:11 +0000 Subject: [PATCH 5/9] fix: path to flatten script Signed-off-by: drptbl --- support-bot/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support-bot/src/index.ts b/support-bot/src/index.ts index 99c9b3a90..1dd73d8f8 100644 --- a/support-bot/src/index.ts +++ b/support-bot/src/index.ts @@ -34,7 +34,7 @@ async function ensureSynpressSourceFile(): Promise { } // Run flatten.cjs to generate the file - const workspaceRoot = path.resolve(process.cwd(), '..') + const workspaceRoot = process.cwd() console.log('Workspace root:', workspaceRoot) const flattenScript = path.join(workspaceRoot, 'flatten.cjs') console.log('Flatten script path:', flattenScript) From c882155dc37879369f05bbbd6019f18717137c99 Mon Sep 17 00:00:00 2001 From: drptbl Date: Thu, 13 Feb 2025 00:20:08 +0000 Subject: [PATCH 6/9] fix: multiple pings Signed-off-by: drptbl --- support-bot/src/index.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/support-bot/src/index.ts b/support-bot/src/index.ts index 1dd73d8f8..120b20168 100644 --- a/support-bot/src/index.ts +++ b/support-bot/src/index.ts @@ -110,16 +110,22 @@ async function initializeBot() { async function sendLongMessage(channel: TextChannel, messageText: string, author: string) { const maxMessageLength = 1900 if (messageText.length <= maxMessageLength) { - await channel.send(`${author}, here's the response:\n${messageText}`) + await channel.send(`${author}, here's your response:\n${messageText}`) return } const lines = messageText.split('\n') let currentMessage = '' + let isFirstMessage = true for (const line of lines) { if (currentMessage.length + line.length + 1 > maxMessageLength) { - await channel.send(`${author}, here's part of the response:\n${currentMessage}`) + if (isFirstMessage) { + await channel.send(`${author}, here's your response:\n${currentMessage}`) + isFirstMessage = false + } else { + await channel.send(currentMessage) + } currentMessage = line } else { currentMessage += (currentMessage.length > 0 ? '\n' : '') + line @@ -127,7 +133,11 @@ async function initializeBot() { } if (currentMessage.length > 0) { - await channel.send(`${author}, here's part of the response:\n${currentMessage}`) + if (isFirstMessage) { + await channel.send(`${author}, here's your response:\n${currentMessage}`) + } else { + await channel.send(currentMessage) + } } } From 741823d1c2e653c2346a2555cc444faa398bdb2c Mon Sep 17 00:00:00 2001 From: drptbl Date: Thu, 13 Feb 2025 00:22:30 +0000 Subject: [PATCH 7/9] fix: message chunking Signed-off-by: drptbl --- support-bot/src/index.ts | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/support-bot/src/index.ts b/support-bot/src/index.ts index 120b20168..7309656c2 100644 --- a/support-bot/src/index.ts +++ b/support-bot/src/index.ts @@ -109,6 +109,8 @@ async function initializeBot() { async function sendLongMessage(channel: TextChannel, messageText: string, author: string) { const maxMessageLength = 1900 + + // If message fits in one chunk, send it as is if (messageText.length <= maxMessageLength) { await channel.send(`${author}, here's your response:\n${messageText}`) return @@ -117,21 +119,51 @@ async function initializeBot() { const lines = messageText.split('\n') let currentMessage = '' let isFirstMessage = true + let insideCodeBlock = false + let codeBlockLang = '' + let pendingCodeBlockClose = false for (const line of lines) { - if (currentMessage.length + line.length + 1 > maxMessageLength) { + const isCodeBlockStart = line.trim().match(/^```(\w*)/) + const isCodeBlockEnd = line.trim() === '```' + + // Track code block state + if (isCodeBlockStart && !insideCodeBlock) { + insideCodeBlock = true + codeBlockLang = isCodeBlockStart[1] + } else if (isCodeBlockEnd && insideCodeBlock) { + insideCodeBlock = false + pendingCodeBlockClose = false + } + + // Check if adding this line would exceed the limit + const wouldExceedLimit = currentMessage.length + line.length + 1 > maxMessageLength + + if (wouldExceedLimit) { + // If we're inside a code block, we need to close it properly + let messageToSend = currentMessage + if (insideCodeBlock) { + messageToSend += '\n```' + pendingCodeBlockClose = true + } + + // Send the current chunk if (isFirstMessage) { - await channel.send(`${author}, here's your response:\n${currentMessage}`) + await channel.send(`${author}, here's your response:\n${messageToSend}`) isFirstMessage = false } else { - await channel.send(currentMessage) + await channel.send(messageToSend) } - currentMessage = line + + // Start new chunk, reopening code block if needed + currentMessage = pendingCodeBlockClose ? `\`\`\`${codeBlockLang}\n${line}` : line + pendingCodeBlockClose = false } else { currentMessage += (currentMessage.length > 0 ? '\n' : '') + line } } + // Send any remaining content if (currentMessage.length > 0) { if (isFirstMessage) { await channel.send(`${author}, here's your response:\n${currentMessage}`) From 7a9638acb69d66266da9227e1fd55b111719573c Mon Sep 17 00:00:00 2001 From: drptbl Date: Thu, 13 Feb 2025 00:22:39 +0000 Subject: [PATCH 8/9] chore: add flatten script Signed-off-by: drptbl --- support-bot/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/support-bot/package.json b/support-bot/package.json index 4eae3f844..f0c44535b 100644 --- a/support-bot/package.json +++ b/support-bot/package.json @@ -7,6 +7,7 @@ "scripts": { "build": "tsc", "dev": "tsx watch src/index.ts", + "flatten": "node flatten.cjs", "format": "biome format . --write", "lint": "biome check .", "start": "node dist/index.js" From e951512a544a86469fc9417c371b94db60218ab0 Mon Sep 17 00:00:00 2001 From: drptbl Date: Thu, 13 Feb 2025 00:26:35 +0000 Subject: [PATCH 9/9] chore: use systeminstructions Signed-off-by: drptbl --- support-bot/src/index.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/support-bot/src/index.ts b/support-bot/src/index.ts index 7309656c2..67856a02d 100644 --- a/support-bot/src/index.ts +++ b/support-bot/src/index.ts @@ -57,7 +57,9 @@ async function initializeBot() { const modelConfig = { // model: 'gemini-2.0-pro-exp-02-05' - model: 'gemini-1.5-pro-latest' + model: 'gemini-1.5-pro-latest', + systemInstruction: + 'When responding, make sure that response is not longer than 1900 characters. Also make sure to respond only to synpress-related questions.' } const generationConfig = { @@ -211,9 +213,6 @@ async function initializeBot() { }, { text: fileContent - }, - { - text: 'When responding, make sure that response is not longer than 1900 characters. Also make sure to respond only to synpress-related questions.' } ] },