mirror of
https://github.com/xHyroM/bun-discord-bot.git
synced 2024-12-03 11:21:05 +01:00
refactor!: rewrite (#32)
Thanks @Didas-git for big help https://github.com/xHyroM/bun-discord-bot/pull/31 --------- Co-authored-by: DidaS <didasoficial@gmail.com>
This commit is contained in:
parent
dc87f7e804
commit
16c842808d
48 changed files with 922 additions and 1817 deletions
4
.github/workflows/validate.yml
vendored
4
.github/workflows/validate.yml
vendored
|
@ -26,7 +26,9 @@ jobs:
|
|||
run: cp $HOME/files.json ./scripts/validate_tags/
|
||||
|
||||
- name: Validate tags
|
||||
run: bun run validate:tags
|
||||
run: |
|
||||
bun i
|
||||
bun run validate:tags
|
||||
env:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-sha: ${{ github.event.pull_request.head.sha }}
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -80,10 +80,7 @@ web_modules/
|
|||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
.env.*
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Bun discord bot
|
||||
Official serverless discord bot for [bun](https://bun.sh/) [discord server](https://bun.sh/discord)
|
||||
Official discord bot for [bun](https://bun.sh/) [discord server](https://bun.sh/discord)
|
||||
|
||||
## Contributing tags
|
||||
To create a new tag, you need to fork this repository and add new file to `data/tags/name.md` file
|
||||
|
|
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -5,5 +5,12 @@ keywords:
|
|||
- "windows support"
|
||||
---
|
||||
|
||||
Bun does not currently have support for Windows, so you must use WSL (Windows Subsystem for Linux).
|
||||
To install WSL, check [microsoft documentation](<https://docs.microsoft.com/en-us/windows/wsl/install>)
|
||||
Bun provides a *limited*, experimental native builds for [Windows](<https://bun.sh/docs/installation#windows>).
|
||||
To install, paste this into your terminal:
|
||||
```ps
|
||||
# WARNING: No stability is guaranteed on the experimental Windows builds
|
||||
powershell -c "iwr bun.sh/install.ps1|iex"
|
||||
```
|
||||
|
||||
However, it's still recommended to use [WSL](<https://docs.microsoft.com/en-us/windows/wsl/install>) for the best experience.
|
||||
For more information, you can look into <#1149339379446325248>
|
7
globals.d.ts
vendored
Normal file
7
globals.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
declare module "bun" {
|
||||
interface Env {
|
||||
DISCORD_BOT_TOKEN: string;
|
||||
BUN_ONLY_CHANNEL_ID: string;
|
||||
MESSAGE_PREFIX: string;
|
||||
}
|
||||
}
|
13
package.json
13
package.json
|
@ -1,19 +1,22 @@
|
|||
{
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"name": "bun-discord-bot",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "bun src/index.ts",
|
||||
"validate:tags": "cd scripts/validate_tags && bun install && bun start"
|
||||
"start": "NODE_ENV=production bun src/index.ts",
|
||||
"validate:tags": "bun scripts/validate_tags/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "^0.7.3"
|
||||
"bun-types": "^1.0.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lilybird/handlers": "^0.2.1",
|
||||
"@lilybird/jsx": "^0.2.0",
|
||||
"@paperdave/logger": "^3.0.1",
|
||||
"algoliasearch": "^4.19.1",
|
||||
"discord.js": "^14.13.0",
|
||||
"glob": "^10.3.3",
|
||||
"gray-matter": "^4.0.3",
|
||||
"lilybird": "^0.4.0",
|
||||
"zlib-sync": "^0.1.8"
|
||||
}
|
||||
}
|
||||
|
|
672
pnpm-lock.yaml
672
pnpm-lock.yaml
|
@ -1,672 +0,0 @@
|
|||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
'@paperdave/logger':
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
algoliasearch:
|
||||
specifier: ^4.19.1
|
||||
version: 4.19.1
|
||||
discord.js:
|
||||
specifier: ^14.13.0
|
||||
version: 14.13.0
|
||||
glob:
|
||||
specifier: ^10.3.3
|
||||
version: 10.3.3
|
||||
gray-matter:
|
||||
specifier: ^4.0.3
|
||||
version: 4.0.3
|
||||
zlib-sync:
|
||||
specifier: ^0.1.8
|
||||
version: 0.1.8
|
||||
|
||||
devDependencies:
|
||||
bun-types:
|
||||
specifier: ^0.7.3
|
||||
version: 0.7.3
|
||||
|
||||
packages:
|
||||
|
||||
/@algolia/cache-browser-local-storage@4.19.1:
|
||||
resolution: {integrity: sha512-FYAZWcGsFTTaSAwj9Std8UML3Bu8dyWDncM7Ls8g+58UOe4XYdlgzXWbrIgjaguP63pCCbMoExKr61B+ztK3tw==}
|
||||
dependencies:
|
||||
'@algolia/cache-common': 4.19.1
|
||||
dev: false
|
||||
|
||||
/@algolia/cache-common@4.19.1:
|
||||
resolution: {integrity: sha512-XGghi3l0qA38HiqdoUY+wvGyBsGvKZ6U3vTiMBT4hArhP3fOGLXpIINgMiiGjTe4FVlTa5a/7Zf2bwlIHfRqqg==}
|
||||
dev: false
|
||||
|
||||
/@algolia/cache-in-memory@4.19.1:
|
||||
resolution: {integrity: sha512-+PDWL+XALGvIginigzu8oU6eWw+o76Z8zHbBovWYcrtWOEtinbl7a7UTt3x3lthv+wNuFr/YD1Gf+B+A9V8n5w==}
|
||||
dependencies:
|
||||
'@algolia/cache-common': 4.19.1
|
||||
dev: false
|
||||
|
||||
/@algolia/client-account@4.19.1:
|
||||
resolution: {integrity: sha512-Oy0ritA2k7AMxQ2JwNpfaEcgXEDgeyKu0V7E7xt/ZJRdXfEpZcwp9TOg4TJHC7Ia62gIeT2Y/ynzsxccPw92GA==}
|
||||
dependencies:
|
||||
'@algolia/client-common': 4.19.1
|
||||
'@algolia/client-search': 4.19.1
|
||||
'@algolia/transporter': 4.19.1
|
||||
dev: false
|
||||
|
||||
/@algolia/client-analytics@4.19.1:
|
||||
resolution: {integrity: sha512-5QCq2zmgdZLIQhHqwl55ZvKVpLM3DNWjFI4T+bHr3rGu23ew2bLO4YtyxaZeChmDb85jUdPDouDlCumGfk6wOg==}
|
||||
dependencies:
|
||||
'@algolia/client-common': 4.19.1
|
||||
'@algolia/client-search': 4.19.1
|
||||
'@algolia/requester-common': 4.19.1
|
||||
'@algolia/transporter': 4.19.1
|
||||
dev: false
|
||||
|
||||
/@algolia/client-common@4.19.1:
|
||||
resolution: {integrity: sha512-3kAIVqTcPrjfS389KQvKzliC559x+BDRxtWamVJt8IVp7LGnjq+aVAXg4Xogkur1MUrScTZ59/AaUd5EdpyXgA==}
|
||||
dependencies:
|
||||
'@algolia/requester-common': 4.19.1
|
||||
'@algolia/transporter': 4.19.1
|
||||
dev: false
|
||||
|
||||
/@algolia/client-personalization@4.19.1:
|
||||
resolution: {integrity: sha512-8CWz4/H5FA+krm9HMw2HUQenizC/DxUtsI5oYC0Jxxyce1vsr8cb1aEiSJArQT6IzMynrERif1RVWLac1m36xw==}
|
||||
dependencies:
|
||||
'@algolia/client-common': 4.19.1
|
||||
'@algolia/requester-common': 4.19.1
|
||||
'@algolia/transporter': 4.19.1
|
||||
dev: false
|
||||
|
||||
/@algolia/client-search@4.19.1:
|
||||
resolution: {integrity: sha512-mBecfMFS4N+yK/p0ZbK53vrZbL6OtWMk8YmnOv1i0LXx4pelY8TFhqKoTit3NPVPwoSNN0vdSN9dTu1xr1XOVw==}
|
||||
dependencies:
|
||||
'@algolia/client-common': 4.19.1
|
||||
'@algolia/requester-common': 4.19.1
|
||||
'@algolia/transporter': 4.19.1
|
||||
dev: false
|
||||
|
||||
/@algolia/logger-common@4.19.1:
|
||||
resolution: {integrity: sha512-i6pLPZW/+/YXKis8gpmSiNk1lOmYCmRI6+x6d2Qk1OdfvX051nRVdalRbEcVTpSQX6FQAoyeaui0cUfLYW5Elw==}
|
||||
dev: false
|
||||
|
||||
/@algolia/logger-console@4.19.1:
|
||||
resolution: {integrity: sha512-jj72k9GKb9W0c7TyC3cuZtTr0CngLBLmc8trzZlXdfvQiigpUdvTi1KoWIb2ZMcRBG7Tl8hSb81zEY3zI2RlXg==}
|
||||
dependencies:
|
||||
'@algolia/logger-common': 4.19.1
|
||||
dev: false
|
||||
|
||||
/@algolia/requester-browser-xhr@4.19.1:
|
||||
resolution: {integrity: sha512-09K/+t7lptsweRTueHnSnmPqIxbHMowejAkn9XIcJMLdseS3zl8ObnS5GWea86mu3vy4+8H+ZBKkUN82Zsq/zg==}
|
||||
dependencies:
|
||||
'@algolia/requester-common': 4.19.1
|
||||
dev: false
|
||||
|
||||
/@algolia/requester-common@4.19.1:
|
||||
resolution: {integrity: sha512-BisRkcWVxrDzF1YPhAckmi2CFYK+jdMT60q10d7z3PX+w6fPPukxHRnZwooiTUrzFe50UBmLItGizWHP5bDzVQ==}
|
||||
dev: false
|
||||
|
||||
/@algolia/requester-node-http@4.19.1:
|
||||
resolution: {integrity: sha512-6DK52DHviBHTG2BK/Vv2GIlEw7i+vxm7ypZW0Z7vybGCNDeWzADx+/TmxjkES2h15+FZOqVf/Ja677gePsVItA==}
|
||||
dependencies:
|
||||
'@algolia/requester-common': 4.19.1
|
||||
dev: false
|
||||
|
||||
/@algolia/transporter@4.19.1:
|
||||
resolution: {integrity: sha512-nkpvPWbpuzxo1flEYqNIbGz7xhfhGOKGAZS7tzC+TELgEmi7z99qRyTfNSUlW7LZmB3ACdnqAo+9A9KFBENviQ==}
|
||||
dependencies:
|
||||
'@algolia/cache-common': 4.19.1
|
||||
'@algolia/logger-common': 4.19.1
|
||||
'@algolia/requester-common': 4.19.1
|
||||
dev: false
|
||||
|
||||
/@discordjs/builders@1.6.5:
|
||||
resolution: {integrity: sha512-SdweyCs/+mHj+PNhGLLle7RrRFX9ZAhzynHahMCLqp5Zeq7np7XC6/mgzHc79QoVlQ1zZtOkTTiJpOZu5V8Ufg==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
dependencies:
|
||||
'@discordjs/formatters': 0.3.2
|
||||
'@discordjs/util': 1.0.1
|
||||
'@sapphire/shapeshift': 3.9.2
|
||||
discord-api-types: 0.37.50
|
||||
fast-deep-equal: 3.1.3
|
||||
ts-mixer: 6.0.3
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/@discordjs/collection@1.5.3:
|
||||
resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
dev: false
|
||||
|
||||
/@discordjs/formatters@0.3.2:
|
||||
resolution: {integrity: sha512-lE++JZK8LSSDRM5nLjhuvWhGuKiXqu+JZ/DsOR89DVVia3z9fdCJVcHF2W/1Zxgq0re7kCzmAJlCMMX3tetKpA==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
dependencies:
|
||||
discord-api-types: 0.37.50
|
||||
dev: false
|
||||
|
||||
/@discordjs/rest@2.0.1:
|
||||
resolution: {integrity: sha512-/eWAdDRvwX/rIE2tuQUmKaxmWeHmGealttIzGzlYfI4+a7y9b6ZoMp8BG/jaohs8D8iEnCNYaZiOFLVFLQb8Zg==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
dependencies:
|
||||
'@discordjs/collection': 1.5.3
|
||||
'@discordjs/util': 1.0.1
|
||||
'@sapphire/async-queue': 1.5.0
|
||||
'@sapphire/snowflake': 3.5.1
|
||||
'@vladfrangu/async_event_emitter': 2.2.2
|
||||
discord-api-types: 0.37.50
|
||||
magic-bytes.js: 1.0.15
|
||||
tslib: 2.6.2
|
||||
undici: 5.22.1
|
||||
dev: false
|
||||
|
||||
/@discordjs/util@1.0.1:
|
||||
resolution: {integrity: sha512-d0N2yCxB8r4bn00/hvFZwM7goDcUhtViC5un4hPj73Ba4yrChLSJD8fy7Ps5jpTLg1fE9n4K0xBLc1y9WGwSsA==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
dev: false
|
||||
|
||||
/@discordjs/ws@1.0.1:
|
||||
resolution: {integrity: sha512-avvAolBqN3yrSvdBPcJ/0j2g42ABzrv3PEL76e3YTp2WYMGH7cuspkjfSyNWaqYl1J+669dlLp+YFMxSVQyS5g==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
dependencies:
|
||||
'@discordjs/collection': 1.5.3
|
||||
'@discordjs/rest': 2.0.1
|
||||
'@discordjs/util': 1.0.1
|
||||
'@sapphire/async-queue': 1.5.0
|
||||
'@types/ws': 8.5.5
|
||||
'@vladfrangu/async_event_emitter': 2.2.2
|
||||
discord-api-types: 0.37.50
|
||||
tslib: 2.6.2
|
||||
ws: 8.13.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@isaacs/cliui@8.0.2:
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
string-width: 5.1.2
|
||||
string-width-cjs: /string-width@4.2.3
|
||||
strip-ansi: 7.1.0
|
||||
strip-ansi-cjs: /strip-ansi@6.0.1
|
||||
wrap-ansi: 8.1.0
|
||||
wrap-ansi-cjs: /wrap-ansi@7.0.0
|
||||
dev: false
|
||||
|
||||
/@paperdave/logger@3.0.1:
|
||||
resolution: {integrity: sha512-sLl/oM6U5KUU1bLzjDzshsJnrlGEPT7J0BFMqYr6HzX0RJbsKgRf4XRJgXsusT+rFDfOSHrJAootuImlt8q/9A==}
|
||||
engines: {node: '>=16'}
|
||||
dependencies:
|
||||
'@paperdave/utils': 1.8.0
|
||||
ansi-escapes: 6.2.0
|
||||
chalk: 5.3.0
|
||||
strip-ansi: 7.1.0
|
||||
dev: false
|
||||
|
||||
/@paperdave/utils@1.8.0:
|
||||
resolution: {integrity: sha512-/1ckfqC0migr4GjtFhpqNUIgPibign4kRMmEzGsZ6hilvQ0cbL8Hu96ZzvM/matlOK4tOeEj9aH4f1Bip8AH1A==}
|
||||
engines: {node: '>=16'}
|
||||
dependencies:
|
||||
utility-types: 3.10.0
|
||||
yaml: 2.3.1
|
||||
dev: false
|
||||
|
||||
/@pkgjs/parseargs@0.11.0:
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@sapphire/async-queue@1.5.0:
|
||||
resolution: {integrity: sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==}
|
||||
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
|
||||
dev: false
|
||||
|
||||
/@sapphire/shapeshift@3.9.2:
|
||||
resolution: {integrity: sha512-YRbCXWy969oGIdqR/wha62eX8GNHsvyYi0Rfd4rNW6tSVVa8p0ELiMEuOH/k8rgtvRoM+EMV7Csqz77YdwiDpA==}
|
||||
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
lodash: 4.17.21
|
||||
dev: false
|
||||
|
||||
/@sapphire/snowflake@3.5.1:
|
||||
resolution: {integrity: sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==}
|
||||
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
|
||||
dev: false
|
||||
|
||||
/@types/node@20.5.3:
|
||||
resolution: {integrity: sha512-ITI7rbWczR8a/S6qjAW7DMqxqFMjjTo61qZVWJ1ubPvbIQsL5D/TvwjYEalM8Kthpe3hTzOGrF2TGbAu2uyqeA==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
|
||||
/@types/ws@8.5.5:
|
||||
resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@types/node': 20.5.3
|
||||
dev: false
|
||||
|
||||
/@vladfrangu/async_event_emitter@2.2.2:
|
||||
resolution: {integrity: sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==}
|
||||
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
|
||||
dev: false
|
||||
|
||||
/algoliasearch@4.19.1:
|
||||
resolution: {integrity: sha512-IJF5b93b2MgAzcE/tuzW0yOPnuUyRgGAtaPv5UUywXM8kzqfdwZTO4sPJBzoGz1eOy6H9uEchsJsBFTELZSu+g==}
|
||||
dependencies:
|
||||
'@algolia/cache-browser-local-storage': 4.19.1
|
||||
'@algolia/cache-common': 4.19.1
|
||||
'@algolia/cache-in-memory': 4.19.1
|
||||
'@algolia/client-account': 4.19.1
|
||||
'@algolia/client-analytics': 4.19.1
|
||||
'@algolia/client-common': 4.19.1
|
||||
'@algolia/client-personalization': 4.19.1
|
||||
'@algolia/client-search': 4.19.1
|
||||
'@algolia/logger-common': 4.19.1
|
||||
'@algolia/logger-console': 4.19.1
|
||||
'@algolia/requester-browser-xhr': 4.19.1
|
||||
'@algolia/requester-common': 4.19.1
|
||||
'@algolia/requester-node-http': 4.19.1
|
||||
'@algolia/transporter': 4.19.1
|
||||
dev: false
|
||||
|
||||
/ansi-escapes@6.2.0:
|
||||
resolution: {integrity: sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==}
|
||||
engines: {node: '>=14.16'}
|
||||
dependencies:
|
||||
type-fest: 3.13.1
|
||||
dev: false
|
||||
|
||||
/ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/ansi-regex@6.0.1:
|
||||
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
dev: false
|
||||
|
||||
/ansi-styles@6.2.1:
|
||||
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/argparse@1.0.10:
|
||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||
dependencies:
|
||||
sprintf-js: 1.0.3
|
||||
dev: false
|
||||
|
||||
/balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: false
|
||||
|
||||
/brace-expansion@2.0.1:
|
||||
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
dev: false
|
||||
|
||||
/bun-types@0.7.3:
|
||||
resolution: {integrity: sha512-kssLD5mDLoawmLZFgQRRq0Wy+dca/os6TZ0MHWyFVoVAEwSrpAxmNCZ1K1GUelfhlDaL2FikRxeF9GkATdzXZg==}
|
||||
dev: true
|
||||
|
||||
/busboy@1.6.0:
|
||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
dependencies:
|
||||
streamsearch: 1.1.0
|
||||
dev: false
|
||||
|
||||
/chalk@5.3.0:
|
||||
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
|
||||
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
||||
dev: false
|
||||
|
||||
/color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
dev: false
|
||||
|
||||
/color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
dev: false
|
||||
|
||||
/cross-spawn@7.0.3:
|
||||
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
|
||||
engines: {node: '>= 8'}
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
dev: false
|
||||
|
||||
/discord-api-types@0.37.50:
|
||||
resolution: {integrity: sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg==}
|
||||
dev: false
|
||||
|
||||
/discord.js@14.13.0:
|
||||
resolution: {integrity: sha512-Kufdvg7fpyTEwANGy9x7i4od4yu5c6gVddGi5CKm4Y5a6sF0VBODObI3o0Bh7TGCj0LfNT8Qp8z04wnLFzgnbA==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
dependencies:
|
||||
'@discordjs/builders': 1.6.5
|
||||
'@discordjs/collection': 1.5.3
|
||||
'@discordjs/formatters': 0.3.2
|
||||
'@discordjs/rest': 2.0.1
|
||||
'@discordjs/util': 1.0.1
|
||||
'@discordjs/ws': 1.0.1
|
||||
'@sapphire/snowflake': 3.5.1
|
||||
'@types/ws': 8.5.5
|
||||
discord-api-types: 0.37.50
|
||||
fast-deep-equal: 3.1.3
|
||||
lodash.snakecase: 4.1.1
|
||||
tslib: 2.6.2
|
||||
undici: 5.22.1
|
||||
ws: 8.13.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
dev: false
|
||||
|
||||
/emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
dev: false
|
||||
|
||||
/emoji-regex@9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
dev: false
|
||||
|
||||
/esprima@4.0.1:
|
||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/extend-shallow@2.0.1:
|
||||
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
is-extendable: 0.1.1
|
||||
dev: false
|
||||
|
||||
/fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
dev: false
|
||||
|
||||
/foreground-child@3.1.1:
|
||||
resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
|
||||
engines: {node: '>=14'}
|
||||
dependencies:
|
||||
cross-spawn: 7.0.3
|
||||
signal-exit: 4.1.0
|
||||
dev: false
|
||||
|
||||
/glob@10.3.3:
|
||||
resolution: {integrity: sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
foreground-child: 3.1.1
|
||||
jackspeak: 2.3.0
|
||||
minimatch: 9.0.3
|
||||
minipass: 7.0.3
|
||||
path-scurry: 1.10.1
|
||||
dev: false
|
||||
|
||||
/gray-matter@4.0.3:
|
||||
resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
|
||||
engines: {node: '>=6.0'}
|
||||
dependencies:
|
||||
js-yaml: 3.14.1
|
||||
kind-of: 6.0.3
|
||||
section-matter: 1.0.0
|
||||
strip-bom-string: 1.0.0
|
||||
dev: false
|
||||
|
||||
/is-extendable@0.1.1:
|
||||
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/is-fullwidth-code-point@3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
dev: false
|
||||
|
||||
/jackspeak@2.3.0:
|
||||
resolution: {integrity: sha512-uKmsITSsF4rUWQHzqaRUuyAir3fZfW3f202Ee34lz/gZCi970CPZwyQXLGNgWJvvZbvFyzeyGq0+4fcG/mBKZg==}
|
||||
engines: {node: '>=14'}
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
dev: false
|
||||
|
||||
/js-yaml@3.14.1:
|
||||
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
argparse: 1.0.10
|
||||
esprima: 4.0.1
|
||||
dev: false
|
||||
|
||||
/kind-of@6.0.3:
|
||||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/lodash.snakecase@4.1.1:
|
||||
resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
|
||||
dev: false
|
||||
|
||||
/lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: false
|
||||
|
||||
/lru-cache@10.0.1:
|
||||
resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==}
|
||||
engines: {node: 14 || >=16.14}
|
||||
dev: false
|
||||
|
||||
/magic-bytes.js@1.0.15:
|
||||
resolution: {integrity: sha512-bpRmwbRHqongRhA+mXzbLWjVy7ylqmfMBYaQkSs6pac0z6hBTvsgrH0r4FBYd/UYVJBmS6Rp/O+oCCQVLzKV1g==}
|
||||
dev: false
|
||||
|
||||
/minimatch@9.0.3:
|
||||
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
dev: false
|
||||
|
||||
/minipass@7.0.3:
|
||||
resolution: {integrity: sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dev: false
|
||||
|
||||
/nan@2.17.0:
|
||||
resolution: {integrity: sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==}
|
||||
dev: false
|
||||
|
||||
/path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/path-scurry@1.10.1:
|
||||
resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dependencies:
|
||||
lru-cache: 10.0.1
|
||||
minipass: 7.0.3
|
||||
dev: false
|
||||
|
||||
/section-matter@1.0.0:
|
||||
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
extend-shallow: 2.0.1
|
||||
kind-of: 6.0.3
|
||||
dev: false
|
||||
|
||||
/shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
dev: false
|
||||
|
||||
/shebang-regex@3.0.0:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/signal-exit@4.1.0:
|
||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||
engines: {node: '>=14'}
|
||||
dev: false
|
||||
|
||||
/sprintf-js@1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
dev: false
|
||||
|
||||
/streamsearch@1.1.0:
|
||||
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dev: false
|
||||
|
||||
/string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
dev: false
|
||||
|
||||
/string-width@5.1.2:
|
||||
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
eastasianwidth: 0.2.0
|
||||
emoji-regex: 9.2.2
|
||||
strip-ansi: 7.1.0
|
||||
dev: false
|
||||
|
||||
/strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
dev: false
|
||||
|
||||
/strip-ansi@7.1.0:
|
||||
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
ansi-regex: 6.0.1
|
||||
dev: false
|
||||
|
||||
/strip-bom-string@1.0.0:
|
||||
resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/ts-mixer@6.0.3:
|
||||
resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==}
|
||||
dev: false
|
||||
|
||||
/tslib@2.6.2:
|
||||
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||
dev: false
|
||||
|
||||
/type-fest@3.13.1:
|
||||
resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==}
|
||||
engines: {node: '>=14.16'}
|
||||
dev: false
|
||||
|
||||
/undici@5.22.1:
|
||||
resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==}
|
||||
engines: {node: '>=14.0'}
|
||||
dependencies:
|
||||
busboy: 1.6.0
|
||||
dev: false
|
||||
|
||||
/utility-types@3.10.0:
|
||||
resolution: {integrity: sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==}
|
||||
engines: {node: '>= 4'}
|
||||
dev: false
|
||||
|
||||
/which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
dev: false
|
||||
|
||||
/wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
dev: false
|
||||
|
||||
/wrap-ansi@8.1.0:
|
||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
ansi-styles: 6.2.1
|
||||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.0
|
||||
dev: false
|
||||
|
||||
/ws@8.13.0:
|
||||
resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/yaml@2.3.1:
|
||||
resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==}
|
||||
engines: {node: '>= 14'}
|
||||
dev: false
|
||||
|
||||
/zlib-sync@0.1.8:
|
||||
resolution: {integrity: sha512-Xbu4odT5SbLsa1HFz8X/FvMgUbJYWxJYKB2+bqxJ6UOIIPaVGrqHEB3vyXDltSA6tTqBhSGYLgiVpzPQHYi3lA==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
nan: 2.17.0
|
||||
dev: false
|
Binary file not shown.
121
scripts/validate_tags/index.ts
Normal file
121
scripts/validate_tags/index.ts
Normal file
|
@ -0,0 +1,121 @@
|
|||
import type { Tag } from "../../src/structs/Tag.js";
|
||||
import * as matter from "gray-matter";
|
||||
import { join, dirname } from "node:path";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { Glob } from "bun";
|
||||
|
||||
const githubToken = process.env["github-token"];
|
||||
const commitSha = process.env["commit-sha"];
|
||||
const pullRequestNumber = process.env["pr-number"];
|
||||
|
||||
const urlRegex =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)/gi;
|
||||
|
||||
// Check if files/tags.toml was changed
|
||||
const files = (await Bun.file("./files.json").json()) as string[];
|
||||
if (!files.some((f) => f.includes("tags"))) process.exit(0);
|
||||
|
||||
const errors: string[] = [];
|
||||
|
||||
const tags: Tag[] = [];
|
||||
const tagPaths = Array.from(
|
||||
new Glob(
|
||||
join(import.meta.dir, "..", "..", "..", "data", "tags", "*.md")
|
||||
).scanSync()
|
||||
);
|
||||
for (const tagPath of tagPaths) {
|
||||
const content = readFileSync(tagPath);
|
||||
const frontMatter = matter(content);
|
||||
|
||||
tags.push({
|
||||
name: dirname(tagPath),
|
||||
question: frontMatter.data.question,
|
||||
keywords: frontMatter.data.keywords,
|
||||
answer: frontMatter.content,
|
||||
category_ids: frontMatter.data.category_ids ?? null,
|
||||
channel_ids: frontMatter.data.channel_ids ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await requestGithub(`issues/${pullRequestNumber}/labels`, {
|
||||
labels: ["tags"],
|
||||
});
|
||||
} catch (e) {
|
||||
errors.push(e.message);
|
||||
}
|
||||
|
||||
for (const tag of tags) {
|
||||
const key = tag.name;
|
||||
|
||||
if (!tag?.keywords || tag.keywords.length === 0)
|
||||
errors.push(`**[${key}]:** Tag must have keywords`);
|
||||
if (tag?.keywords?.[0] !== key)
|
||||
errors.push(
|
||||
`**[${key}]:** First keyword of tag is not the same as the tag name`
|
||||
);
|
||||
if (!tag.answer) errors.push(`**[${key}]:** Tag must have content`);
|
||||
|
||||
if (tag.answer) {
|
||||
for (const url of tag.answer.match(urlRegex) || []) {
|
||||
const firstChar = tag.answer.split(url)[0].slice(-1);
|
||||
const lastChar = tag.answer.split(url)[1].slice(0, 1);
|
||||
if (firstChar !== "<" || lastChar !== ">")
|
||||
errors.push(`**[${key}]:** Link must be wrapped in <>`);
|
||||
}
|
||||
}
|
||||
|
||||
if (tag.keywords) {
|
||||
const keywords = [...new Set(tag.keywords)];
|
||||
if (keywords.length !== tag.keywords.length)
|
||||
errors.push(`**[${key}]:** Keywords must be unique`);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length === 0) {
|
||||
await requestGithub(`pulls/${pullRequestNumber}/reviews`, {
|
||||
commit_id: commitSha,
|
||||
event: "APPROVE",
|
||||
});
|
||||
|
||||
await requestGithub(`pulls/${pullRequestNumber}/requested_reviewers`, {
|
||||
reviewers: ["xHyroM"],
|
||||
});
|
||||
|
||||
await requestGithub(
|
||||
`issues/${pullRequestNumber}/labels/waiting`,
|
||||
{},
|
||||
"DELETE"
|
||||
);
|
||||
|
||||
await requestGithub(`issues/${pullRequestNumber}/labels`, {
|
||||
labels: ["ready"],
|
||||
});
|
||||
} else {
|
||||
await requestGithub(`pulls/${pullRequestNumber}/reviews`, {
|
||||
commit_id: commitSha,
|
||||
body: "### Please fix the following problems:\n" + errors.join("\n"),
|
||||
event: "REQUEST_CHANGES",
|
||||
});
|
||||
|
||||
await requestGithub(`issues/${pullRequestNumber}/labels`, {
|
||||
labels: ["waiting"],
|
||||
});
|
||||
}
|
||||
|
||||
async function requestGithub(
|
||||
url: string,
|
||||
body: any,
|
||||
method?: "POST" | "DELETE"
|
||||
) {
|
||||
await fetch(`https://api.github.com/repos/xHyroM/bun-discord-bot/${url}`, {
|
||||
method: method || "POST",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `token ${githubToken}`,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
export {};
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"name": "validate_tags",
|
||||
"scripts": {
|
||||
"start": "bun src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": "^10.3.3"
|
||||
}
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
import type { Tag } from "../../../src/structs/Tag.ts";
|
||||
import { globSync as glob } from "glob";
|
||||
import * as matter from "gray-matter";
|
||||
import { join, dirname } from "node:path";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const githubToken = process.env['github-token'];
|
||||
const commitSha = process.env['commit-sha'];
|
||||
const pullRequestNumber = process.env['pr-number'];
|
||||
|
||||
const urlRegex =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)/gi;
|
||||
|
||||
// Check if files/tags.toml was changed
|
||||
const files = await Bun.file('./files.json').json() as string[];
|
||||
if (!files.some(f => f.includes("tags"))) process.exit(0);
|
||||
|
||||
const errors = [];
|
||||
|
||||
const tags: Tag[] = [];
|
||||
const tagPaths = glob(join(__dirname, "..", "..", "..", "data", "tags", "*.md"));
|
||||
for (const tagPath of tagPaths) {
|
||||
const content = readFileSync(tagPath);
|
||||
const frontMatter = matter(content);
|
||||
|
||||
tags.push({
|
||||
name: dirname(tagPath),
|
||||
question: frontMatter.data.question,
|
||||
keywords: frontMatter.data.keywords,
|
||||
answer: frontMatter.content,
|
||||
category_ids: frontMatter.data.category_ids ?? null,
|
||||
channel_ids: frontMatter.data.channel_ids ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await requestGithub(`issues/${pullRequestNumber}/labels`, {
|
||||
labels: ['tags'],
|
||||
});
|
||||
} catch (e) {
|
||||
errors.push(e.message);
|
||||
}
|
||||
|
||||
for (const tag of tags) {
|
||||
const key = tag.name;
|
||||
|
||||
if (!tag?.keywords || tag.keywords.length === 0)
|
||||
errors.push(`**[${key}]:** Tag must have keywords`);
|
||||
if (tag?.keywords?.[0] !== key)
|
||||
errors.push(
|
||||
`**[${key}]:** First keyword of tag is not the same as the tag name`
|
||||
);
|
||||
if (!tag.answer) errors.push(`**[${key}]:** Tag must have content`);
|
||||
|
||||
if (tag.answer) {
|
||||
for (const url of tag.answer.match(urlRegex) || []) {
|
||||
const firstChar = tag.answer.split(url)[0].slice(-1);
|
||||
const lastChar = tag.answer.split(url)[1].slice(0, 1);
|
||||
if (firstChar !== '<' || lastChar !== '>')
|
||||
errors.push(`**[${key}]:** Link must be wrapped in <>`);
|
||||
}
|
||||
}
|
||||
|
||||
if (tag.keywords) {
|
||||
const keywords = [...new Set(tag.keywords)];
|
||||
if (keywords.length !== tag.keywords.length)
|
||||
errors.push(`**[${key}]:** Keywords must be unique`);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length === 0) {
|
||||
await requestGithub(`pulls/${pullRequestNumber}/reviews`, {
|
||||
commit_id: commitSha,
|
||||
event: 'APPROVE',
|
||||
});
|
||||
|
||||
await requestGithub(`pulls/${pullRequestNumber}/requested_reviewers`, {
|
||||
reviewers: ['xHyroM'],
|
||||
});
|
||||
|
||||
await requestGithub(
|
||||
`issues/${pullRequestNumber}/labels/waiting`,
|
||||
{},
|
||||
'DELETE'
|
||||
);
|
||||
|
||||
await requestGithub(`issues/${pullRequestNumber}/labels`, {
|
||||
labels: ['ready'],
|
||||
});
|
||||
} else {
|
||||
await requestGithub(`pulls/${pullRequestNumber}/reviews`, {
|
||||
commit_id: commitSha,
|
||||
body: '### Please fix the following problems:\n' + errors.join('\n'),
|
||||
event: 'REQUEST_CHANGES',
|
||||
});
|
||||
|
||||
await requestGithub(`issues/${pullRequestNumber}/labels`, {
|
||||
labels: ['waiting'],
|
||||
});
|
||||
}
|
||||
|
||||
async function requestGithub(
|
||||
url: string,
|
||||
body: any,
|
||||
method?: 'POST' | 'DELETE'
|
||||
) {
|
||||
await fetch(`https://api.github.com/repos/xHyroM/bun-discord-bot/${url}`, {
|
||||
method: method || 'POST',
|
||||
headers: {
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `token ${githubToken}`,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
export {};
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json"
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
import { SlashCommandStringOption, SlashCommandUserOption } from "discord.js";
|
||||
import { defineCommand } from "../loaders/commands.ts";
|
||||
import { AutocompleteContext } from "../structs/context/AutocompleteContext.ts";
|
||||
import { InteractionCommandContext } from "../structs/context/CommandContext.ts";
|
||||
import algoliasearch from "algoliasearch";
|
||||
|
||||
const algoliaClient = algoliasearch("2527C13E0N", "4efc87205e1fce4a1f267cadcab42cb2");
|
||||
const algoliaIndex = algoliaClient.initIndex("bun");
|
||||
|
||||
defineCommand({
|
||||
name: "docs",
|
||||
description: "Search at docs",
|
||||
options: [
|
||||
{
|
||||
...new SlashCommandStringOption()
|
||||
.setName("query")
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true)
|
||||
.setDescription("Select query")
|
||||
.toJSON(),
|
||||
run: async(context: AutocompleteContext) => {
|
||||
const query = context.options.getString("query");
|
||||
const result = await algoliaIndex.search(query, {
|
||||
hitsPerPage: 25,
|
||||
});
|
||||
|
||||
return context.respond(
|
||||
result.hits.map(hit => {
|
||||
const name = getHitName(hit);
|
||||
|
||||
return {
|
||||
name: name.full.length > 100 ? name.full.slice(0, 100) : name.full,
|
||||
value: name.name.length > 100 ? name.name.slice(0, 100) : name.name,
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
...new SlashCommandUserOption()
|
||||
.setName("target")
|
||||
.setRequired(false)
|
||||
.setDescription("User to mention")
|
||||
.toJSON()
|
||||
}
|
||||
],
|
||||
run: async(context: InteractionCommandContext) => {
|
||||
await context.interaction.deferReply();
|
||||
|
||||
const query = context.interaction.options.getString("query");
|
||||
const target = context.interaction.options.getUser("target");
|
||||
|
||||
const result = await algoliaIndex.search(query, {
|
||||
hitsPerPage: 1,
|
||||
});
|
||||
|
||||
const hit = result.hits[0];
|
||||
// @ts-expect-error exist
|
||||
const url = hit.url;
|
||||
const name = getHitName(hit);
|
||||
// @ts-expect-error can exist
|
||||
const snippetContent = hit._snippetResult?.content?.value?.replace(/<[^>]+>/g, "");
|
||||
// @ts-expect-error can exist
|
||||
const notice = hit.content?.replace(/\r/g, "");
|
||||
|
||||
const content = [
|
||||
target ? `*Suggestion for <@${target.id}>:*\n` : "",
|
||||
`[*${name.full}*](<${url}>)`,
|
||||
snippetContent ? snippetContent : "",
|
||||
notice ? notice.split("\n").map(s => `> ${s}`).join("\n") : ""
|
||||
].join("\n")
|
||||
|
||||
await context.interaction.editReply({
|
||||
content,
|
||||
allowedMentions: {
|
||||
parse: [ "users" ],
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
function getHitName(hit) {
|
||||
const type = hit.hierarchy.lvl0 === "Documentation" ? "📖" : "🗺️";
|
||||
const hierarchy = Object.values(hit.hierarchy).filter(v => v);
|
||||
hierarchy.shift();
|
||||
|
||||
const name = hierarchy.join(" > ");
|
||||
|
||||
return {
|
||||
full: `${type} ${name}`,
|
||||
name: name,
|
||||
emoji: type,
|
||||
}
|
||||
}
|
94
src/commands/docs.tsx
Normal file
94
src/commands/docs.tsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
import { ApplicationCommand, StringOption, UserOption } from "@lilybird/jsx";
|
||||
import { SlashCommand } from "@lilybird/handlers";
|
||||
import algoliasearch from "algoliasearch";
|
||||
|
||||
// @ts-expect-error It is callable, but algolia for some reason has a namespace with the same name
|
||||
const algoliaClient = algoliasearch(
|
||||
"2527C13E0N",
|
||||
"4efc87205e1fce4a1f267cadcab42cb2"
|
||||
);
|
||||
const algoliaIndex = algoliaClient.initIndex("bun");
|
||||
|
||||
export default {
|
||||
post: "GLOBAL",
|
||||
data: (
|
||||
<ApplicationCommand name="docs" description="Search at docs">
|
||||
<StringOption
|
||||
name="query"
|
||||
description="Select query"
|
||||
required
|
||||
autocomplete
|
||||
/>
|
||||
<UserOption name="target" description="User to mention" />
|
||||
</ApplicationCommand>
|
||||
),
|
||||
autocomplete: async (interaction) => {
|
||||
const query = interaction.data.getFocused<string>().value;
|
||||
const result = await algoliaIndex.search(query, {
|
||||
hitsPerPage: 25,
|
||||
});
|
||||
|
||||
return interaction.respond(
|
||||
result.hits.map((hit: any) => {
|
||||
const name = getHitName(hit);
|
||||
|
||||
return {
|
||||
name: name.full.length > 100 ? name.full.slice(0, 100) : name.full,
|
||||
value: name.name.length > 100 ? name.name.slice(0, 100) : name.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
},
|
||||
run: async (interaction) => {
|
||||
await interaction.deferReply();
|
||||
|
||||
const query = interaction.data.getString("query");
|
||||
const target = interaction.data.getUser("target");
|
||||
|
||||
const result = await algoliaIndex.search(query, {
|
||||
hitsPerPage: 1,
|
||||
});
|
||||
|
||||
const hit = result.hits[0];
|
||||
const url = hit.url;
|
||||
const name = getHitName(hit);
|
||||
const snippetContent = hit._snippetResult?.content?.value?.replace(
|
||||
/<[^>]+>/g,
|
||||
""
|
||||
);
|
||||
const notice = hit.content?.replace(/\r/g, "");
|
||||
|
||||
const content = [
|
||||
target ? `*Suggestion for <@${target}>:*\n` : "",
|
||||
`[*${name.full}*](<${url}>)`,
|
||||
snippetContent ? snippetContent : "",
|
||||
notice
|
||||
? notice
|
||||
.split("\n")
|
||||
.map((s: any) => `> ${s}`)
|
||||
.join("\n")
|
||||
: "",
|
||||
].join("\n");
|
||||
|
||||
await interaction.editReply({
|
||||
content,
|
||||
// allowedMentions: {
|
||||
// parse: ["users"],
|
||||
// }
|
||||
});
|
||||
},
|
||||
} satisfies SlashCommand;
|
||||
|
||||
function getHitName(hit: any) {
|
||||
const type = hit.hierarchy.lvl0 === "Documentation" ? "📖" : "🗺️";
|
||||
const hierarchy = Object.values(hit.hierarchy).filter((v) => v);
|
||||
hierarchy.shift();
|
||||
|
||||
const name = hierarchy.join(" > ");
|
||||
|
||||
return {
|
||||
full: `${type} ${name}`,
|
||||
name: name,
|
||||
emoji: type,
|
||||
};
|
||||
}
|
|
@ -1,279 +0,0 @@
|
|||
import { SlashCommandBooleanOption, SlashCommandStringOption, time } from "discord.js";
|
||||
import { defineCommand } from "../loaders/commands.ts";
|
||||
import { AutocompleteContext } from "../structs/context/AutocompleteContext.ts";
|
||||
import { InteractionCommandContext } from "../structs/context/CommandContext.ts";
|
||||
import { safeSlice, silently } from "../util.ts";
|
||||
|
||||
type State = "open" | "closed_as_completed" | "closed_as_not_planned" | "closed" | "merged" | "draft" | "all";
|
||||
type StateEmoji = "🔴" | "🟠" | "🟢" | "⚫️" | "⚪️" | "🟣" | "📝";
|
||||
type Type = "issues" | "pull_requests" | "both";
|
||||
|
||||
interface Item {
|
||||
html_url: string;
|
||||
number: number;
|
||||
title: string;
|
||||
user: {
|
||||
html_url: string;
|
||||
login: string;
|
||||
}
|
||||
emoji: {
|
||||
state: StateEmoji;
|
||||
type: string;
|
||||
};
|
||||
created_at: Date | null;
|
||||
closed_at: Date | null;
|
||||
pull_request?: {
|
||||
merged_at: Date | null;
|
||||
}
|
||||
}
|
||||
|
||||
defineCommand({
|
||||
name: "github",
|
||||
description: "Query an issue, pull request or direct link to issue, pull request",
|
||||
options: [
|
||||
{
|
||||
...new SlashCommandStringOption()
|
||||
.setName("query")
|
||||
.setDescription("Issue/Pull request number or name")
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true)
|
||||
.setMaxLength(100),
|
||||
run: async(ctx: AutocompleteContext) => {
|
||||
const query = ctx.options.getString("query");
|
||||
const state: State = ctx.options.getString("state") as State || "all";
|
||||
const type: Type = ctx.options.getString("type") as Type || "both";
|
||||
|
||||
const response = await search(query, state, type, 25);
|
||||
|
||||
await silently(ctx.respond(response.map(r => ({
|
||||
name: safeSlice<string>(`${r.emoji.type} ${r.emoji.state} #${r.number} | ${r.title}`, 100),
|
||||
value: r.number.toString()
|
||||
}))));
|
||||
}
|
||||
},
|
||||
{
|
||||
...new SlashCommandStringOption()
|
||||
.setName("state")
|
||||
.setDescription("Issue or Pull request state")
|
||||
.setRequired(false)
|
||||
.addChoices(
|
||||
{
|
||||
name: "🔴🟠 Open",
|
||||
value: "open"
|
||||
},
|
||||
{
|
||||
name: "🟢 Closed as completed",
|
||||
value: "closed_as_completed"
|
||||
},
|
||||
{
|
||||
name: "⚪️ Closed as not planned",
|
||||
value: "closed_as_not_planned"
|
||||
},
|
||||
{
|
||||
name: "⚫️ Closed",
|
||||
value: "closed"
|
||||
},
|
||||
{
|
||||
name: "🟣 Merged",
|
||||
value: "merged"
|
||||
},
|
||||
{
|
||||
name: "📝 Draft",
|
||||
value: "draft",
|
||||
},
|
||||
{
|
||||
name: "🌍 All",
|
||||
value: "all",
|
||||
}
|
||||
)
|
||||
},
|
||||
{
|
||||
...new SlashCommandStringOption()
|
||||
.setName("type")
|
||||
.setDescription("Issue or Pull Requests")
|
||||
.setRequired(false)
|
||||
.addChoices(
|
||||
{
|
||||
name: "🐛 Issues",
|
||||
value: "issues"
|
||||
},
|
||||
{
|
||||
name: "🔨 Pull Requests",
|
||||
value: "pull_requests"
|
||||
},
|
||||
{
|
||||
name: "🌍 Both",
|
||||
value: "both"
|
||||
}
|
||||
)
|
||||
},
|
||||
{
|
||||
...new SlashCommandBooleanOption()
|
||||
.setName("hide")
|
||||
.setDescription("Show this message only for you")
|
||||
.setRequired(false)
|
||||
}
|
||||
],
|
||||
run: async(ctx: InteractionCommandContext) => {
|
||||
const hide = ctx.interaction.options.getBoolean("hide") ?? false;
|
||||
|
||||
await ctx.interaction.deferReply({
|
||||
ephemeral: hide
|
||||
});
|
||||
|
||||
const query = ctx.interaction.options.getString("query");
|
||||
const state: State = ctx.interaction.options.getString("state") as State || "all";
|
||||
const type: Type = ctx.interaction.options.getString("type") as Type || "both";
|
||||
|
||||
const result = (await search(query, state, type))[0];
|
||||
if (!result) {
|
||||
ctx.interaction.editReply({
|
||||
content: `❌ Couldn't find issue or pull request \`${query}\``
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.interaction.editReply({
|
||||
content: [
|
||||
`${result.emoji.type} ${result.emoji.state} [#${result.number} in oven-sh/bun](<${result.html_url}>) by [${result.user.login}](<${result.user.html_url}>) ${stateToText(result)} ${stateToTimestamp(result)}`,
|
||||
result.title
|
||||
].join("\n")
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
function stateToText(item: Item) {
|
||||
switch (item.emoji.state) {
|
||||
case "🔴":
|
||||
case "🟠":
|
||||
case "📝": {
|
||||
return "opened";
|
||||
}
|
||||
case "🟢": {
|
||||
return "closed as completed";
|
||||
}
|
||||
case "⚪️": {
|
||||
return "closed as not planned";
|
||||
}
|
||||
case "⚫️": {
|
||||
return "closed";
|
||||
}
|
||||
case "🟣": {
|
||||
return "merged";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stateToTimestamp(item: Item) {
|
||||
let timestamp: Date;
|
||||
|
||||
switch (item.emoji.state) {
|
||||
case "🔴":
|
||||
case "🟠":
|
||||
case "📝": {
|
||||
timestamp = item.created_at;
|
||||
break;
|
||||
}
|
||||
case "🟢":
|
||||
case "⚪️":
|
||||
case "⚫️": {
|
||||
timestamp = item.closed_at;
|
||||
break;
|
||||
}
|
||||
case "🟣": {
|
||||
timestamp = item.pull_request.merged_at;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return `<t:${Math.round(timestamp.getTime() / 1000)}:R>`;
|
||||
}
|
||||
|
||||
async function search(query: string, state: State, type: Type, length = 1): Promise<Item[]> {
|
||||
let actualQuery = "repo:oven-sh/bun ";
|
||||
|
||||
switch (state) {
|
||||
case "open": {
|
||||
actualQuery += "state:open ";
|
||||
break;
|
||||
}
|
||||
case "closed": {
|
||||
actualQuery += "state:closed ";
|
||||
break;
|
||||
}
|
||||
case "closed_as_completed": {
|
||||
actualQuery += "state:closed reason:completed "
|
||||
break;
|
||||
}
|
||||
case "closed_as_not_planned": {
|
||||
actualQuery += "state:closed reason:\"not planned\" ";
|
||||
break;
|
||||
}
|
||||
case "merged": {
|
||||
actualQuery += "is:merged ";
|
||||
break;
|
||||
}
|
||||
case "draft": {
|
||||
actualQuery += "draft:true ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "issues": {
|
||||
actualQuery += "type:issue "
|
||||
break;
|
||||
}
|
||||
case "pull_requests": {
|
||||
actualQuery += "type:pr "
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// append user query + remove all tags
|
||||
actualQuery += query.replace(/\S+:\S+/g, "").trim();
|
||||
|
||||
const response = await fetch(`https://api.github.com/search/issues?q=${encodeURIComponent(actualQuery)}&per_page=${length}`, {
|
||||
headers: {
|
||||
"Authorization": `Bearer ${process.env.GITHUB_TOKEN}`,
|
||||
"Accept": "application/vnd.github+json"
|
||||
}
|
||||
});
|
||||
|
||||
const body = await response.json();
|
||||
const items = body.items;
|
||||
|
||||
return items.map(item => {
|
||||
let state = "";
|
||||
if (item.state === "closed") {
|
||||
if (item.pull_request) {
|
||||
state = item.pull_request.merged_at ? "🟣" : "⚫️";
|
||||
} else {
|
||||
state = item.state_reason === "completed" ? "🟢" : "⚪️";
|
||||
}
|
||||
} else {
|
||||
if (item.pull_request) {
|
||||
state = item.draft ? "📝" : "🟠";
|
||||
} else {
|
||||
state = "🔴";
|
||||
}
|
||||
}
|
||||
|
||||
const base = {
|
||||
...item,
|
||||
emoji: {
|
||||
state: state,
|
||||
type: item.pull_request ? "🔨" : "🐛"
|
||||
},
|
||||
created_at: new Date(item.created_at),
|
||||
closed_at: item.closed_at ? new Date(item.cloased_at) : null
|
||||
}
|
||||
|
||||
if (item.pull_request) {
|
||||
base.pull_request = {};
|
||||
base.pull_request.merged_at = item.pull_request.merged_at ? new Date(item.pull_request.merged_at) : null;
|
||||
}
|
||||
|
||||
return base;
|
||||
});
|
||||
}
|
275
src/commands/github.tsx
Normal file
275
src/commands/github.tsx
Normal file
|
@ -0,0 +1,275 @@
|
|||
import {
|
||||
ApplicationCommand,
|
||||
BooleanOption,
|
||||
CommandOptions,
|
||||
StringOption,
|
||||
} from "@lilybird/jsx";
|
||||
import { SlashCommand } from "@lilybird/handlers";
|
||||
import { safeSlice, silently } from "../util.ts";
|
||||
|
||||
type State =
|
||||
| "open"
|
||||
| "closed_as_completed"
|
||||
| "closed_as_not_planned"
|
||||
| "closed"
|
||||
| "merged"
|
||||
| "draft"
|
||||
| "all";
|
||||
type StateEmoji = "🔴" | "🟠" | "🟢" | "⚫️" | "⚪️" | "🟣" | "📝";
|
||||
type Type = "issues" | "pull_requests" | "both";
|
||||
|
||||
interface Item {
|
||||
html_url: string;
|
||||
number: number;
|
||||
title: string;
|
||||
user: {
|
||||
html_url: string;
|
||||
login: string;
|
||||
};
|
||||
emoji: {
|
||||
state: StateEmoji;
|
||||
type: string;
|
||||
};
|
||||
created_at: Date | null;
|
||||
closed_at: Date | null;
|
||||
pull_request?: {
|
||||
merged_at: Date | null;
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
post: "GLOBAL",
|
||||
data: (
|
||||
<ApplicationCommand
|
||||
name="github"
|
||||
description="Query an issue, pull request or direct link to issue, pull request"
|
||||
>
|
||||
<StringOption
|
||||
name="query"
|
||||
description="Issue/Pull request number or name"
|
||||
autocomplete
|
||||
required
|
||||
max_length={100}
|
||||
/>
|
||||
<StringOption name="state" description="Issue or Pull request state">
|
||||
<CommandOptions name="🔴🟠 Open" value="open" />
|
||||
<CommandOptions
|
||||
name="🟢 Closed as completed"
|
||||
value="closed_as_completed"
|
||||
/>
|
||||
<CommandOptions
|
||||
name="⚪️ Closed as not planned"
|
||||
value="closed_as_not_planned"
|
||||
/>
|
||||
<CommandOptions name="⚫️ Closed" value="closed" />
|
||||
<CommandOptions name="🟣 Merged" value="merged" />
|
||||
<CommandOptions name="📝 Draft" value="draft" />
|
||||
<CommandOptions name="🌍 All" value="all" />
|
||||
</StringOption>
|
||||
<StringOption name="type" description="Issue/Pull request number or name">
|
||||
<CommandOptions name="🐛 Issues" value="issues" />
|
||||
<CommandOptions name="🔨 Pull Requests" value="pull_requests" />
|
||||
<CommandOptions name="🌍 Both" value="both" />
|
||||
</StringOption>
|
||||
<BooleanOption name="hide" description="Show this message only for you" />
|
||||
</ApplicationCommand>
|
||||
),
|
||||
run: async (interaction) => {
|
||||
const hide = interaction.data.getBoolean("hide") ?? false;
|
||||
|
||||
await interaction.deferReply(hide);
|
||||
|
||||
const query = interaction.data.getString("query", true);
|
||||
const state: State =
|
||||
(interaction.data.getString("state") as State) || "all";
|
||||
const type: Type = (interaction.data.getString("type") as Type) || "both";
|
||||
|
||||
const result = (await search(query, state, type))[0];
|
||||
if (!result) {
|
||||
interaction.editReply({
|
||||
content: `❌ Couldn't find issue or pull request \`${query}\``,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
interaction.editReply({
|
||||
content: [
|
||||
`${result.emoji.type} ${result.emoji.state} [#${
|
||||
result.number
|
||||
} in oven-sh/bun](<${result.html_url}>) by [${result.user.login}](<${
|
||||
result.user.html_url
|
||||
}>) ${stateToText(result)} ${stateToTimestamp(result)}`,
|
||||
result.title,
|
||||
].join("\n"),
|
||||
});
|
||||
},
|
||||
|
||||
autocomplete: async (interaction) => {
|
||||
const query = interaction.data.getFocused<string>().value;
|
||||
const state: State =
|
||||
(interaction.data.getString("state") as State) || "all";
|
||||
const type: Type = (interaction.data.getString("type") as Type) || "both";
|
||||
|
||||
const response = await search(query, state, type, 25);
|
||||
|
||||
await silently(
|
||||
interaction.respond(
|
||||
response.map((r) => ({
|
||||
name: safeSlice<string>(
|
||||
`${r.emoji.type} ${r.emoji.state} #${r.number} | ${r.title}`,
|
||||
100
|
||||
),
|
||||
value: r.number.toString(),
|
||||
}))
|
||||
)
|
||||
);
|
||||
},
|
||||
} satisfies SlashCommand;
|
||||
|
||||
function stateToText(item: Item) {
|
||||
switch (item.emoji.state) {
|
||||
case "🔴":
|
||||
case "🟠":
|
||||
case "📝": {
|
||||
return "opened";
|
||||
}
|
||||
case "🟢": {
|
||||
return "closed as completed";
|
||||
}
|
||||
case "⚪️": {
|
||||
return "closed as not planned";
|
||||
}
|
||||
case "⚫️": {
|
||||
return "closed";
|
||||
}
|
||||
case "🟣": {
|
||||
return "merged";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stateToTimestamp(item: Item) {
|
||||
let timestamp: Date;
|
||||
|
||||
switch (item.emoji.state) {
|
||||
case "🔴":
|
||||
case "🟠":
|
||||
case "📝": {
|
||||
timestamp = item.created_at as Date;
|
||||
break;
|
||||
}
|
||||
case "🟢":
|
||||
case "⚪️":
|
||||
case "⚫️": {
|
||||
timestamp = item.closed_at as Date;
|
||||
break;
|
||||
}
|
||||
case "🟣": {
|
||||
timestamp = item.pull_request?.merged_at as Date;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return `<t:${Math.round(timestamp.getTime() / 1000)}:R>`;
|
||||
}
|
||||
|
||||
async function search(
|
||||
query: string,
|
||||
state: State,
|
||||
type: Type,
|
||||
length = 1
|
||||
): Promise<Item[]> {
|
||||
let actualQuery = "repo:oven-sh/bun ";
|
||||
|
||||
switch (state) {
|
||||
case "open": {
|
||||
actualQuery += "state:open ";
|
||||
break;
|
||||
}
|
||||
case "closed": {
|
||||
actualQuery += "state:closed ";
|
||||
break;
|
||||
}
|
||||
case "closed_as_completed": {
|
||||
actualQuery += "state:closed reason:completed ";
|
||||
break;
|
||||
}
|
||||
case "closed_as_not_planned": {
|
||||
actualQuery += 'state:closed reason:"not planned" ';
|
||||
break;
|
||||
}
|
||||
case "merged": {
|
||||
actualQuery += "is:merged ";
|
||||
break;
|
||||
}
|
||||
case "draft": {
|
||||
actualQuery += "draft:true ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "issues": {
|
||||
actualQuery += "type:issue ";
|
||||
break;
|
||||
}
|
||||
case "pull_requests": {
|
||||
actualQuery += "type:pr ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// append user query + remove all tags
|
||||
actualQuery += query.replace(/\S+:\S+/g, "").trim();
|
||||
|
||||
const response = await fetch(
|
||||
`https://api.github.com/search/issues?q=${encodeURIComponent(
|
||||
actualQuery
|
||||
)}&per_page=${length}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
|
||||
Accept: "application/vnd.github+json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const body = await response.json();
|
||||
const items = body.items;
|
||||
|
||||
return items.map((item: any) => {
|
||||
let state = "";
|
||||
if (item.state === "closed") {
|
||||
if (item.pull_request) {
|
||||
state = item.pull_request.merged_at ? "🟣" : "⚫️";
|
||||
} else {
|
||||
state = item.state_reason === "completed" ? "🟢" : "⚪️";
|
||||
}
|
||||
} else {
|
||||
if (item.pull_request) {
|
||||
state = item.draft ? "📝" : "🟠";
|
||||
} else {
|
||||
state = "🔴";
|
||||
}
|
||||
}
|
||||
|
||||
const base = {
|
||||
...item,
|
||||
emoji: {
|
||||
state: state,
|
||||
type: item.pull_request ? "🔨" : "🐛",
|
||||
},
|
||||
created_at: new Date(item.created_at),
|
||||
closed_at: item.closed_at ? new Date(item.cloased_at) : null,
|
||||
};
|
||||
|
||||
if (item.pull_request) {
|
||||
base.pull_request = {};
|
||||
base.pull_request.merged_at = item.pull_request.merged_at
|
||||
? new Date(item.pull_request.merged_at)
|
||||
: null;
|
||||
}
|
||||
|
||||
return base;
|
||||
});
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import "./version.ts";
|
||||
import "./docs.ts";
|
||||
import "./tag.ts";
|
||||
import "./ping.ts";
|
||||
import "./github.ts";
|
||||
|
||||
import { registerCommands } from "../loaders/commands.ts";
|
||||
await registerCommands();
|
|
@ -1,30 +0,0 @@
|
|||
import { defineCommand } from "../loaders/commands.ts";
|
||||
import { Bubu } from "../structs/Client.ts";
|
||||
import { InteractionCommandContext, MessageCommandContext } from "../structs/context/CommandContext.ts";
|
||||
|
||||
defineCommand({
|
||||
name: "ping",
|
||||
description: "pong",
|
||||
run: async(ctx: InteractionCommandContext) => {
|
||||
const message = await ctx.interaction.deferReply({
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
const restPing = message.createdTimestamp - ctx.interaction.createdTimestamp;
|
||||
|
||||
ctx.interaction.editReply({
|
||||
content: `🏓 WebSocket: \`${Bubu.ws.ping}ms\` | Rest: \`${restPing}ms\``
|
||||
});
|
||||
},
|
||||
runMessage: async(ctx: MessageCommandContext) => {
|
||||
const message = await ctx.reply({
|
||||
content: "🏓...",
|
||||
});
|
||||
|
||||
const restPing = message.createdTimestamp - ctx.message.createdTimestamp;
|
||||
|
||||
message.edit({
|
||||
content: `🏓 WebSocket: \`${Bubu.ws.ping}ms\` | Rest: \`${restPing}ms\``
|
||||
});
|
||||
}
|
||||
})
|
16
src/commands/ping.tsx
Normal file
16
src/commands/ping.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { ApplicationCommand } from "@lilybird/jsx";
|
||||
import { SlashCommand } from "@lilybird/handlers";
|
||||
|
||||
export default {
|
||||
post: "GLOBAL",
|
||||
data: <ApplicationCommand name="ping" description="pong" />,
|
||||
run: async (interaction) => {
|
||||
await interaction.deferReply();
|
||||
|
||||
const { ws, rest } = await interaction.client.ping();
|
||||
|
||||
await interaction.editReply({
|
||||
content: `🏓 WebSocket: \`${ws}ms\` | Rest: \`${rest}ms\``,
|
||||
});
|
||||
},
|
||||
} satisfies SlashCommand;
|
|
@ -1,89 +0,0 @@
|
|||
import { GuildTextBasedChannel, SlashCommandStringOption, SlashCommandUserOption, User } from "discord.js";
|
||||
import { defineCommand } from "../loaders/commands.ts";
|
||||
import { AutocompleteContext } from "../structs/context/AutocompleteContext.ts";
|
||||
import { getTags, searchTag } from "../loaders/tags.ts";
|
||||
import { InteractionCommandContext, MessageCommandContext } from "../structs/context/CommandContext.ts";
|
||||
import { Bubu } from "../structs/Client.ts";
|
||||
|
||||
defineCommand({
|
||||
name: "tag",
|
||||
description: "Get tag",
|
||||
options: [
|
||||
{
|
||||
...new SlashCommandStringOption()
|
||||
.setName("query")
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true)
|
||||
.setDescription("Select query")
|
||||
.toJSON(),
|
||||
run: async(context: AutocompleteContext) => {
|
||||
const query = context.options.getString("query");
|
||||
if (!query) {
|
||||
return context.respond(getTags(context.channel, 25));
|
||||
}
|
||||
|
||||
const tags = searchTag(context.channel, query, true);
|
||||
if (tags.length > 0)
|
||||
return context.respond(tags);
|
||||
|
||||
return context.respond(getTags(context.channel, 25));
|
||||
},
|
||||
},
|
||||
{
|
||||
...new SlashCommandUserOption()
|
||||
.setName("target")
|
||||
.setRequired(false)
|
||||
.setDescription("User to mention")
|
||||
.toJSON()
|
||||
}
|
||||
],
|
||||
run: (ctx: InteractionCommandContext) => {
|
||||
const query = ctx.interaction.options.getString("query");
|
||||
const target = ctx.interaction.options.getUser("target");
|
||||
|
||||
const tag = searchTag(ctx.channel, query, false);
|
||||
if (!tag) {
|
||||
return ctx.reply({
|
||||
content: `\`❌\` Could not find a tag \`${query}\``,
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
|
||||
ctx.reply({
|
||||
content: [
|
||||
target ? `*Suggestion for <@${target.id}>:*\n` : "",
|
||||
`**${tag.question}**`,
|
||||
tag.answer
|
||||
].join("\n"),
|
||||
allowedMentions: {
|
||||
parse: [ "users" ]
|
||||
}
|
||||
});
|
||||
},
|
||||
runMessage: (ctx: MessageCommandContext) => {
|
||||
if (!ctx.message.inGuild()) return;
|
||||
|
||||
const keyword = ctx.options?.[0] ?? "what-is-bun";
|
||||
|
||||
const target = ctx.options?.[1]?.match(/([0-9]+)/)?.[0];
|
||||
const resolvedTarget = target ? Bubu.users.cache.get(target) : null;
|
||||
|
||||
const tag = searchTag(ctx.channel as GuildTextBasedChannel, keyword, false);
|
||||
if (!keyword || !tag) {
|
||||
return ctx.reply({
|
||||
content: `\`❌\` Could not find a tag \`${keyword}\``,
|
||||
});
|
||||
}
|
||||
|
||||
ctx.reply({
|
||||
content: [
|
||||
resolvedTarget ? `*Suggestion for <@${resolvedTarget.id}>:*\n` : "",
|
||||
`**${tag.question}**`,
|
||||
tag.answer
|
||||
].join("\n"),
|
||||
allowedMentions: {
|
||||
parse: [ "users" ]
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
54
src/commands/tag.tsx
Normal file
54
src/commands/tag.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { ApplicationCommand, StringOption, UserOption } from "@lilybird/jsx";
|
||||
import { getTags, searchTag } from "../loaders/tags.ts";
|
||||
import { SlashCommand } from "@lilybird/handlers";
|
||||
|
||||
export default {
|
||||
post: "GLOBAL",
|
||||
data: (
|
||||
<ApplicationCommand name="tag" description="Get tag">
|
||||
<StringOption
|
||||
name="query"
|
||||
description="Select query"
|
||||
required
|
||||
autocomplete
|
||||
/>
|
||||
<UserOption name="target" description="User to mention" />
|
||||
</ApplicationCommand>
|
||||
),
|
||||
run: async (interaction) => {
|
||||
if (!interaction.inGuild()) return;
|
||||
const query = interaction.data.getString("query", true);
|
||||
const target = interaction.data.getUser("target");
|
||||
|
||||
const tag = searchTag(interaction.channel, query, false);
|
||||
if (!tag) {
|
||||
return interaction.reply({
|
||||
content: `\`❌\` Could not find a tag \`${query}\``,
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
|
||||
await interaction.reply({
|
||||
content: [
|
||||
target ? `*Suggestion for <@${target}>:*\n` : "",
|
||||
`**${tag.question}**`,
|
||||
tag.answer,
|
||||
].join("\n"),
|
||||
// allowedMentions: {
|
||||
// parse: ["users"]
|
||||
// }
|
||||
});
|
||||
},
|
||||
autocomplete: async (interaction) => {
|
||||
if (!interaction.inGuild()) return;
|
||||
const query = interaction.data.getFocused<string>().value;
|
||||
if (!query) {
|
||||
return await interaction.respond(getTags(interaction.channel, 25));
|
||||
}
|
||||
|
||||
const tags = searchTag(interaction.channel, query, true);
|
||||
if (tags.length > 0) return await interaction.respond(tags);
|
||||
|
||||
return await interaction.respond(getTags(interaction.channel, 25));
|
||||
},
|
||||
} satisfies SlashCommand;
|
|
@ -1,18 +0,0 @@
|
|||
import { defineCommand } from "../loaders/commands.ts";
|
||||
import { COMMIT_HASH, PRODUCTION } from "../constants.ts";
|
||||
import { InteractionCommandContext } from "../structs/context/CommandContext.ts";
|
||||
|
||||
export default defineCommand({
|
||||
name: "version",
|
||||
description: "Show version",
|
||||
options: [],
|
||||
run: (context: InteractionCommandContext) => {
|
||||
context.interaction.reply({
|
||||
content: [
|
||||
`[git-bun-discord-bot-${COMMIT_HASH}](<https://github.com/xHyroM/bun-discord-bot/tree/${COMMIT_HASH}>) ${!PRODUCTION ? "(dev)" : ""}`,
|
||||
`[v${Bun.version} (${Bun.revision})](<https://github.com/oven-sh/bun/releases/tag/bun-v${Bun.version}>)`
|
||||
].join("\n"),
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
});
|
19
src/commands/version.tsx
Normal file
19
src/commands/version.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { COMMIT_HASH, PRODUCTION } from "../constants.ts";
|
||||
import { ApplicationCommand } from "@lilybird/jsx";
|
||||
import { SlashCommand } from "@lilybird/handlers";
|
||||
|
||||
export default {
|
||||
post: "GLOBAL",
|
||||
data: <ApplicationCommand name="version" description="Show version" />,
|
||||
run: (interaction) => {
|
||||
interaction.reply({
|
||||
content: [
|
||||
`[git-bun-discord-bot-${COMMIT_HASH}](<https://github.com/xHyroM/bun-discord-bot/tree/${COMMIT_HASH}>) ${
|
||||
!PRODUCTION ? "(dev)" : ""
|
||||
}`,
|
||||
`[v${Bun.version} (${Bun.revision})](<https://github.com/oven-sh/bun/releases/tag/bun-v${Bun.version}>)`,
|
||||
].join("\n"),
|
||||
ephemeral: true,
|
||||
});
|
||||
},
|
||||
} satisfies SlashCommand;
|
|
@ -1,11 +1,7 @@
|
|||
import { spawnSync } from "bun";
|
||||
|
||||
export const COMMIT_HASH = spawnSync({
|
||||
cmd: [ "git", "log", "--pretty=format:%h", "-n", "1" ]
|
||||
cmd: ["git", "log", "--pretty=format:%h", "-n", "1"],
|
||||
}).stdout.toString();
|
||||
|
||||
export const PRODUCTION = process.env.NODE_ENV === "production";
|
||||
|
||||
export const MESSAGE_PREFIX = PRODUCTION ? "b" : "<>";
|
||||
|
||||
export const BUN_ONLY_CHANNEL_ID = "1161157663867027476"
|
||||
|
|
25
src/index.ts
25
src/index.ts
|
@ -1,11 +1,28 @@
|
|||
import "./loaders/tags.ts";
|
||||
import "./commands";
|
||||
import "./listeners";
|
||||
|
||||
import { Bubu } from "./structs/Client.ts";
|
||||
import { createHandler } from "@lilybird/handlers";
|
||||
import { createClient, Intents } from "lilybird";
|
||||
|
||||
// Make sure bubu will not crash
|
||||
process.on("unhandledRejection", console.error);
|
||||
process.on("uncaughtException", console.error);
|
||||
|
||||
Bubu.login(process.env.DISCORD_BOT_TOKEN).catch(console.error);
|
||||
const listeners = await createHandler({
|
||||
prefix: process.env.MESSAGE_PREFIX,
|
||||
dirs: {
|
||||
messageCommands: `${import.meta.dir}/message-commands`,
|
||||
slashCommands: `${import.meta.dir}/commands`,
|
||||
listeners: `${import.meta.dir}/listeners`,
|
||||
},
|
||||
});
|
||||
|
||||
await createClient({
|
||||
token: process.env.DISCORD_BOT_TOKEN,
|
||||
intents: [
|
||||
Intents.GUILDS,
|
||||
Intents.GUILD_MESSAGES,
|
||||
Intents.MESSAGE_CONTENT,
|
||||
Intents.GUILD_MEMBERS,
|
||||
],
|
||||
...listeners,
|
||||
});
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import "./ready.ts";
|
||||
import "./interaction_create.ts";
|
||||
import "./message_create.ts";
|
||||
import "./message_update.ts";
|
||||
import "./nickname_moderation.ts";
|
|
@ -1,59 +0,0 @@
|
|||
import { Events, Interaction, ChatInputCommandInteraction, AutocompleteInteraction, APIApplicationCommandSubcommandOption, APIApplicationCommandSubcommandGroupOption } from "discord.js";
|
||||
import { COMMANDS } from "../loaders/commands.ts";
|
||||
import { defineListener } from "../loaders/listeners.ts";
|
||||
import { InteractionCommandContext } from "../structs/context/CommandContext.ts";
|
||||
import { AutocompleteContext } from "../structs/context/AutocompleteContext.ts";
|
||||
import { Option, StringOption } from "../structs/Command.ts";
|
||||
import { error } from "@paperdave/logger";
|
||||
|
||||
defineListener({
|
||||
event: Events.InteractionCreate,
|
||||
run: async(interaction: Interaction) => {
|
||||
if (interaction.isChatInputCommand()) return await handleCommand(interaction);
|
||||
if (interaction.isAutocomplete()) return await handleAutocomplete(interaction);
|
||||
|
||||
return;
|
||||
}
|
||||
})
|
||||
|
||||
async function handleCommand(interaction: ChatInputCommandInteraction) {
|
||||
const command = COMMANDS.get(interaction.commandName);
|
||||
|
||||
if (!command || !command.run) {
|
||||
interaction.reply({
|
||||
content: "Hmm.. Invalid command :P",
|
||||
ephemeral: true,
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
const context = new InteractionCommandContext(command, interaction);
|
||||
await Promise.resolve(command.run(context))
|
||||
.catch(async err => {
|
||||
error(err);
|
||||
|
||||
context.reply({
|
||||
content: `Something went wrong... ${err.message} (${err.code})`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function handleAutocomplete(interaction: AutocompleteInteraction) {
|
||||
const command = COMMANDS.get(interaction.commandName);
|
||||
if (!command) return;
|
||||
|
||||
let options = command.options;
|
||||
|
||||
if (interaction.options.getSubcommandGroup(false))
|
||||
options = (options.find(o => o.name === interaction.options.getSubcommandGroup()) as APIApplicationCommandSubcommandGroupOption)?.options as Option[];
|
||||
|
||||
if (interaction.options.getSubcommand(false))
|
||||
options = (options.find(o => o.name === interaction.options.getSubcommand()) as APIApplicationCommandSubcommandOption)?.options as Option[];
|
||||
|
||||
const focused = interaction.options.getFocused(true);
|
||||
const option = options.find(o => o.name === focused.name) as StringOption;
|
||||
if (!option) return;
|
||||
|
||||
const context = new AutocompleteContext(option, command, interaction);
|
||||
await option.run(context);
|
||||
}
|
9
src/listeners/member_add.ts
Normal file
9
src/listeners/member_add.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { moderateNick } from "../util.ts";
|
||||
import { Event } from "@lilybird/handlers";
|
||||
|
||||
export default {
|
||||
event: "guildMemberAdd",
|
||||
run: (member) => {
|
||||
moderateNick(member);
|
||||
},
|
||||
} satisfies Event<"guildMemberAdd">;
|
9
src/listeners/member_update.ts
Normal file
9
src/listeners/member_update.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { moderateNick } from "../util.ts";
|
||||
import { Event } from "@lilybird/handlers";
|
||||
|
||||
export default {
|
||||
event: "guildMemberUpdate",
|
||||
run: (member) => {
|
||||
moderateNick(member);
|
||||
},
|
||||
} satisfies Event<"guildMemberUpdate">;
|
|
@ -1,105 +0,0 @@
|
|||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Events, Message } from "discord.js";
|
||||
import { defineListener } from "../loaders/listeners.ts";
|
||||
import { MESSAGE_PREFIX, BUN_ONLY_CHANNEL_ID } from "../constants.ts";
|
||||
import { COMMANDS } from "../loaders/commands.ts";
|
||||
import { MessageCommandContext } from "../structs/context/CommandContext.ts";
|
||||
import { extname, basename } from "node:path";
|
||||
import { safeSlice } from "../util.ts";
|
||||
|
||||
const GITHUB_LINE_URL_REGEX = /(?:https?:\/\/)?(?:www\.)?(?:github)\.com\/(?<repo>[a-zA-Z0-9-_]+\/[A-Za-z0-9_.-]+)\/blob\/(?<path>.+?)#L(?<first_line_number>\d+)[-~]?L?(?<second_line_number>\d*)/i;
|
||||
|
||||
defineListener({
|
||||
event: Events.MessageCreate,
|
||||
run: async(message: Message) => {
|
||||
if (handleBunOnlyChannel(message)) return;
|
||||
if (message.system || message.author.bot) return;
|
||||
|
||||
if (!message.content.toLowerCase().startsWith(MESSAGE_PREFIX)) return handleOthers(message);
|
||||
|
||||
const [commandName, ...args] = message.content
|
||||
.slice(MESSAGE_PREFIX.length)
|
||||
.trim()
|
||||
.split(/ +/g);
|
||||
|
||||
if (commandName.length === 0) return;
|
||||
|
||||
const command = COMMANDS.get(commandName);
|
||||
if (!command || !command.runMessage) return;
|
||||
|
||||
const context = new MessageCommandContext(command, message, args);
|
||||
|
||||
await Promise.resolve(command.runMessage(context))
|
||||
.catch(async error => {
|
||||
context.reply({
|
||||
content: `Something went wrong... ${error.message} (${error.code})`
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function handleOthers(message: Message) {
|
||||
handleGithubLink(message);
|
||||
}
|
||||
|
||||
function handleBunOnlyChannel(message: Message) {
|
||||
if (message.channel.id !== BUN_ONLY_CHANNEL_ID) return false;
|
||||
|
||||
if (message.content !== "bun") {
|
||||
message.delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
message.react("🐰");
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleGithubLink(message: Message) {
|
||||
const match = GITHUB_LINE_URL_REGEX.exec(message.content);
|
||||
const groups = match?.groups;
|
||||
if (!groups) return;
|
||||
|
||||
const repo = groups.repo;
|
||||
const path = groups.path;
|
||||
let extension = extname(path).slice(1);
|
||||
const firstLineNumber = parseInt(groups.first_line_number) - 1;
|
||||
const secondLineNumber = parseInt(groups.second_line_number) || firstLineNumber + 1;
|
||||
|
||||
const contentUrl = `https://raw.githubusercontent.com/${repo}/${path}`;
|
||||
const response = await fetch(contentUrl);
|
||||
const content = await response.text();
|
||||
const lines = content.split("\n");
|
||||
|
||||
// limit, max 25 lines - possible flood
|
||||
if (secondLineNumber - firstLineNumber > 25 && lines.length > secondLineNumber) {
|
||||
message.react("❌");
|
||||
return;
|
||||
}
|
||||
|
||||
let text = "";
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (i < firstLineNumber || i >= secondLineNumber) continue;
|
||||
|
||||
const line = lines[i];
|
||||
text += `${line}\n`;
|
||||
}
|
||||
|
||||
// delete the last \n
|
||||
text = text.slice(0, -1);
|
||||
|
||||
if (extension === "zig") extension = "rs";
|
||||
|
||||
message.reply({
|
||||
content: `***${basename(path)}*** — *(L${firstLineNumber + 1}${secondLineNumber ? `-L${secondLineNumber}` : ""})*\n\`\`\`${extension}\n${safeSlice(text, 2000 - 6 - extension.length)}\n\`\`\``,
|
||||
components: [
|
||||
new ActionRowBuilder<ButtonBuilder>()
|
||||
.setComponents(
|
||||
new ButtonBuilder()
|
||||
.setLabel(repo)
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL(`https://github.com/${repo}/blob/${path}#L${firstLineNumber + 1}${secondLineNumber ? `-L${secondLineNumber}` : ""}`)
|
||||
)
|
||||
.toJSON()
|
||||
]
|
||||
})
|
||||
}
|
96
src/listeners/message_create.tsx
Normal file
96
src/listeners/message_create.tsx
Normal file
|
@ -0,0 +1,96 @@
|
|||
import { Message, ButtonStyle } from "lilybird";
|
||||
import { ActionRow, Button } from "@lilybird/jsx";
|
||||
import { extname, basename } from "node:path";
|
||||
import { Event } from "@lilybird/handlers";
|
||||
import { safeSlice } from "../util.ts";
|
||||
|
||||
const GITHUB_LINE_URL_REGEX =
|
||||
/(?:https?:\/\/)?(?:www\.)?(?:github)\.com\/(?<repo>[a-zA-Z0-9-_]+\/[A-Za-z0-9_.-]+)\/blob\/(?<path>.+?)#L(?<first_line_number>\d+)[-~]?L?(?<second_line_number>\d*)/i;
|
||||
|
||||
export default {
|
||||
event: "messageCreate",
|
||||
run: async (message) => {
|
||||
if (handleBunOnlyChannel(message)) return;
|
||||
if (!message.content?.toLowerCase().startsWith(process.env.MESSAGE_PREFIX))
|
||||
return handleOthers(message);
|
||||
},
|
||||
} satisfies Event<"messageCreate">;
|
||||
|
||||
function handleOthers(message: Message): void {
|
||||
handleGithubLink(message);
|
||||
}
|
||||
|
||||
function handleBunOnlyChannel(message: Message): boolean {
|
||||
if (message.channelId !== process.env.BUN_ONLY_CHANNEL_ID) return false;
|
||||
|
||||
if (message.content !== "bun") {
|
||||
message.delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
message.react("🐰");
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleGithubLink(message: Message): Promise<void> {
|
||||
if (!message.content) return;
|
||||
|
||||
const match = GITHUB_LINE_URL_REGEX.exec(message.content);
|
||||
const groups = match?.groups;
|
||||
if (!groups) return;
|
||||
|
||||
const repo = groups.repo;
|
||||
const path = groups.path;
|
||||
let extension = extname(path).slice(1);
|
||||
const firstLineNumber = parseInt(groups.first_line_number) - 1;
|
||||
const secondLineNumber =
|
||||
parseInt(groups.second_line_number) || firstLineNumber + 1;
|
||||
|
||||
const contentUrl = `https://raw.githubusercontent.com/${repo}/${path}`;
|
||||
const response = await fetch(contentUrl);
|
||||
const content = await response.text();
|
||||
const lines = content.split("\n");
|
||||
|
||||
// limit, max 25 lines - possible flood
|
||||
if (
|
||||
secondLineNumber - firstLineNumber > 25 &&
|
||||
lines.length > secondLineNumber
|
||||
) {
|
||||
message.react("❌");
|
||||
return;
|
||||
}
|
||||
|
||||
let text = "";
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (i < firstLineNumber || i >= secondLineNumber) continue;
|
||||
|
||||
const line = lines[i];
|
||||
text += `${line}\n`;
|
||||
}
|
||||
|
||||
// delete the last \n
|
||||
text = text.slice(0, -1);
|
||||
|
||||
if (extension === "zig") extension = "rs";
|
||||
|
||||
message.reply({
|
||||
content: `***${basename(path)}*** — *(L${firstLineNumber + 1}${
|
||||
secondLineNumber ? `-L${secondLineNumber}` : ""
|
||||
})*\n\`\`\`${extension}\n${safeSlice(
|
||||
text,
|
||||
2000 - 6 - extension.length
|
||||
)}\n\`\`\``,
|
||||
components: [
|
||||
<ActionRow>
|
||||
<Button
|
||||
style={ButtonStyle.Link}
|
||||
url={`https://github.com/${repo}/blob/${path}#L${
|
||||
firstLineNumber + 1
|
||||
}${secondLineNumber ? `-L${secondLineNumber}` : ""}`}
|
||||
label={repo}
|
||||
/>
|
||||
</ActionRow>,
|
||||
],
|
||||
});
|
||||
}
|
|
@ -1,22 +1,21 @@
|
|||
import { Events, Message } from "discord.js";
|
||||
import { defineListener } from "../loaders/listeners.ts";
|
||||
import { BUN_ONLY_CHANNEL_ID } from "../constants.ts";
|
||||
import { Event } from "@lilybird/handlers";
|
||||
import { PartialMessage } from "lilybird";
|
||||
|
||||
defineListener({
|
||||
event: Events.MessageUpdate,
|
||||
run: async(_: Message, message: Message) => {
|
||||
export default {
|
||||
event: "messageUpdate",
|
||||
run: async (message) => {
|
||||
if (handleBunOnlyChannel(message)) return;
|
||||
}
|
||||
});
|
||||
},
|
||||
} satisfies Event<"messageUpdate">;
|
||||
|
||||
function handleBunOnlyChannel(message: Message) {
|
||||
if (message.channel.id !== BUN_ONLY_CHANNEL_ID) return false;
|
||||
function handleBunOnlyChannel(message: PartialMessage): boolean {
|
||||
if (message.channelId !== process.env.BUN_ONLY_CHANNEL_ID) return false;
|
||||
|
||||
if (message.content !== "bun") {
|
||||
message.delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
message.react("🐰");
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { Events, GuildMember } from "discord.js";
|
||||
import { defineListener } from "../loaders/listeners.ts";
|
||||
import { silently } from "../util.ts";
|
||||
|
||||
defineListener({
|
||||
event: Events.GuildMemberAdd,
|
||||
run: (member: GuildMember) => moderateNick(member)
|
||||
});
|
||||
|
||||
defineListener({
|
||||
event: Events.GuildMemberUpdate,
|
||||
run: (_: GuildMember, newMember: GuildMember) => moderateNick(newMember)
|
||||
});
|
||||
|
||||
async function moderateNick(member: GuildMember) {
|
||||
const name = member.displayName;
|
||||
const normalizedName = name.normalize("NFKC").replace(/^[!$#@%^`&*()]+/, "");
|
||||
|
||||
if (name === normalizedName) return;
|
||||
|
||||
silently(member.edit({
|
||||
nick: normalizedName,
|
||||
reason: "lame username"
|
||||
}));
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
import { info } from "@paperdave/logger";
|
||||
import { defineListener } from "../loaders/listeners.ts";
|
||||
import { Client, Events } from "discord.js";
|
||||
import { Event } from "@lilybird/handlers";
|
||||
|
||||
defineListener({
|
||||
event: Events.ClientReady,
|
||||
once: true,
|
||||
run: (client: Client) => {
|
||||
info(`Logged in as ${client.user.tag} (${client.user.id})`);
|
||||
}
|
||||
})
|
||||
export default {
|
||||
event: "ready",
|
||||
run: (client) => {
|
||||
info(`Logged in as ${client.user.username} (${client.user.id})`);
|
||||
},
|
||||
} satisfies Event<"ready">;
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import { REST, Routes, SlashCommandBuilder } from "discord.js";
|
||||
import type { Command } from "../structs/Command.ts";
|
||||
|
||||
export const COMMANDS: Map<string, Command> = new Map();
|
||||
export const REST_CLIENT = new REST().setToken(process.env.DISCORD_TOKEN);
|
||||
|
||||
export function defineCommand<T extends Command>(command: T) {
|
||||
COMMANDS.set(command.name, command);
|
||||
}
|
||||
|
||||
export async function registerCommands() {
|
||||
const commands = [...COMMANDS.values()];
|
||||
|
||||
await REST_CLIENT.put(
|
||||
Routes.applicationCommands("995690041793839124"),
|
||||
{
|
||||
body: commands.map(d => ({
|
||||
...new SlashCommandBuilder()
|
||||
.setName(d.name)
|
||||
.setDescription(d.description)
|
||||
.toJSON(),
|
||||
options: d.options ?? []
|
||||
}))
|
||||
}
|
||||
)
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import type { ClientEvents } from "discord.js";
|
||||
import { Bubu } from "../structs/Client.ts";
|
||||
import { Listener } from "../structs/Listener.ts";
|
||||
|
||||
export const LISTENERS: Listener<keyof ClientEvents>[] = [];
|
||||
|
||||
export function defineListener<T extends Listener<keyof ClientEvents>>(listener: T) {
|
||||
LISTENERS.push(listener);
|
||||
|
||||
Bubu[listener.once ? "once" : "on"](
|
||||
listener.event as keyof ClientEvents,
|
||||
listener.run.bind(this)
|
||||
);
|
||||
}
|
|
@ -1,12 +1,18 @@
|
|||
import matter from "gray-matter";
|
||||
import { readFileSync } from "node:fs";
|
||||
import {
|
||||
PartialChannel,
|
||||
GuildTextChannel,
|
||||
ApplicationCommandOptionChoiceStructure,
|
||||
} from "lilybird";
|
||||
import { globSync as glob } from "glob";
|
||||
import { join } from "node:path";
|
||||
import { Tag } from "../structs/Tag";
|
||||
import { APIApplicationCommandOptionChoice, BaseGuildTextChannel, GuildTextBasedChannel, TextBasedChannel } from "discord.js";
|
||||
import { safeSlice } from "../util";
|
||||
import { Tag } from "../structs/Tag.ts";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { safeSlice } from "../util.ts";
|
||||
import matter from "gray-matter";
|
||||
import { join } from "node:path";
|
||||
|
||||
const tags = glob(join(__dirname, "..", "..", "data", "tags", "*.md"));
|
||||
type PartialGuildTextChannel = PartialChannel<GuildTextChannel>;
|
||||
|
||||
const tags = glob(join(import.meta.dir, "..", "..", "data", "tags", "*.md"));
|
||||
|
||||
export const TAGS: Tag[] = [];
|
||||
|
||||
|
@ -15,88 +21,118 @@ for (const tag of tags) {
|
|||
const frontMatter = matter(content);
|
||||
|
||||
TAGS.push({
|
||||
name: frontMatter.data.name,
|
||||
question: frontMatter.data.question,
|
||||
keywords: frontMatter.data.keywords,
|
||||
answer: frontMatter.content,
|
||||
category_ids: frontMatter.data.category_ids ?? null,
|
||||
channel_ids: frontMatter.data.channel_ids ?? null
|
||||
channel_ids: frontMatter.data.channel_ids ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
function getAvailableTags(channel: GuildTextBasedChannel) {
|
||||
return TAGS.filter(t => {
|
||||
function getAvailableTags(channel: PartialGuildTextChannel) {
|
||||
return TAGS.filter((t) => {
|
||||
const channelIds = t.channel_ids;
|
||||
const categoryIds = t.category_ids;
|
||||
|
||||
if (!channelIds && !categoryIds) return true;
|
||||
if (channelIds && channelIds.includes(channel.id)) return true;
|
||||
if (categoryIds && categoryIds.includes(channel.parentId)) return true;
|
||||
if (channelIds && channelIds.includes(channel?.id ?? "")) return true;
|
||||
if (
|
||||
categoryIds &&
|
||||
channel?.parentId &&
|
||||
categoryIds.includes(channel.parentId)
|
||||
)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function getTags(channel: GuildTextBasedChannel, length: number): APIApplicationCommandOptionChoice[] {
|
||||
export function getTags(
|
||||
channel: PartialGuildTextChannel,
|
||||
length: number
|
||||
): Array<ApplicationCommandOptionChoiceStructure> {
|
||||
const availableTags = getAvailableTags(channel);
|
||||
return safeSlice(
|
||||
availableTags.map((tag) => (
|
||||
{
|
||||
name: `🚀 ${tag.question}`,
|
||||
value: tag.question
|
||||
}
|
||||
)),
|
||||
length);
|
||||
return safeSlice<Array<ApplicationCommandOptionChoiceStructure>>(
|
||||
availableTags.map((tag) => ({
|
||||
name: `🚀 ${tag.question}`,
|
||||
value: tag.question,
|
||||
})),
|
||||
length
|
||||
);
|
||||
}
|
||||
|
||||
export function searchTag<T extends boolean>(channel: GuildTextBasedChannel, providedQuery: string, multiple?: T): T extends true ? APIApplicationCommandOptionChoice[] : Tag {
|
||||
export function searchTag<T extends boolean>(
|
||||
channel: PartialGuildTextChannel,
|
||||
providedQuery: string,
|
||||
multiple?: T
|
||||
): T extends true ? Array<ApplicationCommandOptionChoiceStructure> : Tag {
|
||||
const availableTags = getAvailableTags(channel);
|
||||
const query = providedQuery?.toLowerCase()?.replace(/-/g, " ");
|
||||
|
||||
|
||||
if (!multiple) {
|
||||
const exactKeyword = availableTags.find(tag => tag.keywords.find((k) => k.toLowerCase() === query));
|
||||
const keywordMatch = availableTags.find(tag => tag.keywords.find((k) => k.toLowerCase().includes(query)));
|
||||
const questionMatch = availableTags.find(tag => tag.question.toLowerCase().includes(query));
|
||||
const answerMatch = availableTags.find(tag => tag.answer.toLowerCase().includes(query));
|
||||
const exactKeyword = availableTags.find((tag) =>
|
||||
tag.keywords.find((k) => k.toLowerCase() === query)
|
||||
);
|
||||
const keywordMatch = availableTags.find((tag) =>
|
||||
tag.keywords.find((k) => k.toLowerCase().includes(query))
|
||||
);
|
||||
const questionMatch = availableTags.find((tag) =>
|
||||
tag.question.toLowerCase().includes(query)
|
||||
);
|
||||
const answerMatch = availableTags.find((tag) =>
|
||||
tag.answer.toLowerCase().includes(query)
|
||||
);
|
||||
|
||||
const tag = exactKeyword ?? questionMatch ?? keywordMatch ?? answerMatch;
|
||||
return tag as T extends true ? APIApplicationCommandOptionChoice[] : Tag;
|
||||
return tag as T extends true
|
||||
? Array<ApplicationCommandOptionChoiceStructure>
|
||||
: Tag;
|
||||
}
|
||||
|
||||
const exactKeywords: APIApplicationCommandOptionChoice[] = [];
|
||||
const keywordMatches: APIApplicationCommandOptionChoice[] = [];
|
||||
const questionMatches: APIApplicationCommandOptionChoice[] = [];
|
||||
const answerMatches: APIApplicationCommandOptionChoice[] = [];
|
||||
const exactKeywords: Array<ApplicationCommandOptionChoiceStructure> = [];
|
||||
const keywordMatches: Array<ApplicationCommandOptionChoiceStructure> = [];
|
||||
const questionMatches: Array<ApplicationCommandOptionChoiceStructure> = [];
|
||||
const answerMatches: Array<ApplicationCommandOptionChoiceStructure> = [];
|
||||
|
||||
for (const tag of availableTags) {
|
||||
const exactKeyword = tag.keywords.find((t) => t.toLowerCase() === query);
|
||||
const includesKeyword = tag.keywords.find((t) => t.toLowerCase().includes(query));
|
||||
const exactKeyword = tag.keywords.find((t) => t.toLowerCase() === query);
|
||||
const includesKeyword = tag.keywords.find((t) =>
|
||||
t.toLowerCase().includes(query)
|
||||
);
|
||||
const questionMatch = tag.question.toLowerCase().includes(query);
|
||||
const answerMatch = tag.answer.toLowerCase().includes(query);
|
||||
|
||||
if (exactKeyword) {
|
||||
exactKeywords.push({
|
||||
name: `✅ ${tag.question}`,
|
||||
value: tag.question
|
||||
value: tag.question,
|
||||
});
|
||||
} else if (includesKeyword) {
|
||||
keywordMatches.push({
|
||||
name: `🔑 ${tag.question}`,
|
||||
value: tag.question,
|
||||
})
|
||||
} else if(questionMatch) {
|
||||
});
|
||||
} else if (questionMatch) {
|
||||
questionMatches.push({
|
||||
name: `❓ ${tag.question}`,
|
||||
value: tag.question
|
||||
})
|
||||
value: tag.question,
|
||||
});
|
||||
} else if (answerMatch) {
|
||||
answerMatches.push({
|
||||
name: `📄 ${tag.question}`,
|
||||
value: tag.question
|
||||
})
|
||||
value: tag.question,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const tags = [...exactKeywords, ...questionMatches, ...keywordMatches, ...answerMatches];
|
||||
return tags as T extends true ? APIApplicationCommandOptionChoice[] : Tag;
|
||||
const tags = [
|
||||
...exactKeywords,
|
||||
...questionMatches,
|
||||
...keywordMatches,
|
||||
...answerMatches,
|
||||
];
|
||||
return tags as T extends true
|
||||
? Array<ApplicationCommandOptionChoiceStructure>
|
||||
: Tag;
|
||||
}
|
||||
|
|
16
src/message-commands/ping.ts
Normal file
16
src/message-commands/ping.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { MessageCommand } from "@lilybird/handlers";
|
||||
|
||||
export default {
|
||||
name: "ping",
|
||||
run: async (message) => {
|
||||
await message.reply({
|
||||
content: "🏓...",
|
||||
});
|
||||
|
||||
const { ws, rest } = await message.client.ping();
|
||||
|
||||
await message.edit({
|
||||
content: `🏓 WebSocket: \`${ws}ms\` | Rest: \`${rest}ms\``,
|
||||
});
|
||||
},
|
||||
} satisfies MessageCommand;
|
32
src/message-commands/tag.ts
Normal file
32
src/message-commands/tag.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { MessageCommand } from "@lilybird/handlers";
|
||||
import { searchTag } from "../loaders/tags.ts";
|
||||
|
||||
export default {
|
||||
name: "tag",
|
||||
run: async (message, args) => {
|
||||
const keyword = args[0] ?? "what-is-bun";
|
||||
|
||||
const target = args[1]?.match(/([0-9]+)/)?.[0];
|
||||
const resolvedTarget = target
|
||||
? await message.client.rest.getUser(target)
|
||||
: null;
|
||||
|
||||
const tag = searchTag(await message.fetchChannel(), keyword, false);
|
||||
if (!keyword || !tag) {
|
||||
return message.reply({
|
||||
content: `\`❌\` Could not find a tag \`${keyword}\``,
|
||||
});
|
||||
}
|
||||
|
||||
message.reply({
|
||||
content: [
|
||||
resolvedTarget ? `*Suggestion for <@${resolvedTarget.id}>:*\n` : "",
|
||||
`**${tag.question}**`,
|
||||
tag.answer,
|
||||
].join("\n"),
|
||||
// allowedMentions: {
|
||||
// parse: ["users"]
|
||||
// }
|
||||
});
|
||||
},
|
||||
} satisfies MessageCommand;
|
|
@ -1,11 +0,0 @@
|
|||
import { Client, GatewayIntentBits } from "discord.js";
|
||||
|
||||
export const Bubu = new Client({
|
||||
intents: GatewayIntentBits.Guilds | GatewayIntentBits.GuildMessages | GatewayIntentBits.MessageContent | GatewayIntentBits.GuildMembers,
|
||||
allowedMentions: {
|
||||
parse: [],
|
||||
repliedUser: false,
|
||||
}
|
||||
});
|
||||
|
||||
Bubu.on("error", console.log);
|
|
@ -1,41 +0,0 @@
|
|||
import { APIApplicationCommandAttachmentOption, APIApplicationCommandBasicOption, APIApplicationCommandBooleanOption, APIApplicationCommandChannelOption, APIApplicationCommandIntegerOption, APIApplicationCommandMentionableOption, APIApplicationCommandNumberOption, APIApplicationCommandOptionChoice, APIApplicationCommandRoleOption, APIApplicationCommandStringOption, APIApplicationCommandSubcommandGroupOption, APIApplicationCommandSubcommandOption, APIApplicationCommandUserOption, ApplicationCommandOptionType, LocalizationMap, SharedNameAndDescription, SlashCommandAttachmentOption, SlashCommandBooleanOption, SlashCommandChannelOption, SlashCommandIntegerOption, SlashCommandMentionableOption, SlashCommandNumberOption, SlashCommandRoleOption, SlashCommandStringOption, SlashCommandUserOption } from "discord.js";
|
||||
import { AutocompleteContext } from "./context/AutocompleteContext";
|
||||
import { CommandContext } from "./context/CommandContext";
|
||||
|
||||
export type Option = APIApplicationCommandAttachmentOption |
|
||||
APIApplicationCommandBooleanOption |
|
||||
APIApplicationCommandChannelOption |
|
||||
APIApplicationCommandIntegerOption |
|
||||
APIApplicationCommandMentionableOption |
|
||||
APIApplicationCommandNumberOption |
|
||||
APIApplicationCommandRoleOption |
|
||||
APIApplicationCommandUserOption |
|
||||
APIApplicationCommandSubcommandOption |
|
||||
APIApplicationCommandSubcommandGroupOption |
|
||||
StringOption;
|
||||
|
||||
export interface StringOption {
|
||||
name: string;
|
||||
name_localizations?: LocalizationMap;
|
||||
description: string;
|
||||
description_localizations?: LocalizationMap;
|
||||
min_length?: number;
|
||||
max_length?: number;
|
||||
required?: boolean;
|
||||
type: ApplicationCommandOptionType.String;
|
||||
autocomplete?: boolean;
|
||||
choices?: APIApplicationCommandOptionChoice[];
|
||||
run?: (interaction: AutocompleteContext) => any;
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
name: string;
|
||||
description: string;
|
||||
options?: Option[];
|
||||
run?: (
|
||||
context: CommandContext<true>
|
||||
) => any;
|
||||
runMessage?: (
|
||||
context: CommandContext<false>
|
||||
) => any;
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import type { ClientEvents } from "discord.js";
|
||||
|
||||
export interface Listener<E extends keyof ClientEvents> {
|
||||
event: E;
|
||||
once?: boolean;
|
||||
run: (
|
||||
...args: ClientEvents[E]
|
||||
) => any;
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import { ApplicationCommandOptionChoiceData, AutocompleteInteraction } from "discord.js";
|
||||
import { Command, Option } from "../Command.ts";
|
||||
|
||||
export class AutocompleteContext {
|
||||
public option: Option;
|
||||
public command: Command;
|
||||
public interaction: AutocompleteInteraction;
|
||||
|
||||
public constructor(option: Option, command: Command, interaction: AutocompleteInteraction) {
|
||||
this.option = option;
|
||||
this.command = command;
|
||||
this.interaction = interaction;
|
||||
}
|
||||
|
||||
get channel() {
|
||||
return this.interaction.channel;
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this.interaction.user;
|
||||
}
|
||||
|
||||
get member() {
|
||||
return this.interaction.member;
|
||||
}
|
||||
|
||||
get options() {
|
||||
return this.interaction.options;
|
||||
}
|
||||
|
||||
public respond(options: ApplicationCommandOptionChoiceData[]) {
|
||||
return this.interaction.respond(options);
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
import { ChatInputCommandInteraction, InteractionReplyOptions, Message, MessageCreateOptions, MessagePayload, InteractionResponse, Channel, User, GuildMember, APIInteractionGuildMember } from "discord.js";
|
||||
import type { Command } from "../Command.ts";
|
||||
|
||||
export interface CommandContext<T extends boolean> {
|
||||
command: Command;
|
||||
|
||||
user: User;
|
||||
member: GuildMember | APIInteractionGuildMember;
|
||||
channel: Channel;
|
||||
|
||||
reply(options: string | MessagePayload | (T extends true ? InteractionReplyOptions : MessageCreateOptions)): (T extends true ? Promise<Message | InteractionResponse> : Promise<Message>);
|
||||
}
|
||||
|
||||
export class InteractionCommandContext implements CommandContext<true> {
|
||||
public command: Command;
|
||||
public interaction: ChatInputCommandInteraction;
|
||||
|
||||
public constructor(command: Command, interaction: ChatInputCommandInteraction) {
|
||||
this.command = command;
|
||||
this.interaction = interaction;
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this.interaction.user;
|
||||
}
|
||||
|
||||
get member() {
|
||||
return this.interaction.member;
|
||||
}
|
||||
|
||||
get channel() {
|
||||
return this.interaction.channel;
|
||||
}
|
||||
|
||||
get resolved() {
|
||||
return this.interaction.options.resolved;
|
||||
}
|
||||
|
||||
public reply(options: string | MessagePayload | InteractionReplyOptions): Promise<Message | InteractionResponse> {
|
||||
return this.interaction.reply(options);
|
||||
}
|
||||
}
|
||||
|
||||
export class MessageCommandContext implements CommandContext<false> {
|
||||
public command: Command;
|
||||
public message: Message;
|
||||
public options: string[];
|
||||
|
||||
public constructor(command: Command, message: Message, args: string[]) {
|
||||
this.command = command;
|
||||
this.message = message;
|
||||
|
||||
// TODO: change args structure to application commands like
|
||||
this.options = args;
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this.message.author;
|
||||
}
|
||||
|
||||
get member() {
|
||||
return this.message.member;
|
||||
}
|
||||
|
||||
get channel() {
|
||||
return this.message.channel;
|
||||
}
|
||||
|
||||
public reply(options: string | MessagePayload | MessageCreateOptions): Promise<Message<boolean>> {
|
||||
return this.channel.send(options);
|
||||
}
|
||||
}
|
24
src/util.ts
24
src/util.ts
|
@ -1,6 +1,10 @@
|
|||
export function safeSlice<T>(input: T, length: number) {
|
||||
// @ts-expect-error i know where im using it
|
||||
return input.length > length ? input.slice(0, length) : input;
|
||||
import { GuildMember } from "lilybird";
|
||||
|
||||
export function safeSlice<T extends string | Array<any>>(
|
||||
input: T,
|
||||
length: number
|
||||
): T {
|
||||
return <T>(input.length > length ? input.slice(0, length) : input);
|
||||
}
|
||||
|
||||
export async function silently<T>(value: Promise<T>) {
|
||||
|
@ -8,3 +12,17 @@ export async function silently<T>(value: Promise<T>) {
|
|||
await value;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export async function moderateNick(member: GuildMember) {
|
||||
let name = member.nick ?? member.user.username;
|
||||
const normalizedName = name.normalize("NFKC").replace(/^[!$#@%^`&*()]+/, "");
|
||||
|
||||
if (name === normalizedName) return;
|
||||
|
||||
silently(
|
||||
member.modify({
|
||||
nick: normalizedName,
|
||||
reason: "lame username",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext"],
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ESNext",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"baseUrl": ".",
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@lilybird/jsx",
|
||||
"strict": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
// "bun-types" is the important part
|
||||
"types": ["bun-types"]
|
||||
}
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["bun-types", "./globals"]
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue