Compare commits
8 Commits
e53d38e0ad
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| b0d10c3503 | |||
| 6fb38d36f3 | |||
| a6255de889 | |||
| 8c2e889f2a | |||
| 88679d2eda | |||
| d4387a8264 | |||
| 02c404069c | |||
| c834acdfec |
37
.devcontainer/devcontainer-lock.json
Normal file
37
.devcontainer/devcontainer-lock.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainer-community/devcontainer-features/lazygit:1": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "ghcr.io/devcontainer-community/devcontainer-features/lazygit@sha256:a9a4b920a615d869bd12149f0430957d496883415b3b436d8948c487e4eb3567",
|
||||||
|
"integrity": "sha256:a9a4b920a615d869bd12149f0430957d496883415b3b436d8948c487e4eb3567"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers-community/npm-features/typescript:1": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "ghcr.io/devcontainers-community/npm-features/typescript@sha256:13a0f63e88513a6022431c39b7ca4ec732ba0760cdb6d882638f4ddf73deb0e7",
|
||||||
|
"integrity": "sha256:13a0f63e88513a6022431c39b7ca4ec732ba0760cdb6d882638f4ddf73deb0e7",
|
||||||
|
"dependsOn": [
|
||||||
|
"ghcr.io/devcontainers/features/node"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers-extra/features/claude-code:2": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "ghcr.io/devcontainers-extra/features/claude-code@sha256:37b0d444a704021ee5f6d24242a4621bf337867d110e4e3c06b863a3a78122ac",
|
||||||
|
"integrity": "sha256:37b0d444a704021ee5f6d24242a4621bf337867d110e4e3c06b863a3a78122ac"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/node": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "ghcr.io/devcontainers/features/node@sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857",
|
||||||
|
"integrity": "sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857"
|
||||||
|
},
|
||||||
|
"ghcr.io/jsburckhardt/devcontainer-features/just:1": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "ghcr.io/jsburckhardt/devcontainer-features/just@sha256:5c90013b36669270be21c69e7d8e5b6148b4b0b34fca9e104a599edf0d7c11af",
|
||||||
|
"integrity": "sha256:5c90013b36669270be21c69e7d8e5b6148b4b0b34fca9e104a599edf0d7c11af"
|
||||||
|
},
|
||||||
|
"ghcr.io/rails/devcontainer/features/bun:1": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "ghcr.io/rails/devcontainer/features/bun@sha256:08057c197a8cde49b08749681607bf0d69aed79e280225cf43ca5d1782028789",
|
||||||
|
"integrity": "sha256:08057c197a8cde49b08749681607bf0d69aed79e280225cf43ca5d1782028789"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
.devcontainer/devcontainer.json
Normal file
34
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
|
||||||
|
{
|
||||||
|
"name": "Ubuntu",
|
||||||
|
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/base:resolute",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/jsburckhardt/devcontainer-features/just:1": {},
|
||||||
|
"ghcr.io/devcontainers-community/npm-features/typescript:1": {},
|
||||||
|
"ghcr.io/devcontainers-extra/features/claude-code:2": {},
|
||||||
|
"ghcr.io/devcontainer-community/devcontainer-features/lazygit:1": {},
|
||||||
|
"ghcr.io/rails/devcontainer/features/bun:1": {}
|
||||||
|
},
|
||||||
|
"mounts": [
|
||||||
|
"source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind",
|
||||||
|
"source=${localEnv:HOME}/.claude.json,target=/home/vscode/.claude.json,type=bind"
|
||||||
|
],
|
||||||
|
"postCreateCommand": "sudo apt install -y ripgrep"
|
||||||
|
|
||||||
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
|
// "features": {},
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
// "forwardPorts": [],
|
||||||
|
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
// "postCreateCommand": "uname -a",
|
||||||
|
|
||||||
|
// Configure tool-specific properties.
|
||||||
|
// "customizations": {},
|
||||||
|
|
||||||
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||||
|
// "remoteUser": "root"
|
||||||
|
}
|
||||||
12
.github/dependabot.yml
vendored
Normal file
12
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for more information:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
# https://containers.dev/guide/dependabot
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "devcontainers"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.organizeImports.biome": "explicit"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Justfile
Normal file
10
Justfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
fmt:
|
||||||
|
just format
|
||||||
|
|
||||||
|
format:
|
||||||
|
bunx --bun @biomejs/biome format --write *.ts plugins/
|
||||||
|
|
||||||
|
|
||||||
|
lint:
|
||||||
|
bunx --bun @biomejs/biome lint --fix *.ts plugins/
|
||||||
12
biome.jsonc
Normal file
12
biome.jsonc
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"javascript": {
|
||||||
|
// This is taken from the Google TS style guide:
|
||||||
|
// https://github.com/google/gts/blob/main/.prettierrc.json
|
||||||
|
"formatter": {
|
||||||
|
"bracketSpacing": false,
|
||||||
|
"quoteStyle": "single",
|
||||||
|
"trailingCommas": "all",
|
||||||
|
"arrowParentheses": "asNeeded"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
187
bun.lock
187
bun.lock
@@ -10,6 +10,9 @@
|
|||||||
"sequelize": "^6.37.7",
|
"sequelize": "^6.37.7",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vitest": "^3.2.4",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
@@ -43,12 +46,104 @@
|
|||||||
|
|
||||||
"@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="],
|
"@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="],
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.5", "", { "os": "android", "cpu": "arm" }, "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.5", "", { "os": "android", "cpu": "arm64" }, "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg=="],
|
||||||
|
|
||||||
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.5", "", { "os": "android", "cpu": "x64" }, "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.5", "", { "os": "linux", "cpu": "arm" }, "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.5", "", { "os": "linux", "cpu": "x64" }, "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.5", "", { "os": "none", "cpu": "arm64" }, "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.5", "", { "os": "none", "cpu": "x64" }, "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.5", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg=="],
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g=="],
|
||||||
|
|
||||||
"@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="],
|
"@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="],
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="],
|
||||||
|
|
||||||
"@npmcli/fs": ["@npmcli/fs@1.1.1", "", { "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" } }, "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ=="],
|
"@npmcli/fs": ["@npmcli/fs@1.1.1", "", { "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" } }, "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ=="],
|
||||||
|
|
||||||
"@npmcli/move-file": ["@npmcli/move-file@1.1.2", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg=="],
|
"@npmcli/move-file": ["@npmcli/move-file@1.1.2", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.44.2", "", { "os": "android", "cpu": "arm" }, "sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.44.2", "", { "os": "android", "cpu": "arm64" }, "sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.44.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.44.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.44.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.44.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.44.2", "", { "os": "linux", "cpu": "arm" }, "sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.44.2", "", { "os": "linux", "cpu": "arm" }, "sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.44.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.44.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.44.2", "", { "os": "linux", "cpu": "none" }, "sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.44.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.44.2", "", { "os": "linux", "cpu": "none" }, "sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.44.2", "", { "os": "linux", "cpu": "none" }, "sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.44.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.44.2", "", { "os": "linux", "cpu": "x64" }, "sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.44.2", "", { "os": "linux", "cpu": "x64" }, "sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.44.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.44.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.44.2", "", { "os": "win32", "cpu": "x64" }, "sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA=="],
|
||||||
|
|
||||||
"@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="],
|
"@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="],
|
||||||
|
|
||||||
"@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="],
|
"@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="],
|
||||||
@@ -57,8 +152,14 @@
|
|||||||
|
|
||||||
"@tootallnate/once": ["@tootallnate/once@1.1.2", "", {}, "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="],
|
"@tootallnate/once": ["@tootallnate/once@1.1.2", "", {}, "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="],
|
||||||
|
|
||||||
|
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
||||||
|
|
||||||
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
||||||
|
|
||||||
|
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||||
|
|
||||||
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|
||||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.0.7", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw=="],
|
"@types/node": ["@types/node@24.0.7", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw=="],
|
||||||
@@ -67,6 +168,20 @@
|
|||||||
|
|
||||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||||
|
|
||||||
|
"@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
|
||||||
|
|
||||||
|
"@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],
|
||||||
|
|
||||||
|
"@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
|
||||||
|
|
||||||
|
"@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="],
|
||||||
|
|
||||||
|
"@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="],
|
||||||
|
|
||||||
|
"@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
|
||||||
|
|
||||||
|
"@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||||
|
|
||||||
"@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.6", "", {}, "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="],
|
"@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.6", "", {}, "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="],
|
||||||
|
|
||||||
"abbrev": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="],
|
"abbrev": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="],
|
||||||
@@ -83,6 +198,8 @@
|
|||||||
|
|
||||||
"are-we-there-yet": ["are-we-there-yet@3.0.1", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg=="],
|
"are-we-there-yet": ["are-we-there-yet@3.0.1", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg=="],
|
||||||
|
|
||||||
|
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||||
|
|
||||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
|
|
||||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||||
@@ -95,8 +212,14 @@
|
|||||||
|
|
||||||
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||||
|
|
||||||
|
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||||
|
|
||||||
"cacache": ["cacache@15.3.0", "", { "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", "infer-owner": "^1.0.4", "lru-cache": "^6.0.0", "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", "mkdirp": "^1.0.3", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^8.0.1", "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ=="],
|
"cacache": ["cacache@15.3.0", "", { "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", "infer-owner": "^1.0.4", "lru-cache": "^6.0.0", "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", "mkdirp": "^1.0.3", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^8.0.1", "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ=="],
|
||||||
|
|
||||||
|
"chai": ["chai@5.2.0", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw=="],
|
||||||
|
|
||||||
|
"check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="],
|
||||||
|
|
||||||
"chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
|
"chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
|
||||||
|
|
||||||
"chrono-node": ["chrono-node@2.8.3", "", { "dependencies": { "dayjs": "^1.10.0" } }, "sha512-YukiXak31pshonVWaeJ9cZ4xxWIlbsyn5qYUkG5pQ+usZ6l22ASXDIk0kHUQkIBNOCLRevFkHJjnGKXwZNtyZw=="],
|
"chrono-node": ["chrono-node@2.8.3", "", { "dependencies": { "dayjs": "^1.10.0" } }, "sha512-YukiXak31pshonVWaeJ9cZ4xxWIlbsyn5qYUkG5pQ+usZ6l22ASXDIk0kHUQkIBNOCLRevFkHJjnGKXwZNtyZw=="],
|
||||||
@@ -115,6 +238,8 @@
|
|||||||
|
|
||||||
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
|
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
|
||||||
|
|
||||||
|
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
|
||||||
|
|
||||||
"deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
|
"deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
|
||||||
|
|
||||||
"delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="],
|
"delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="],
|
||||||
@@ -137,10 +262,20 @@
|
|||||||
|
|
||||||
"err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="],
|
"err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="],
|
||||||
|
|
||||||
|
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
||||||
|
|
||||||
|
"esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="],
|
||||||
|
|
||||||
|
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||||
|
|
||||||
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
|
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
|
||||||
|
|
||||||
|
"expect-type": ["expect-type@1.2.1", "", {}, "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw=="],
|
||||||
|
|
||||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||||
|
|
||||||
|
"fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="],
|
||||||
|
|
||||||
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
|
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
|
||||||
|
|
||||||
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
||||||
@@ -149,6 +284,8 @@
|
|||||||
|
|
||||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||||
|
|
||||||
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
"gauge": ["gauge@4.0.4", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^3.0.7", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg=="],
|
"gauge": ["gauge@4.0.4", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^3.0.7", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg=="],
|
||||||
|
|
||||||
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
|
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
|
||||||
@@ -193,16 +330,22 @@
|
|||||||
|
|
||||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||||
|
|
||||||
|
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
||||||
|
|
||||||
"jsbn": ["jsbn@1.1.0", "", {}, "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="],
|
"jsbn": ["jsbn@1.1.0", "", {}, "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="],
|
||||||
|
|
||||||
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
||||||
|
|
||||||
"lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="],
|
"lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="],
|
||||||
|
|
||||||
|
"loupe": ["loupe@3.1.4", "", {}, "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
"lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
||||||
|
|
||||||
"magic-bytes.js": ["magic-bytes.js@1.12.1", "", {}, "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA=="],
|
"magic-bytes.js": ["magic-bytes.js@1.12.1", "", {}, "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA=="],
|
||||||
|
|
||||||
|
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
||||||
|
|
||||||
"make-fetch-happen": ["make-fetch-happen@9.1.0", "", { "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^4.0.1", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^6.0.0", "minipass": "^3.1.3", "minipass-collect": "^1.0.2", "minipass-fetch": "^1.3.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.2", "promise-retry": "^2.0.1", "socks-proxy-agent": "^6.0.0", "ssri": "^8.0.0" } }, "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg=="],
|
"make-fetch-happen": ["make-fetch-happen@9.1.0", "", { "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^4.0.1", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^6.0.0", "minipass": "^3.1.3", "minipass-collect": "^1.0.2", "minipass-fetch": "^1.3.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.2", "promise-retry": "^2.0.1", "socks-proxy-agent": "^6.0.0", "ssri": "^8.0.0" } }, "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg=="],
|
||||||
|
|
||||||
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
|
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
|
||||||
@@ -235,6 +378,8 @@
|
|||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||||
|
|
||||||
"napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
|
"napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
|
||||||
|
|
||||||
"negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
|
"negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
|
||||||
@@ -255,8 +400,18 @@
|
|||||||
|
|
||||||
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
|
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
|
||||||
|
|
||||||
|
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
|
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
|
||||||
|
|
||||||
"pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="],
|
"pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="],
|
||||||
|
|
||||||
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
|
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
|
||||||
|
|
||||||
|
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||||
|
|
||||||
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
|
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
|
||||||
|
|
||||||
"promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="],
|
"promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="],
|
||||||
@@ -275,6 +430,8 @@
|
|||||||
|
|
||||||
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
|
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
|
||||||
|
|
||||||
|
"rollup": ["rollup@4.44.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.44.2", "@rollup/rollup-android-arm64": "4.44.2", "@rollup/rollup-darwin-arm64": "4.44.2", "@rollup/rollup-darwin-x64": "4.44.2", "@rollup/rollup-freebsd-arm64": "4.44.2", "@rollup/rollup-freebsd-x64": "4.44.2", "@rollup/rollup-linux-arm-gnueabihf": "4.44.2", "@rollup/rollup-linux-arm-musleabihf": "4.44.2", "@rollup/rollup-linux-arm64-gnu": "4.44.2", "@rollup/rollup-linux-arm64-musl": "4.44.2", "@rollup/rollup-linux-loongarch64-gnu": "4.44.2", "@rollup/rollup-linux-powerpc64le-gnu": "4.44.2", "@rollup/rollup-linux-riscv64-gnu": "4.44.2", "@rollup/rollup-linux-riscv64-musl": "4.44.2", "@rollup/rollup-linux-s390x-gnu": "4.44.2", "@rollup/rollup-linux-x64-gnu": "4.44.2", "@rollup/rollup-linux-x64-musl": "4.44.2", "@rollup/rollup-win32-arm64-msvc": "4.44.2", "@rollup/rollup-win32-ia32-msvc": "4.44.2", "@rollup/rollup-win32-x64-msvc": "4.44.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg=="],
|
||||||
|
|
||||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||||
|
|
||||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
@@ -287,6 +444,8 @@
|
|||||||
|
|
||||||
"set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
|
"set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
|
||||||
|
|
||||||
|
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||||
|
|
||||||
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||||
|
|
||||||
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
|
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
|
||||||
@@ -299,12 +458,18 @@
|
|||||||
|
|
||||||
"socks-proxy-agent": ["socks-proxy-agent@6.2.1", "", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ=="],
|
"socks-proxy-agent": ["socks-proxy-agent@6.2.1", "", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ=="],
|
||||||
|
|
||||||
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
"sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
|
"sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
|
||||||
|
|
||||||
"sqlite3": ["sqlite3@5.1.7", "", { "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.1", "tar": "^6.1.11" }, "optionalDependencies": { "node-gyp": "8.x" } }, "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog=="],
|
"sqlite3": ["sqlite3@5.1.7", "", { "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.1", "tar": "^6.1.11" }, "optionalDependencies": { "node-gyp": "8.x" } }, "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog=="],
|
||||||
|
|
||||||
"ssri": ["ssri@8.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ=="],
|
"ssri": ["ssri@8.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ=="],
|
||||||
|
|
||||||
|
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||||
|
|
||||||
|
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
||||||
|
|
||||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||||
@@ -313,12 +478,26 @@
|
|||||||
|
|
||||||
"strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
|
"strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
|
||||||
|
|
||||||
|
"strip-literal": ["strip-literal@3.0.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA=="],
|
||||||
|
|
||||||
"tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
|
"tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
|
||||||
|
|
||||||
"tar-fs": ["tar-fs@2.1.3", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg=="],
|
"tar-fs": ["tar-fs@2.1.3", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg=="],
|
||||||
|
|
||||||
"tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
"tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
||||||
|
|
||||||
|
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
||||||
|
|
||||||
|
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
||||||
|
|
||||||
|
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||||
|
|
||||||
|
"tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
|
||||||
|
|
||||||
|
"tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
|
||||||
|
|
||||||
|
"tinyspy": ["tinyspy@4.0.3", "", {}, "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A=="],
|
||||||
|
|
||||||
"toposort-class": ["toposort-class@1.0.1", "", {}, "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg=="],
|
"toposort-class": ["toposort-class@1.0.1", "", {}, "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg=="],
|
||||||
|
|
||||||
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
|
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
|
||||||
@@ -341,8 +520,16 @@
|
|||||||
|
|
||||||
"validator": ["validator@13.15.15", "", {}, "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A=="],
|
"validator": ["validator@13.15.15", "", {}, "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A=="],
|
||||||
|
|
||||||
|
"vite": ["vite@7.0.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw=="],
|
||||||
|
|
||||||
|
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
||||||
|
|
||||||
|
"vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="],
|
||||||
|
|
||||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
|
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||||
|
|
||||||
"wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="],
|
"wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="],
|
||||||
|
|
||||||
"wkx": ["wkx@0.5.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg=="],
|
"wkx": ["wkx@0.5.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg=="],
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js";
|
|
||||||
|
|
||||||
import { Settings } from "./common";
|
|
||||||
|
|
||||||
import { Nag, CheckIn } from './common';
|
|
||||||
|
|
||||||
const data = new SlashCommandBuilder()
|
|
||||||
.setName("checkin")
|
|
||||||
.setDescription("Check-in for your daily nag")
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("text")
|
|
||||||
.setDescription("Optional description of what you have achieved"),
|
|
||||||
);
|
|
||||||
|
|
||||||
async function initialize(settings: Settings) {}
|
|
||||||
|
|
||||||
function execute(interaction: ChatInputCommandInteraction) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
import {
|
|
||||||
ChatInputCommandInteraction,
|
|
||||||
Client,
|
|
||||||
SlashCommandBuilder,
|
|
||||||
TextChannel,
|
|
||||||
} from "discord.js";
|
|
||||||
import {
|
|
||||||
Sequelize,
|
|
||||||
Model,
|
|
||||||
INTEGER,
|
|
||||||
STRING,
|
|
||||||
BOOLEAN,
|
|
||||||
DATE,
|
|
||||||
literal,
|
|
||||||
} from "sequelize";
|
|
||||||
|
|
||||||
export interface Settings {
|
|
||||||
client: Client; // Main Discord client object
|
|
||||||
db: Sequelize; // Database access object
|
|
||||||
publicChannel?: string; // Channel to use if a reminder is public
|
|
||||||
loopIntervalSec?: number; // Loop interval in seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Nag extends Model {
|
|
||||||
// Primary key
|
|
||||||
declare id: number;
|
|
||||||
// User who created this nag
|
|
||||||
declare userId: number;
|
|
||||||
// Description of what you're supposed to do
|
|
||||||
declare text: string;
|
|
||||||
// Custom failure text
|
|
||||||
declare failText?: string;
|
|
||||||
// Should we @here?
|
|
||||||
declare mentionHere?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CheckIn extends Model {
|
|
||||||
declare nagId: string;
|
|
||||||
// Date of the last time user ran /checkin
|
|
||||||
declare lastCheckIn: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initAndSyncTables(sequelize: Sequelize) {
|
|
||||||
Nag.init(
|
|
||||||
{
|
|
||||||
id: {
|
|
||||||
primaryKey: true,
|
|
||||||
type: INTEGER,
|
|
||||||
autoIncrement: true,
|
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
type: INTEGER,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
type: STRING,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
failText: {
|
|
||||||
type: STRING,
|
|
||||||
},
|
|
||||||
mentionHere: {
|
|
||||||
type: BOOLEAN,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ sequelize },
|
|
||||||
);
|
|
||||||
CheckIn.init(
|
|
||||||
{
|
|
||||||
nagId: {
|
|
||||||
type: INTEGER,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
lastCheckIn: {
|
|
||||||
type: DATE,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ sequelize },
|
|
||||||
);
|
|
||||||
Nag.hasOne(CheckIn, { foreignKey: "nagId" });
|
|
||||||
await Nag.sync();
|
|
||||||
await CheckIn.sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
import { Guild, Channel } from "discord.js";
|
|
||||||
|
|
||||||
export class Plugin {
|
|
||||||
settings: Settings;
|
|
||||||
publicChannel?: Channel;
|
|
||||||
interval: NodeJS.Timeout;
|
|
||||||
|
|
||||||
constructor(settings: Settings) {
|
|
||||||
this.settings = settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
if (!this.settings.loopIntervalSec) {
|
|
||||||
this.settings.loopIntervalSec = 60; // 1 minute
|
|
||||||
}
|
|
||||||
this.interval = setInterval(
|
|
||||||
this.loop,
|
|
||||||
this.settings.loopIntervalSec * 1000,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async triggerNag(nag: Nag) {
|
|
||||||
const client = this.settings.client;
|
|
||||||
const chan = client.channels.cache.get("1234"); // TODO
|
|
||||||
if (!(chan instanceof TextChannel)) {
|
|
||||||
return; // TODO
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const failText =
|
|
||||||
nag.failText ??
|
|
||||||
`<@${nag.userId}> didn't complete "${nag.text}". Shame shame!`;
|
|
||||||
const mentionHere = nag.mentionHere ? "<@here> " : "";
|
|
||||||
const msg = `${mentionHere}${failText}`;
|
|
||||||
await chan.send(msg);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Error while creating Nag:", error); // TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loop() {
|
|
||||||
console.debug("nag.js main loop");
|
|
||||||
// Find all nags where the last check-in was before (next check in) - (24 hours)
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
clearInterval(this.interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import {
|
|
||||||
ChatInputCommandInteraction,
|
|
||||||
Client,
|
|
||||||
SlashCommandBuilder,
|
|
||||||
} from "discord.js";
|
|
||||||
import { Sequelize, literal } from "sequelize";
|
|
||||||
|
|
||||||
import { Nag, CheckIn, Settings } from "./common";
|
|
||||||
import { Chrono } from "chrono-node";
|
|
||||||
|
|
||||||
const data = new SlashCommandBuilder()
|
|
||||||
.setName("nag")
|
|
||||||
.setDescription("Let Blitzcrank nag you every day about something")
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setRequired(true)
|
|
||||||
.setName("text")
|
|
||||||
.setDescription("What you have to do every day"),
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("failText")
|
|
||||||
.setDescription("Custom message to be broadcast on failure"),
|
|
||||||
)
|
|
||||||
.addBooleanOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("mentionHere")
|
|
||||||
.setDescription("Whether to DM you or @ a channel")
|
|
||||||
.setRequired(false),
|
|
||||||
);
|
|
||||||
|
|
||||||
function lateCheckedInUsers() {
|
|
||||||
return Nag.findAll({
|
|
||||||
include: [CheckIn],
|
|
||||||
where: literal(
|
|
||||||
"checkInTime <= datetime('now', '-1 day', 'start of day', '+9 hours')",
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initialize(settings: Settings) {}
|
|
||||||
|
|
||||||
async function execute(interaction: ChatInputCommandInteraction) {
|
|
||||||
const text = interaction.options.getString("text");
|
|
||||||
if (text === null || text === undefined) {
|
|
||||||
await interaction.reply("Nag can't have a blank `text`, try again.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const nag = await Nag.create({
|
|
||||||
userId: interaction.user.id,
|
|
||||||
text: text,
|
|
||||||
failText: interaction.options.getString("failText"),
|
|
||||||
mentionHere: interaction.options.getBoolean("mentionHere") ?? false,
|
|
||||||
});
|
|
||||||
await nag.save();
|
|
||||||
const chrono = new Chrono();
|
|
||||||
const checkIn = chrono.parseDate("today at 9AM");
|
|
||||||
if (!checkIn) {
|
|
||||||
await interaction.reply(
|
|
||||||
"Internal error while saving your nag. Tell Drew the bot is broken!!!",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await CheckIn.create({
|
|
||||||
nagId: nag.id,
|
|
||||||
checkIn: checkIn,
|
|
||||||
});
|
|
||||||
await interaction.reply(
|
|
||||||
`I'll check every day at 9AM if you've completed '${text}'. If not, I'll nag you! Use /checkin to prevent a shameful callout, and /unnag to cancel.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (settings: Settings) {
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
execute,
|
|
||||||
initialize: async () => await initialize(settings),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import {
|
|
||||||
ChatInputCommandInteraction,
|
|
||||||
Client,
|
|
||||||
SlashCommandBuilder,
|
|
||||||
} from "discord.js";
|
|
||||||
import { Sequelize } from "sequelize";
|
|
||||||
|
|
||||||
import { Settings } from './common'
|
|
||||||
|
|
||||||
|
|
||||||
const data = new SlashCommandBuilder()
|
|
||||||
.setName("unnag")
|
|
||||||
.setDescription("Remove a nag");
|
|
||||||
|
|
||||||
|
|
||||||
async function initialize(settings: Settings) {
|
|
||||||
}
|
|
||||||
|
|
||||||
async function execute(interaction: ChatInputCommandInteraction) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (settings: Settings) {
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
execute,
|
|
||||||
initialize: async() => await initialize(settings),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { type Client, SlashCommandBuilder } from "discord.js";
|
|
||||||
import type { Sequelize } from "sequelize";
|
|
||||||
|
|
||||||
export default function (settings: { client: Client; db: Sequelize }) {
|
|
||||||
return {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("ping")
|
|
||||||
.setDescription("Send a ping to the bot"),
|
|
||||||
|
|
||||||
initialize: async () => {},
|
|
||||||
|
|
||||||
execute: async (interaction) => {
|
|
||||||
await interaction.reply(
|
|
||||||
`Pong! This command was run by ${interaction.user.username}, who joined on ${interaction.member.joinedAt}.`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
// import type { ChatInputCommandInteraction } from "discord.js";
|
|
||||||
import {
|
|
||||||
PermissionsBitField,
|
|
||||||
SlashCommandBuilder,
|
|
||||||
TextChannel,
|
|
||||||
type ChatInputCommandInteraction,
|
|
||||||
type Client,
|
|
||||||
} from "discord.js";
|
|
||||||
import { type Sequelize, Model, INTEGER, TEXT, DATE, BOOLEAN } from "sequelize";
|
|
||||||
import * as sequelize from "sequelize";
|
|
||||||
import * as chrono from "chrono-node";
|
|
||||||
|
|
||||||
// const REMINDERS_CHANNEL = "1062196593379520593"; // #bot-test-channel
|
|
||||||
// const REMINDERS_CHANNEL = ""; // #general
|
|
||||||
|
|
||||||
interface Settings {
|
|
||||||
client: Client; // Main Discord client object
|
|
||||||
db: Sequelize; // Database access object
|
|
||||||
responseMode: string;
|
|
||||||
publicChannel: string; // Channel to use if a reminder is public
|
|
||||||
loopIntervalSec?: number; // Loop interval in seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = new SlashCommandBuilder()
|
|
||||||
.setName("remind")
|
|
||||||
.setDescription("Remind me to do something")
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("when")
|
|
||||||
.setDescription("Short description of when you want the reminder")
|
|
||||||
.setRequired(true),
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName("what")
|
|
||||||
.setDescription("Short description of what you want to be reminded of")
|
|
||||||
.setRequired(true),
|
|
||||||
);
|
|
||||||
|
|
||||||
class Reminder extends Model {
|
|
||||||
declare id: number;
|
|
||||||
declare userId: string;
|
|
||||||
declare text: string | null;
|
|
||||||
declare trigger: Date;
|
|
||||||
declare isValid: boolean;
|
|
||||||
declare requestChannel: string;
|
|
||||||
declare requestMessage: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Plugin {
|
|
||||||
settings: Settings;
|
|
||||||
interval: NodeJS.Timeout;
|
|
||||||
|
|
||||||
constructor(settings: Settings) {
|
|
||||||
this.settings = settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
getChannel(reminder: Reminder) {
|
|
||||||
let channel: TextChannel | null = null;
|
|
||||||
const client = this.settings.client;
|
|
||||||
const publicChannel = this.settings.publicChannel;
|
|
||||||
// if (this.settings.responseMode === "private") {
|
|
||||||
// channel = getSendableTextChannel(client, reminder.requestChannel);
|
|
||||||
// }
|
|
||||||
if (this.settings.responseMode === "public" || !channel) {
|
|
||||||
channel = getSendableTextChannel(client, publicChannel);
|
|
||||||
}
|
|
||||||
if (!channel) {
|
|
||||||
throw Error("Cannot find valid channel to send reminder on");
|
|
||||||
}
|
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
async loop() {
|
|
||||||
console.debug("remind.js main loop");
|
|
||||||
const results = await Reminder.findAll({
|
|
||||||
// NOTE: 'trigger' is the time when the reminder should go off. If trigger -
|
|
||||||
// now is positive, trigger is in the future. If trigger - now <=
|
|
||||||
// 1.0, then we have less than one minute before the timer should go
|
|
||||||
// off.
|
|
||||||
where: sequelize.literal(
|
|
||||||
"isValid AND (julianday(trigger) - julianday('now')) <= 1.0",
|
|
||||||
),
|
|
||||||
});
|
|
||||||
for (const reminder of results) {
|
|
||||||
try {
|
|
||||||
const now = new Date(Date.now());
|
|
||||||
const delay = reminder.trigger.getTime() - now.getTime();
|
|
||||||
console.log(
|
|
||||||
`Callback for Reminder ${reminder.get("id")} triggering in ${delay}ms`,
|
|
||||||
);
|
|
||||||
const channel = this.getChannel(reminder);
|
|
||||||
setTimeout(async () => await triggerReminder(channel, reminder), delay);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
`Error while processing Reminder ${reminder.id}: ${error}`,
|
|
||||||
);
|
|
||||||
console.trace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
this.interval = setInterval(
|
|
||||||
this.loop.bind(this),
|
|
||||||
1000 * (this.settings.loopIntervalSec ?? 60),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
clearInterval(this.interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initialize(settings: Settings) {
|
|
||||||
console.log("Initializing remind.js");
|
|
||||||
|
|
||||||
// Populate Reminder table inside SQLite DB
|
|
||||||
Reminder.init(
|
|
||||||
{
|
|
||||||
id: {
|
|
||||||
type: INTEGER,
|
|
||||||
primaryKey: true,
|
|
||||||
autoIncrement: true,
|
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
type: TEXT,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
text: TEXT,
|
|
||||||
trigger: {
|
|
||||||
type: DATE,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
isValid: BOOLEAN,
|
|
||||||
requestChannel: TEXT,
|
|
||||||
requestMessage: TEXT,
|
|
||||||
},
|
|
||||||
{ sequelize: settings.db },
|
|
||||||
);
|
|
||||||
await Reminder.sync();
|
|
||||||
|
|
||||||
// Try and determine how the bot will send reminders
|
|
||||||
console.debug(`Accessing channel ${settings.publicChannel}`);
|
|
||||||
if (!getSendableTextChannel(settings.client, settings.publicChannel)) {
|
|
||||||
throw Error(
|
|
||||||
"Invalid value for publicChannel; specify a channel the bot can access.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the 'plugin' object which will store all state required to control the mainloop.
|
|
||||||
if (settings.loopIntervalSec == null || settings.loopIntervalSec <= 0) {
|
|
||||||
console.log("Setting plugin loop interval to default of 60 seconds");
|
|
||||||
settings.loopIntervalSec = 60;
|
|
||||||
}
|
|
||||||
const plugin = new Plugin(settings);
|
|
||||||
plugin.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSendableTextChannel(client: Client, chanId: string) {
|
|
||||||
// console.log(`getSendableTextChannel(${client}, ${chanId})`);
|
|
||||||
const channel = client.channels.cache.get(chanId);
|
|
||||||
if (!client.user) {
|
|
||||||
throw Error("Can't check non-existent client");
|
|
||||||
}
|
|
||||||
if (!channel) {
|
|
||||||
throw Error("No such channel is visible to Blitz");
|
|
||||||
}
|
|
||||||
if (!channel?.isSendable()) {
|
|
||||||
throw Error("Channel is not a text channel, or otherwise not Sendable");
|
|
||||||
}
|
|
||||||
if (!(channel instanceof TextChannel)) {
|
|
||||||
throw Error("Channel is not a guild channel");
|
|
||||||
}
|
|
||||||
const permissions = channel?.permissionsFor(client.user);
|
|
||||||
const requiredPerms = new PermissionsBitField([
|
|
||||||
PermissionsBitField.Flags.SendMessages,
|
|
||||||
PermissionsBitField.Flags.ViewChannel,
|
|
||||||
]);
|
|
||||||
if (!permissions?.has(requiredPerms)) {
|
|
||||||
throw Error("Missing required permissions: SendMessages, ViewChannel");
|
|
||||||
}
|
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function triggerReminder(channel: TextChannel, reminder: Reminder) {
|
|
||||||
try {
|
|
||||||
await channel.send({
|
|
||||||
content: `Reminder for <@${reminder.userId}>: ${reminder.text}`,
|
|
||||||
});
|
|
||||||
reminder.isValid = false;
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Error trigggering Reminder ${reminder.id}: ${error}`);
|
|
||||||
} finally {
|
|
||||||
await reminder.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function execute(interaction: ChatInputCommandInteraction) {
|
|
||||||
const whenString = interaction.options.getString("when") ?? "now";
|
|
||||||
const when = chrono.parseDate(whenString);
|
|
||||||
if (!when) {
|
|
||||||
await interaction.reply(`Sorry, I don't understand '${when}' as a date`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const reminder = await Reminder.create({
|
|
||||||
userId: interaction.user.id,
|
|
||||||
text: interaction.options.getString("what"),
|
|
||||||
trigger: when, // TODO
|
|
||||||
requestMessage: interaction.id,
|
|
||||||
requestChannel: interaction.channelId,
|
|
||||||
isValid: true,
|
|
||||||
});
|
|
||||||
reminder.save();
|
|
||||||
await interaction.reply(`Ok, I'll remind you at ${when}`);
|
|
||||||
console.info(
|
|
||||||
`Created Reminder ${reminder.id} to be triggered at ${reminder.trigger}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (settings: Settings) {
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
execute,
|
|
||||||
initialize: async () => await initialize(settings),
|
|
||||||
Reminder,
|
|
||||||
settings,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import {
|
|
||||||
SlashCommandBuilder,
|
|
||||||
TextChannel,
|
|
||||||
type ChatInputCommandInteraction,
|
|
||||||
type Client,
|
|
||||||
type InviteStageInstance,
|
|
||||||
} from "discord.js";
|
|
||||||
import type { Sequelize } from "sequelize";
|
|
||||||
|
|
||||||
import { quotes } from "./quotes.json";
|
|
||||||
|
|
||||||
async function execute(interaction: ChatInputCommandInteraction) {
|
|
||||||
try {
|
|
||||||
const index = Math.floor(Math.random() * quotes.length);
|
|
||||||
await interaction.reply?.({
|
|
||||||
content: `> *${quotes[index].quote}*
|
|
||||||
> — ${quotes[index].author}`,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Problem sending to channel: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initialize() {}
|
|
||||||
|
|
||||||
export default function (settings: {}) {
|
|
||||||
return {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName("quote")
|
|
||||||
.setDescription("Print a quote from League of Legends."),
|
|
||||||
initialize: initialize,
|
|
||||||
execute: execute,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
{
|
|
||||||
"quotes": [
|
|
||||||
{ "author": "Blitzcrank", "quote": "A rolling golem gathers no rust." },
|
|
||||||
{ "author": "Blitzcrank", "quote": "Fired up and ready to serve." },
|
|
||||||
{ "author": "Blitzcrank", "quote": "Metal is harder than flesh." },
|
|
||||||
{ "author": "Camille", "quote": "[Speaking to Warwick] We are all monsters. Now, you are just one on the outside." },
|
|
||||||
{ "author": "Camille", "quote": "Efficiency is paramount to success." },
|
|
||||||
{ "author": "Camille", "quote": "Elegance never goes out of fashion." },
|
|
||||||
{ "author": "Camille", "quote": "Extremes are easy, it's the balance that is difficult." },
|
|
||||||
{ "author": "Camille", "quote": "I don't play the game, I make the rules." },
|
|
||||||
{ "author": "Camille", "quote": "I'm what you would call a 'deniable asset'." },
|
|
||||||
{ "author": "Camille", "quote": "It is not the weapon that defines you, but how you wield it." },
|
|
||||||
{ "author": "Camille", "quote": "It's not lies that cut, but the sharpness of the truth." },
|
|
||||||
{ "author": "Camille", "quote": "Mediocrity is the root of all evil." },
|
|
||||||
{ "author": "Camille", "quote": "Morality is a beautiful servant and a dangerous master." },
|
|
||||||
{ "author": "Camille", "quote": "Precision is the difference between a butcher and a surgeon." },
|
|
||||||
{ "author": "Camille", "quote": "Privilege must be preserved at all costs." },
|
|
||||||
{ "author": "Camille", "quote": "Progress is honed on necessary death." },
|
|
||||||
{ "author": "Camille", "quote": "Progress is served by technology, not controlled by it." },
|
|
||||||
{ "author": "Camille", "quote": "Regret is what tempers the steel of our soul." },
|
|
||||||
{ "author": "Camille", "quote": "Results are all that matters." },
|
|
||||||
{ "author": "Camille", "quote": "Self-made women need to be more prevalent." },
|
|
||||||
{ "author": "Camille", "quote": "Sometimes scars are the most refined attire one can wear." },
|
|
||||||
{ "author": "Camille", "quote": "The right word cuts more deeply than a knife." },
|
|
||||||
{ "author": "Camille", "quote": "The task at hand is the only one that matters." },
|
|
||||||
{ "author": "Camille", "quote": "The world is not black or white, but a delicious shade of grey." },
|
|
||||||
{ "author": "Camille", "quote": "Violence is a means to an end." },
|
|
||||||
{ "author": "Ekko", "quote": "A second chance? I thought I was on my fifth!" },
|
|
||||||
{ "author": "Ekko", "quote": "Good a time as any to act reckless." },
|
|
||||||
{ "author": "Ekko", "quote": "It's not how much time you have, it's how you use it." },
|
|
||||||
{ "author": "Ekko", "quote": "Never had luck. Never needed it." },
|
|
||||||
{ "author": "Ekko", "quote": "Time doesn't heal all wounds." },
|
|
||||||
{ "author": "Heimerdinger", "quote": "42... there's just something about that number." },
|
|
||||||
{ "author": "Heimerdinger", "quote": "I prefer a battle of wits, but you're unarmed!" },
|
|
||||||
{ "author": "Heimerdinger", "quote": "Why do chemists call helium, curium, and barium 'the medical elements'? Because, if you can't 'helium' or 'curium', you 'barium'! Hm hm!" },
|
|
||||||
{ "author": "Heimerdinger", "quote": "ヽ༼ຈل͜ຈ༽ノ raise your dongers" },
|
|
||||||
{ "author": "Jhin", "quote": "Art must exist beyond reason." },
|
|
||||||
{ "author": "Jhin", "quote": "Four!" },
|
|
||||||
{ "author": "Jhin", "quote": "I cannot be good. I must be perfection." },
|
|
||||||
{ "author": "Jhin", "quote": "I swear each performance is the last, but I lie every time." },
|
|
||||||
{ "author": "Jhin", "quote": "In carnage, I bloom, like a flower in the dawn." },
|
|
||||||
{ "author": "Jhin", "quote": "It is by my will alone I set my mind in motion." },
|
|
||||||
{ "author": "Jhin", "quote": "It's fun to kill a man, to take all that he had, and could ever have." },
|
|
||||||
{ "author": "Jhin", "quote": "You will learn what beauty truly is." },
|
|
||||||
{ "author": "Jinx", "quote": "Fishbones, you know what we oughta do? 'Do the laundry, wash dishes and pay some bills.' Stupid dumb rocket launcher..." },
|
|
||||||
{ "author": "Jinx", "quote": "I'm crazy! Got a doctor's note." },
|
|
||||||
{ "author": "Jinx", "quote": "I'm trying to care! But I just... can't!" },
|
|
||||||
{ "author": "Jinx", "quote": "Rules are made to be broken... like buildings! Or people!" },
|
|
||||||
{ "author": "Joseph Miklos", "quote": "'It's only a short way'? Is that a short joke?!" },
|
|
||||||
{ "author": "Joseph Miklos", "quote": "I am evil! Stop laughing!" },
|
|
||||||
{ "author": "Joseph Miklos", "quote": "Know that if the tables were turned, I would show you no mercy!" },
|
|
||||||
{ "author": "Joseph Miklos", "quote": "You will die by my hand!" },
|
|
||||||
{ "author": "Mordekaiser", "quote": "Ah, life is a bitter shame." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "All mortals reek with the stench of decaying flesh." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "Fools fear death, the strong wield it." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "I alone am the bastion between eternal existence and oblivion." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "I carve my kingdom beyond, from the ashes of nothing, no mortals, not even gods, will stop me from claiming what is mine." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "I have bent the realm of the dead to my will, this world shall be next." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "I raise my iron fist to subjugate the living." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "I will grind their petty souls into mortar." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "I will silence the incessant thrum of mortal hearts." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "In the world beyond, blackened ichor filled a crumbling sky, as souls withered to nothing. But I refused to fade." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "Mortals plan in fear for tomorrow, I build for eternity." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "Naive men pray to the gods; they will learn to pray to me." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "Only the worthy receive the gift of Nightfall's kiss." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "Shed the frailty of flesh, embrace the cold edge of iron." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "The dead belong to me, the living shall be next." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "The world has tried to forget my existence, time to remind them why they fear." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "Twice slain, thrice born." },
|
|
||||||
{ "author": "Mordekaiser", "quote": "Weaklings cower in the light, I bring eternal darkness." },
|
|
||||||
{ "author": "Poppy", "quote": "I'm in, one-hundred-percent! That's everything, right?" },
|
|
||||||
{ "author": "Poppy", "quote": "I'm no hero—just a Yordle with a hammer." },
|
|
||||||
{ "author": "Poppy", "quote": "Just had a thought—three pigtails!" },
|
|
||||||
{ "author": "Poppy", "quote": "The hammer does most of the work, I just swing it." },
|
|
||||||
{ "author": "Rammus", "quote": "OK." },
|
|
||||||
{ "author": "Rammus", "quote": "🆗" },
|
|
||||||
{ "author": "Senna", "quote": "I forgive. No one else has to." },
|
|
||||||
{ "author": "Senna", "quote": "I remember my nightmares. Wish I could remember to dream." },
|
|
||||||
{ "author": "Sion", "quote": "A black eye for the earth!" },
|
|
||||||
{ "author": "Sion", "quote": "Death had its chance." },
|
|
||||||
{ "author": "Sion", "quote": "Noxus suffers no cowards." },
|
|
||||||
{ "author": "Sion", "quote": "The quiet... eats at me." },
|
|
||||||
{ "author": "Swain", "quote": "A calculated risk is no risk at all." },
|
|
||||||
{ "author": "Swain", "quote": "A new vantage, is all the advantage I need." },
|
|
||||||
{ "author": "Swain", "quote": "And to think, they called me a 'cripple'." },
|
|
||||||
{ "author": "Swain", "quote": "Destiny marches—like any man." },
|
|
||||||
{ "author": "Swain", "quote": "Diplomacy is a subtle art." },
|
|
||||||
{ "author": "Swain", "quote": "Hmph... I do so enjoy explaining things to idiots." },
|
|
||||||
{ "author": "Swain", "quote": "I cannot lead if I allow fools to stumble about before me." },
|
|
||||||
{ "author": "Swain", "quote": "I could kill them all. But it would be far crueler to show them that I am right." },
|
|
||||||
{ "author": "Swain", "quote": "I have killed more men with words than by my own hand. Not for lack of trying." },
|
|
||||||
{ "author": "Swain", "quote": "I suppose I should be grateful they have the decency to fear me." },
|
|
||||||
{ "author": "Swain", "quote": "I've heard what they call me. What a waste of their final words." },
|
|
||||||
{ "author": "Swain", "quote": "If they already call me a villain, what will they call me when I succeed?" },
|
|
||||||
{ "author": "Swain", "quote": "Is it not enough for Noxus to be strong?" },
|
|
||||||
{ "author": "Swain", "quote": "It is not the visions that haunt me—but what I do not see." },
|
|
||||||
{ "author": "Swain", "quote": "Never make a bargain with a demon... that you intend to keep." },
|
|
||||||
{ "author": "Swain", "quote": "One can read the future in battle lines, assuming one can read." },
|
|
||||||
{ "author": "Swain", "quote": "People often ask for a hero, when a villain is what they truly need." },
|
|
||||||
{ "author": "Swain", "quote": "Pity stays the hand of the merciful, but not mine." },
|
|
||||||
{ "author": "Swain", "quote": "Tell me again all the crimes I've committed, and I'll tell you the price of victory." },
|
|
||||||
{ "author": "Swain", "quote": "The more they try to kill me, the more they reveal I am on the right path." },
|
|
||||||
{ "author": "Swain", "quote": "The outcome was decided when they brought an army; and I brought a demon." },
|
|
||||||
{ "author": "Swain", "quote": "The right to rule, held, in my hand." },
|
|
||||||
{ "author": "Swain", "quote": "There is always a choice. The truth is no exception." },
|
|
||||||
{ "author": "Swain", "quote": "They are blind to the cold logic of this world." },
|
|
||||||
{ "author": "Swain", "quote": "They are five steps from realizing: I am ten steps ahead." },
|
|
||||||
{ "author": "Swain", "quote": "They expect me to play fairly... We aren't even playing the same game." },
|
|
||||||
{ "author": "Swain", "quote": "What is one more demon, when I already have so many?" },
|
|
||||||
{ "author": "Swain", "quote": "Would they even struggle to survive, if they knew what was to come?" },
|
|
||||||
{ "author": "Swain", "quote": "You can sit on a throne, that doesn't make you a ruler. It only means you have an arse." },
|
|
||||||
{ "author": "Tahm Kench", "quote": "All creation is born famished and starving." },
|
|
||||||
{ "author": "Tahm Kench", "quote": "Child, you're a couple cows short of a steak!" },
|
|
||||||
{ "author": "Tahm Kench", "quote": "The only real sin is to deny a craving." },
|
|
||||||
{ "author": "Tahm Kench", "quote": "We all gourmandize from time to time." },
|
|
||||||
{ "author": "Teemo", "quote": "Size doesn't mean everything." },
|
|
||||||
{ "author": "Thresh", "quote": "I am the thing under the bed." },
|
|
||||||
{ "author": "Thresh", "quote": "Me, mad? Haha... quite likely." },
|
|
||||||
{ "author": "Urgot", "quote": "Cast into a pit of despair, I climbed out on the corpses." },
|
|
||||||
{ "author": "Urgot", "quote": "I am stronger than man, stronger than machine, I am an idea." },
|
|
||||||
{ "author": "Urgot", "quote": "I am the very definition of a self-made man." },
|
|
||||||
{ "author": "Urgot", "quote": "If they do not stop me, they will die. It is just that simple." },
|
|
||||||
{ "author": "Urgot", "quote": "Pain is the act of becoming." },
|
|
||||||
{ "author": "Urgot", "quote": "We will rise from the rubble, stronger than before." },
|
|
||||||
{ "author": "Urgot", "quote": "You cannot know strength... Until you are broken." },
|
|
||||||
{ "author": "Veigar", "quote": "'It's only a short way'? Is that a short joke?!" },
|
|
||||||
{ "author": "Veigar", "quote": "I am evil! Stop laughing!" },
|
|
||||||
{ "author": "Veigar", "quote": "Know that if the tables were turned, I would show you no mercy!" },
|
|
||||||
{ "author": "Vex", "quote": "'Death is the true meaning of life.' Whoa! That's deep." },
|
|
||||||
{ "author": "Vex", "quote": "[Speaking to Lux] Oh, no. Happiness and rainbows? I'm gonna barf twice." },
|
|
||||||
{ "author": "Vex", "quote": "And then I told her, 'Get outta my room!' And she said, 'This is my house, young lady and'... Oh, hang on, Shadow. I'll finish this later." },
|
|
||||||
{ "author": "Vex", "quote": "Calm down, Shadow. I'm trying to sulk." },
|
|
||||||
{ "author": "Vex", "quote": "I am not cute. I am dark and forlorn and hopelessly morbid!" },
|
|
||||||
{ "author": "Vex", "quote": "I could start a club for people who hate people! Ehh, but no one would show up." },
|
|
||||||
{ "author": "Vex", "quote": "This is going to be... awful, in a very good way. A good, awful way. You know what I mean!" },
|
|
||||||
{ "author": "Vex", "quote": "Welcome to Sad Town. Population: Me. Everyone else get out." },
|
|
||||||
{ "author": "Viktor", "quote": "All that is logical is true, absolute, irrefutable." },
|
|
||||||
{ "author": "Viktor", "quote": "Choice is false. It is how we clothe and forgive the baser instincts that spur us to division." },
|
|
||||||
{ "author": "Viktor", "quote": "Emotion and logic cannot coexist. One must be shed to gain the other." },
|
|
||||||
{ "author": "Viktor", "quote": "Emotion... clashes with reason." },
|
|
||||||
{ "author": "Viktor", "quote": "Governed by instinct, humans are no more than flawed and flailing animals." },
|
|
||||||
{ "author": "Viktor", "quote": "Hexcorization requires no justification. What purpose is there in explaining a horseshoe to the horse?" },
|
|
||||||
{ "author": "Viktor", "quote": "Humanity... is self-corrupting." },
|
|
||||||
{ "author": "Viktor", "quote": "I am not the man I was, but who I wished to be." },
|
|
||||||
{ "author": "Viktor", "quote": "I am the only one with the means to cure suffering. But it is a lonely path." },
|
|
||||||
{ "author": "Viktor", "quote": "I chose to become this. Difficulty had no bearing nor did danger. It was... necessary." },
|
|
||||||
{ "author": "Viktor", "quote": "I did not know true... beauty, until the Arcane." },
|
|
||||||
{ "author": "Viktor", "quote": "I offer no choice, for there is none." },
|
|
||||||
{ "author": "Viktor", "quote": "I sense... trepidation. But one cannot grow if left unchallenged." },
|
|
||||||
{ "author": "Viktor", "quote": "Mankind clings to its past. Glorifies its present. And lives in dread of tomorrow." },
|
|
||||||
{ "author": "Viktor", "quote": "Passion double-crosses, subverts, divides." },
|
|
||||||
{ "author": "Viktor", "quote": "Sentiment is incompatible with control." },
|
|
||||||
{ "author": "Viktor", "quote": "So much of what we value is inconsequential." },
|
|
||||||
{ "author": "Viktor", "quote": "They think humanity can survive with emotion? Survive as what? Creatures blinded by impulse?" },
|
|
||||||
{ "author": "Viktor", "quote": "To live with flaws, is to be subject to them." },
|
|
||||||
{ "author": "Viktor", "quote": "True change must be imposed, not offered." },
|
|
||||||
{ "author": "Viktor", "quote": "What I am doing is not torment. Torment is allowing the mind to corrupt the soul." },
|
|
||||||
{ "author": "Volibear", "quote": "A thousand scars, what is one more?" },
|
|
||||||
{ "author": "Volibear", "quote": "The creations of mortals fail. The wild remains." },
|
|
||||||
{ "author": "Volibear", "quote": "The land slumbers, but it is not dead. With my roar, I wake it. With my thunder, I call it." },
|
|
||||||
{ "author": "Volibear", "quote": "They have forgotten the old ways. The old ways have not forgotten them." },
|
|
||||||
{ "author": "Volibear", "quote": "Warm-bloods rose on two legs... and forgot how to run." },
|
|
||||||
{ "author": "Xerath", "quote": "I am power incarnate! Who dares oppose me?" },
|
|
||||||
{ "author": "Xerath", "quote": "I am the will of man, unbound by flesh." },
|
|
||||||
{ "author": "Xerath", "quote": "I see the forces that hold the universe together." },
|
|
||||||
{ "author": "Xerath", "quote": "The secrets of magic are mine alone." },
|
|
||||||
{ "author": "Yone", "quote": "Long before blades and sorcery are needed, words... can save a soul." },
|
|
||||||
{ "author": "Yone", "quote": "Sleep is not for the weak, but for the blessed." },
|
|
||||||
{ "author": "Yone", "quote": "Sometimes, to save someone, you must fight them." }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
47
database.ts
47
database.ts
@@ -1,34 +1,43 @@
|
|||||||
import { Model, Sequelize, STRING } from "sequelize";
|
import {Model, Sequelize, STRING} from 'sequelize';
|
||||||
|
|
||||||
import "node:fs/promises";
|
import 'node:fs/promises';
|
||||||
import { readFile } from "node:fs";
|
import {readFile} from 'node:fs';
|
||||||
|
|
||||||
export const sql = new Sequelize("sqlite://./blitzcrank.sqlite");
|
export const sequelize = new Sequelize('sqlite://./blitzcrank.sqlite');
|
||||||
|
|
||||||
export class GuildSetting extends Model {
|
export class GuildSetting extends Model {
|
||||||
|
// Discord ID for the Guild (server)
|
||||||
declare guildId: string;
|
declare guildId: string;
|
||||||
|
// Name of the option
|
||||||
declare key: string;
|
declare key: string;
|
||||||
|
// Value being set
|
||||||
declare value?: string;
|
declare value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
GuildSetting.init(
|
export async function initializeDatabase() {
|
||||||
{
|
|
||||||
guildId: { type: STRING },
|
|
||||||
key: { type: STRING },
|
|
||||||
value: { type: STRING },
|
|
||||||
},
|
|
||||||
{ sequelize: sql },
|
|
||||||
);
|
|
||||||
|
|
||||||
export async function initDb() {
|
|
||||||
try {
|
try {
|
||||||
await sql.authenticate();
|
await sequelize.authenticate();
|
||||||
console.log("Connected to database");
|
console.log('Connected to database');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Unable to connect to the database:", error);
|
console.error('Unable to connect to the database:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function initializeModels() {
|
||||||
|
GuildSetting.init(
|
||||||
|
{
|
||||||
|
guildId: {type: STRING},
|
||||||
|
key: {type: STRING},
|
||||||
|
value: {type: STRING},
|
||||||
|
},
|
||||||
|
{sequelize},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function syncModels() {
|
||||||
|
await GuildSetting.sync();
|
||||||
|
}
|
||||||
|
|
||||||
export async function readSettingsFromFile(path: string) {
|
export async function readSettingsFromFile(path: string) {
|
||||||
readFile(path, async (err, data) => {
|
readFile(path, async (err, data) => {
|
||||||
try {
|
try {
|
||||||
@@ -43,10 +52,10 @@ export async function readSettingsFromFile(path: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GuildSetting.bulkCreate(toInsert, { updateOnDuplicate: ["value"] });
|
GuildSetting.bulkCreate(toInsert, {updateOnDuplicate: ['value']});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// TODO
|
// TODO
|
||||||
console.log("Could not read settings:", error);
|
console.log('Could not read settings:', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
17
environ.ts
Normal file
17
environ.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export interface EnvSettings {
|
||||||
|
BLITZCRANK_API_TOKEN: string;
|
||||||
|
BLITZCRANK_APP_ID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readEnvSettings(): EnvSettings {
|
||||||
|
// TODO(DWM): I hate this.
|
||||||
|
const BLITZCRANK_API_TOKEN = process.env.BLITZCRANK_API_TOKEN;
|
||||||
|
if (BLITZCRANK_API_TOKEN === undefined || BLITZCRANK_API_TOKEN === null) {
|
||||||
|
throw TypeError();
|
||||||
|
}
|
||||||
|
const BLITZCRANK_APP_ID = process.env.BLITZCRANK_APP_ID;
|
||||||
|
if (BLITZCRANK_APP_ID === undefined || BLITZCRANK_APP_ID === null) {
|
||||||
|
throw TypeError();
|
||||||
|
}
|
||||||
|
return { BLITZCRANK_API_TOKEN, BLITZCRANK_APP_ID };
|
||||||
|
}
|
||||||
111
index.ts
111
index.ts
@@ -1,12 +1,3 @@
|
|||||||
import type { Interaction } from "discord.js";
|
|
||||||
import { Client, Events, GatewayIntentBits, MessageFlags } from "discord.js";
|
|
||||||
import type {
|
|
||||||
SlashCommandBuilder,
|
|
||||||
SlashCommandOptionsOnlyBuilder,
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { sql, GuildSetting, initDb } from "./database";
|
|
||||||
|
|
||||||
const BLITZCRANK_BANNER = `
|
const BLITZCRANK_BANNER = `
|
||||||
****++++++++++*+++
|
****++++++++++*+++
|
||||||
**+*+******+********+******+*++*
|
**+*+******+********+******+*++*
|
||||||
@@ -62,6 +53,29 @@ const BLITZCRANK_BANNER = `
|
|||||||
"FIRED UP AND READY TO SERVE!"
|
"FIRED UP AND READY TO SERVE!"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
import type {Interaction} from 'discord.js';
|
||||||
|
import {
|
||||||
|
Client,
|
||||||
|
Collection,
|
||||||
|
Events,
|
||||||
|
GatewayIntentBits,
|
||||||
|
MessageFlags,
|
||||||
|
REST,
|
||||||
|
Routes,
|
||||||
|
} from 'discord.js';
|
||||||
|
import {GuildSetting, initializeDatabase, initializeModels, sequelize, syncModels} from './database';
|
||||||
|
import {readEnvSettings} from './environ';
|
||||||
|
|
||||||
|
import {Command} from './plugin';
|
||||||
|
import { NagPlugin } from './plugins/nag'
|
||||||
|
import { PingPlugin } from './plugins/ping'
|
||||||
|
import { QuotePlugin } from './plugins/quote'
|
||||||
|
import { RemindPlugin } from './plugins/remind'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const envSettings = readEnvSettings();
|
||||||
|
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
intents: [
|
intents: [
|
||||||
GatewayIntentBits.Guilds,
|
GatewayIntentBits.Guilds,
|
||||||
@@ -70,54 +84,46 @@ const client = new Client({
|
|||||||
GatewayIntentBits.MessageContent,
|
GatewayIntentBits.MessageContent,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
import { Routes } from "discord.js";
|
|
||||||
import { guildId, appId, token, remindersChannelId } from "./config.json";
|
|
||||||
import { REST } from "discord.js";
|
|
||||||
|
|
||||||
const rest = new REST();
|
const rest = new REST();
|
||||||
rest.setToken(token);
|
rest.setToken(envSettings.BLITZCRANK_API_TOKEN);
|
||||||
|
|
||||||
interface Command {
|
|
||||||
data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder;
|
|
||||||
execute: (interaction: any) => Promise<void>;
|
|
||||||
initialize: (any) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
import { Collection } from "discord.js";
|
|
||||||
const commands = new Collection<string, Command>();
|
const commands = new Collection<string, Command>();
|
||||||
|
|
||||||
import PingCommand from "./commands/calendar/ping";
|
const settings = {
|
||||||
import RemindCommand from "./commands/calendar/remind";
|
|
||||||
import QuoteCommand from "./commands/quotes/quote";
|
|
||||||
|
|
||||||
console.debug(`${remindersChannelId}`);
|
|
||||||
|
|
||||||
commands.set("ping", PingCommand({ client: client, db: sql }));
|
|
||||||
commands.set(
|
|
||||||
"remind",
|
|
||||||
RemindCommand({
|
|
||||||
client: client,
|
client: client,
|
||||||
db: sql,
|
database: sequelize,
|
||||||
publicChannel: remindersChannelId,
|
}
|
||||||
responseMode: "public",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
commands.set("quote", QuoteCommand({}));
|
|
||||||
|
|
||||||
async function syncCommands() {
|
const plugins = [
|
||||||
|
new NagPlugin(settings),
|
||||||
|
new QuotePlugin(settings),
|
||||||
|
new RemindPlugin(settings),
|
||||||
|
new PingPlugin(settings),
|
||||||
|
]
|
||||||
|
|
||||||
|
async function addApplicationGuildCommands() {
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
for (const command of plugin.commands) {
|
||||||
|
commands.set(command.data.name, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [_guildId, guild] of client.guilds.cache) {
|
||||||
try {
|
try {
|
||||||
console.log(`Started refreshing slash commands`);
|
console.log(`Started refreshing slash commands`);
|
||||||
const _data = await rest.put(
|
const _data = await rest.put(
|
||||||
Routes.applicationGuildCommands(appId, guildId),
|
Routes.applicationGuildCommands(
|
||||||
|
envSettings.BLITZCRANK_APP_ID,
|
||||||
|
guild.id,
|
||||||
|
),
|
||||||
{
|
{
|
||||||
body: commands.mapValues((cmd) => cmd.data.toJSON()),
|
body: commands.mapValues(cmd => cmd.data.toJSON()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
console.log(`Successfully reloaded slash commands`);
|
console.log(`Successfully reloaded slash commands`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.on(Events.InteractionCreate, async (interaction: Interaction) => {
|
client.on(Events.InteractionCreate, async (interaction: Interaction) => {
|
||||||
@@ -135,7 +141,7 @@ client.on(Events.InteractionCreate, async (interaction: Interaction) => {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
if (interaction.replied || interaction.deferred) {
|
if (interaction.replied || interaction.deferred) {
|
||||||
await interaction.followUp({
|
await interaction.followUp({
|
||||||
content: "There was an error while executing this command",
|
content: 'There was an error while executing this command',
|
||||||
flags: MessageFlags.Ephemeral,
|
flags: MessageFlags.Ephemeral,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -143,22 +149,17 @@ client.on(Events.InteractionCreate, async (interaction: Interaction) => {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
client.once(Events.ClientReady, async (readyClient) => {
|
client.once(Events.ClientReady, async readyClient => {
|
||||||
await syncCommands();
|
await initializeDatabase();
|
||||||
initDb(); // TODO
|
await initializeModels();
|
||||||
GuildSetting.sync(); // TODO
|
await syncModels();
|
||||||
|
await addApplicationGuildCommands();
|
||||||
|
|
||||||
for (const [_name, cmd] of commands) {
|
for (const ln of BLITZCRANK_BANNER.split('\n')) {
|
||||||
await cmd?.initialize({
|
|
||||||
client: client,
|
|
||||||
db: sql,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Print banner
|
|
||||||
for (const ln of BLITZCRANK_BANNER.split("\n")) {
|
|
||||||
console.log(ln);
|
console.log(ln);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Logged in as ${readyClient.user.tag}`);
|
console.log(`Logged in as ${readyClient.user.tag}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.login(token);
|
client.login(envSettings.BLITZCRANK_API_TOKEN);
|
||||||
|
|||||||
33
package.json
33
package.json
@@ -1,30 +1,33 @@
|
|||||||
{
|
{
|
||||||
"name": "blitzcrank",
|
"name": "blitzcrank",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "FIRED UP AND READY TO SERVE",
|
"author": "Drew Malzahn",
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/dwwmmn/blitzcrank.git"
|
"url": "git+https://github.com/dwwmmn/blitzcrank.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"main": "index.js",
|
||||||
"discord",
|
|
||||||
"bot"
|
|
||||||
],
|
|
||||||
"author": "Drew Malzahn",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/dwwmmn/blitzcrank/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/dwwmmn/blitzcrank#readme",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@biomejs/biome": "^2.0.6",
|
"@biomejs/biome": "^2.0.6",
|
||||||
"chrono-node": "^2.8.3",
|
"chrono-node": "^2.8.3",
|
||||||
"discord.js": "^14.21.0",
|
"discord.js": "^14.21.0",
|
||||||
"sequelize": "^6.37.7",
|
"sequelize": "^6.37.7",
|
||||||
"sqlite3": "^5.1.7"
|
"sqlite3": "^5.1.7"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/dwwmmn/blitzcrank/issues"
|
||||||
|
},
|
||||||
|
"description": "FIRED UP AND READY TO SERVE",
|
||||||
|
"homepage": "https://github.com/dwwmmn/blitzcrank#readme",
|
||||||
|
"keywords": [
|
||||||
|
"discord",
|
||||||
|
"bot"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vitest": "^3.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
plugin.ts
Normal file
32
plugin.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import type { ChatInputCommandInteraction, Client, SlashCommandBuilder } from "discord.js";
|
||||||
|
import type { Sequelize } from "sequelize";
|
||||||
|
|
||||||
|
export interface Settings {
|
||||||
|
// Main Discord client object
|
||||||
|
client: Client;
|
||||||
|
// Database access object
|
||||||
|
database: Sequelize;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Command {
|
||||||
|
data: SlashCommandBuilder;
|
||||||
|
execute: (iteraction: ChatInputCommandInteraction) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Plugin {
|
||||||
|
start: () => Promise<void>;
|
||||||
|
stop: () => Promise<void>;
|
||||||
|
commands: Command[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BasePlugin implements Plugin {
|
||||||
|
settings: Settings;
|
||||||
|
commands: Command[]
|
||||||
|
|
||||||
|
constructor(settings: Settings) {
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {}
|
||||||
|
async stop() {}
|
||||||
|
}
|
||||||
44
plugins/nag/checkin.ts
Normal file
44
plugins/nag/checkin.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import {
|
||||||
|
type ChatInputCommandInteraction,
|
||||||
|
SlashCommandBuilder,
|
||||||
|
} from 'discord.js';
|
||||||
|
|
||||||
|
import type {Settings} from '../../plugin';
|
||||||
|
import {CheckIn, Nag} from './models';
|
||||||
|
|
||||||
|
export default function (_settings: Settings) {
|
||||||
|
return {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('checkin')
|
||||||
|
.setDescription('Check-in for your daily nag')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option
|
||||||
|
.setName('text')
|
||||||
|
.setDescription('Optional description of what you have achieved'),
|
||||||
|
),
|
||||||
|
|
||||||
|
execute: async (interaction: ChatInputCommandInteraction) => {
|
||||||
|
// TODO For now there is only one nag, but in the future this could be different.
|
||||||
|
// So let's construct it as a loop for now.
|
||||||
|
const result = await Nag.findAll({
|
||||||
|
where: {
|
||||||
|
userId: interaction.user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
await interaction.reply(
|
||||||
|
"Couldn't find any nags for you, what are you checking in for?",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const nag of result) {
|
||||||
|
await CheckIn.create({
|
||||||
|
nagId: nag.id,
|
||||||
|
lastCheckIn: new Date(Date.now()),
|
||||||
|
});
|
||||||
|
await interaction.reply('Thanks for checking in!');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
99
plugins/nag/index.test.ts
Normal file
99
plugins/nag/index.test.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import {Sequelize } from 'sequelize';
|
||||||
|
import {afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import {
|
||||||
|
findGuiltyNags,
|
||||||
|
getCheckIn,
|
||||||
|
nextCheckInDate,
|
||||||
|
} from '.';
|
||||||
|
import { CheckIn, initializeModels, Nag } from './models'
|
||||||
|
|
||||||
|
describe('nextCheckInDate', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers(); // Tell vitest to use fake timers
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers(); // Reset date after test runs
|
||||||
|
});
|
||||||
|
it('Returns 9AM if called before 9AM that day', () => {
|
||||||
|
const now = new Date(Date.now());
|
||||||
|
const at9AM = new Date(
|
||||||
|
now.getFullYear(),
|
||||||
|
now.getMonth(),
|
||||||
|
now.getDate(),
|
||||||
|
9,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
vi.setSystemTime(
|
||||||
|
new Date(now.getFullYear(), now.getMonth(), now.getDate(), 8, 0),
|
||||||
|
);
|
||||||
|
expect(nextCheckInDate()).toEqual(at9AM);
|
||||||
|
});
|
||||||
|
it('Returns 9AM tomorrow if called after 9AM', () => {
|
||||||
|
const dayInMS = 24 * 60 * 60 * 1000;
|
||||||
|
const now = new Date(Date.now());
|
||||||
|
const tomorrow = new Date(Date.now() + dayInMS);
|
||||||
|
const tomorrow9AM = new Date(
|
||||||
|
tomorrow.getFullYear(),
|
||||||
|
tomorrow.getMonth(),
|
||||||
|
tomorrow.getDate(),
|
||||||
|
9,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
vi.setSystemTime(
|
||||||
|
new Date(now.getFullYear(), now.getMonth(), now.getDate(), 9, 30),
|
||||||
|
);
|
||||||
|
expect(nextCheckInDate()).toEqual(tomorrow9AM);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Finding nags without check-ins', async () => {
|
||||||
|
const sequelize = new Sequelize('sqlite://:memory:');
|
||||||
|
const exampleNag = {
|
||||||
|
userId: '1234',
|
||||||
|
guildId: '1234',
|
||||||
|
channelId: '1234',
|
||||||
|
messageId: '1234',
|
||||||
|
text: 'Example nag 1',
|
||||||
|
mentionHere: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
await initializeModels(sequelize);
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
await Nag.sync();
|
||||||
|
await CheckIn.sync();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await Nag.drop();
|
||||||
|
await CheckIn.drop();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Finds nags without any check-ins', async () => {
|
||||||
|
const now = new Date();
|
||||||
|
vi.setSystemTime(
|
||||||
|
new Date(now.getFullYear(), now.getMonth(), now.getDate(), 9),
|
||||||
|
);
|
||||||
|
await Nag.create(exampleNag);
|
||||||
|
const results = await findGuiltyNags();
|
||||||
|
expect(results.map(nag => nag.userId)).toEqual(['1234']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Ignores nags with a recent check-in', async () => {
|
||||||
|
const currentCheckInTime = getCheckIn(9, 0);
|
||||||
|
vi.setSystemTime(currentCheckInTime);
|
||||||
|
const newNag = await Nag.create(exampleNag);
|
||||||
|
newNag.save();
|
||||||
|
const newCheckIn = await CheckIn.create({
|
||||||
|
nagId: newNag.id,
|
||||||
|
// 1 hour previously; i.e. we checked in before the required time
|
||||||
|
lastCheckIn: new Date(currentCheckInTime.getTime() - 60 * 60 * 1000),
|
||||||
|
});
|
||||||
|
console.log(newCheckIn.lastCheckIn);
|
||||||
|
newCheckIn.save();
|
||||||
|
const results = await findGuiltyNags();
|
||||||
|
expect(results.map(nag => nag.userId)).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
149
plugins/nag/index.ts
Normal file
149
plugins/nag/index.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import { TextChannel } from 'discord.js';
|
||||||
|
import { Op } from 'sequelize';
|
||||||
|
import { BasePlugin, type Command, type Settings } from '../../plugin'
|
||||||
|
import CheckinCommand from './checkin'
|
||||||
|
import checkin from './checkin';
|
||||||
|
import { CheckIn, initializeModels, Nag } from './models'
|
||||||
|
import NagCommand from './nag'
|
||||||
|
import UnnagCommand from './unnag'
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param hour Number between 0-24 representing the hour the check-in is performed.
|
||||||
|
* @param offset Number of days from today; this can be positive for future days or negative for past days.
|
||||||
|
* @returns Date object representing the check-in time
|
||||||
|
*/
|
||||||
|
export function getCheckIn(hour: number, offset: number = 0) {
|
||||||
|
const nDaysInMS = offset * 24 * 60 * 60 * 1000;
|
||||||
|
const day = new Date(Date.now() + nDaysInMS);
|
||||||
|
const checkInDate = new Date(
|
||||||
|
day.getFullYear(),
|
||||||
|
day.getMonth(),
|
||||||
|
day.getDate(),
|
||||||
|
hour,
|
||||||
|
);
|
||||||
|
return checkInDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findGuiltyNags() {
|
||||||
|
const results = await Nag.findAll();
|
||||||
|
const guiltyNags: Nag[] = [];
|
||||||
|
const prevCheckIn = getCheckIn(9, -1);
|
||||||
|
const currentCheckIn = getCheckIn(9, 0);
|
||||||
|
|
||||||
|
for (const nag of results) {
|
||||||
|
const checkInResults = await CheckIn.findAll({
|
||||||
|
where: {
|
||||||
|
id: nag.id,
|
||||||
|
lastCheckIn: {
|
||||||
|
[Op.between]: [prevCheckIn, currentCheckIn],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (checkInResults.length <= 0) {
|
||||||
|
guiltyNags.push(nag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return guiltyNags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns The Date representing the next check-in, which will be either
|
||||||
|
* today (if we are asking in the morning) or tomorrow (if we are asking
|
||||||
|
* after the check-in time for today.)
|
||||||
|
*/
|
||||||
|
export function nextCheckInDate() {
|
||||||
|
const todaysCheckInDate = getCheckIn(9, 0);
|
||||||
|
if (Date.now() - todaysCheckInDate.getTime() < 0) {
|
||||||
|
return getCheckIn(9, 0);
|
||||||
|
} else {
|
||||||
|
return getCheckIn(9, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextCheckInMs() {
|
||||||
|
const delayMs = nextCheckInDate().getTime() - Date.now();
|
||||||
|
if (delayMs <= 0) {
|
||||||
|
// The value of nextCheckInDate is guaranteed to be in the future; if not, that's a bug in the program.
|
||||||
|
throw Error('Invalid value for nextCheckInDate');
|
||||||
|
}
|
||||||
|
return delayMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle state for the various nag-related commands, mainly related to
|
||||||
|
* tracking of timers and triggers.
|
||||||
|
*/
|
||||||
|
export class NagPlugin extends BasePlugin {
|
||||||
|
// NOTE(DWM): I really hate the word 'Manager' when applied to code, but I
|
||||||
|
// thought for quite a bit about what to name these classes and I just can't
|
||||||
|
// come up with anything solid. (Pun intended.)
|
||||||
|
|
||||||
|
interval?: NodeJS.Timeout;
|
||||||
|
|
||||||
|
constructor(settings: Settings) {
|
||||||
|
super(settings);
|
||||||
|
initializeModels(settings.database);
|
||||||
|
this.commands = [
|
||||||
|
CheckinCommand(settings) as Command, // TODO Why, TS?...
|
||||||
|
NagCommand(settings) as Command, // TODO Why, TS?...
|
||||||
|
UnnagCommand(settings),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
this.interval = setTimeout(this.loop, nextCheckInMs());
|
||||||
|
}
|
||||||
|
|
||||||
|
async triggerNag(nag: Nag) {
|
||||||
|
const client = this.settings.client;
|
||||||
|
// First, try and find the appropriate channel to send on
|
||||||
|
const guild = client.guilds.cache.get(nag.guildId);
|
||||||
|
if (!guild) {
|
||||||
|
console.error(
|
||||||
|
`No longer have access to Guild ${nag.guildId}; consider deleting this nag manually :(`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const channel = guild.channels.cache.get(nag.channelId);
|
||||||
|
if (!channel) {
|
||||||
|
console.error(
|
||||||
|
`No longer have access to Channel ${nag.channelId}; consider deleting this nag manually :(`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!channel?.isSendable() || !(channel instanceof TextChannel)) {
|
||||||
|
console.error("Somehow, we specified a channel which isn't sendable");
|
||||||
|
return; // TODO
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const failText =
|
||||||
|
nag.failText ??
|
||||||
|
`<@${nag.userId}> didn't complete "${nag.text}". Shame shame!`;
|
||||||
|
const mentionHere = nag.mentionHere ? '<@here> ' : '';
|
||||||
|
const msg = `${mentionHere}${failText}`;
|
||||||
|
await channel.send(msg);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error while creating Nag:', error); // TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loop() {
|
||||||
|
// According to MDN we don't need to do this, but it makes me feel happier
|
||||||
|
// knowing that we won't accidentally call clearInterval(...) on a timer
|
||||||
|
// that isn't running anymore.
|
||||||
|
this.interval = undefined;
|
||||||
|
|
||||||
|
console.debug('nag.js main loop');
|
||||||
|
const guiltyNags = await findGuiltyNags();
|
||||||
|
for (const nag of guiltyNags) {
|
||||||
|
await this.triggerNag(nag);
|
||||||
|
}
|
||||||
|
this.interval = setTimeout(this.loop, nextCheckInMs());
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop() {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
72
plugins/nag/models.ts
Normal file
72
plugins/nag/models.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import {
|
||||||
|
BOOLEAN,
|
||||||
|
DATE,
|
||||||
|
INTEGER,
|
||||||
|
Model,
|
||||||
|
type Sequelize,
|
||||||
|
STRING,
|
||||||
|
} from 'sequelize';
|
||||||
|
|
||||||
|
export class Nag extends Model {
|
||||||
|
// Primary key
|
||||||
|
declare id: number;
|
||||||
|
// User who created this nag
|
||||||
|
declare userId: string;
|
||||||
|
// Guild (server) of original nag request
|
||||||
|
declare guildId: string;
|
||||||
|
// Channel of original nag request
|
||||||
|
declare channelId: string;
|
||||||
|
// Message of original nag request
|
||||||
|
declare messageId: string;
|
||||||
|
// Description of what you're supposed to do
|
||||||
|
declare text: string;
|
||||||
|
// Custom failure text
|
||||||
|
declare failText?: string;
|
||||||
|
// Should we @here?
|
||||||
|
declare mentionHere?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CheckIn extends Model {
|
||||||
|
// Date of the last time user ran /checkin
|
||||||
|
declare lastCheckIn: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initializeModels(sequelize: Sequelize) {
|
||||||
|
Nag.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
primaryKey: true,
|
||||||
|
type: INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
failText: {
|
||||||
|
type: STRING,
|
||||||
|
},
|
||||||
|
mentionHere: {
|
||||||
|
type: BOOLEAN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{sequelize},
|
||||||
|
);
|
||||||
|
CheckIn.init(
|
||||||
|
{
|
||||||
|
lastCheckIn: {
|
||||||
|
type: DATE,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{sequelize},
|
||||||
|
);
|
||||||
|
Nag.hasMany(CheckIn);
|
||||||
|
CheckIn.belongsTo(Nag);
|
||||||
|
await Nag.sync();
|
||||||
|
await CheckIn.sync();
|
||||||
|
}
|
||||||
92
plugins/nag/nag.ts
Normal file
92
plugins/nag/nag.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import {Chrono} from 'chrono-node';
|
||||||
|
import {
|
||||||
|
type ChatInputCommandInteraction,
|
||||||
|
SlashCommandBuilder,
|
||||||
|
} from 'discord.js';
|
||||||
|
import type { Settings } from '../../plugin'
|
||||||
|
import {CheckIn, Nag} from './models';
|
||||||
|
|
||||||
|
async function execute(interaction: ChatInputCommandInteraction) {
|
||||||
|
const text = interaction.options.getString('text');
|
||||||
|
if (text === null || text === undefined) {
|
||||||
|
await interaction.reply("Nag can't have a blank `text`, try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check if we already have an existing nag. In theory, this should be supported entirely, however
|
||||||
|
// I want to keep things simple for now.
|
||||||
|
const existingNags = await Nag.findAll({
|
||||||
|
where: {
|
||||||
|
userId: interaction.user.id,
|
||||||
|
},
|
||||||
|
// order: [["createdAt", "ASC"]],
|
||||||
|
});
|
||||||
|
console.log('Successfully looked for checkIns');
|
||||||
|
if (existingNags && existingNags.length > 0) {
|
||||||
|
// TODO: Hmm... For now, I guess we can just update the database.
|
||||||
|
for (const nag of existingNags) {
|
||||||
|
nag.text = text;
|
||||||
|
nag.failText = interaction.options.getString('failtext') ?? undefined;
|
||||||
|
await nag.save();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await interaction.reply(
|
||||||
|
`I'll check every day at 9AM if you've completed '${text}'. If not, I'll nag you! Use /checkin to prevent a shameful callout, and /unnag to cancel.`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Otherwise, we need to create a new nag.
|
||||||
|
const nag = await Nag.create({
|
||||||
|
userId: interaction.user.id,
|
||||||
|
guildId: interaction.guild?.id,
|
||||||
|
channelId: interaction.channel?.id,
|
||||||
|
messageId: interaction.id,
|
||||||
|
text: text,
|
||||||
|
failText: interaction.options.getString('failtext'),
|
||||||
|
mentionHere: interaction.options.getBoolean('mentionhere') ?? false,
|
||||||
|
});
|
||||||
|
await nag.save();
|
||||||
|
const chrono = new Chrono();
|
||||||
|
const checkIn = chrono.parseDate('today at 9AM');
|
||||||
|
if (!checkIn) {
|
||||||
|
await interaction.reply(
|
||||||
|
'Internal error while saving your nag. Tell Drew the bot is broken!!!',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await CheckIn.create({
|
||||||
|
nag: {
|
||||||
|
id: nag.id,
|
||||||
|
},
|
||||||
|
lastCheckIn: new Date(Date.now()),
|
||||||
|
});
|
||||||
|
await interaction.reply(
|
||||||
|
`I'll check every day at 9AM if you've completed '${text}'. If not, I'll nag you! Use /checkin to prevent a shameful callout, and /unnag to cancel.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function (_settings: Settings) {
|
||||||
|
return {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('nag')
|
||||||
|
.setDescription('Let Blitzcrank nag you every day about something')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option
|
||||||
|
.setRequired(true)
|
||||||
|
.setName('text')
|
||||||
|
.setDescription('What you have to do every day'),
|
||||||
|
)
|
||||||
|
.addStringOption(option =>
|
||||||
|
option
|
||||||
|
.setName('failtext')
|
||||||
|
.setDescription('Custom message to be broadcast on failure')
|
||||||
|
.setRequired(false),
|
||||||
|
)
|
||||||
|
.addBooleanOption(option =>
|
||||||
|
option
|
||||||
|
.setName('mentionhere')
|
||||||
|
.setDescription('Whether to DM you or @ this channel')
|
||||||
|
.setRequired(false),
|
||||||
|
),
|
||||||
|
execute,
|
||||||
|
};
|
||||||
|
}
|
||||||
25
plugins/nag/unnag.ts
Normal file
25
plugins/nag/unnag.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
type ChatInputCommandInteraction,
|
||||||
|
SlashCommandBuilder,
|
||||||
|
} from 'discord.js';
|
||||||
|
import type {Settings} from '../../plugin';
|
||||||
|
import {Nag} from './models';
|
||||||
|
|
||||||
|
export default function (_settings: Settings) {
|
||||||
|
return {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('unnag')
|
||||||
|
.setDescription('Remove a nag'),
|
||||||
|
execute: async (interaction: ChatInputCommandInteraction) => {
|
||||||
|
// Find all nags for this user and delete them.
|
||||||
|
// TODO In the future, we should support having multiple nags
|
||||||
|
const results = await Nag.findAll({
|
||||||
|
where: {userId: interaction.user.id},
|
||||||
|
});
|
||||||
|
for (const result of results) {
|
||||||
|
await result.destroy();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
22
plugins/ping.ts
Normal file
22
plugins/ping.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import {SlashCommandBuilder} from 'discord.js';
|
||||||
|
|
||||||
|
import {BasePlugin, type Settings} from '../plugin';
|
||||||
|
|
||||||
|
export class PingPlugin extends BasePlugin {
|
||||||
|
constructor(settings: Settings) {
|
||||||
|
super(settings);
|
||||||
|
this.commands = [
|
||||||
|
{
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('ping')
|
||||||
|
.setDescription('Send a ping to the bot'),
|
||||||
|
|
||||||
|
execute: async interaction => {
|
||||||
|
await interaction.reply(
|
||||||
|
`Pong! This command was run by ${interaction.user.username}, who joined on ${interaction.member?.joinedAt}.`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
29
plugins/quote/index.ts
Normal file
29
plugins/quote/index.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import type {ChatInputCommandInteraction} from 'discord.js';
|
||||||
|
import {SlashCommandBuilder} from 'discord.js';
|
||||||
|
import {BasePlugin, type Settings } from '../../plugin';
|
||||||
|
import {quotes} from './quotes.json';
|
||||||
|
|
||||||
|
export class QuotePlugin extends BasePlugin {
|
||||||
|
constructor(settings: Settings) {
|
||||||
|
super(settings);
|
||||||
|
this.commands = [
|
||||||
|
{
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('quote')
|
||||||
|
.setDescription('Print a quote from League of Legends.'),
|
||||||
|
|
||||||
|
execute: async (interaction: ChatInputCommandInteraction) => {
|
||||||
|
try {
|
||||||
|
const index = Math.floor(Math.random() * quotes.length);
|
||||||
|
await interaction.reply?.({
|
||||||
|
content: `> *${quotes[index].quote}*
|
||||||
|
> — ${quotes[index].author}`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Problem sending to channel: ${error}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
519
plugins/quote/quotes.json
Normal file
519
plugins/quote/quotes.json
Normal file
@@ -0,0 +1,519 @@
|
|||||||
|
{
|
||||||
|
"quotes": [
|
||||||
|
{ "author": "Blitzcrank", "quote": "A rolling golem gathers no rust." },
|
||||||
|
{ "author": "Blitzcrank", "quote": "Fired up and ready to serve." },
|
||||||
|
{ "author": "Blitzcrank", "quote": "Metal is harder than flesh." },
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "[Speaking to Warwick] We are all monsters. Now, you are just one on the outside."
|
||||||
|
},
|
||||||
|
{ "author": "Camille", "quote": "Efficiency is paramount to success." },
|
||||||
|
{ "author": "Camille", "quote": "Elegance never goes out of fashion." },
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "Extremes are easy, it's the balance that is difficult."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "I don't play the game, I make the rules."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "I'm what you would call a 'deniable asset'."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "It is not the weapon that defines you, but how you wield it."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "It's not lies that cut, but the sharpness of the truth."
|
||||||
|
},
|
||||||
|
{ "author": "Camille", "quote": "Mediocrity is the root of all evil." },
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "Morality is a beautiful servant and a dangerous master."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "Precision is the difference between a butcher and a surgeon."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "Privilege must be preserved at all costs."
|
||||||
|
},
|
||||||
|
{ "author": "Camille", "quote": "Progress is honed on necessary death." },
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "Progress is served by technology, not controlled by it."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "Regret is what tempers the steel of our soul."
|
||||||
|
},
|
||||||
|
{ "author": "Camille", "quote": "Results are all that matters." },
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "Self-made women need to be more prevalent."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "Sometimes scars are the most refined attire one can wear."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "The right word cuts more deeply than a knife."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "The task at hand is the only one that matters."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Camille",
|
||||||
|
"quote": "The world is not black or white, but a delicious shade of grey."
|
||||||
|
},
|
||||||
|
{ "author": "Camille", "quote": "Violence is a means to an end." },
|
||||||
|
{
|
||||||
|
"author": "Ekko",
|
||||||
|
"quote": "A second chance? I thought I was on my fifth!"
|
||||||
|
},
|
||||||
|
{ "author": "Ekko", "quote": "Good a time as any to act reckless." },
|
||||||
|
{
|
||||||
|
"author": "Ekko",
|
||||||
|
"quote": "It's not how much time you have, it's how you use it."
|
||||||
|
},
|
||||||
|
{ "author": "Ekko", "quote": "Never had luck. Never needed it." },
|
||||||
|
{ "author": "Ekko", "quote": "Time doesn't heal all wounds." },
|
||||||
|
{
|
||||||
|
"author": "Heimerdinger",
|
||||||
|
"quote": "42... there's just something about that number."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Heimerdinger",
|
||||||
|
"quote": "I prefer a battle of wits, but you're unarmed!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Heimerdinger",
|
||||||
|
"quote": "Why do chemists call helium, curium, and barium 'the medical elements'? Because, if you can't 'helium' or 'curium', you 'barium'! Hm hm!"
|
||||||
|
},
|
||||||
|
{ "author": "Heimerdinger", "quote": "ヽ༼ຈل͜ຈ༽ノ raise your dongers" },
|
||||||
|
{ "author": "Jhin", "quote": "Art must exist beyond reason." },
|
||||||
|
{ "author": "Jhin", "quote": "Four!" },
|
||||||
|
{ "author": "Jhin", "quote": "I cannot be good. I must be perfection." },
|
||||||
|
{
|
||||||
|
"author": "Jhin",
|
||||||
|
"quote": "I swear each performance is the last, but I lie every time."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Jhin",
|
||||||
|
"quote": "In carnage, I bloom, like a flower in the dawn."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Jhin",
|
||||||
|
"quote": "It is by my will alone I set my mind in motion."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Jhin",
|
||||||
|
"quote": "It's fun to kill a man, to take all that he had, and could ever have."
|
||||||
|
},
|
||||||
|
{ "author": "Jhin", "quote": "You will learn what beauty truly is." },
|
||||||
|
{
|
||||||
|
"author": "Jinx",
|
||||||
|
"quote": "Fishbones, you know what we oughta do? 'Do the laundry, wash dishes and pay some bills.' Stupid dumb rocket launcher..."
|
||||||
|
},
|
||||||
|
{ "author": "Jinx", "quote": "I'm crazy! Got a doctor's note." },
|
||||||
|
{ "author": "Jinx", "quote": "I'm trying to care! But I just... can't!" },
|
||||||
|
{
|
||||||
|
"author": "Jinx",
|
||||||
|
"quote": "Rules are made to be broken... like buildings! Or people!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Joseph Miklos",
|
||||||
|
"quote": "'It's only a short way'? Is that a short joke?!"
|
||||||
|
},
|
||||||
|
{ "author": "Joseph Miklos", "quote": "I am evil! Stop laughing!" },
|
||||||
|
{
|
||||||
|
"author": "Joseph Miklos",
|
||||||
|
"quote": "Know that if the tables were turned, I would show you no mercy!"
|
||||||
|
},
|
||||||
|
{ "author": "Joseph Miklos", "quote": "You will die by my hand!" },
|
||||||
|
{ "author": "Mordekaiser", "quote": "Ah, life is a bitter shame." },
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "All mortals reek with the stench of decaying flesh."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "Fools fear death, the strong wield it."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "I alone am the bastion between eternal existence and oblivion."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "I carve my kingdom beyond, from the ashes of nothing, no mortals, not even gods, will stop me from claiming what is mine."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "I have bent the realm of the dead to my will, this world shall be next."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "I raise my iron fist to subjugate the living."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "I will grind their petty souls into mortar."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "I will silence the incessant thrum of mortal hearts."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "In the world beyond, blackened ichor filled a crumbling sky, as souls withered to nothing. But I refused to fade."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "Mortals plan in fear for tomorrow, I build for eternity."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "Naive men pray to the gods; they will learn to pray to me."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "Only the worthy receive the gift of Nightfall's kiss."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "Shed the frailty of flesh, embrace the cold edge of iron."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "The dead belong to me, the living shall be next."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "The world has tried to forget my existence, time to remind them why they fear."
|
||||||
|
},
|
||||||
|
{ "author": "Mordekaiser", "quote": "Twice slain, thrice born." },
|
||||||
|
{
|
||||||
|
"author": "Mordekaiser",
|
||||||
|
"quote": "Weaklings cower in the light, I bring eternal darkness."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Poppy",
|
||||||
|
"quote": "I'm in, one-hundred-percent! That's everything, right?"
|
||||||
|
},
|
||||||
|
{ "author": "Poppy", "quote": "I'm no hero—just a Yordle with a hammer." },
|
||||||
|
{ "author": "Poppy", "quote": "Just had a thought—three pigtails!" },
|
||||||
|
{
|
||||||
|
"author": "Poppy",
|
||||||
|
"quote": "The hammer does most of the work, I just swing it."
|
||||||
|
},
|
||||||
|
{ "author": "Rammus", "quote": "OK." },
|
||||||
|
{ "author": "Rammus", "quote": "🆗" },
|
||||||
|
{ "author": "Senna", "quote": "I forgive. No one else has to." },
|
||||||
|
{
|
||||||
|
"author": "Senna",
|
||||||
|
"quote": "I remember my nightmares. Wish I could remember to dream."
|
||||||
|
},
|
||||||
|
{ "author": "Sion", "quote": "A black eye for the earth!" },
|
||||||
|
{ "author": "Sion", "quote": "Death had its chance." },
|
||||||
|
{ "author": "Sion", "quote": "Noxus suffers no cowards." },
|
||||||
|
{ "author": "Sion", "quote": "The quiet... eats at me." },
|
||||||
|
{ "author": "Swain", "quote": "A calculated risk is no risk at all." },
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "A new vantage, is all the advantage I need."
|
||||||
|
},
|
||||||
|
{ "author": "Swain", "quote": "And to think, they called me a 'cripple'." },
|
||||||
|
{ "author": "Swain", "quote": "Destiny marches—like any man." },
|
||||||
|
{ "author": "Swain", "quote": "Diplomacy is a subtle art." },
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "Hmph... I do so enjoy explaining things to idiots."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "I cannot lead if I allow fools to stumble about before me."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "I could kill them all. But it would be far crueler to show them that I am right."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "I have killed more men with words than by my own hand. Not for lack of trying."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "I suppose I should be grateful they have the decency to fear me."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "I've heard what they call me. What a waste of their final words."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "If they already call me a villain, what will they call me when I succeed?"
|
||||||
|
},
|
||||||
|
{ "author": "Swain", "quote": "Is it not enough for Noxus to be strong?" },
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "It is not the visions that haunt me—but what I do not see."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "Never make a bargain with a demon... that you intend to keep."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "One can read the future in battle lines, assuming one can read."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "People often ask for a hero, when a villain is what they truly need."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "Pity stays the hand of the merciful, but not mine."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "Tell me again all the crimes I've committed, and I'll tell you the price of victory."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "The more they try to kill me, the more they reveal I am on the right path."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "The outcome was decided when they brought an army; and I brought a demon."
|
||||||
|
},
|
||||||
|
{ "author": "Swain", "quote": "The right to rule, held, in my hand." },
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "There is always a choice. The truth is no exception."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "They are blind to the cold logic of this world."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "They are five steps from realizing: I am ten steps ahead."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "They expect me to play fairly... We aren't even playing the same game."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "What is one more demon, when I already have so many?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "Would they even struggle to survive, if they knew what was to come?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Swain",
|
||||||
|
"quote": "You can sit on a throne, that doesn't make you a ruler. It only means you have an arse."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Tahm Kench",
|
||||||
|
"quote": "All creation is born famished and starving."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Tahm Kench",
|
||||||
|
"quote": "Child, you're a couple cows short of a steak!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Tahm Kench",
|
||||||
|
"quote": "The only real sin is to deny a craving."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Tahm Kench",
|
||||||
|
"quote": "We all gourmandize from time to time."
|
||||||
|
},
|
||||||
|
{ "author": "Teemo", "quote": "Size doesn't mean everything." },
|
||||||
|
{ "author": "Thresh", "quote": "I am the thing under the bed." },
|
||||||
|
{ "author": "Thresh", "quote": "Me, mad? Haha... quite likely." },
|
||||||
|
{
|
||||||
|
"author": "Urgot",
|
||||||
|
"quote": "Cast into a pit of despair, I climbed out on the corpses."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Urgot",
|
||||||
|
"quote": "I am stronger than man, stronger than machine, I am an idea."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Urgot",
|
||||||
|
"quote": "I am the very definition of a self-made man."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Urgot",
|
||||||
|
"quote": "If they do not stop me, they will die. It is just that simple."
|
||||||
|
},
|
||||||
|
{ "author": "Urgot", "quote": "Pain is the act of becoming." },
|
||||||
|
{
|
||||||
|
"author": "Urgot",
|
||||||
|
"quote": "We will rise from the rubble, stronger than before."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Urgot",
|
||||||
|
"quote": "You cannot know strength... Until you are broken."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Veigar",
|
||||||
|
"quote": "'It's only a short way'? Is that a short joke?!"
|
||||||
|
},
|
||||||
|
{ "author": "Veigar", "quote": "I am evil! Stop laughing!" },
|
||||||
|
{
|
||||||
|
"author": "Veigar",
|
||||||
|
"quote": "Know that if the tables were turned, I would show you no mercy!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Vex",
|
||||||
|
"quote": "'Death is the true meaning of life.' Whoa! That's deep."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Vex",
|
||||||
|
"quote": "[Speaking to Lux] Oh, no. Happiness and rainbows? I'm gonna barf twice."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Vex",
|
||||||
|
"quote": "And then I told her, 'Get outta my room!' And she said, 'This is my house, young lady and'... Oh, hang on, Shadow. I'll finish this later."
|
||||||
|
},
|
||||||
|
{ "author": "Vex", "quote": "Calm down, Shadow. I'm trying to sulk." },
|
||||||
|
{
|
||||||
|
"author": "Vex",
|
||||||
|
"quote": "I am not cute. I am dark and forlorn and hopelessly morbid!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Vex",
|
||||||
|
"quote": "I could start a club for people who hate people! Ehh, but no one would show up."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Vex",
|
||||||
|
"quote": "This is going to be... awful, in a very good way. A good, awful way. You know what I mean!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Vex",
|
||||||
|
"quote": "Welcome to Sad Town. Population: Me. Everyone else get out."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "All that is logical is true, absolute, irrefutable."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "Choice is false. It is how we clothe and forgive the baser instincts that spur us to division."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "Emotion and logic cannot coexist. One must be shed to gain the other."
|
||||||
|
},
|
||||||
|
{ "author": "Viktor", "quote": "Emotion... clashes with reason." },
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "Governed by instinct, humans are no more than flawed and flailing animals."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "Hexcorization requires no justification. What purpose is there in explaining a horseshoe to the horse?"
|
||||||
|
},
|
||||||
|
{ "author": "Viktor", "quote": "Humanity... is self-corrupting." },
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "I am not the man I was, but who I wished to be."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "I am the only one with the means to cure suffering. But it is a lonely path."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "I chose to become this. Difficulty had no bearing nor did danger. It was... necessary."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "I did not know true... beauty, until the Arcane."
|
||||||
|
},
|
||||||
|
{ "author": "Viktor", "quote": "I offer no choice, for there is none." },
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "I sense... trepidation. But one cannot grow if left unchallenged."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "Mankind clings to its past. Glorifies its present. And lives in dread of tomorrow."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "Passion double-crosses, subverts, divides."
|
||||||
|
},
|
||||||
|
{ "author": "Viktor", "quote": "Sentiment is incompatible with control." },
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "So much of what we value is inconsequential."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "They think humanity can survive with emotion? Survive as what? Creatures blinded by impulse?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "To live with flaws, is to be subject to them."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "True change must be imposed, not offered."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Viktor",
|
||||||
|
"quote": "What I am doing is not torment. Torment is allowing the mind to corrupt the soul."
|
||||||
|
},
|
||||||
|
{ "author": "Volibear", "quote": "A thousand scars, what is one more?" },
|
||||||
|
{
|
||||||
|
"author": "Volibear",
|
||||||
|
"quote": "The creations of mortals fail. The wild remains."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Volibear",
|
||||||
|
"quote": "The land slumbers, but it is not dead. With my roar, I wake it. With my thunder, I call it."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Volibear",
|
||||||
|
"quote": "They have forgotten the old ways. The old ways have not forgotten them."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Volibear",
|
||||||
|
"quote": "Warm-bloods rose on two legs... and forgot how to run."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Xerath",
|
||||||
|
"quote": "I am power incarnate! Who dares oppose me?"
|
||||||
|
},
|
||||||
|
{ "author": "Xerath", "quote": "I am the will of man, unbound by flesh." },
|
||||||
|
{
|
||||||
|
"author": "Xerath",
|
||||||
|
"quote": "I see the forces that hold the universe together."
|
||||||
|
},
|
||||||
|
{ "author": "Xerath", "quote": "The secrets of magic are mine alone." },
|
||||||
|
{
|
||||||
|
"author": "Yone",
|
||||||
|
"quote": "Long before blades and sorcery are needed, words... can save a soul."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Yone",
|
||||||
|
"quote": "Sleep is not for the weak, but for the blessed."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Yone",
|
||||||
|
"quote": "Sometimes, to save someone, you must fight them."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
136
plugins/remind/index.ts
Normal file
136
plugins/remind/index.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import * as chrono from 'chrono-node';
|
||||||
|
import {
|
||||||
|
type ChatInputCommandInteraction,
|
||||||
|
SlashCommandBuilder,
|
||||||
|
TextChannel,
|
||||||
|
} from 'discord.js';
|
||||||
|
import * as sequelize from 'sequelize';
|
||||||
|
|
||||||
|
import {BasePlugin, type Command, type Settings} from '../../plugin';
|
||||||
|
|
||||||
|
import {initializeModels, Reminder, syncModels} from './models';
|
||||||
|
|
||||||
|
async function triggerReminder(channel: TextChannel, reminder: Reminder) {
|
||||||
|
try {
|
||||||
|
await channel.send({
|
||||||
|
content: `Reminder for <@${reminder.userId}>: ${reminder.text}`,
|
||||||
|
});
|
||||||
|
reminder.isValid = false;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Error trigggering Reminder ${reminder.id}: ${error}`);
|
||||||
|
} finally {
|
||||||
|
await reminder.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RemindPlugin extends BasePlugin {
|
||||||
|
interval: NodeJS.Timeout;
|
||||||
|
|
||||||
|
constructor(settings: Settings) {
|
||||||
|
super(settings);
|
||||||
|
initializeModels(settings.database);
|
||||||
|
this.commands = [
|
||||||
|
{
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('remind')
|
||||||
|
.setDescription('Remind me to do something')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option
|
||||||
|
.setName('when')
|
||||||
|
.setDescription('Short description of when you want the reminder')
|
||||||
|
.setRequired(true),
|
||||||
|
)
|
||||||
|
.addStringOption(option =>
|
||||||
|
option
|
||||||
|
.setName('what')
|
||||||
|
.setDescription(
|
||||||
|
'Short description of what you want to be reminded of',
|
||||||
|
)
|
||||||
|
.setRequired(true),
|
||||||
|
),
|
||||||
|
|
||||||
|
execute: async (interaction: ChatInputCommandInteraction) => {
|
||||||
|
const whenString = interaction.options.getString('when') ?? 'now';
|
||||||
|
const when = chrono.parseDate(whenString);
|
||||||
|
if (!when) {
|
||||||
|
await interaction.reply(
|
||||||
|
`Sorry, I don't understand '${when}' as a date`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const reminder = await Reminder.create({
|
||||||
|
userId: interaction.user.id,
|
||||||
|
text: interaction.options.getString('what'),
|
||||||
|
trigger: when, // TODO
|
||||||
|
requestMessage: interaction.id,
|
||||||
|
requestChannel: interaction.channelId,
|
||||||
|
isValid: true,
|
||||||
|
});
|
||||||
|
reminder.save();
|
||||||
|
await interaction.reply(`Ok, I'll remind you at ${when}`);
|
||||||
|
console.info(
|
||||||
|
`Created Reminder ${reminder.id} to be triggered at ${reminder.trigger}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
} as Command, // TODO
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getChannel(reminder: Reminder) {
|
||||||
|
// TODO I don't really know what else needs to go into this function
|
||||||
|
// I had some permissions stuff but it seemed very out-of-place
|
||||||
|
// I don't know why isSendable() doesn't cover permissions? Or maybe
|
||||||
|
// it does?
|
||||||
|
// Either way this function kind of smells
|
||||||
|
const client = this.settings.client;
|
||||||
|
const channel = client.channels.cache.get(reminder.requestChannel);
|
||||||
|
if (!(channel instanceof TextChannel)) {
|
||||||
|
throw TypeError("Can't send to a non-ext channel");
|
||||||
|
}
|
||||||
|
if (!channel) {
|
||||||
|
throw Error('Cannot find valid channel to send reminder on');
|
||||||
|
}
|
||||||
|
if (!channel.isSendable()) {
|
||||||
|
throw Error("Can't send to that channel");
|
||||||
|
}
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loop() {
|
||||||
|
console.debug('remind.js main loop');
|
||||||
|
const results = await Reminder.findAll({
|
||||||
|
// NOTE: 'trigger' is the time when the reminder should go off. If trigger -
|
||||||
|
// now is positive, trigger is in the future. If trigger - now <=
|
||||||
|
// 1.0, then we have less than one minute before the timer should go
|
||||||
|
// off.
|
||||||
|
where: sequelize.literal(
|
||||||
|
"isValid AND (julianday(trigger) - julianday('now')) <= 1.0",
|
||||||
|
),
|
||||||
|
});
|
||||||
|
for (const reminder of results) {
|
||||||
|
try {
|
||||||
|
const now = new Date(Date.now());
|
||||||
|
const delay = reminder.trigger.getTime() - now.getTime();
|
||||||
|
console.log(
|
||||||
|
`Callback for Reminder ${reminder.get('id')} triggering in ${delay}ms`,
|
||||||
|
);
|
||||||
|
const channel = this.getChannel(reminder);
|
||||||
|
setTimeout(async () => await triggerReminder(channel, reminder), delay);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`Error while processing Reminder ${reminder.id}: ${error}`,
|
||||||
|
);
|
||||||
|
console.trace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
await syncModels();
|
||||||
|
this.interval = setInterval(this.loop.bind(this), 1000 * 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop() {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
plugins/remind/models.ts
Normal file
41
plugins/remind/models.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { BOOLEAN, DATE, INTEGER, Model, Sequelize, TEXT } from "sequelize";
|
||||||
|
|
||||||
|
export class Reminder extends Model {
|
||||||
|
declare id: number;
|
||||||
|
declare userId: string;
|
||||||
|
declare text: string | null;
|
||||||
|
declare trigger: Date;
|
||||||
|
declare isValid: boolean;
|
||||||
|
declare requestChannel: string;
|
||||||
|
declare requestMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initializeModels(sequelize: Sequelize) {
|
||||||
|
Reminder.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: TEXT,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
text: TEXT,
|
||||||
|
trigger: {
|
||||||
|
type: DATE,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
isValid: BOOLEAN,
|
||||||
|
requestChannel: TEXT,
|
||||||
|
requestMessage: TEXT,
|
||||||
|
},
|
||||||
|
{sequelize},
|
||||||
|
);
|
||||||
|
// await Reminder.sync(); // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function syncModels() {
|
||||||
|
await Reminder.sync();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user