Initial Commit
This commit is contained in:
26
packages/discord/.github/workflows/release.yml
vendored
Normal file
26
packages/discord/.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Release
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- run: npx changelogithub
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
38
packages/discord/.github/workflows/unit-test.yml
vendored
Normal file
38
packages/discord/.github/workflows/unit-test.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Unit Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Set node LTS
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: pnpm
|
||||
|
||||
- name: Install
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Test
|
||||
run: pnpm run test
|
||||
4
packages/discord/.gitignore
vendored
Normal file
4
packages/discord/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
*.log
|
||||
.DS_Store
|
||||
8
packages/discord/.prettierrc.yaml
Normal file
8
packages/discord/.prettierrc.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
trailingComma: all
|
||||
tabWidth: 2
|
||||
useTabs: false
|
||||
semi: true
|
||||
singleQuote: true
|
||||
printWidth: 140
|
||||
experimentalTernaries: true
|
||||
quoteProps: consistent
|
||||
3
packages/discord/.vscode/settings.json
vendored
Normal file
3
packages/discord/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
23
packages/discord/README.md
Normal file
23
packages/discord/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# tsdown-starter
|
||||
|
||||
A starter for creating a TypeScript package.
|
||||
|
||||
## Development
|
||||
|
||||
- Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
- Run the unit tests:
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
- Build the library:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
215
packages/discord/bun.lock
Normal file
215
packages/discord/bun.lock
Normal file
@@ -0,0 +1,215 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "tsdown-starter",
|
||||
"dependencies": {
|
||||
"@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.2.21",
|
||||
"@types/node": "^22.15.17",
|
||||
"bumpp": "^10.1.0",
|
||||
"tsdown": "^0.11.9",
|
||||
"typescript": "^5.8.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.70.0", "", {}, "sha512-ngyLUpUjO3dpqygSRQDx7nMx8+BmXbWOU4oIwTJFV2MVIDG7knIZwgdwXlQWLg3C3oxg1lS7ppMtPKqKFb7wzw=="],
|
||||
|
||||
"@projectdysnomia/dysnomia": ["@projectdysnomia/dysnomia@github:projectdysnomia/dysnomia#5e3300e", { "dependencies": { "ws": "^8.18.0" }, "optionalDependencies": { "@stablelib/xchacha20poly1305": "~1.0.1", "opusscript": "^0.1.1" }, "peerDependencies": { "@discordjs/opus": "^0.9.0", "erlpack": "github:discord/erlpack", "eventemitter3": "^5.0.1", "pako": "^2.1.0", "sodium-native": "^4.1.1", "zlib-sync": "^0.1.9" }, "optionalPeers": ["@discordjs/opus", "erlpack", "eventemitter3", "pako", "sodium-native", "zlib-sync"] }, "projectdysnomia-dysnomia-5e3300e"],
|
||||
|
||||
"@quansync/fs": ["@quansync/fs@0.1.5", "", { "dependencies": { "quansync": "^0.2.11" } }, "sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-geUG/FUpm+membLC0NQBb39vVyOfguYZ2oyXc7emr6UjH6TeEECT4b0CPZXKFnELareTiU/Jfl70/eEgNxyQeA=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-7wPXDwcOtv2I+pWTL2UNpNAxMAGukgBT90Jz4DCfwaYdGvQncF7J0S7IWrRVsRFhBavxM+65RcueE3VXw5UIbg=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-agO5mONTNKVrcIt4SRxw5Ni0FOVV3gaH8dIiNp1A4JeU91b9kw7x+JRuNJAQuM2X3pYqVvA6qh13UTNOsaqM/Q=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9", "", { "os": "linux", "cpu": "arm" }, "sha512-dDNDV9p/8WYDriS9HCcbH6y6+JP38o3enj/pMkdkmkxEnZ0ZoHIfQ9RGYWeRYU56NKBCrya4qZBJx49Jk9LRug=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-kZKegmHG1ZvfsFIwYU6DeFSxSIcIliXzeznsJHUo9D9/dlVSDi/PUvsRKcuJkQjZoejM6pk8MHN/UfgGdIhPHw=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-f+VL8mO31pyMJiJPr2aA1ryYONkP2UqgbwK7fKtKHZIeDd/AoUGn3+ujPqDhuy2NxgcJ5H8NaSvDpG1tJMHh+g=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.9", "", { "os": "linux", "cpu": "x64" }, "sha512-GiUEZ0WPjX5LouDoC3O8aJa4h6BLCpIvaAboNw5JoRour/3dC6rbtZZ/B5FC3/ySsN3/dFOhAH97ylQxoZJi7A=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.9", "", { "os": "linux", "cpu": "x64" }, "sha512-AMb0dicw+QHh6RxvWo4BRcuTMgS0cwUejJRMpSyIcHYnKTbj6nUW4HbWNQuDfZiF27l6F5gEwBS+YLUdVzL9vg=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.9", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.4" }, "cpu": "none" }, "sha512-+pdaiTx7L8bWKvsAuCE0HAxP1ze1WOLoWGCawcrZbMSY10dMh2i82lJiH6tXGXbfYYwsNWhWE2NyG4peFZvRfQ=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-A7kN248viWvb8eZMzQu024TBKGoyoVYBsDG2DtoP8u2pzwoh5yDqUL291u01o4f8uzpUHq8mfwQJmcGChFu8KQ=="],
|
||||
|
||||
"@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-DzKN7iEYjAP8AK8F2G2aCej3fk43Y/EQrVrR3gF0XREes56chjQ7bXIhw819jv74BbxGdnpPcslhet/cgt7WRA=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.9", "", { "os": "win32", "cpu": "x64" }, "sha512-GMWgTvvbZ8TfBsAiJpoz4SRq3IN3aUMn0rYm8q4I8dcEk4J1uISyfb6ZMzvqW+cvScTWVKWZNqnrmYOKLLUt4w=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.9", "", {}, "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w=="],
|
||||
|
||||
"@stablelib/aead": ["@stablelib/aead@1.0.1", "", {}, "sha512-q39ik6sxGHewqtO0nP4BuSe3db5G1fEJE8ukvngS2gLkBXyy6E7pLubhbYgnkDFv6V8cWaxcE4Xn0t6LWcJkyg=="],
|
||||
|
||||
"@stablelib/binary": ["@stablelib/binary@1.0.1", "", { "dependencies": { "@stablelib/int": "^1.0.1" } }, "sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q=="],
|
||||
|
||||
"@stablelib/chacha": ["@stablelib/chacha@1.0.1", "", { "dependencies": { "@stablelib/binary": "^1.0.1", "@stablelib/wipe": "^1.0.1" } }, "sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg=="],
|
||||
|
||||
"@stablelib/chacha20poly1305": ["@stablelib/chacha20poly1305@1.0.1", "", { "dependencies": { "@stablelib/aead": "^1.0.1", "@stablelib/binary": "^1.0.1", "@stablelib/chacha": "^1.0.1", "@stablelib/constant-time": "^1.0.1", "@stablelib/poly1305": "^1.0.1", "@stablelib/wipe": "^1.0.1" } }, "sha512-MmViqnqHd1ymwjOQfghRKw2R/jMIGT3wySN7cthjXCBdO+qErNPUBnRzqNpnvIwg7JBCg3LdeCZZO4de/yEhVA=="],
|
||||
|
||||
"@stablelib/constant-time": ["@stablelib/constant-time@1.0.1", "", {}, "sha512-tNOs3uD0vSJcK6z1fvef4Y+buN7DXhzHDPqRLSXUel1UfqMB1PWNsnnAezrKfEwTLpN0cGH2p9NNjs6IqeD0eg=="],
|
||||
|
||||
"@stablelib/int": ["@stablelib/int@1.0.1", "", {}, "sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w=="],
|
||||
|
||||
"@stablelib/poly1305": ["@stablelib/poly1305@1.0.1", "", { "dependencies": { "@stablelib/constant-time": "^1.0.1", "@stablelib/wipe": "^1.0.1" } }, "sha512-1HlG3oTSuQDOhSnLwJRKeTRSAdFNVB/1djy2ZbS35rBSJ/PFqx9cf9qatinWghC2UbfOYD8AcrtbUQl8WoxabA=="],
|
||||
|
||||
"@stablelib/wipe": ["@stablelib/wipe@1.0.1", "", {}, "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg=="],
|
||||
|
||||
"@stablelib/xchacha20": ["@stablelib/xchacha20@1.0.1", "", { "dependencies": { "@stablelib/binary": "^1.0.1", "@stablelib/chacha": "^1.0.1", "@stablelib/wipe": "^1.0.1" } }, "sha512-1YkiZnFF4veUwBVhDnDYwo6EHeKzQK4FnLiO7ezCl/zu64uG0bCCAUROJaBkaLH+5BEsO3W7BTXTguMbSLlWSw=="],
|
||||
|
||||
"@stablelib/xchacha20poly1305": ["@stablelib/xchacha20poly1305@1.0.1", "", { "dependencies": { "@stablelib/aead": "^1.0.1", "@stablelib/chacha20poly1305": "^1.0.1", "@stablelib/constant-time": "^1.0.1", "@stablelib/wipe": "^1.0.1", "@stablelib/xchacha20": "^1.0.1" } }, "sha512-B1Abj0sMJ8h3HNmGnJ7vHBrAvxuNka6cJJoZ1ILN7iuacXp7sUYcgOVEOTLWj+rtQMpspY9tXSCRLPmN1mQNWg=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
|
||||
|
||||
"@types/node": ["@types/node@22.18.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="],
|
||||
|
||||
"ansis": ["ansis@4.1.0", "", {}, "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w=="],
|
||||
|
||||
"args-tokenizer": ["args-tokenizer@0.3.0", "", {}, "sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q=="],
|
||||
|
||||
"ast-kit": ["ast-kit@2.1.2", "", { "dependencies": { "@babel/parser": "^7.28.0", "pathe": "^2.0.3" } }, "sha512-cl76xfBQM6pztbrFWRnxbrDm9EOqDr1BF6+qQnnDZG2Co2LjyUktkN9GTJfBAfdae+DbT2nJf2nCGAdDDN7W2g=="],
|
||||
|
||||
"birpc": ["birpc@2.5.0", "", {}, "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ=="],
|
||||
|
||||
"bumpp": ["bumpp@10.2.3", "", { "dependencies": { "ansis": "^4.1.0", "args-tokenizer": "^0.3.0", "c12": "^3.2.0", "cac": "^6.7.14", "escalade": "^3.2.0", "jsonc-parser": "^3.3.1", "package-manager-detector": "^1.3.0", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.14", "yaml": "^2.8.1" }, "bin": { "bumpp": "bin/bumpp.mjs" } }, "sha512-nsFBZACxuBVu6yzDSaZZaWpX5hTQ+++9WtYkmO+0Bd3cpSq0Mzvqw5V83n+fOyRj3dYuZRFCQf5Z9NNfZj+Rnw=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
|
||||
|
||||
"c12": ["c12@3.2.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.5.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-ixkEtbYafL56E6HiFuonMm1ZjoKtIo7TH68/uiEq4DAwv9NcUX2nJ95F8TrbMeNjqIkZpruo3ojXQJ+MGG5gcQ=="],
|
||||
|
||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
||||
"citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
|
||||
|
||||
"confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
|
||||
|
||||
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||
|
||||
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
||||
|
||||
"diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
|
||||
|
||||
"dotenv": ["dotenv@17.2.2", "", {}, "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q=="],
|
||||
|
||||
"dts-resolver": ["dts-resolver@2.1.2", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-xeXHBQkn2ISSXxbJWD828PFjtyg+/UrMDo7W4Ffcs7+YWCquxU8YjV1KoxuiL+eJ5pg3ll+bC6flVv61L3LKZg=="],
|
||||
|
||||
"empathic": ["empathic@1.1.0", "", {}, "sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="],
|
||||
|
||||
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
|
||||
|
||||
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||
|
||||
"jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||
|
||||
"nypm": ["nypm@0.6.1", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.2.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w=="],
|
||||
|
||||
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
||||
|
||||
"opusscript": ["opusscript@0.1.1", "", {}, "sha512-mL0fZZOUnXdZ78woRXp18lApwpp0lF5tozJOD1Wut0dgrA9WuQTgSels/CSmFleaAZrJi/nci5KOVtbuxeWoQA=="],
|
||||
|
||||
"package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
|
||||
|
||||
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
|
||||
|
||||
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
"rolldown": ["rolldown@1.0.0-beta.9", "", { "dependencies": { "@oxc-project/types": "0.70.0", "@rolldown/pluginutils": "1.0.0-beta.9", "ansis": "^4.0.0" }, "optionalDependencies": { "@rolldown/binding-darwin-arm64": "1.0.0-beta.9", "@rolldown/binding-darwin-x64": "1.0.0-beta.9", "@rolldown/binding-freebsd-x64": "1.0.0-beta.9", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.9", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.9", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.9", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.9", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.9", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.9", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.9", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.9", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.9" }, "peerDependencies": { "@oxc-project/runtime": "0.70.0" }, "optionalPeers": ["@oxc-project/runtime"], "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-ZgZky52n6iF0UainGKjptKGrOG4Con2S5sdc4C4y2Oj25D5PHAY8Y8E5f3M2TSd/zlhQs574JlMeTe3vREczSg=="],
|
||||
|
||||
"rolldown-plugin-dts": ["rolldown-plugin-dts@0.13.14", "", { "dependencies": { "@babel/generator": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/types": "^7.28.1", "ast-kit": "^2.1.1", "birpc": "^2.5.0", "debug": "^4.4.1", "dts-resolver": "^2.1.1", "get-tsconfig": "^4.10.1" }, "peerDependencies": { "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-beta.9", "typescript": "^5.0.0", "vue-tsc": "^2.2.0 || ^3.0.0" }, "optionalPeers": ["@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-wjNhHZz9dlN6PTIXyizB6u/mAg1wEFMW9yw7imEVe3CxHSRnNHVyycIX0yDEOVJfDNISLPbkCIPEpFpizy5+PQ=="],
|
||||
|
||||
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"tsdown": ["tsdown@0.11.13", "", { "dependencies": { "ansis": "^4.0.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "debug": "^4.4.1", "diff": "^8.0.1", "empathic": "^1.1.0", "hookable": "^5.5.3", "rolldown": "1.0.0-beta.9", "rolldown-plugin-dts": "^0.13.3", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.13", "unconfig": "^7.3.2" }, "peerDependencies": { "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.js" } }, "sha512-VSfoNm8MJXFdg7PJ4p2javgjMRiQQHpkP9N3iBBTrmCixcT6YZ9ZtqYMW3NDHczqR0C0Qnur1HMQr1ZfZcmrng=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||
|
||||
"unconfig": ["unconfig@7.3.3", "", { "dependencies": { "@quansync/fs": "^0.1.5", "defu": "^6.1.4", "jiti": "^2.5.1", "quansync": "^0.2.11" } }, "sha512-QCkQoOnJF8L107gxfHL0uavn7WD9b3dpBcFX6HtfQYmjw2YzWxGuFQ0N0J6tE9oguCBJn9KOvfqYDCMPHIZrBA=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
||||
|
||||
"yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
|
||||
}
|
||||
}
|
||||
10
packages/discord/bunfig.toml
Normal file
10
packages/discord/bunfig.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[test]
|
||||
coverage = true
|
||||
coverageReporter = ["text", "lcov"]
|
||||
coveragePathIgnorePatterns = [
|
||||
"fixtures/**",
|
||||
"dist/**"
|
||||
]
|
||||
|
||||
[run]
|
||||
bun = true
|
||||
8
packages/discord/fixtures/commands/test1.command.ts
Normal file
8
packages/discord/fixtures/commands/test1.command.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { CommandHandler } from '@/commands/command-handler.type';
|
||||
|
||||
const handler: CommandHandler<{ name: string; type: 1; description: string }> = {
|
||||
definition: { name: 'test1', type: 1, description: 'Test command 1' },
|
||||
execute: async () => {},
|
||||
};
|
||||
|
||||
export default handler;
|
||||
8
packages/discord/fixtures/commands/test2.command.ts
Normal file
8
packages/discord/fixtures/commands/test2.command.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { CommandHandler } from '@/commands/command-handler.type';
|
||||
|
||||
const handler: CommandHandler<{ name: string; type: 1; description: string }> = {
|
||||
definition: { name: 'test2', type: 1, description: 'Test command 2' },
|
||||
execute: async () => {},
|
||||
};
|
||||
|
||||
export default handler;
|
||||
21
packages/discord/fixtures/jsd/test.ts
Normal file
21
packages/discord/fixtures/jsd/test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as StarKitten from '@star-kitten/discord';
|
||||
import type { ExecutableInteraction } from '@star-kitten/discord';
|
||||
import { createActionRow, createButton, createContainer, createTextDisplay } from '@star-kitten/discord/components';
|
||||
import type { PageContext } from '@star-kitten/discord/pages';
|
||||
import { type Appraisal } from '@star-kitten/eve/third-party/janice.js';
|
||||
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
|
||||
|
||||
export function renderAppraisal(
|
||||
appraisal: Appraisal,
|
||||
pageCtx: PageContext<any>,
|
||||
interaction: ExecutableInteraction,
|
||||
) {
|
||||
const formatter = new Intl.NumberFormat(interaction.locale || 'en-US', {
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
const world = 'world';
|
||||
return (
|
||||
StarKitten.createElement("ActionRow", {}, StarKitten.createElement("Container", {"color":"0x1da57a"}, StarKitten.createElement("TextDisplay", {}, ""+ `Hello ${world}` +""), pageCtx.state.currentPage !== "share" ? StarKitten.createElement("ActionRow", {}, StarKitten.createElement("Button", {"key":"share","disabled":"{!unknown}"}, "Share in Channel")) : undefined))
|
||||
)
|
||||
}
|
||||
29
packages/discord/fixtures/jsd/test.tsx
Normal file
29
packages/discord/fixtures/jsd/test.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import type {} from '@star-kitten/discord/jsx';
|
||||
import { ActionRow, Container, Button, TextDisplay } from '@star-kitten/discord';
|
||||
|
||||
export function renderAppraisal() {
|
||||
const formatter = new Intl.NumberFormat('en-US', {
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
const world = 'world';
|
||||
const rand = Math.random() * 1000;
|
||||
const pageCtx = { state: { currentPage: 'home' } };
|
||||
|
||||
let jsx = (
|
||||
<ActionRow>
|
||||
<Container color="0x1da57a">
|
||||
<TextDisplay content={`Hello ${world}`} />
|
||||
{pageCtx.state.currentPage !== 'share' ?
|
||||
<ActionRow>
|
||||
<Button customId="share" label="Share in Channel" disabled={rand < 500} />
|
||||
</ActionRow>
|
||||
: undefined}
|
||||
</Container>
|
||||
</ActionRow>
|
||||
);
|
||||
|
||||
console.log(jsx);
|
||||
}
|
||||
|
||||
renderAppraisal();
|
||||
69
packages/discord/index.d.ts
vendored
Normal file
69
packages/discord/index.d.ts
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
type ActionRow,
|
||||
type Button,
|
||||
type ChannelSelectMenu,
|
||||
type GuildChannelTypes,
|
||||
type MentionableSelectMenu,
|
||||
type PartialEmoji,
|
||||
type RoleSelectMenu,
|
||||
type StringSelectMenu,
|
||||
type TextInput,
|
||||
type UserSelectMenu,
|
||||
type LabelComponent,
|
||||
type ContainerComponent,
|
||||
type TextDisplayComponent,
|
||||
type SectionComponent,
|
||||
type MediaGalleryComponent,
|
||||
type SeparatorComponent,
|
||||
type FileComponent,
|
||||
type InteractionButton,
|
||||
type URLButton,
|
||||
type PremiumButton,
|
||||
type ThumbnailComponent,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
|
||||
declare namespace JSX {
|
||||
type Component =
|
||||
| ActionRow
|
||||
| Button
|
||||
| StringSelectMenu
|
||||
| UserSelectMenu
|
||||
| RoleSelectMenu
|
||||
| MentionableSelectMenu
|
||||
| ChannelSelectMenu
|
||||
| TextInput
|
||||
| LabelComponent
|
||||
| ContainerComponent
|
||||
| {
|
||||
type: 10;
|
||||
content: string;
|
||||
}
|
||||
| SectionComponent
|
||||
| MediaGalleryComponent
|
||||
| SeparatorComponent
|
||||
| FileComponent
|
||||
| InteractionButton
|
||||
| URLButton
|
||||
| PremiumButton
|
||||
| ThumbnailComponent;
|
||||
|
||||
type Element = Component | Promise<Component>;
|
||||
|
||||
interface ElementClass {
|
||||
render: any;
|
||||
}
|
||||
|
||||
interface ElementAttributesProperty {
|
||||
props: {};
|
||||
}
|
||||
|
||||
interface IntrinsicElements {
|
||||
// Allow any element, but prefer known elements
|
||||
[elemName: string]: any;
|
||||
// Known elements
|
||||
ActionRow: { children: any | any[] };
|
||||
Button: { label: string; customId: string; style?: number; emoji?: PartialEmoji; disabled?: boolean };
|
||||
Container: { accent?: number; spoiler?: boolean; children: any | any[] };
|
||||
TextDisplay: { content: string };
|
||||
}
|
||||
}
|
||||
58
packages/discord/package.json
Normal file
58
packages/discord/package.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@star-kitten/discord",
|
||||
"version": "0.0.0",
|
||||
"description": "Star Kitten Discord library",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/author/library#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/author/library/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/author/library.git"
|
||||
},
|
||||
"author": "Author Name <author.name@mail.com>",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./commands": "./dist/commands/index.js",
|
||||
"./components": "./dist/components/index.js",
|
||||
"./pages": "./dist/pages/index.js",
|
||||
"./common": "./dist/common/index.js",
|
||||
"./package.json": "./package.json",
|
||||
"./jsx": "./src/jsx/jsx.ts",
|
||||
"./jsx-runtime": "./dist/jsx/index.js",
|
||||
"./jsx-dev-runtime": "./dist/jsx/index.js"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"dev": "tsdown --watch",
|
||||
"test": "bun test",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"release": "bumpp && npm publish"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.2.21",
|
||||
"@types/node": "^22.15.17",
|
||||
"bumpp": "^10.1.0",
|
||||
"tsdown": "^0.11.9",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev",
|
||||
"@star-kitten/util": "workspace:^0.0.0",
|
||||
"acorn": "^8.14.0",
|
||||
"acorn-jsx": "^5.3.2",
|
||||
"html-dom-parser": "^5.1.1",
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
}
|
||||
14
packages/discord/src/commands/command-context.type.ts
Normal file
14
packages/discord/src/commands/command-context.type.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { Cache } from '@core/cache.type';
|
||||
import type { KVStore } from '@core/kv-store.type.ts';
|
||||
import type { Client } from '@projectdysnomia/dysnomia';
|
||||
import type { CommandState } from './command-state';
|
||||
|
||||
export interface PartialContext<T = any> {
|
||||
client: Client;
|
||||
cache: Cache;
|
||||
kv: KVStore;
|
||||
id?: string; // unique id for this command instance
|
||||
state?: CommandState<T>; // state associated with this command instance
|
||||
}
|
||||
|
||||
export type CommandContext<T = any> = Required<PartialContext<T>>;
|
||||
32
packages/discord/src/commands/command-handler.ts
Normal file
32
packages/discord/src/commands/command-handler.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
AutocompleteInteraction,
|
||||
CommandInteraction,
|
||||
ComponentInteraction,
|
||||
Constants,
|
||||
ModalSubmitInteraction,
|
||||
type ApplicationCommandOptionAutocomplete,
|
||||
type ApplicationCommandOptions,
|
||||
type ApplicationCommandStructure,
|
||||
type ChatInputApplicationCommandStructure,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
import type { CommandContext, PartialContext } from './command-context.type';
|
||||
|
||||
export interface CommandHandler<T extends ApplicationCommandStructure> {
|
||||
definition: T;
|
||||
execute: (interaction: ExecutableInteraction, ctx: CommandContext) => Promise<void>;
|
||||
}
|
||||
|
||||
export type ExecutableInteraction = CommandInteraction | ModalSubmitInteraction | ComponentInteraction | AutocompleteInteraction;
|
||||
|
||||
export type ChatCommandDefinition = Omit<ChatInputApplicationCommandStructure, 'type'>;
|
||||
export function createChatCommand(
|
||||
definition: ChatCommandDefinition,
|
||||
execute: (interaction: CommandInteraction, ctx: CommandContext) => Promise<void>,
|
||||
): CommandHandler<ChatInputApplicationCommandStructure> {
|
||||
const def = definition as ChatInputApplicationCommandStructure;
|
||||
def.type = 1; // CHAT_INPUT
|
||||
return {
|
||||
definition: def,
|
||||
execute,
|
||||
};
|
||||
}
|
||||
45
packages/discord/src/commands/command-helpers.ts
Normal file
45
packages/discord/src/commands/command-helpers.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
Interaction,
|
||||
CommandInteraction,
|
||||
Constants,
|
||||
ModalSubmitInteraction,
|
||||
ComponentInteraction,
|
||||
AutocompleteInteraction,
|
||||
PingInteraction,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
import type { ExecutableInteraction } from './command-handler';
|
||||
|
||||
export function isApplicationCommand(interaction: Interaction): interaction is CommandInteraction {
|
||||
return interaction.type === Constants.InteractionTypes.APPLICATION_COMMAND;
|
||||
}
|
||||
|
||||
export function isModalSubmit(interaction: Interaction): interaction is ModalSubmitInteraction {
|
||||
return interaction.type === Constants.InteractionTypes.MODAL_SUBMIT;
|
||||
}
|
||||
|
||||
export function isMessageComponent(interaction: Interaction): interaction is ComponentInteraction {
|
||||
return interaction.type === Constants.InteractionTypes.MESSAGE_COMPONENT;
|
||||
}
|
||||
|
||||
export function isAutocomplete(interaction: Interaction): interaction is AutocompleteInteraction {
|
||||
return interaction.type === Constants.InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE;
|
||||
}
|
||||
|
||||
export function isPing(interaction: Interaction): interaction is PingInteraction {
|
||||
return interaction.type === Constants.InteractionTypes.PING;
|
||||
}
|
||||
|
||||
export function commandHasName(interaction: Interaction, name: string): boolean {
|
||||
return isApplicationCommand(interaction) && interaction.data.name === name;
|
||||
}
|
||||
|
||||
export function commandHasIdPrefix(interaction: Interaction, prefix: string): boolean {
|
||||
return (isModalSubmit(interaction) || isMessageComponent(interaction)) && interaction.data.custom_id.startsWith(prefix);
|
||||
}
|
||||
|
||||
export function getCommandName(interaction: ExecutableInteraction): string | undefined {
|
||||
if (isApplicationCommand(interaction) || isAutocomplete(interaction)) {
|
||||
return interaction.data.name;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
63
packages/discord/src/commands/command-injection.ts
Normal file
63
packages/discord/src/commands/command-injection.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { type InteractionModalContent, type Component } from '@projectdysnomia/dysnomia';
|
||||
import type { CommandContext, PartialContext } from './command-context.type';
|
||||
import { isApplicationCommand, isMessageComponent } from './command-helpers';
|
||||
import type { ExecutableInteraction } from './command-handler';
|
||||
|
||||
export function injectInteraction(interaction: ExecutableInteraction, ctx: PartialContext): [ExecutableInteraction, CommandContext] {
|
||||
// Wrap the interaction methods to inject command tracking ids into all custom_ids for modals and components.
|
||||
if (ctx.state.name && (isApplicationCommand(interaction) || isMessageComponent(interaction))) {
|
||||
const _originalCreateModal = interaction.createModal.bind(interaction);
|
||||
interaction.createModal = (content: InteractionModalContent) => {
|
||||
validateCustomIdLength(content.custom_id);
|
||||
content.custom_id = `${content.custom_id}_${ctx.state.id}`;
|
||||
return _originalCreateModal(content);
|
||||
};
|
||||
|
||||
const _originalCreateMessage = interaction.createMessage.bind(interaction);
|
||||
interaction.createMessage = (content) => {
|
||||
if (typeof content === 'string') return _originalCreateMessage(content);
|
||||
if (content.components) {
|
||||
addCommandIdToComponentCustomIds(content.components, ctx.state.id);
|
||||
}
|
||||
return _originalCreateMessage(content);
|
||||
};
|
||||
|
||||
const _originalEditMessage = interaction.editMessage.bind(interaction);
|
||||
interaction.editMessage = (messageID, content) => {
|
||||
if (typeof content === 'string') return _originalEditMessage(messageID, content);
|
||||
if (content.components) {
|
||||
addCommandIdToComponentCustomIds(content.components, ctx.state.id);
|
||||
}
|
||||
return _originalEditMessage(messageID, content);
|
||||
};
|
||||
|
||||
const _originalCreateFollowup = interaction.createFollowup.bind(interaction);
|
||||
interaction.createFollowup = (content) => {
|
||||
if (typeof content === 'string') return _originalCreateFollowup(content);
|
||||
if (content.components) {
|
||||
addCommandIdToComponentCustomIds(content.components, ctx.state.id);
|
||||
}
|
||||
return _originalCreateFollowup(content);
|
||||
};
|
||||
}
|
||||
return [interaction, ctx as CommandContext];
|
||||
}
|
||||
|
||||
function validateCustomIdLength(customId: string) {
|
||||
if (customId.length > 80) {
|
||||
throw new Error(`Custom ID too long: ${customId.length} characters (max 80) with this framework. Consider using shorter IDs.`);
|
||||
}
|
||||
}
|
||||
|
||||
function addCommandIdToComponentCustomIds(components: Component[], commandId: string) {
|
||||
components.forEach((component) => {
|
||||
if (!component) return;
|
||||
if ('custom_id' in component) {
|
||||
validateCustomIdLength(component.custom_id as string);
|
||||
component.custom_id = `${component.custom_id}_${commandId}`;
|
||||
}
|
||||
if ('components' in component && Array.isArray(component.components)) {
|
||||
addCommandIdToComponentCustomIds(component.components, commandId);
|
||||
}
|
||||
});
|
||||
}
|
||||
56
packages/discord/src/commands/command-state.ts
Normal file
56
packages/discord/src/commands/command-state.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { createReactiveState } from '@star-kitten/util/reactive-state.js';
|
||||
import type { PartialContext } from './command-context.type';
|
||||
import { isApplicationCommand, isAutocomplete } from './command-helpers';
|
||||
import type { ExecutableInteraction } from './command-handler';
|
||||
|
||||
export interface CommandState<T = any> {
|
||||
id: string; // unique id for this command instance
|
||||
name: string; // command name
|
||||
data: T; // internal data storage
|
||||
}
|
||||
|
||||
export async function getCommandState<T>(interaction: ExecutableInteraction, ctx: PartialContext): Promise<CommandState<T>> {
|
||||
const id = instanceIdFromInteraction(interaction);
|
||||
|
||||
let state: CommandState<T>;
|
||||
|
||||
// get state from kv store if possible
|
||||
if (ctx.kv.has(`command-state:${id}`)) {
|
||||
state = await ctx.kv.get<CommandState<T>>(`command-state:${id}`);
|
||||
}
|
||||
if (!state) {
|
||||
state = { id: id, name: '', data: {} as T };
|
||||
}
|
||||
const [reactiveState, subscribe] = createReactiveState(state);
|
||||
subscribe(async (newState) => {
|
||||
if (ctx.kv) {
|
||||
await ctx.kv.set(`command-state:${id}`, newState);
|
||||
}
|
||||
});
|
||||
ctx.state = reactiveState;
|
||||
return reactiveState;
|
||||
}
|
||||
|
||||
function instanceIdFromInteraction(interaction: ExecutableInteraction) {
|
||||
if (isAutocomplete(interaction)) {
|
||||
// autocomplete should not be stateful, they get no id
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isApplicationCommand(interaction)) {
|
||||
// for application commands, we create a new instance id
|
||||
const instance_id = crypto.randomUUID();
|
||||
return instance_id;
|
||||
}
|
||||
|
||||
const interact = interaction;
|
||||
const customId: string = interact.data.custom_id;
|
||||
const commandId = customId.split('_').pop();
|
||||
interaction;
|
||||
// command id should be a uuid
|
||||
if (commandId && /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(commandId)) {
|
||||
return commandId;
|
||||
}
|
||||
console.error(`Invalid command id extracted from interaction: ${customId}`);
|
||||
return '';
|
||||
}
|
||||
59
packages/discord/src/commands/handle-commands.test.ts
Normal file
59
packages/discord/src/commands/handle-commands.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { expect, test, mock, beforeEach, afterEach } from 'bun:test';
|
||||
import { handleCommands } from './handle-commands';
|
||||
import { CommandInteraction, Constants, ModalSubmitInteraction, type ApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import type { CommandHandler } from '../../dist';
|
||||
|
||||
let commands: Record<string, CommandHandler<ApplicationCommandStructure>>;
|
||||
|
||||
beforeEach(() => {
|
||||
commands = {};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
commands = {};
|
||||
});
|
||||
|
||||
mock.module('./command-helpers', () => ({
|
||||
getCommandName: () => 'testCommand',
|
||||
}));
|
||||
|
||||
test('handleCommands executes command when interaction is CommandInteraction and command exists', async () => {
|
||||
const mockExecute = mock(() => Promise.resolve());
|
||||
const mockCommand = { definition: { name: 'testCommand' } as any, execute: mockExecute };
|
||||
commands['testCommand'] = mockCommand;
|
||||
|
||||
const mockInteraction = {
|
||||
type: Constants.InteractionTypes.APPLICATION_COMMAND,
|
||||
data: { name: 'testCommand' },
|
||||
} as any;
|
||||
Object.setPrototypeOf(mockInteraction, CommandInteraction.prototype);
|
||||
|
||||
handleCommands(mockInteraction, commands, {} as any);
|
||||
|
||||
expect(mockExecute).toHaveBeenCalledWith(mockInteraction, expect.any(Object));
|
||||
});
|
||||
|
||||
test('handleCommands executes command when interaction is CommandInteraction and command exists', async () => {
|
||||
const mockExecute = mock(() => Promise.resolve());
|
||||
const mockCommand = { definition: { name: 'testCommand' } as any, execute: mockExecute };
|
||||
commands['testCommand'] = mockCommand;
|
||||
|
||||
const mockInteraction = {
|
||||
type: Constants.InteractionTypes.MODAL_SUBMIT,
|
||||
data: { name: 'testCommand' },
|
||||
} as any;
|
||||
Object.setPrototypeOf(mockInteraction, ModalSubmitInteraction.prototype);
|
||||
|
||||
handleCommands(mockInteraction, commands, {} as any);
|
||||
|
||||
expect(mockExecute).toHaveBeenCalledWith(mockInteraction, expect.any(Object));
|
||||
});
|
||||
|
||||
test('handleCommands does nothing when interaction not a CommandInteraction, ModalSubmitInteraction, MessageComponentInteraction, or AutoCompleteInteraction', () => {
|
||||
const mockInteraction = {
|
||||
instanceof: (cls: any) => false,
|
||||
} as any;
|
||||
|
||||
// Should not throw or do anything
|
||||
expect(() => handleCommands(mockInteraction, commands, {} as any)).not.toThrow();
|
||||
});
|
||||
73
packages/discord/src/commands/handle-commands.ts
Normal file
73
packages/discord/src/commands/handle-commands.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { type ApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import { getCommandName, isApplicationCommand, isAutocomplete, isMessageComponent, isModalSubmit } from './command-helpers';
|
||||
import type { PartialContext } from './command-context.type';
|
||||
import type { CommandHandler, ExecutableInteraction } from './command-handler';
|
||||
import { injectInteraction } from './command-injection';
|
||||
import { getCommandState } from './command-state';
|
||||
|
||||
export async function handleCommands(
|
||||
interaction: ExecutableInteraction,
|
||||
commands: Record<string, CommandHandler<ApplicationCommandStructure>>,
|
||||
ctx: PartialContext,
|
||||
) {
|
||||
ctx.state = await getCommandState(interaction, ctx);
|
||||
if (!ctx.state.name) {
|
||||
ctx.state.name = getCommandName(interaction);
|
||||
}
|
||||
|
||||
if (isAutocomplete(interaction) && ctx.state.name) {
|
||||
const acCommand = commands[ctx.state.name];
|
||||
return acCommand.execute(interaction, ctx as any);
|
||||
}
|
||||
|
||||
if (!ctx.state.id) {
|
||||
console.error(`No command ID found for interaction ${interaction.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const command = commands[ctx.state.name || ''];
|
||||
if (!command) {
|
||||
console.warn(`No command found for interaction: ${JSON.stringify(interaction, undefined, 2)}`);
|
||||
return;
|
||||
}
|
||||
cleanInteractionCustomIds(interaction, ctx.state.id);
|
||||
const [injectedInteraction, fullContext] = await injectInteraction(interaction, ctx);
|
||||
return command.execute(injectedInteraction, fullContext);
|
||||
}
|
||||
|
||||
export function initializeCommandHandling(commands: Record<string, CommandHandler<ApplicationCommandStructure>>, ctx: PartialContext) {
|
||||
ctx.client.on('interactionCreate', async (interaction) => {
|
||||
if (isApplicationCommand(interaction) || isModalSubmit(interaction) || isMessageComponent(interaction) || isAutocomplete(interaction)) {
|
||||
handleCommands(interaction, commands, ctx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cleanInteractionCustomIds(interaction: ExecutableInteraction, id: string) {
|
||||
if ('components' in interaction && Array.isArray(interaction.components) && id) {
|
||||
removeCommandIdFromComponentCustomIds(interaction.components, id);
|
||||
}
|
||||
if ('data' in interaction && id) {
|
||||
if ('custom_id' in interaction.data && typeof interaction.data.custom_id === 'string') {
|
||||
interaction.data.custom_id = interaction.data.custom_id.replace(`_${id}`, '');
|
||||
}
|
||||
if ('components' in interaction.data && Array.isArray(interaction.data.components)) {
|
||||
removeCommandIdFromComponentCustomIds(interaction.data.components as any, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeCommandIdFromComponentCustomIds(components: { custom_id?: string; components?: any[] }[], commandId: string) {
|
||||
components.forEach((component) => {
|
||||
if ('custom_id' in component) {
|
||||
component.custom_id = component.custom_id.replace(`_${commandId}`, '');
|
||||
}
|
||||
if ('components' in component && Array.isArray(component.components)) {
|
||||
removeCommandIdFromComponentCustomIds(component.components, commandId);
|
||||
}
|
||||
|
||||
if ('component' in component && 'custom_id' in (component as any).component && Array.isArray(component.components)) {
|
||||
(component.component as any).custom_id = (component.component as any).custom_id.replace(`_${commandId}`, '');
|
||||
}
|
||||
});
|
||||
}
|
||||
19
packages/discord/src/commands/import-commands.test.ts
Normal file
19
packages/discord/src/commands/import-commands.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { expect, test, mock } from 'bun:test';
|
||||
import { importCommands } from './import-commands';
|
||||
import path from 'node:path';
|
||||
|
||||
test('importCommands imports commands from files matching pattern', async () => {
|
||||
const commands = await importCommands('**/*.command.{js,ts}', path.join(__dirname, '../../fixtures'));
|
||||
|
||||
expect(commands).toHaveProperty('test1');
|
||||
expect(commands).toHaveProperty('test2');
|
||||
expect(commands.test1.definition.name).toBe('test1');
|
||||
expect(commands.test2.definition.name).toBe('test2');
|
||||
});
|
||||
|
||||
test('importCommands uses default pattern and baseDir', async () => {
|
||||
const commands = await importCommands();
|
||||
|
||||
// Since there are no command files in src, it should be empty
|
||||
expect(commands).toEqual({});
|
||||
});
|
||||
19
packages/discord/src/commands/import-commands.ts
Normal file
19
packages/discord/src/commands/import-commands.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Glob } from 'bun';
|
||||
import { join } from 'node:path';
|
||||
import type { CommandHandler } from './command-handler';
|
||||
import type { ApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
|
||||
export async function importCommands(
|
||||
pattern: string = '**/*.command.{js,ts}',
|
||||
baseDir: string = join(process.cwd(), 'src'),
|
||||
commandRegistry: Record<string, CommandHandler<ApplicationCommandStructure>> = {},
|
||||
): Promise<Record<string, CommandHandler<ApplicationCommandStructure>>> {
|
||||
const glob = new Glob(pattern);
|
||||
|
||||
for await (const file of glob.scan({ cwd: baseDir, absolute: true })) {
|
||||
const command = (await import(file)).default as CommandHandler<ApplicationCommandStructure>;
|
||||
commandRegistry[command.definition.name] = command;
|
||||
}
|
||||
|
||||
return commandRegistry;
|
||||
}
|
||||
8
packages/discord/src/commands/index.ts
Normal file
8
packages/discord/src/commands/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export * from './command-handler';
|
||||
export * from './import-commands';
|
||||
export * from './handle-commands';
|
||||
export * from './command-helpers';
|
||||
export * from './register-commands';
|
||||
export * from './command-context.type';
|
||||
export * from './command-state';
|
||||
export * from './option-builders';
|
||||
80
packages/discord/src/commands/option-builders.ts
Normal file
80
packages/discord/src/commands/option-builders.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
Constants,
|
||||
type ApplicationCommandOptions,
|
||||
type ApplicationCommandOptionsBoolean,
|
||||
type ApplicationCommandOptionsInteger,
|
||||
type ApplicationCommandOptionsMentionable,
|
||||
type ApplicationCommandOptionsNumber,
|
||||
type ApplicationCommandOptionsRole,
|
||||
type ApplicationCommandOptionsString,
|
||||
type ApplicationCommandOptionsSubCommand,
|
||||
type ApplicationCommandOptionsSubCommandGroup,
|
||||
type ApplicationCommandOptionsUser,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
|
||||
export type StringOptionDefinition = Omit<ApplicationCommandOptionsString, 'type'> & { autocomplete?: boolean };
|
||||
export function stringOption(options: StringOptionDefinition): ApplicationCommandOptionsString {
|
||||
const def = options as ApplicationCommandOptionsString;
|
||||
def.type = Constants.ApplicationCommandOptionTypes.STRING;
|
||||
return def;
|
||||
}
|
||||
export type IntegerOptionDefinition = Omit<ApplicationCommandOptionsInteger, 'type'> & { autocomplete?: boolean };
|
||||
export function integerOption(options: IntegerOptionDefinition): ApplicationCommandOptionsInteger {
|
||||
const def = options as ApplicationCommandOptionsInteger;
|
||||
def.type = Constants.ApplicationCommandOptionTypes.INTEGER;
|
||||
return def;
|
||||
}
|
||||
export type BooleanOptionDefinition = Omit<ApplicationCommandOptionsBoolean, 'type'>;
|
||||
export function booleanOption(options: BooleanOptionDefinition): ApplicationCommandOptionsBoolean {
|
||||
const def = options as ApplicationCommandOptionsBoolean;
|
||||
def.type = Constants.ApplicationCommandOptionTypes.BOOLEAN;
|
||||
return def;
|
||||
}
|
||||
export type UserOptionDefinition = Omit<ApplicationCommandOptionsUser, 'type'> & { autocomplete?: boolean };
|
||||
export function userOption(options: UserOptionDefinition): ApplicationCommandOptionsUser {
|
||||
const def = options as ApplicationCommandOptionsUser;
|
||||
def.type = Constants.ApplicationCommandOptionTypes.USER;
|
||||
return def;
|
||||
}
|
||||
export type ChannelOptionDefinition = Omit<ApplicationCommandOptions, 'type'> & { autocomplete?: boolean };
|
||||
export function channelOption(options: ChannelOptionDefinition): ApplicationCommandOptions {
|
||||
const def = options as ApplicationCommandOptions;
|
||||
def.type = Constants.ApplicationCommandOptionTypes.CHANNEL;
|
||||
return def;
|
||||
}
|
||||
export type RoleOptionDefinition = Omit<ApplicationCommandOptionsRole, 'type'> & { autocomplete?: boolean };
|
||||
export function roleOption(options: RoleOptionDefinition): ApplicationCommandOptionsRole {
|
||||
const def = options as ApplicationCommandOptionsRole;
|
||||
def.type = Constants.ApplicationCommandOptionTypes.ROLE;
|
||||
return def;
|
||||
}
|
||||
export type MentionableOptionDefinition = Omit<ApplicationCommandOptionsMentionable, 'type'>;
|
||||
export function mentionableOption(options: MentionableOptionDefinition): ApplicationCommandOptionsMentionable {
|
||||
const def = options as ApplicationCommandOptionsMentionable;
|
||||
def.type = Constants.ApplicationCommandOptionTypes.MENTIONABLE;
|
||||
return def;
|
||||
}
|
||||
export type NumberOptionDefinition = Omit<ApplicationCommandOptionsNumber, 'type'> & { autocomplete?: boolean };
|
||||
export function numberOption(options: NumberOptionDefinition): ApplicationCommandOptionsNumber {
|
||||
const def = options as ApplicationCommandOptionsNumber;
|
||||
def.type = Constants.ApplicationCommandOptionTypes.NUMBER;
|
||||
return def;
|
||||
}
|
||||
export type AttachmentOptionDefinition = Omit<ApplicationCommandOptions, 'type'>;
|
||||
export function attachmentOption(options: AttachmentOptionDefinition): ApplicationCommandOptions {
|
||||
const def = options as ApplicationCommandOptions;
|
||||
def.type = Constants.ApplicationCommandOptionTypes.ATTACHMENT;
|
||||
return def;
|
||||
}
|
||||
export type SubCommandOptionDefinition = Omit<ApplicationCommandOptionsSubCommand, 'type'>;
|
||||
export function subCommandOption(options: SubCommandOptionDefinition): ApplicationCommandOptionsSubCommand {
|
||||
const def = options as ApplicationCommandOptionsSubCommand;
|
||||
def.type = Constants.ApplicationCommandOptionTypes.SUB_COMMAND;
|
||||
return def;
|
||||
}
|
||||
export type SubCommandGroupOptionDefinition = Omit<ApplicationCommandOptionsSubCommandGroup, 'type'>;
|
||||
export function subCommandGroupOption(options: SubCommandGroupOptionDefinition): ApplicationCommandOptionsSubCommandGroup {
|
||||
const def = options as ApplicationCommandOptionsSubCommandGroup;
|
||||
def.type = Constants.ApplicationCommandOptionTypes.SUB_COMMAND_GROUP;
|
||||
return def;
|
||||
}
|
||||
11
packages/discord/src/commands/register-commands.ts
Normal file
11
packages/discord/src/commands/register-commands.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { ApplicationCommandStructure, Client } from '@projectdysnomia/dysnomia';
|
||||
|
||||
export async function registerCommands(client: Client, commands: ApplicationCommandStructure[]) {
|
||||
if (!client) throw new Error('Client not initialized');
|
||||
if (!(await client.getCommands()).length || process.env.RESET_COMMANDS === 'true' || process.env.NODE_ENV === 'development') {
|
||||
console.debug('Registering commands...');
|
||||
const response = await client.bulkEditCommands(commands);
|
||||
console.debug(`Registered ${response.length} commands.`);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
1
packages/discord/src/common/index.ts
Normal file
1
packages/discord/src/common/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './text';
|
||||
2
packages/discord/src/common/text.ts
Normal file
2
packages/discord/src/common/text.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const WHITE_SPACE = ' '; // non-breaking space
|
||||
export const BREAKING_WHITE_SPACE = '\u200B';
|
||||
314
packages/discord/src/components/builders.ts
Normal file
314
packages/discord/src/components/builders.ts
Normal file
@@ -0,0 +1,314 @@
|
||||
import {
|
||||
Constants,
|
||||
type ActionRow,
|
||||
type Button,
|
||||
type ChannelSelectMenu,
|
||||
type GuildChannelTypes,
|
||||
type MentionableSelectMenu,
|
||||
type PartialEmoji,
|
||||
type RoleSelectMenu,
|
||||
type StringSelectMenu,
|
||||
type TextInput,
|
||||
type UserSelectMenu,
|
||||
type LabelComponent,
|
||||
type ContainerComponent,
|
||||
type TextDisplayComponent,
|
||||
type SectionComponent,
|
||||
type MediaGalleryComponent,
|
||||
type SeparatorComponent,
|
||||
type FileComponent,
|
||||
type InteractionButton,
|
||||
type URLButton,
|
||||
type PremiumButton,
|
||||
type ThumbnailComponent,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
|
||||
export type ActionRowItem = Button | StringSelectMenu | UserSelectMenu | RoleSelectMenu | MentionableSelectMenu | ChannelSelectMenu;
|
||||
export const createActionRow = (...components: ActionRowItem[]): ActionRow => ({
|
||||
type: Constants.ComponentTypes.ACTION_ROW,
|
||||
components,
|
||||
});
|
||||
|
||||
export enum ButtonStyle {
|
||||
PRIMARY = 1,
|
||||
SECONDARY = 2,
|
||||
SUCCESS = 3,
|
||||
DANGER = 4,
|
||||
}
|
||||
|
||||
export interface ButtonOptions {
|
||||
style?: ButtonStyle;
|
||||
emoji?: PartialEmoji;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const createButton = (label: string, custom_id: string, options?: ButtonOptions): InteractionButton => ({
|
||||
type: Constants.ComponentTypes.BUTTON,
|
||||
style: options?.style ?? Constants.ButtonStyles.PRIMARY,
|
||||
label,
|
||||
custom_id,
|
||||
...options,
|
||||
});
|
||||
|
||||
export interface URLButtonOptions {
|
||||
emoji?: PartialEmoji;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const createURLButton = (label: string, url: string, options?: URLButtonOptions): URLButton => ({
|
||||
type: Constants.ComponentTypes.BUTTON,
|
||||
style: Constants.ButtonStyles.LINK,
|
||||
label,
|
||||
url,
|
||||
...options,
|
||||
});
|
||||
|
||||
export interface PremiumButtonOptions {
|
||||
emoji?: PartialEmoji;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const createPremiumButton = (sku_id: string, options?: PremiumButtonOptions): PremiumButton => ({
|
||||
type: Constants.ComponentTypes.BUTTON,
|
||||
style: Constants.ButtonStyles.PREMIUM,
|
||||
sku_id,
|
||||
...options,
|
||||
});
|
||||
|
||||
export interface StringSelectOpts {
|
||||
placeholder?: string;
|
||||
min_values?: number;
|
||||
max_values?: number;
|
||||
disabled?: boolean;
|
||||
required?: boolean; // Note: not actually a property of StringSelectMenu, but useful for modals
|
||||
}
|
||||
|
||||
export interface StringSelectOption {
|
||||
label: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
emoji?: {
|
||||
name?: string;
|
||||
id?: string;
|
||||
animated?: boolean;
|
||||
};
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
export const createStringSelect = (
|
||||
custom_id: string,
|
||||
selectOpts: StringSelectOpts,
|
||||
...options: StringSelectOption[]
|
||||
): StringSelectMenu => ({
|
||||
type: Constants.ComponentTypes.STRING_SELECT,
|
||||
custom_id,
|
||||
options,
|
||||
placeholder: selectOpts.placeholder ?? '',
|
||||
min_values: selectOpts.min_values ?? 1,
|
||||
max_values: selectOpts.max_values ?? 1,
|
||||
disabled: selectOpts.disabled ?? false,
|
||||
required: selectOpts.required ?? false, // Note: not actually a property of StringSelectMenu, but useful for modals
|
||||
});
|
||||
|
||||
export interface TextInputOptions {
|
||||
isParagraph?: boolean;
|
||||
label?: string;
|
||||
min_length?: number;
|
||||
max_length?: number;
|
||||
required?: boolean;
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export const createTextInput = (custom_id: string, options?: TextInputOptions): TextInput => ({
|
||||
type: Constants.ComponentTypes.TEXT_INPUT,
|
||||
custom_id,
|
||||
style: options.isParagraph ? Constants.TextInputStyles.PARAGRAPH : Constants.TextInputStyles.SHORT,
|
||||
label: options?.label ?? '',
|
||||
min_length: options?.min_length ?? 0,
|
||||
max_length: options?.max_length ?? 4000,
|
||||
required: options?.required ?? false,
|
||||
value: options?.value ?? '',
|
||||
placeholder: options?.placeholder ?? '',
|
||||
});
|
||||
|
||||
export interface UserSelectOptions {
|
||||
placeholder?: string;
|
||||
min_values?: number;
|
||||
max_values?: number;
|
||||
disabled?: boolean;
|
||||
default_values?: Array<{ id: string; type: 'user' }>;
|
||||
}
|
||||
|
||||
export const createUserSelect = (custom_id: string, options?: UserSelectOptions): UserSelectMenu => ({
|
||||
type: Constants.ComponentTypes.USER_SELECT,
|
||||
custom_id,
|
||||
placeholder: options?.placeholder ?? '',
|
||||
min_values: options?.min_values ?? 1,
|
||||
max_values: options?.max_values ?? 1,
|
||||
disabled: options?.disabled ?? false,
|
||||
default_values: options?.default_values ?? [],
|
||||
});
|
||||
|
||||
export interface RoleSelectOptions {
|
||||
placeholder?: string;
|
||||
min_values?: number;
|
||||
max_values?: number;
|
||||
disabled?: boolean;
|
||||
default_values?: Array<{ id: string; type: 'role' }>;
|
||||
}
|
||||
|
||||
export const createRoleSelect = (custom_id: string, options?: RoleSelectOptions): RoleSelectMenu => ({
|
||||
type: Constants.ComponentTypes.ROLE_SELECT,
|
||||
custom_id,
|
||||
placeholder: options?.placeholder ?? '',
|
||||
min_values: options?.min_values ?? 1,
|
||||
max_values: options?.max_values ?? 1,
|
||||
disabled: options?.disabled ?? false,
|
||||
default_values: options?.default_values ?? [],
|
||||
});
|
||||
|
||||
export interface MentionableSelectOptions {
|
||||
placeholder?: string;
|
||||
min_values?: number;
|
||||
max_values?: number;
|
||||
disabled?: boolean;
|
||||
default_values?: Array<{ id: string; type: 'user' | 'role' }>;
|
||||
}
|
||||
|
||||
export const createMentionableSelect = (custom_id: string, options?: MentionableSelectOptions): MentionableSelectMenu => ({
|
||||
type: Constants.ComponentTypes.MENTIONABLE_SELECT,
|
||||
custom_id,
|
||||
placeholder: options?.placeholder ?? '',
|
||||
min_values: options?.min_values ?? 1,
|
||||
max_values: options?.max_values ?? 1,
|
||||
disabled: options?.disabled ?? false,
|
||||
default_values: options?.default_values ?? [],
|
||||
});
|
||||
|
||||
export interface ChannelSelectOptions {
|
||||
channel_types?: GuildChannelTypes[];
|
||||
placeholder?: string;
|
||||
min_values?: number;
|
||||
max_values?: number;
|
||||
disabled?: boolean;
|
||||
default_values?: Array<{ id: string; type: 'channel' }>;
|
||||
}
|
||||
|
||||
export const createChannelSelect = (custom_id: string, options?: ChannelSelectOptions): ChannelSelectMenu => ({
|
||||
type: Constants.ComponentTypes.CHANNEL_SELECT,
|
||||
custom_id,
|
||||
channel_types: options?.channel_types ?? [],
|
||||
placeholder: options?.placeholder ?? '',
|
||||
min_values: options?.min_values ?? 1,
|
||||
max_values: options?.max_values ?? 1,
|
||||
disabled: options?.disabled ?? false,
|
||||
default_values: options?.default_values ?? [],
|
||||
});
|
||||
|
||||
export interface SectionOptions {
|
||||
components: Array<TextDisplayComponent>;
|
||||
accessory: Button | ThumbnailComponent;
|
||||
}
|
||||
|
||||
export const createSection = (accessory: Button | ThumbnailComponent, ...components: Array<TextDisplayComponent>): SectionComponent => ({
|
||||
type: Constants.ComponentTypes.SECTION,
|
||||
accessory,
|
||||
components,
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a text display component where the text will be displayed similar to a message: supports markdown
|
||||
* @param content The text content to display.
|
||||
* @returns The created text display component.
|
||||
*/
|
||||
export const createTextDisplay = (content: string) => ({
|
||||
type: Constants.ComponentTypes.TEXT_DISPLAY,
|
||||
content,
|
||||
});
|
||||
|
||||
export interface ThumbnailOptions {
|
||||
media: {
|
||||
url: string; // Supports arbitrary urls and attachment://<filename> references
|
||||
};
|
||||
description?: string;
|
||||
spoiler?: boolean;
|
||||
}
|
||||
|
||||
export const createThumbnail = (url: string, description?: string, spoiler?: boolean): ThumbnailComponent => ({
|
||||
type: Constants.ComponentTypes.THUMBNAIL,
|
||||
media: {
|
||||
url,
|
||||
},
|
||||
description,
|
||||
spoiler,
|
||||
});
|
||||
|
||||
export interface MediaItem {
|
||||
url: string; // Supports arbitrary urls and attachment://<filename> references
|
||||
description?: string;
|
||||
spoiler?: boolean;
|
||||
}
|
||||
|
||||
export const createMediaGallery = (...items: MediaItem[]): MediaGalleryComponent => ({
|
||||
type: Constants.ComponentTypes.MEDIA_GALLERY,
|
||||
items: items.map((item) => ({
|
||||
type: Constants.ComponentTypes.FILE,
|
||||
media: { url: item.url },
|
||||
description: item.description,
|
||||
spoiler: item.spoiler,
|
||||
})),
|
||||
});
|
||||
|
||||
export interface FileOptions {
|
||||
url: string; // Supports only attachment://<filename> references
|
||||
spoiler?: boolean;
|
||||
}
|
||||
|
||||
export const createFile = (url: string, spoiler?: boolean): FileComponent => ({
|
||||
type: Constants.ComponentTypes.FILE,
|
||||
file: {
|
||||
url,
|
||||
},
|
||||
spoiler,
|
||||
});
|
||||
|
||||
export enum Padding {
|
||||
SMALL = 1,
|
||||
LARGE = 2,
|
||||
}
|
||||
|
||||
export interface SeparatorOptions {
|
||||
divider?: boolean;
|
||||
spacing?: Padding;
|
||||
}
|
||||
export const createSeparator = (spacing?: Padding, divider?: boolean): SeparatorComponent => ({
|
||||
type: Constants.ComponentTypes.SEPARATOR,
|
||||
divider,
|
||||
spacing: spacing ?? Padding.SMALL,
|
||||
});
|
||||
|
||||
export interface ContainerOptions {
|
||||
accent_color?: number;
|
||||
spoiler?: boolean;
|
||||
}
|
||||
|
||||
export type ContainerItems =
|
||||
| ActionRow
|
||||
| TextDisplayComponent
|
||||
| SectionComponent
|
||||
| MediaGalleryComponent
|
||||
| SeparatorComponent
|
||||
| FileComponent;
|
||||
|
||||
export const createContainer = (options: ContainerOptions, ...components: ContainerItems[]): ContainerComponent => ({
|
||||
type: Constants.ComponentTypes.CONTAINER,
|
||||
...options,
|
||||
components,
|
||||
});
|
||||
|
||||
export const createModalLabel = (label: string, component: TextInput | StringSelectMenu): LabelComponent => ({
|
||||
type: Constants.ComponentTypes.LABEL,
|
||||
label,
|
||||
component,
|
||||
});
|
||||
23
packages/discord/src/components/helpers.ts
Normal file
23
packages/discord/src/components/helpers.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
Constants,
|
||||
type ComponentBase,
|
||||
type ModalSubmitInteractionDataLabelComponent,
|
||||
type ModalSubmitInteractionDataStringSelectComponent,
|
||||
type ModalSubmitInteractionDataTextInputComponent,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
|
||||
export function isModalLabel(component: ComponentBase): component is ModalSubmitInteractionDataLabelComponent {
|
||||
return component.type === Constants.ComponentTypes.LABEL;
|
||||
}
|
||||
|
||||
export function isModalTextInput(component: ComponentBase): component is ModalSubmitInteractionDataTextInputComponent {
|
||||
return component.type === Constants.ComponentTypes.TEXT_INPUT;
|
||||
}
|
||||
|
||||
export function isModalSelect(component: ComponentBase): component is ModalSubmitInteractionDataStringSelectComponent {
|
||||
return component.type === Constants.ComponentTypes.STRING_SELECT;
|
||||
}
|
||||
|
||||
export function componentHasIdPrefix(component: ComponentBase, prefix: string): boolean {
|
||||
return (isModalTextInput(component) || isModalSelect(component)) && component.custom_id.startsWith(prefix);
|
||||
}
|
||||
2
packages/discord/src/components/index.ts
Normal file
2
packages/discord/src/components/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './helpers';
|
||||
export * from './builders';
|
||||
54
packages/discord/src/core/bot.ts
Normal file
54
packages/discord/src/core/bot.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { importCommands, initializeCommandHandling, registerCommands } from '@commands';
|
||||
import { Client } from '@projectdysnomia/dysnomia';
|
||||
import kv, { asyncKV } from '@star-kitten/util/kv.js';
|
||||
import type { KVStore } from './kv-store.type.ts';
|
||||
import type { Cache } from './cache.type.ts';
|
||||
|
||||
export interface DiscordBotOptions {
|
||||
token?: string;
|
||||
intents?: number[];
|
||||
commandPattern?: string;
|
||||
commandBaseDir?: string;
|
||||
keyStore?: KVStore;
|
||||
cache?: Cache;
|
||||
onError?: (error: Error) => void;
|
||||
onReady?: () => void;
|
||||
}
|
||||
|
||||
export function startDiscordBot({
|
||||
token = process.env.DISCORD_BOT_TOKEN || '',
|
||||
intents = [],
|
||||
commandPattern = '**/*.command.{js,ts}',
|
||||
commandBaseDir = 'src',
|
||||
keyStore = asyncKV,
|
||||
cache = kv,
|
||||
onError,
|
||||
onReady,
|
||||
}: DiscordBotOptions = {}): Client {
|
||||
const client = new Client(`Bot ${token}`, {
|
||||
gateway: {
|
||||
intents,
|
||||
},
|
||||
});
|
||||
|
||||
client.on('ready', async () => {
|
||||
console.debug(`Logged in as ${client.user?.username}#${client.user?.discriminator}`);
|
||||
onReady?.();
|
||||
const commands = await importCommands(commandPattern, commandBaseDir);
|
||||
await registerCommands(
|
||||
client,
|
||||
Object.values(commands).map((cmd) => cmd.definition),
|
||||
);
|
||||
initializeCommandHandling(commands, { client, cache, kv: keyStore });
|
||||
console.debug('Bot is ready and command handling is initialized.');
|
||||
});
|
||||
|
||||
client.on('error', (error) => {
|
||||
console.error('An error occurred:', error);
|
||||
onError?.(error);
|
||||
});
|
||||
|
||||
client.connect().catch(console.error);
|
||||
|
||||
return client;
|
||||
}
|
||||
6
packages/discord/src/core/cache.type.ts
Normal file
6
packages/discord/src/core/cache.type.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface Cache {
|
||||
get: <T>(key: string) => T | undefined;
|
||||
set: <T>(key: string, value: T, ttl?: number | string) => boolean;
|
||||
del: (key: string | string[]) => number;
|
||||
has: (key: string) => boolean;
|
||||
}
|
||||
3
packages/discord/src/core/index.ts
Normal file
3
packages/discord/src/core/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './bot';
|
||||
export * from './cache.type';
|
||||
export * from './kv-store.type.ts';
|
||||
7
packages/discord/src/core/kv-store.type.ts.ts
Normal file
7
packages/discord/src/core/kv-store.type.ts.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface KVStore {
|
||||
get: <T>(key: string) => Promise<T | undefined>;
|
||||
set: (key: string, value: any) => Promise<boolean>;
|
||||
delete: (key: string) => Promise<number>;
|
||||
has: (key: string) => Promise<boolean>;
|
||||
clear: () => Promise<void>;
|
||||
}
|
||||
4
packages/discord/src/index.ts
Normal file
4
packages/discord/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './locales';
|
||||
export * from './commands';
|
||||
export * from './core';
|
||||
export * from './jsx';
|
||||
7
packages/discord/src/jsd/createElement.ts
Normal file
7
packages/discord/src/jsd/createElement.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export function createElement(tag: string, attrs: Record<string, any> = {}, ...children: any[]) {
|
||||
return {
|
||||
tag,
|
||||
attrs,
|
||||
children,
|
||||
};
|
||||
}
|
||||
2
packages/discord/src/jsd/index.ts
Normal file
2
packages/discord/src/jsd/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './parser';
|
||||
export * from './createElement';
|
||||
10
packages/discord/src/jsd/parser.test.ts
Normal file
10
packages/discord/src/jsd/parser.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import { parseJSDFile } from './parser_new';
|
||||
import path from 'node:path';
|
||||
|
||||
describe('parseJSDFile', () => {
|
||||
it('should parse a JSD file', async () => {
|
||||
const result = await parseJSDFile(path.join(__dirname, '../../fixtures/jsd/test.tsd'));
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
});
|
||||
97
packages/discord/src/jsd/parser.ts
Normal file
97
packages/discord/src/jsd/parser.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import parse, { type DOMNode } from 'html-dom-parser';
|
||||
import type { ChildNode } from 'domhandler';
|
||||
|
||||
const JSD_STRING = /\(\s*(<.*)>\s*\)/gs;
|
||||
|
||||
export async function parseJSDFile(filename: string) {
|
||||
const content = (await fs.readFile(filename)).toString();
|
||||
|
||||
const matches = JSD_STRING.exec(content);
|
||||
if (matches) {
|
||||
let html = matches[1] + '>';
|
||||
const root = parse(html);
|
||||
const translated = translate(root[0]);
|
||||
const str = content.replace(matches[1] + '>', translated);
|
||||
await fs.writeFile(filename.replace('.tsd', '.ts'), str);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
interface state {
|
||||
inInterpolation?: boolean;
|
||||
children?: string[][];
|
||||
parent?: Text[];
|
||||
}
|
||||
|
||||
function translate(root: DOMNode | ChildNode | null, state: state = {}): string | null {
|
||||
if (!root || typeof root !== 'object') return null;
|
||||
|
||||
let children = [];
|
||||
if ('children' in root && Array.isArray(root.children) && root.children.length > 0) {
|
||||
for (const child of root.children) {
|
||||
const translated = translate(child, state);
|
||||
if (translated) {
|
||||
if (state.inInterpolation && state.parent[state.children.length - 1] === child) {
|
||||
state.children[state.children.length - 1].push(translated);
|
||||
} else {
|
||||
children.push(translated);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ('nodeType' in root && root.nodeType === 3) {
|
||||
if (root.data.trim() === '') return null;
|
||||
return parseText(root.data.trim(), state, root);
|
||||
}
|
||||
|
||||
if ('name' in root && root.name) {
|
||||
let tagName = root.name || 'unknown';
|
||||
let attrs = 'attribs' in root ? root.attribs : {};
|
||||
return `StarKitten.createElement("${tagName}", ${JSON.stringify(attrs)}${children.length > 0 ? ', ' + children.join(', ') : ''})`;
|
||||
}
|
||||
}
|
||||
|
||||
const JSD_INTERPOLATION = /\{(.+)\}/gs;
|
||||
const JSD_START_EXP_INTERPOLATION = /\{(.+)\(/gs;
|
||||
const JSD_END_EXP_INTERPOLATION = /\)(.+)\}/gs;
|
||||
|
||||
function parseText(text: string, state: state = {}, parent: Text = {}): string {
|
||||
let interpolations = text.match(JSD_INTERPOLATION);
|
||||
if (!interpolations) {
|
||||
if (text.match(JSD_START_EXP_INTERPOLATION)) {
|
||||
state.inInterpolation = true;
|
||||
state.children = state.children || [[]];
|
||||
state.parent = state.parent || [];
|
||||
state.parent.push(parent);
|
||||
return text.substring(1, text.length - 1);
|
||||
} else if (text.match(JSD_END_EXP_INTERPOLATION)) {
|
||||
const combined = state.children?.[state.children.length - 1].join(' ');
|
||||
state.children?.[state.children.length - 1].splice(0);
|
||||
state.children?.pop();
|
||||
state.parent?.pop();
|
||||
if (state.children.length === 0) {
|
||||
state.inInterpolation = false;
|
||||
return combined + ' ' + text.substring(1, text.length - 1);
|
||||
}
|
||||
}
|
||||
return `"${text}"`;
|
||||
} else {
|
||||
text = replaceInterpolations(text);
|
||||
return `"${text}"`;
|
||||
}
|
||||
}
|
||||
|
||||
function replaceInterpolations(text: string, isOnJSON: boolean = false) {
|
||||
let interpolations = null;
|
||||
|
||||
while ((interpolations = JSD_INTERPOLATION.exec(text))) {
|
||||
if (isOnJSON) {
|
||||
text = text.replace(`"{${interpolations[1]}}"`, interpolations[1]);
|
||||
} else {
|
||||
text = text.replace(`{${interpolations[1]}}`, `"+ ${interpolations[1]} +"`);
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
101
packages/discord/src/jsd/parser_new.ts
Normal file
101
packages/discord/src/jsd/parser_new.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import * as acorn from 'acorn';
|
||||
import jsx from 'acorn-jsx';
|
||||
|
||||
const JSD_STRING = /\(\s*(<.*)>\s*\)/gs;
|
||||
|
||||
const parser = acorn.Parser.extend(jsx());
|
||||
|
||||
export async function parseJSDFile(filename: string) {
|
||||
const content = (await fs.readFile(filename)).toString();
|
||||
|
||||
const matches = JSD_STRING.exec(content);
|
||||
if (matches) {
|
||||
const jsxc = matches[1] + '>';
|
||||
const ast = parser.parse(jsxc, { ecmaVersion: 2020, sourceType: 'module' });
|
||||
const translated = traverseJSX((ast.body[0] as any).expression);
|
||||
const str = content.replace(matches[1] + '>', translated);
|
||||
await fs.writeFile(filename.replace('.tsd', '.ts'), str);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function traverseJSX(node: any): string {
|
||||
if (node.type === 'JSXElement') {
|
||||
const tag = node.openingElement.name.name;
|
||||
const attrs: Record<string, any> = {};
|
||||
for (const attr of node.openingElement.attributes) {
|
||||
if (attr.type === 'JSXAttribute') {
|
||||
const name = attr.name.name;
|
||||
const value = attr.value;
|
||||
if (value.type === 'Literal') {
|
||||
attrs[name] = value.value;
|
||||
} else if (value.type === 'JSXExpressionContainer') {
|
||||
attrs[name] = `{${generateCode(value.expression)}}`;
|
||||
} else if (value) {
|
||||
attrs[name] = value.raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
const children = [];
|
||||
for (const child of node.children) {
|
||||
const translated = traverseJSX(child);
|
||||
if (translated) {
|
||||
children.push(translated);
|
||||
}
|
||||
}
|
||||
return `StarKitten.createElement("${tag}", ${JSON.stringify(attrs)}${children.length > 0 ? ', ' + children.join(', ') : ''})`;
|
||||
} else if (node.type === 'JSXExpressionContainer') {
|
||||
const expr = generateCode(node.expression);
|
||||
if (node.expression.type === 'TemplateLiteral' || (node.expression.type === 'Literal' && typeof node.expression.value === 'string')) {
|
||||
return `""+ ${expr} +""`;
|
||||
} else {
|
||||
return expr;
|
||||
}
|
||||
} else if (node.type === 'JSXText') {
|
||||
const text = node.value.trim();
|
||||
if (text) {
|
||||
return `"${text}"`;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function generateCode(node: any): string {
|
||||
if (node.type === 'JSXElement') {
|
||||
return traverseJSX(node);
|
||||
} else if (node.type === 'Identifier') {
|
||||
return node.name;
|
||||
} else if (node.type === 'Literal') {
|
||||
return JSON.stringify(node.value);
|
||||
} else if (node.type === 'TemplateLiteral') {
|
||||
const quasis = node.quasis.map((q: any) => q.value.raw);
|
||||
const expressions = node.expressions.map((e: any) => generateCode(e));
|
||||
let result = quasis[0];
|
||||
for (let i = 0; i < expressions.length; i++) {
|
||||
result += '${' + expressions[i] + '}' + quasis[i + 1];
|
||||
}
|
||||
return '`' + result + '`';
|
||||
} else if (node.type === 'MemberExpression') {
|
||||
const op = node.optional ? '?.' : '.';
|
||||
return generateCode(node.object) + op + (node.computed ? '[' + generateCode(node.property) + ']' : generateCode(node.property));
|
||||
} else if (node.type === 'OptionalMemberExpression') {
|
||||
return generateCode(node.object) + '?.' + (node.computed ? '[' + generateCode(node.property) + ']' : generateCode(node.property));
|
||||
} else if (node.type === 'CallExpression') {
|
||||
return generateCode(node.callee) + '(' + node.arguments.map((a: any) => generateCode(a)).join(', ') + ')';
|
||||
} else if (node.type === 'BinaryExpression') {
|
||||
return generateCode(node.left) + ' ' + node.operator + ' ' + generateCode(node.right);
|
||||
} else if (node.type === 'ConditionalExpression') {
|
||||
return generateCode(node.test) + ' ? ' + generateCode(node.consequent) + ' : ' + generateCode(node.alternate);
|
||||
} else if (node.type === 'LogicalExpression') {
|
||||
return generateCode(node.left) + ' ' + node.operator + ' ' + generateCode(node.right);
|
||||
} else if (node.type === 'UnaryExpression') {
|
||||
return node.operator + generateCode(node.argument);
|
||||
} else if (node.type === 'ObjectExpression') {
|
||||
return '{' + node.properties.map((p: any) => generateCode(p.key) + ': ' + generateCode(p.value)).join(', ') + '}';
|
||||
} else if (node.type === 'ArrayExpression') {
|
||||
return '[' + node.elements.map((e: any) => generateCode(e)).join(', ') + ']';
|
||||
} else {
|
||||
return node.raw || node.name || 'unknown';
|
||||
}
|
||||
}
|
||||
5
packages/discord/src/jsx/components/action-row.ts
Normal file
5
packages/discord/src/jsx/components/action-row.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createActionRow } from '@components';
|
||||
|
||||
export function ActionRow(props: { children: any | any[] }) {
|
||||
return createActionRow(...(Array.isArray(props.children) ? props.children : [props.children]));
|
||||
}
|
||||
6
packages/discord/src/jsx/components/button.ts
Normal file
6
packages/discord/src/jsx/components/button.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { createButton, type ButtonStyle } from '@components';
|
||||
import type { PartialEmoji } from '@projectdysnomia/dysnomia';
|
||||
|
||||
export function Button(props: { label: string; customId: string; style?: ButtonStyle; emoji?: PartialEmoji; disabled?: boolean }) {
|
||||
return createButton(props.label, props.customId, { style: props.style, emoji: props.emoji, disabled: props.disabled });
|
||||
}
|
||||
8
packages/discord/src/jsx/components/container.ts
Normal file
8
packages/discord/src/jsx/components/container.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createContainer } from '@components';
|
||||
|
||||
export function Container(props: { accent?: number; spoiler?: boolean; children: any | any[] }) {
|
||||
return createContainer(
|
||||
{ accent_color: props.accent, spoiler: props.spoiler },
|
||||
...(Array.isArray(props.children) ? props.children : [props.children]),
|
||||
);
|
||||
}
|
||||
4
packages/discord/src/jsx/components/index.ts
Normal file
4
packages/discord/src/jsx/components/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './action-row';
|
||||
export * from './button';
|
||||
export * from './container';
|
||||
export * from './text-display';
|
||||
5
packages/discord/src/jsx/components/text-display.ts
Normal file
5
packages/discord/src/jsx/components/text-display.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createTextDisplay } from '@components/builders';
|
||||
|
||||
export function TextDisplay(props: { content: string }) {
|
||||
return createTextDisplay(props.content);
|
||||
}
|
||||
3
packages/discord/src/jsx/index.ts
Normal file
3
packages/discord/src/jsx/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './runtime';
|
||||
export * from './components';
|
||||
export * as JSX from './jsx';
|
||||
69
packages/discord/src/jsx/jsx.ts
Normal file
69
packages/discord/src/jsx/jsx.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
type ActionRow,
|
||||
type Button,
|
||||
type ChannelSelectMenu,
|
||||
type MentionableSelectMenu,
|
||||
type PartialEmoji,
|
||||
type RoleSelectMenu,
|
||||
type StringSelectMenu,
|
||||
type TextInput,
|
||||
type UserSelectMenu,
|
||||
type LabelComponent,
|
||||
type ContainerComponent,
|
||||
type TextDisplayComponent,
|
||||
type SectionComponent,
|
||||
type MediaGalleryComponent,
|
||||
type SeparatorComponent,
|
||||
type FileComponent,
|
||||
type InteractionButton,
|
||||
type URLButton,
|
||||
type PremiumButton,
|
||||
type ThumbnailComponent,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
|
||||
export type Component =
|
||||
| ActionRow
|
||||
| Button
|
||||
| StringSelectMenu
|
||||
| UserSelectMenu
|
||||
| RoleSelectMenu
|
||||
| MentionableSelectMenu
|
||||
| ChannelSelectMenu
|
||||
| TextInput
|
||||
| LabelComponent
|
||||
| ContainerComponent
|
||||
| TextDisplayComponent
|
||||
| SectionComponent
|
||||
| MediaGalleryComponent
|
||||
| SeparatorComponent
|
||||
| FileComponent
|
||||
| InteractionButton
|
||||
| URLButton
|
||||
| PremiumButton
|
||||
| ThumbnailComponent;
|
||||
|
||||
export type Element = Component | Promise<Component>;
|
||||
|
||||
export interface ElementClass {
|
||||
render: any;
|
||||
}
|
||||
|
||||
export interface ElementAttributesProperty {
|
||||
props: {};
|
||||
}
|
||||
|
||||
export interface IntrinsicElements {
|
||||
// Allow any element, but prefer known elements
|
||||
[elemName: string]: any;
|
||||
// Known elements
|
||||
ActionRow: { children: any | any[] };
|
||||
Button: {
|
||||
label: string;
|
||||
customId: string;
|
||||
style?: number;
|
||||
emoji?: PartialEmoji;
|
||||
disabled?: boolean;
|
||||
};
|
||||
Container: { color?: string; accent?: number; spoiler?: boolean; children: any | any[] };
|
||||
TextDisplay: { content: string };
|
||||
}
|
||||
30
packages/discord/src/jsx/runtime.ts
Normal file
30
packages/discord/src/jsx/runtime.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export function jsx(type: any, props: Record<string, any>) {
|
||||
console.log('JSX', type, props);
|
||||
if (typeof type === 'function') {
|
||||
return type(props);
|
||||
}
|
||||
return {
|
||||
type,
|
||||
props,
|
||||
};
|
||||
}
|
||||
|
||||
export function jsxDEV(
|
||||
type: any,
|
||||
props: Record<string, any>,
|
||||
key: string | number | symbol,
|
||||
isStaticChildren: boolean,
|
||||
source: any,
|
||||
self: any,
|
||||
) {
|
||||
console.log('JSX DEV', type, props);
|
||||
if (typeof type === 'function') {
|
||||
return type(props);
|
||||
}
|
||||
return {
|
||||
type,
|
||||
props: { ...props, key },
|
||||
_source: source,
|
||||
_self: self,
|
||||
};
|
||||
}
|
||||
8
packages/discord/src/jsx/types.d.ts
vendored
Normal file
8
packages/discord/src/jsx/types.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { Component, IntrinsicElements as StarKittenIntrinsicElements } from './jsx';
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
type Element = Component;
|
||||
interface IntrinsicElements extends StarKittenIntrinsicElements {}
|
||||
}
|
||||
}
|
||||
26
packages/discord/src/locales.ts
Normal file
26
packages/discord/src/locales.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export type Locales = 'en' | 'ru' | 'de' | 'fr' | 'ja' | 'es' | 'zh' | 'ko';
|
||||
export const ALL_LOCALES: Locales[] = ['en', 'ru', 'de', 'fr', 'ja', 'es', 'zh', 'ko'];
|
||||
export const DEFAULT_LOCALE: Locales = 'en';
|
||||
export const LOCALE_NAMES: { [key in Locales]: string } = {
|
||||
en: 'English',
|
||||
ru: 'Русский',
|
||||
de: 'Deutsch',
|
||||
fr: 'Français',
|
||||
ja: '日本語',
|
||||
es: 'Español',
|
||||
zh: '中文',
|
||||
ko: '한국어',
|
||||
};
|
||||
export function toDiscordLocale(locale: Locales): string {
|
||||
switch (locale) {
|
||||
case 'en': return 'en-US';
|
||||
case 'ru': return 'ru';
|
||||
case 'de': return 'de';
|
||||
case 'fr': return 'fr';
|
||||
case 'ja': return 'ja';
|
||||
case 'es': return 'es-ES';
|
||||
case 'zh': return 'zh-CN';
|
||||
case 'ko': return 'ko';
|
||||
default: return 'en-US';
|
||||
}
|
||||
}
|
||||
2
packages/discord/src/pages/index.ts
Normal file
2
packages/discord/src/pages/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './pages';
|
||||
export * from './subroutes';
|
||||
166
packages/discord/src/pages/pages.ts
Normal file
166
packages/discord/src/pages/pages.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { isAutocomplete, isMessageComponent, isModalSubmit, isPing, type CommandContext } from '@commands';
|
||||
import {
|
||||
Constants,
|
||||
type InteractionContentEdit,
|
||||
type InteractionModalContent,
|
||||
type CommandInteraction,
|
||||
type ComponentInteraction,
|
||||
type ModalSubmitInteraction,
|
||||
Interaction,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
|
||||
export type PagesInteraction = CommandInteraction | ModalSubmitInteraction | ComponentInteraction;
|
||||
export enum PageType {
|
||||
MODAL = 'modal',
|
||||
MESSAGE = 'message',
|
||||
FOLLOWUP = 'followup',
|
||||
}
|
||||
|
||||
export interface Page<T> {
|
||||
key: string;
|
||||
type?: PageType; // defaults to MESSAGE
|
||||
followUpFlags?: number;
|
||||
render: (
|
||||
ctx: PageContext<T>,
|
||||
) => (InteractionModalContent | InteractionContentEdit) | Promise<InteractionModalContent | InteractionContentEdit>;
|
||||
}
|
||||
|
||||
export interface PagesOptions<T> {
|
||||
pages: Record<string, Page<T>>;
|
||||
initialPage?: string;
|
||||
timeout?: number; // in seconds
|
||||
ephemeral?: boolean; // whether the initial message should be ephemeral
|
||||
useEmbeds?: boolean; // will not enable components v2
|
||||
initialStateData?: T; // initial state to merge with default state
|
||||
router?: (ctx: PageContext<T>) => string; // function to determine the next page key
|
||||
}
|
||||
|
||||
export interface PageState<T> {
|
||||
currentPage: string;
|
||||
timeoutAt: number; // timestamp in ms
|
||||
lastInteractionAt?: number; // timestamp in ms
|
||||
messageId?: string;
|
||||
channelId?: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface PageContext<T> {
|
||||
state: PageState<T>;
|
||||
custom_id: string; // current interaction custom_id
|
||||
interaction: PagesInteraction;
|
||||
goToPage: (pageKey: string) => Promise<InteractionContentEdit>;
|
||||
}
|
||||
|
||||
function createPageContext<T>(interaction: PagesInteraction, options: PagesOptions<T>, state: PageState<T>): PageContext<T> {
|
||||
return {
|
||||
state,
|
||||
interaction,
|
||||
custom_id: 'custom_id' in interaction.data ? interaction.data.custom_id : (options.initialPage ?? 'root'),
|
||||
goToPage: (pageKey: string) => {
|
||||
const page = options.pages[pageKey];
|
||||
this.state.currentPage = pageKey;
|
||||
if (!page) {
|
||||
throw new Error(`Page with key "${pageKey}" not found`);
|
||||
}
|
||||
return page.render(createPageContext(interaction, options, { ...state, currentPage: pageKey })) as Promise<InteractionContentEdit>;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function defaultPageState<T>(options: PagesOptions<T>): PageState<T> {
|
||||
const timeoutAt = options.timeout ? Date.now() + options.timeout * 1000 : Infinity;
|
||||
return {
|
||||
currentPage: options.initialPage ?? options.pages[0].key,
|
||||
timeoutAt,
|
||||
lastInteractionAt: Date.now(),
|
||||
data: options.initialStateData ?? ({} as T),
|
||||
};
|
||||
}
|
||||
|
||||
function getPageState<T>(options: PagesOptions<T>, cmdCtx: CommandContext & { state: { __pageState?: PageState<T> } }) {
|
||||
const cmdState = cmdCtx.state;
|
||||
if ('__pageState' in cmdState && cmdState.__pageState) {
|
||||
return cmdState.__pageState as PageState<T>;
|
||||
}
|
||||
cmdState.__pageState = defaultPageState(options);
|
||||
return cmdState.__pageState as PageState<T>;
|
||||
}
|
||||
|
||||
function validateOptions<T>(options: PagesOptions<T>) {
|
||||
const keys = Object.keys(options.pages);
|
||||
const uniqueKeys = new Set(keys);
|
||||
if (uniqueKeys.size !== keys.length) {
|
||||
throw new Error('Duplicate page keys found');
|
||||
}
|
||||
}
|
||||
|
||||
function getFlags(options: PagesOptions<any>) {
|
||||
let flags = 0;
|
||||
if (options.ephemeral) {
|
||||
flags |= Constants.MessageFlags.EPHEMERAL;
|
||||
}
|
||||
if (!options.useEmbeds) {
|
||||
flags |= Constants.MessageFlags.IS_COMPONENTS_V2;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
export async function usePages<T>(options: PagesOptions<T>, interaction: Interaction, cmdCtx: CommandContext) {
|
||||
if (isAutocomplete(interaction) || isPing(interaction)) {
|
||||
throw new Error('usePages cannot be used with autocomplete or ping interactions');
|
||||
}
|
||||
const pagesInteraction = interaction as PagesInteraction;
|
||||
validateOptions(options);
|
||||
const pageState = getPageState(options, cmdCtx);
|
||||
const pageContext = createPageContext(pagesInteraction, options, pageState);
|
||||
const pageKey =
|
||||
options.router ? options.router(pageContext) : (pageContext.custom_id ?? options.initialPage ?? Object.keys(options.pages)[0]);
|
||||
// if we have subroutes, we only want the main route from the page key
|
||||
const page = options.pages[pageKey.split(':')[0]] ?? options.pages[0];
|
||||
pageContext.state.currentPage = page.key;
|
||||
|
||||
if (page.type === PageType.MODAL && !isModalSubmit(pagesInteraction)) {
|
||||
// we don't defer modals and can't respond to a modal with a modal.
|
||||
const cnt = page.render(pageContext);
|
||||
const content = isPromise(cnt) ? await cnt : cnt;
|
||||
return await pagesInteraction.createModal(content as InteractionModalContent);
|
||||
}
|
||||
|
||||
if (page.type === PageType.FOLLOWUP) {
|
||||
if (!pageState.messageId) {
|
||||
throw new Error('Cannot send a followup message before an initial message has been sent');
|
||||
}
|
||||
const flags = page.type === PageType.FOLLOWUP ? (page.followUpFlags ?? getFlags(options)) : getFlags(options);
|
||||
await pagesInteraction.defer(flags);
|
||||
const cnt = page.render(pageContext);
|
||||
const content = isPromise(cnt) ? await cnt : cnt;
|
||||
return await pagesInteraction.createFollowup({
|
||||
flags,
|
||||
...(content as InteractionContentEdit),
|
||||
});
|
||||
}
|
||||
|
||||
if (pageState.messageId && isMessageComponent(pagesInteraction)) {
|
||||
await pagesInteraction.deferUpdate();
|
||||
const cnt = page.render(pageContext);
|
||||
const content = isPromise(cnt) ? await cnt : cnt;
|
||||
return await pagesInteraction.editMessage(pageState.messageId, content as InteractionContentEdit);
|
||||
}
|
||||
|
||||
{
|
||||
await pagesInteraction.defer(getFlags(options));
|
||||
const cnt = page.render(pageContext);
|
||||
const content = isPromise(cnt) ? await cnt : cnt;
|
||||
const message = await pagesInteraction.createFollowup({
|
||||
flags: getFlags(options),
|
||||
...(content as InteractionContentEdit),
|
||||
});
|
||||
pageState.messageId = message.id;
|
||||
pageState.channelId = message.channel?.id;
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
function isPromise<T>(value: T | Promise<T>): value is Promise<T> {
|
||||
return typeof (value as Promise<T>)?.then === 'function';
|
||||
}
|
||||
99
packages/discord/src/pages/subroutes.ts
Normal file
99
packages/discord/src/pages/subroutes.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { PartialEmoji } from '@projectdysnomia/dysnomia';
|
||||
import { createActionRow, createButton, createMediaGallery, type ButtonOptions, type ContainerItems } from '@components';
|
||||
import type { PageContext } from './pages';
|
||||
|
||||
export function getSubrouteKey(prefix: string, subroutes: string[]) {
|
||||
return `${prefix}:${subroutes.join(':')}`;
|
||||
}
|
||||
|
||||
export function parseSubrouteKey(key: string, expectedPrefix: string, expectedLength: number, defaults: string[] = []) {
|
||||
const parts = key.split(':');
|
||||
if (parts[0] !== expectedPrefix) {
|
||||
throw new Error(`Unexpected prefix: ${parts[0]}`);
|
||||
}
|
||||
if (parts.length - 1 < expectedLength && defaults.length) {
|
||||
// fill in defaults
|
||||
parts.push(...defaults.slice(parts.length - 1));
|
||||
}
|
||||
if (parts.length !== expectedLength + 1) {
|
||||
throw new Error(`Expected ${expectedLength} subroutes, but got ${parts.length - 1}`);
|
||||
}
|
||||
return parts.slice(1);
|
||||
}
|
||||
|
||||
export function renderSubrouteButtons(
|
||||
currentSubroute: string,
|
||||
subRoutes: string[],
|
||||
subrouteIndex: number,
|
||||
prefix: string,
|
||||
subroutes: { label: string; value: string; emoji?: PartialEmoji }[],
|
||||
options?: Partial<ButtonOptions>,
|
||||
) {
|
||||
return subroutes
|
||||
.filter((sr) => sr !== undefined)
|
||||
.map(({ label, value, emoji }) => {
|
||||
const routes = [...subRoutes];
|
||||
routes[subrouteIndex] = currentSubroute == value ? '_' : value;
|
||||
return createButton(label, getSubrouteKey(prefix, routes), {
|
||||
...options,
|
||||
disabled: value === currentSubroute,
|
||||
emoji,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export interface SubrouteOptions {
|
||||
label: string;
|
||||
value: string;
|
||||
emoji?: PartialEmoji;
|
||||
}
|
||||
|
||||
export function renderSubroutes<T, CType = ContainerItems>(
|
||||
context: PageContext<T>,
|
||||
prefix: string,
|
||||
subroutes: (SubrouteOptions & {
|
||||
banner?: string;
|
||||
actionRowPosition?: 'top' | 'bottom';
|
||||
})[][],
|
||||
render: (currentSubroute: string, ctx: PageContext<T>) => CType,
|
||||
btnOptions?: Partial<ButtonOptions>,
|
||||
defaultSubroutes?: string[], // if not provided, will use the first option of each subroute
|
||||
): CType[] {
|
||||
const currentSubroutes = parseSubrouteKey(
|
||||
context.custom_id,
|
||||
prefix,
|
||||
subroutes.length,
|
||||
defaultSubroutes || subroutes.map((s) => s[0].value),
|
||||
);
|
||||
|
||||
const components = subroutes
|
||||
.filter((sr) => sr.length > 0)
|
||||
.map((srOpts, index) => {
|
||||
const opts = srOpts.filter((sr) => sr !== undefined);
|
||||
if (opts.length === 0) return undefined;
|
||||
// find the current subroute, or default to the first
|
||||
const sri = opts.findIndex((s) => s.value === currentSubroutes[index]);
|
||||
const current = opts[sri] || opts[0];
|
||||
const components = [];
|
||||
|
||||
const actionRow = createActionRow(...renderSubrouteButtons(current.value, currentSubroutes, index, prefix, opts, btnOptions));
|
||||
|
||||
if (current.banner) {
|
||||
components.push(createMediaGallery({ url: current.banner }));
|
||||
}
|
||||
|
||||
if (!current.actionRowPosition || current.actionRowPosition === 'top') {
|
||||
components.push(actionRow);
|
||||
}
|
||||
|
||||
components.push(render(current.value, context));
|
||||
|
||||
if (current.actionRowPosition === 'bottom') {
|
||||
components.push(actionRow);
|
||||
}
|
||||
return components;
|
||||
})
|
||||
.flat()
|
||||
.filter((c) => c !== undefined);
|
||||
return components;
|
||||
}
|
||||
41
packages/discord/tsconfig.json
Normal file
41
packages/discord/tsconfig.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@star-kitten/discord",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": false,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
|
||||
// Paths
|
||||
"paths": {
|
||||
"@*": ["./src/*"],
|
||||
"@types": ["./types/*"]
|
||||
},
|
||||
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
|
||||
"typeRoots": ["src/types", "./node_modules/@types"]
|
||||
},
|
||||
"include": ["src", "types", "src/jsx/types.d.ts"],
|
||||
"exclude": ["node_modules", "dist", "build", "**/*.test.ts"]
|
||||
}
|
||||
17
packages/discord/tsdown.config.ts
Normal file
17
packages/discord/tsdown.config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
entry: [
|
||||
'./src/index.ts',
|
||||
'./src/commands/index.ts',
|
||||
'./src/components/index.ts',
|
||||
'./src/pages/index.ts',
|
||||
'./src/common/index.ts',
|
||||
'./src/jsx/index.ts',
|
||||
],
|
||||
platform: 'node',
|
||||
dts: true,
|
||||
external: ['bun', 'bun:sqlite'],
|
||||
},
|
||||
]);
|
||||
65
packages/discord/types/index.d.ts
vendored
Normal file
65
packages/discord/types/index.d.ts
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
type ActionRow,
|
||||
type Button,
|
||||
type ChannelSelectMenu,
|
||||
type GuildChannelTypes,
|
||||
type MentionableSelectMenu,
|
||||
type PartialEmoji,
|
||||
type RoleSelectMenu,
|
||||
type StringSelectMenu,
|
||||
type TextInput,
|
||||
type UserSelectMenu,
|
||||
type LabelComponent,
|
||||
type ContainerComponent,
|
||||
type TextDisplayComponent,
|
||||
type SectionComponent,
|
||||
type MediaGalleryComponent,
|
||||
type SeparatorComponent,
|
||||
type FileComponent,
|
||||
type InteractionButton,
|
||||
type URLButton,
|
||||
type PremiumButton,
|
||||
type ThumbnailComponent,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
|
||||
declare namespace JSX {
|
||||
type Component =
|
||||
| Button
|
||||
| StringSelectMenu
|
||||
| UserSelectMenu
|
||||
| RoleSelectMenu
|
||||
| MentionableSelectMenu
|
||||
| ChannelSelectMenu
|
||||
| TextInput
|
||||
| LabelComponent
|
||||
| ContainerComponent
|
||||
| TextDisplayComponent
|
||||
| SectionComponent
|
||||
| MediaGalleryComponent
|
||||
| SeparatorComponent
|
||||
| FileComponent
|
||||
| InteractionButton
|
||||
| URLButton
|
||||
| PremiumButton
|
||||
| ThumbnailComponent;
|
||||
|
||||
type Element = Component | Promise<Component>;
|
||||
|
||||
interface ElementClass {
|
||||
render: any;
|
||||
}
|
||||
|
||||
interface ElementAttributesProperty {
|
||||
props: {};
|
||||
}
|
||||
|
||||
interface IntrinsicElements {
|
||||
// Allow any element, but prefer known elements
|
||||
[elemName: string]: any;
|
||||
// Known elements
|
||||
ActionRow: { children: any | any[] };
|
||||
Button: { label: string; customId: string; style?: number; emoji?: PartialEmoji; disabled?: boolean };
|
||||
Container: { accent?: number; spoiler?: boolean; children: any | any[] };
|
||||
TextDisplay: { content: string };
|
||||
}
|
||||
}
|
||||
31
packages/eve-bot/.env.development
Normal file
31
packages/eve-bot/.env.development
Normal file
@@ -0,0 +1,31 @@
|
||||
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
|
||||
#/ public-key encryption for .env files /
|
||||
#/ [how it works](https://dotenvx.com/encryption) /
|
||||
#/----------------------------------------------------------/
|
||||
DOTENV_PUBLIC_KEY_DEVELOPMENT="02572da3d4f3a844588a944214c0e142a5a01deaa6551456af146d34b574024416"
|
||||
|
||||
# .env.development
|
||||
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
|
||||
#/ public-key encryption for .env files /
|
||||
#/ [how it works](https://dotenvx.com/encryption) /
|
||||
#/----------------------------------------------------------/
|
||||
DOTENV_PUBLIC_KEY="02292a330aa041b5f7efc51504e0c208accba67a6877a217ab43cbb59c3c0c3e66"
|
||||
|
||||
# .env
|
||||
DEBUG="true"
|
||||
PORT="3001"
|
||||
NODE_ENV="development"
|
||||
LOG_LEVEL="debug"
|
||||
BASE_URL="http://dev.starkitten.cafe"
|
||||
EVE_CLIENT_ID="4cfee6b0c8fc40b29e0415f6309bd756"
|
||||
EVE_CLIENT_SECRET="1Y5SBrdRMNuy9YWfwGMHsvUmTejqcQqDj4muuhOj"
|
||||
EVE_CALLBACK_URL="http://dev.starkitten.cafe/api/auth/callback"
|
||||
ESI_USER_AGENT="Star Kitten DEV/0.0.1 (jb@jb.codes; +https://github.com/roman-kaas/star-kitten)"
|
||||
DISCORD_APP_ID="1292871047942504572"
|
||||
DISCORD_APP_SECRET="Ioji-sKdo2hoCJe8A82M_c6AwRcuDphE"
|
||||
DISCORD_PUBLIC_KEY="9ac29ee2d8170cb720caab3a382221aff577f82d7bfa03cd0dd737832053246c"
|
||||
DISCORD_BOT_TOKEN="MTI5Mjg3MTA0Nzk0MjUwNDU3Mg.Gx8VvN.hrJMd94CZl0dHFhFS40lmB_ynNfJTC56QATHBg"
|
||||
DISCORD_TEST_GUILD_ID="424296600773459998"
|
||||
JANICE_KEY="DUyi5Q3Dod48IoswUBkEfNRs8Qf3cwNN"
|
||||
PERPLEXITY_API_KEY="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"
|
||||
STAR_KITTEN_KV_DB_PATH="data/dev-kv.db"
|
||||
23
packages/eve-bot/.env.production
Normal file
23
packages/eve-bot/.env.production
Normal file
@@ -0,0 +1,23 @@
|
||||
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
|
||||
#/ public-key encryption for .env files /
|
||||
#/ [how it works](https://dotenvx.com/encryption) /
|
||||
#/----------------------------------------------------------/
|
||||
DOTENV_PUBLIC_KEY_PRODUCTION="02f0469506f6722d8fcc179c199ff159ca32f082000c8e7a1465891adb50a4c031"
|
||||
|
||||
# .env.production
|
||||
DEBUG="encrypted:BPl4gO9RYUusva9u3Su7apXsjhWpaNHxRROXvRjxva6DA1/3o+meDrcFyoqvruR2PCNu3gOvI8TJ1xLUh16QaL2D88We/Q5HvGl7XsZrOadsK3TSY2jgECyKpKI8bz+f6HdIeCzJ"
|
||||
PORT="encrypted:BO/LozOnJXfRaDydiiL3K+xUIFPWk9F9XKiVqEknmTR/dhOpW3nIKFp+0a64w2bxWY9wJ20Px4FEeceI3A5KZpUUbgHvTnjKXgNAh6eXYYtqJl2mToYSOWggv00fjhfcBeTP8mQ="
|
||||
NODE_ENV="encrypted:BBWmfcjkSBbkCF0YxRSB6grqFNbsx3eQyieKJLnqrN9iIofkhXKBq5FQyiH5+xiQ7lfDhms2Vm6T9qu/D67oJBpsGY2qxB3r5uMvMzWPEwQ+8Sz0NVde5UYIfkOmN5n/ci77YH1ymiJpWPk="
|
||||
LOG_LEVEL="encrypted:BIX0F+K4nCIdP0E2npM+gfrTGwBTOGPvwuDupRVfe77eZG2ix2GQFeTbPU8l1At1gWodqW179+8JvJslrRBuZUYK4bPuORvlgzQ9mrrLEqP1vCiCu5AzwXwHTTo/DfmHIm0z/dI="
|
||||
BASE_URL="encrypted:BGbVLeMkYn+PXS0DJHAaPJKf64gQ8+WhlZOgevtMM9cw3oacEege6ic2DlPvpxP9F7h41cjy1KIvw3HHbR3yXuB2PlU/r6nZ7QL2pRBklYhkixAWVR7/ri9Ke3XR8OV68KXA9GsW2AS/r9ceG6Z6jd237JmMpLr+"
|
||||
DISCORD_APP_ID="encrypted:BIsMPoPKxeByxEZpN3uBLUH/yar4YIFfxSHHwbqjs/TMmsz6oyNwGxhJyWyqoJOVeVGZ+ZsWKp6L7Hj75BqlmQynNPpKcOQNN55zAOJYkol3fWiNlEKmPLjgT7fKKlSnzFNfmdpqncOPUlT36WW8jvY+dd0="
|
||||
DISCORD_APP_SECRET="encrypted:BJtSW4wyAbBSky5dqJqLI0Xb24JqDiRBcpnJAWVW+F0bVzCBGNaMMVn7XjCIgL9h1z09IBQquxRgo9kh5szSHHNO7J8a/5kYVMUrD6BPYWU5GcNRe6unInL0OlXzUFJ4PE9SxRBSErgU6PoO1jXfQYXNTa0B4SKTeEzvPfXFmOeu"
|
||||
DISCORD_PUBLIC_KEY="encrypted:BJ/UXLOd1IikRwGNWC/PsAVhCPytbOP6XlC9S/8XJIacXz3GPukiCGEzTZoq4YTEwGwyP93yT5imIkiYS6xCAMSqPk9KzShNP8aDleZl2vSoCiW+QZ8WCVQBBmk5c/A/SuKPaLDWtjsfRlOnAkoIKYujxlmYL5UJV8MPA4Jz+ECsSPiZ8MOWbmRBeq9JAI+eERpDgjOyBgPw4V2JCLSOCvw="
|
||||
DISCORD_BOT_TOKEN="encrypted:BOjPtcjzYCT47YeCYwnY6db53IGWHqUJvvolnxuR1D/008z5g1DEM21mRI2EMri1n0bNXqqmt2aQX4jNyUzEz2Tjmf4QTLim+L+XuGz1cm813ADYFzWNiCE+F45pu8xd2p/Ku0x8xGgkPrZSRm/jP3g8NXk80e1IfUd1WRCcSB6lDdYMl4GkYU+IKysPIf8FqaZMV6F6bjoYdsFkeAspZVJQECOqlLdWAg=="
|
||||
EVE_CLIENT_ID="encrypted:BB+B4lgs4yMPla7a4PDtZfENuUQ2VmT6huvn9ySC6cDsxVM0HjXhziRK3/Yui23s129WIqksUi2Ywt9RIjLptWkOroP2CRA5hNLff+yzqTyZYrt35PVGdHHjkBmvcuxybrf2hzkIvkxOgvfi4WgR6Pl5N5ALTVUn3+6tJ5+p2aqJ"
|
||||
EVE_CLIENT_SECRET="encrypted:BK/OQ/AA+DNvPhhIZQgXi7a4GIEo2Gc/aMfEiHGyNvLJn5WW5b2wCCKCwR6sNVUhEgAha6X73wT0rRxhGpSwPGe7IN0+JAb/1HYxZgFOCPegZHk1a8h2cadUcPDQYfOZeY2R+h/ldkz0KG5YteWD3A9iNNmTidYF3oszecESVTnMUuQ4NFKZsiQ="
|
||||
EVE_CALLBACK_URL="encrypted:BNHPMxv+ozxFm7H9jOOECZyvpB+5JzA/8Y0EUtHq/v+r6V3uIKLPkrfDzqrccDvGqMcYrVNrexx9Iy/DbWa4lnxAhOyytcXhXRDKj4lGe5jOKj/VhDGvvBPmRHAdv7+kCXbohBz7fN5OE67ZIr8+x1L5oBojIBjSwmBKpnsXq9E3Q4+Q0qrudCJB"
|
||||
ESI_USER_AGENT="encrypted:BEafFp9jiHx2EvlFmetxAwAcEkzc7qoN8vJc7I6wkuv2b+MB/eizjh24r33vyGrj92IJJV4numo8hLFnc2MfsUYOoVaE+f90Yh4OKkG9GUVqzNsI8psNJZ7nY0a7wURS5VM5bwbc6G98u0R4tc2HpGpMQPssu8lmRU5AsKnqSN5MTPp8R5ij3jEzMOpVZxxwnJgY+ViozHEQ2v1VK8dJ+C1BDFskAmb9iGSd6w=="
|
||||
AUTH_DB_PATH="encrypted:BNZrtEfMB3JWWcXyIfaNme9cOXfMJULF+zqBxXK3sQW/aj5l6Q5s2lf1PFWlU88csMFjYqL57HbnjbBIAnR1VY0sNunGv1+gIOU6Dt2bOt0RBrqjVHaaPLst4IseDxBSsnYr+GHHEBlEP3R+EIbG7PWgDdQm73z9"
|
||||
JANICE_KEY="encrypted:BIi+eFEI8t63MaSkW6ROvGS2zEuEbMosRJGVLvtLxAikPN7EWTa1KFwP1w44DV56Gx4HXBKBwLm4NlAxqjqpB0PJIPJyDy41evFZ0Nugua8dvOJ06VefMxNxvay33lvDybsfoeDuuu/OYi+zXur95B71f5bUUfuA296eAm/fT7X2"
|
||||
PERPLEXITY_API_KEY="encrypted:BOXAcpkE+Qve39rIWzpIqBZBQAuLtIwUdyHguYmBOlrNdsJTRNC2vlqMLyA/B/SiMe1NfsDPEZNEB/vM0ue6SEugSh6trcZXqG06Gbl89bFxAiCVaz9j+qt+urcUivsr1sF44TNFZXIzSYMsO1O02PGqa7l/Ht153SKjaTOaJdCbtZMV6VqR6HX1TttPmqnXifugLZF4"
|
||||
182
packages/eve-bot/.gitignore
vendored
Normal file
182
packages/eve-bot/.gitignore
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
|
||||
.env.keys
|
||||
.flaskenv*
|
||||
!.env.project
|
||||
!.env.vault
|
||||
|
||||
data/
|
||||
db/
|
||||
litefs/
|
||||
brainstorming/
|
||||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
|
||||
cloudflared.exe
|
||||
5
packages/eve-bot/.prettierrc
Normal file
5
packages/eve-bot/.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120
|
||||
}
|
||||
8
packages/eve-bot/.prettierrc.yaml
Normal file
8
packages/eve-bot/.prettierrc.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
trailingComma: all
|
||||
tabWidth: 2
|
||||
useTabs: false
|
||||
semi: true
|
||||
singleQuote: true
|
||||
printWidth: 140
|
||||
experimentalTernaries: true
|
||||
quoteProps: consistent
|
||||
99
packages/eve-bot/README.md
Normal file
99
packages/eve-bot/README.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Star Kitten Discord Bot
|
||||
|
||||
A Discord bot for [EVE Online](https://www.eveonline.com/).
|
||||
|
||||
# [Click this link to use this bot!](https://discord.com/oauth2/authorize?client_id=1288711114388930601)
|
||||
|
||||
## Running the Bot
|
||||
|
||||
This bot runs on [Bun](https://bun.sh/)! To install, run one of the following commands.
|
||||
|
||||
_Linux & MacOS_
|
||||
|
||||
```bash
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
```
|
||||
|
||||
_Windows_
|
||||
|
||||
```bash
|
||||
powershell -c "irm bun.sh/install.ps1 | iex"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Install dependencies.
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
### Link the Library & download static data
|
||||
|
||||
`star-kitten-lib` has not been published, so link to it locally before running this web project.
|
||||
|
||||
```bash
|
||||
cd star-kitten-lib
|
||||
bun link
|
||||
cd ../web
|
||||
bun link star-kitten-lib
|
||||
```
|
||||
|
||||
### Download static eve reference data & Hoboleaks archive from [EVE Ref](https://everef.net/).
|
||||
|
||||
```bash
|
||||
cd star-kitten-lib
|
||||
bun get-data
|
||||
```
|
||||
|
||||
### Initialize the sqlite database
|
||||
|
||||
```bash
|
||||
cd star-kitten-lib
|
||||
bun generate-migrations
|
||||
bun migrate
|
||||
```
|
||||
Drizzle's migrations seems to fail on the first try sometimes, so just grab the .sql from the generation and run those against the kitten.db file to create the tables & indexes.
|
||||
|
||||
### Run the bot
|
||||
Run the bot locally.
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create a .env file in the root directory with the following values:
|
||||
|
||||
```yaml
|
||||
#General
|
||||
BASE_URL=http://localhost:3000
|
||||
DEBUG=true
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
LOG_LEVEL=debug
|
||||
|
||||
|
||||
# EVE - https://developers.eveonline.com/applications
|
||||
EVE_CLIENT_ID=YOUR_EVE_CLIENT_ID
|
||||
EVE_CLIENT_SECRET=YOUR_EVE_SECRET
|
||||
EVE_CALLBACK_URL=http://localhost:3000/auth/callback
|
||||
ESI_USER_AGENT=ADD_YOUR_USER_AGENT_INFO_HERE
|
||||
|
||||
#Discord - https://discord.com/developers/applications
|
||||
DISCORD_APP_ID=YOUR_APP_ID
|
||||
DISCORD_CLIENT_SECRET=YOUR_CLIENT_SECRET
|
||||
DISCORD_PUBLIC_KEY=YOUR_PUBLIC_KEY
|
||||
DISCORD_BOT_TOKEN=YOUR_BOT_TOKEN
|
||||
|
||||
# ID of a test server to have immediate command refreshes
|
||||
DISCORD_TEST_GUILD_ID=YOUR_TEST_SERVER_ID
|
||||
|
||||
# For using Janice's Appraisal API
|
||||
JANICE_KEY=XXX
|
||||
|
||||
# For using Perplexities AI API
|
||||
PERPLEXITY_API_KEY=XXX
|
||||
|
||||
```
|
||||
121
packages/eve-bot/bun.lock
Normal file
121
packages/eve-bot/bun.lock
Normal file
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "star-kitten",
|
||||
"dependencies": {
|
||||
"@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dotenvx/dotenvx": "^1.49.0",
|
||||
"@types/bun": "^1.2.21",
|
||||
"@types/node": "^24.3.1",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.49.0", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.2", "which": "^4.0.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-M1cyP6YstFQCjih54SAxCqHLMMi8QqV8tenpgGE48RTXWD7vfMYJiw/6xcCDpS2h28AcLpTsFCZA863Ge9yxzA=="],
|
||||
|
||||
"@ecies/ciphers": ["@ecies/ciphers@0.2.4", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-t+iX+Wf5nRKyNzk8dviW3Ikb/280+aEJAnw9YXvCp2tYGPSkMki+NRY+8aNLmVFv3eNtMdvViPNOPxS8SZNP+w=="],
|
||||
|
||||
"@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
|
||||
|
||||
"@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="],
|
||||
|
||||
"@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
|
||||
|
||||
"@projectdysnomia/dysnomia": ["@projectdysnomia/dysnomia@github:projectdysnomia/dysnomia#5e3300e", { "dependencies": { "ws": "^8.18.0" }, "optionalDependencies": { "@stablelib/xchacha20poly1305": "~1.0.1", "opusscript": "^0.1.1" }, "peerDependencies": { "@discordjs/opus": "^0.9.0", "erlpack": "github:discord/erlpack", "eventemitter3": "^5.0.1", "pako": "^2.1.0", "sodium-native": "^4.1.1", "zlib-sync": "^0.1.9" }, "optionalPeers": ["@discordjs/opus", "erlpack", "eventemitter3", "pako", "sodium-native", "zlib-sync"] }, "projectdysnomia-dysnomia-5e3300e"],
|
||||
|
||||
"@stablelib/aead": ["@stablelib/aead@1.0.1", "", {}, "sha512-q39ik6sxGHewqtO0nP4BuSe3db5G1fEJE8ukvngS2gLkBXyy6E7pLubhbYgnkDFv6V8cWaxcE4Xn0t6LWcJkyg=="],
|
||||
|
||||
"@stablelib/binary": ["@stablelib/binary@1.0.1", "", { "dependencies": { "@stablelib/int": "^1.0.1" } }, "sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q=="],
|
||||
|
||||
"@stablelib/chacha": ["@stablelib/chacha@1.0.1", "", { "dependencies": { "@stablelib/binary": "^1.0.1", "@stablelib/wipe": "^1.0.1" } }, "sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg=="],
|
||||
|
||||
"@stablelib/chacha20poly1305": ["@stablelib/chacha20poly1305@1.0.1", "", { "dependencies": { "@stablelib/aead": "^1.0.1", "@stablelib/binary": "^1.0.1", "@stablelib/chacha": "^1.0.1", "@stablelib/constant-time": "^1.0.1", "@stablelib/poly1305": "^1.0.1", "@stablelib/wipe": "^1.0.1" } }, "sha512-MmViqnqHd1ymwjOQfghRKw2R/jMIGT3wySN7cthjXCBdO+qErNPUBnRzqNpnvIwg7JBCg3LdeCZZO4de/yEhVA=="],
|
||||
|
||||
"@stablelib/constant-time": ["@stablelib/constant-time@1.0.1", "", {}, "sha512-tNOs3uD0vSJcK6z1fvef4Y+buN7DXhzHDPqRLSXUel1UfqMB1PWNsnnAezrKfEwTLpN0cGH2p9NNjs6IqeD0eg=="],
|
||||
|
||||
"@stablelib/int": ["@stablelib/int@1.0.1", "", {}, "sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w=="],
|
||||
|
||||
"@stablelib/poly1305": ["@stablelib/poly1305@1.0.1", "", { "dependencies": { "@stablelib/constant-time": "^1.0.1", "@stablelib/wipe": "^1.0.1" } }, "sha512-1HlG3oTSuQDOhSnLwJRKeTRSAdFNVB/1djy2ZbS35rBSJ/PFqx9cf9qatinWghC2UbfOYD8AcrtbUQl8WoxabA=="],
|
||||
|
||||
"@stablelib/wipe": ["@stablelib/wipe@1.0.1", "", {}, "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg=="],
|
||||
|
||||
"@stablelib/xchacha20": ["@stablelib/xchacha20@1.0.1", "", { "dependencies": { "@stablelib/binary": "^1.0.1", "@stablelib/chacha": "^1.0.1", "@stablelib/wipe": "^1.0.1" } }, "sha512-1YkiZnFF4veUwBVhDnDYwo6EHeKzQK4FnLiO7ezCl/zu64uG0bCCAUROJaBkaLH+5BEsO3W7BTXTguMbSLlWSw=="],
|
||||
|
||||
"@stablelib/xchacha20poly1305": ["@stablelib/xchacha20poly1305@1.0.1", "", { "dependencies": { "@stablelib/aead": "^1.0.1", "@stablelib/chacha20poly1305": "^1.0.1", "@stablelib/constant-time": "^1.0.1", "@stablelib/wipe": "^1.0.1", "@stablelib/xchacha20": "^1.0.1" } }, "sha512-B1Abj0sMJ8h3HNmGnJ7vHBrAvxuNka6cJJoZ1ILN7iuacXp7sUYcgOVEOTLWj+rtQMpspY9tXSCRLPmN1mQNWg=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
|
||||
|
||||
"@types/node": ["@types/node@24.3.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
|
||||
|
||||
"commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"dotenv": ["dotenv@17.2.2", "", {}, "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q=="],
|
||||
|
||||
"eciesjs": ["eciesjs@0.4.15", "", { "dependencies": { "@ecies/ciphers": "^0.2.3", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.1", "@noble/hashes": "^1.8.0" } }, "sha512-r6kEJXDKecVOCj2nLMuXK/FCPeurW33+3JRpfXVbjLja3XUYFfD9I/JBreH6sUyzcm3G/YQboBjMla6poKeSdA=="],
|
||||
|
||||
"execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
|
||||
|
||||
"human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
|
||||
|
||||
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
|
||||
|
||||
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
|
||||
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
|
||||
|
||||
"object-treeify": ["object-treeify@1.1.33", "", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="],
|
||||
|
||||
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
|
||||
"opusscript": ["opusscript@0.1.1", "", {}, "sha512-mL0fZZOUnXdZ78woRXp18lApwpp0lF5tozJOD1Wut0dgrA9WuQTgSels/CSmFleaAZrJi/nci5KOVtbuxeWoQA=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
|
||||
|
||||
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||
|
||||
"undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
|
||||
|
||||
"which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
|
||||
|
||||
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
||||
|
||||
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
}
|
||||
}
|
||||
33
packages/eve-bot/package.json
Normal file
33
packages/eve-bot/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "star-kitten",
|
||||
"version": "0.0.1",
|
||||
"description": "A Discord bot for Eve Online",
|
||||
"author": "j-b-3",
|
||||
"type": "module",
|
||||
"module": "src/main.ts",
|
||||
"devDependencies": {
|
||||
"@dotenvx/dotenvx": "^1.49.0",
|
||||
"@types/bun": "^1.2.21",
|
||||
"@types/node": "^24.3.1",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev",
|
||||
"@star-kitten/discord": "workspace:^0.0.0",
|
||||
"@star-kitten/eve": "workspace:^0.0.0",
|
||||
"@star-kitten/util": "workspace:^0.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bunx dotenvx run -f .env.development -- bun run --watch src/main.ts",
|
||||
"start": "bunx @dotenvx/dotenvx run -f .env.production -- bun src/main.ts",
|
||||
"build": "mkdirp ./db && bun build --minify --sourcemap src/main.ts --target bun --outdir ./dist",
|
||||
"lint": "prettier --check .",
|
||||
"format": "prettier --write .",
|
||||
"test": "bun test",
|
||||
"get-data": "bun refresh:reference-data && bun refresh:hoboleaks && bun static-export",
|
||||
"refresh:reference-data": "bun run ../util/dist/download-and-extract.js https://data.everef.net/reference-data/reference-data-latest.tar.xz ./data/reference-data",
|
||||
"refresh:hoboleaks": "bun run ../util/dist/download-and-extract.js https://data.everef.net/hoboleaks-sde/hoboleaks-sde-latest.tar.xz ./data/hoboleaks",
|
||||
"static-export": "bun run ../eve/scripts/export-solar-systems.ts"
|
||||
}
|
||||
}
|
||||
90
packages/eve-bot/src/commands/appraise/appraise.command.ts
Normal file
90
packages/eve-bot/src/commands/appraise/appraise.command.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Constants, type ChatInputApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import { appraiseItems, type Appraisal } from '@star-kitten/eve/third-party/janice.js';
|
||||
import { isModalSubmit } from '@star-kitten/discord/commands';
|
||||
import { componentHasIdPrefix, isModalLabel, isModalSelect, isModalTextInput } from '@star-kitten/discord/components';
|
||||
import type { CommandContext, ExecutableInteraction } from '@star-kitten/discord/commands';
|
||||
import { PageType, usePages } from '@star-kitten/discord/pages';
|
||||
import { renderAppraisal } from './renderAppraisal';
|
||||
import { renderAppraisalModal } from './renderAppraisalModal';
|
||||
|
||||
const definition: ChatInputApplicationCommandStructure = {
|
||||
type: Constants.ApplicationCommandTypes.CHAT_INPUT,
|
||||
name: 'appraise',
|
||||
nameLocalizations: {
|
||||
de: 'bewerten',
|
||||
'es-ES': 'tasar',
|
||||
fr: 'estimer',
|
||||
ja: '査定',
|
||||
ko: '감정',
|
||||
ru: 'оценить',
|
||||
'zh-CN': '评估',
|
||||
},
|
||||
description: 'Evaluate the worth of your space junk',
|
||||
descriptionLocalizations: {
|
||||
de: 'Bewerten Sie den Wert Ihres Weltraumschrotts',
|
||||
'es-ES': 'Evalúa el valor de tu chatarra espacial',
|
||||
fr: 'Évaluez la valeur de vos déchets spatiaux',
|
||||
ja: 'あなたの宇宙のガラクタの価値を評価します',
|
||||
ko: '우주 쓰레기의 가치를 평가하십시오',
|
||||
ru: 'Оцените стоимость вашего космического мусора',
|
||||
'zh-CN': '评估您宇宙垃圾的价值',
|
||||
},
|
||||
};
|
||||
|
||||
export interface AppraisalState {
|
||||
appraisal?: Appraisal;
|
||||
}
|
||||
|
||||
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
|
||||
return await usePages<AppraisalState>(
|
||||
{
|
||||
pages: {
|
||||
appraiseModal: {
|
||||
key: 'appraiseModal',
|
||||
type: PageType.MODAL,
|
||||
render: async () => renderAppraisalModal(interaction),
|
||||
},
|
||||
appraisalResult: {
|
||||
key: 'appraisalResult',
|
||||
render: async (pageCtx) => {
|
||||
if (!isModalSubmit(interaction)) {
|
||||
throw new Error('Expected a modal submit interaction for appraisalResult page');
|
||||
}
|
||||
let marketId = 2; // Default to Jita
|
||||
let items = '';
|
||||
|
||||
interaction.data.components.forEach((comp) => {
|
||||
if (isModalLabel(comp)) {
|
||||
if (isModalSelect(comp.component) && componentHasIdPrefix(comp.component, `market`)) {
|
||||
marketId = Number.parseInt(comp.component.values[0]) || marketId;
|
||||
} else if (isModalTextInput(comp.component) && componentHasIdPrefix(comp.component, `input`)) {
|
||||
items = comp.component.value || items;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const appraisal = await appraiseItems(items, marketId);
|
||||
pageCtx.state.data.appraisal = appraisal;
|
||||
return renderAppraisal(appraisal, pageCtx, interaction);
|
||||
},
|
||||
},
|
||||
share: {
|
||||
key: 'share',
|
||||
type: PageType.FOLLOWUP,
|
||||
followUpFlags: Constants.MessageFlags.IS_COMPONENTS_V2,
|
||||
render: async (pageCtx) => renderAppraisal(pageCtx.state.data.appraisal!, pageCtx, interaction),
|
||||
},
|
||||
},
|
||||
initialPage: 'appraiseModal',
|
||||
timeout: 300, // 5 minutes
|
||||
ephemeral: true,
|
||||
},
|
||||
interaction,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
definition,
|
||||
execute,
|
||||
};
|
||||
50
packages/eve-bot/src/commands/appraise/renderAppraisal.ts
Normal file
50
packages/eve-bot/src/commands/appraise/renderAppraisal.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { ExecutableInteraction } from '@star-kitten/discord';
|
||||
import { createActionRow, createButton, createContainer, createTextDisplay } from '@star-kitten/discord/components';
|
||||
import type { PageContext } from '@star-kitten/discord/pages';
|
||||
import { type Appraisal } from '@star-kitten/eve/third-party/janice.js';
|
||||
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
|
||||
import type { AppraisalState } from './appraise.command';
|
||||
|
||||
export function renderAppraisal(
|
||||
appraisal: Appraisal,
|
||||
pageCtx: PageContext<AppraisalState>,
|
||||
interaction: ExecutableInteraction,
|
||||
) {
|
||||
const formatter = new Intl.NumberFormat(interaction.locale || 'en-US', {
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
|
||||
const container = createContainer(
|
||||
{
|
||||
accent_color: 0x1da57a,
|
||||
},
|
||||
createTextDisplay(`
|
||||
# [Appraisal ${appraisal.id} @ ${appraisal.market.name}](https://janice.e-351.com/a/${appraisal.id})
|
||||
### Buy: \`${formatter.format(appraisal.effectivePrices.totalBuyPrice)}\` ISK
|
||||
### Split: \`${formatter.format(appraisal.effectivePrices.totalSplitPrice)}\` ISK
|
||||
### Sell: \`${formatter.format(appraisal.effectivePrices.totalSellPrice)}\` ISK
|
||||
-# Volume: ${formatter.format(appraisal.totalPackagedVolume)} m³
|
||||
\`\`\`
|
||||
Buy: Sell: Qty: Item:
|
||||
${appraisal.items.map((i) => `${formatNumberToShortForm(i.effectivePrices.buyPrice).padEnd(10)}${formatNumberToShortForm(i.effectivePrices.sellPrice).padEnd(10)}${formatNumberToShortForm(i.amount).padEnd(10)}${i.itemType.name}`).join('\n')}
|
||||
\`\`\`
|
||||
-# https://janice.e-351.com/a/${appraisal.id}\n\n
|
||||
`),
|
||||
);
|
||||
|
||||
if (pageCtx.state.currentPage !== 'share') {
|
||||
container.components.push(
|
||||
createActionRow(
|
||||
createButton('Share in Channel', 'share', {
|
||||
disabled: !interaction.channel?.id,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 1,
|
||||
components: [container],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { Interaction } from '@projectdysnomia/dysnomia';
|
||||
import { createModalLabel, createStringSelect, createTextInput } from '@star-kitten/discord/components';
|
||||
import { markets } from '@star-kitten/eve/third-party/janice.js';
|
||||
|
||||
export function renderAppraisalModal(interaction: Interaction) {
|
||||
return {
|
||||
// next page to render will be appraisalResult
|
||||
custom_id: `appraisalResult`,
|
||||
title: 'Appraise Items',
|
||||
components: [
|
||||
createModalLabel(
|
||||
'Select your market (default: Jita)',
|
||||
createStringSelect(
|
||||
'market',
|
||||
{
|
||||
placeholder: 'Select a market',
|
||||
},
|
||||
...markets.map((m) => ({
|
||||
label: m.name,
|
||||
value: m.id.toString(),
|
||||
default: m.id === 2, // Jita
|
||||
})),
|
||||
),
|
||||
),
|
||||
createModalLabel(
|
||||
'Enter items to appraise',
|
||||
createTextInput('input', {
|
||||
isParagraph: true,
|
||||
placeholder: `Enter list of items to be appraised.
|
||||
Tritanium 22222
|
||||
Pyerite 8000
|
||||
Mexallon 2444`,
|
||||
}),
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
98
packages/eve-bot/src/commands/search/_old_ItemLookup.ts
Normal file
98
packages/eve-bot/src/commands/search/_old_ItemLookup.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { ButtonStyle, ChatInputCommandInteraction, CommandInteraction, MessageFlags } from 'discord.js';
|
||||
import { typeSearch } from './search';
|
||||
import { createActionRow, useNavigation, type ResumeableInteraction } from '@lib/discord';
|
||||
import { getTypeBlueprints, getTypeSchematics, getTypeSkills, getTypeVariants, typeHasAttributes, type Type } from 'star-kitten-lib/eve';
|
||||
import { mainPage, attributesPage, fittingPage, skillsPage, industryPage } from './pages';
|
||||
|
||||
export enum PageKey {
|
||||
MAIN = 'main',
|
||||
ATTRIBUTES = 'attributes',
|
||||
FITTING = 'fitting',
|
||||
SKILLS = 'skills',
|
||||
INDUSTRY = 'industry',
|
||||
}
|
||||
|
||||
export interface TypeContext {
|
||||
type: Type;
|
||||
interaction: CommandInteraction;
|
||||
disabled?: boolean;
|
||||
buildButtonRow: (key: string, context: TypeContext) => any[];
|
||||
}
|
||||
|
||||
export interface ItemLookupOptions {
|
||||
ephemeral: boolean;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export async function itemLookup(interaction: ChatInputCommandInteraction, options: ItemLookupOptions, saveResume: (messageId: string) => void) {
|
||||
const deferred = await interaction.deferReply({ flags: options.ephemeral ? MessageFlags.Ephemeral : undefined });
|
||||
const name = interaction.options.getString('name') ?? '';
|
||||
|
||||
await lookup(deferred.interaction as any, options, name, interaction.guild?.preferredLocale, saveResume);
|
||||
}
|
||||
|
||||
export async function resumeItemLookup(interaction: ResumeableInteraction, options: ItemLookupOptions, name: string, saveResume: (messageId: string) => void) {
|
||||
await lookup(interaction, options, name, interaction.guild?.preferredLocale, saveResume);
|
||||
}
|
||||
|
||||
|
||||
async function lookup(messageOrInteraction: ResumeableInteraction | ChatInputCommandInteraction, options: ItemLookupOptions, name: string, locale: string, saveResume: (messageId: string) => void) {
|
||||
const type = await typeSearch(name);
|
||||
if (!type) {
|
||||
if (messageOrInteraction instanceof ChatInputCommandInteraction) {
|
||||
messageOrInteraction.editReply({ content: `${options.type} ${name} not found` });
|
||||
} else {
|
||||
messageOrInteraction.message.edit({ content: `${options.type} ${name} not found` });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const updateContext = async (key: string, context: TypeContext) => {
|
||||
return Promise.resolve(key);
|
||||
};
|
||||
|
||||
const buildButtonRow = (key: string, context: TypeContext) => {
|
||||
return createActionRow(
|
||||
{ customId: PageKey.MAIN, label: 'Main', style: ButtonStyle.Primary, disabled: key === PageKey.MAIN },
|
||||
typeHasAttributes(context.type) && {
|
||||
customId: PageKey.ATTRIBUTES,
|
||||
label: 'Attributes',
|
||||
style: ButtonStyle.Primary,
|
||||
// disabled: key === PageKey.ATTRIBUTES,
|
||||
},
|
||||
typeHasAttributes(context.type) && {
|
||||
customId: PageKey.FITTING,
|
||||
label: `Fitting${getTypeVariants(context.type).length > 0 ? ' | Variants' : ''}`,
|
||||
style: ButtonStyle.Primary,
|
||||
// disabled: key === PageKey.FITTING,
|
||||
},
|
||||
getTypeSkills(context.type)?.length > 0 && {
|
||||
customId: PageKey.SKILLS,
|
||||
label: 'Skills',
|
||||
style: ButtonStyle.Primary,
|
||||
// disabled: key === PageKey.SKILLS,
|
||||
},
|
||||
(getTypeBlueprints(context.type)?.length > 0 || getTypeSchematics(context.type)?.length > 0) && {
|
||||
customId: PageKey.INDUSTRY,
|
||||
label: 'Industry',
|
||||
style: ButtonStyle.Primary,
|
||||
// disabled: key === PageKey.INDUSTRY,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
useNavigation({
|
||||
interaction: messageOrInteraction,
|
||||
key: 'main',
|
||||
pages: [
|
||||
mainPage(PageKey.MAIN, locale),
|
||||
attributesPage(PageKey.ATTRIBUTES, locale),
|
||||
fittingPage(PageKey.FITTING, locale),
|
||||
skillsPage(PageKey.SKILLS, locale),
|
||||
industryPage(PageKey.INDUSTRY, locale),
|
||||
],
|
||||
context: { type, buildButtonRow, interaction: messageOrInteraction },
|
||||
updateContext,
|
||||
saveResume,
|
||||
});
|
||||
}
|
||||
189
packages/eve-bot/src/commands/search/pages/_old_attributes.ts
Normal file
189
packages/eve-bot/src/commands/search/pages/_old_attributes.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { renderThreeColumns, type Page } from '@lib/discord';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import {
|
||||
attributeOrdering,
|
||||
type Type,
|
||||
CommonAttribute,
|
||||
eveRefLink,
|
||||
getTypeIconUrl,
|
||||
typeHasAnyAttribute,
|
||||
getGroup,
|
||||
typeGetAttribute,
|
||||
getUnit,
|
||||
renderUnit,
|
||||
} from 'star-kitten-lib/eve';
|
||||
import type { PageKey, TypeContext } from '../_old_ItemLookup';
|
||||
|
||||
export function attributesPage(key: PageKey.ATTRIBUTES, locale: string = 'en'): Page<TypeContext> {
|
||||
return {
|
||||
key,
|
||||
content: async (context: TypeContext) => {
|
||||
const type = context.type;
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(type.name[locale] ?? type.name.en)
|
||||
.setThumbnail(getTypeIconUrl(type))
|
||||
.setURL(eveRefLink(type.type_id))
|
||||
.setFooter({ text: `id: ${type.type_id}` })
|
||||
.setColor('Green');
|
||||
|
||||
const embeds = [embed];
|
||||
const fields = [];
|
||||
|
||||
if (type.dogma_attributes) {
|
||||
const useOrders =
|
||||
getGroup(type.group_id).category_id === 11
|
||||
? attributeOrdering['11']
|
||||
: getGroup(type.group_id).category_id === 87
|
||||
? attributeOrdering['87']
|
||||
: attributeOrdering.default;
|
||||
|
||||
Object.entries(useOrders).map((pair) => {
|
||||
const [attributePath, attrs] = pair;
|
||||
const combined = attrs['groupedAttributes']
|
||||
? attrs.normalAttributes.concat(...(attrs['groupedAttributes']?.map(([name, id]) => id) ?? []))
|
||||
: attrs.normalAttributes;
|
||||
if (!typeHasAnyAttribute(type, combined)) return;
|
||||
const split = attributePath.split('/');
|
||||
const name = split[split.length - 1];
|
||||
fields.push(
|
||||
...renderThreeColumns(
|
||||
name,
|
||||
getAttributeNames(type, combined, locale),
|
||||
[],
|
||||
getAttributeValues(type, combined, locale),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// for (const [name, attrs] of Object.entries(attrMap)) {
|
||||
// if (!type.hasAnyAttribute(attrs)) continue;
|
||||
// if (name === 'Cargo | Drones' && type.group.category.category_id === CommonCategory.MODULE) continue;
|
||||
// fields.push(...renderThreeColumns(
|
||||
// name,
|
||||
// getAttributeNames(type, attrs, locale),
|
||||
// [],
|
||||
// getAttributeValues(type, attrs, locale)
|
||||
// ));
|
||||
// }
|
||||
|
||||
// there is a max number of 24 fields per embed
|
||||
embed.addFields(fields.splice(0, 24));
|
||||
while (fields.length > 0) {
|
||||
embeds.push(new EmbedBuilder().addFields(fields.splice(0, 24)));
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'page',
|
||||
embeds,
|
||||
components: [context.buildButtonRow(key, context)],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const structureAttrs = [
|
||||
CommonAttribute.StructureHitpoints,
|
||||
CommonAttribute.Mass,
|
||||
CommonAttribute.Volume,
|
||||
CommonAttribute.InertiaModifier,
|
||||
CommonAttribute.StructureEMResistance,
|
||||
CommonAttribute.StructureThermalResistance,
|
||||
CommonAttribute.StructureKineticResistance,
|
||||
CommonAttribute.StructureExplosiveResistance,
|
||||
];
|
||||
|
||||
const droneAttrs = [CommonAttribute.CargoCapacity, CommonAttribute.DroneBandwidth, CommonAttribute.DroneCapacity];
|
||||
|
||||
const armorAttrs = [
|
||||
CommonAttribute.ArmorHitpoints,
|
||||
CommonAttribute.ArmorEMResistance,
|
||||
CommonAttribute.ArmorThermalResistance,
|
||||
CommonAttribute.ArmorKineticResistance,
|
||||
CommonAttribute.ArmorExplosiveResistance,
|
||||
];
|
||||
|
||||
const shieldAttrs = [
|
||||
CommonAttribute.ShieldCapacity,
|
||||
CommonAttribute.ShieldRechargeTime,
|
||||
CommonAttribute.ShieldEMResistance,
|
||||
CommonAttribute.ShieldThermalResistance,
|
||||
CommonAttribute.ShieldKineticResistance,
|
||||
CommonAttribute.ShieldExplosiveResistance,
|
||||
];
|
||||
|
||||
const elResAttrs = [
|
||||
CommonAttribute.CapacitorWarfareResistance,
|
||||
CommonAttribute.StasisWebifierResistance,
|
||||
CommonAttribute.WeaponDisruptionResistance,
|
||||
];
|
||||
|
||||
const capAttrs = [CommonAttribute.CapacitorCapacity, CommonAttribute.CapacitorRechargeTime];
|
||||
|
||||
const targetAttrs = [
|
||||
CommonAttribute.MaxTargetRange,
|
||||
CommonAttribute.MaxLockedTargets,
|
||||
CommonAttribute.SignatureRadius,
|
||||
CommonAttribute.ScanResolution,
|
||||
CommonAttribute.RadarSensorStrength,
|
||||
CommonAttribute.MagnetometricSensorStrength,
|
||||
CommonAttribute.GravimetricSensorStrength,
|
||||
CommonAttribute.LadarSensorStrength,
|
||||
];
|
||||
|
||||
const jumpAttrs = [
|
||||
CommonAttribute.JumpDriveCapacitorNeed,
|
||||
CommonAttribute.MaxJumpRange,
|
||||
CommonAttribute.JumpDriveFuelNeed,
|
||||
CommonAttribute.JumpDriveConsumptionAmount,
|
||||
CommonAttribute.FuelBayCapacity,
|
||||
CommonAttribute.ConduitJumpConsumptionAmount,
|
||||
CommonAttribute.COnduitJumpPassengerCapacity,
|
||||
];
|
||||
|
||||
const propAttrs = [CommonAttribute.MaxVelocity, CommonAttribute.WarpSpeed];
|
||||
|
||||
const weaponAttrs = [
|
||||
CommonAttribute.DamageMultiplier,
|
||||
CommonAttribute.AccuracyFalloff,
|
||||
CommonAttribute.OptimalRange,
|
||||
CommonAttribute.RateOfFire,
|
||||
CommonAttribute.TrackingSpeed,
|
||||
CommonAttribute.ReloadTime,
|
||||
CommonAttribute.ActivationTime,
|
||||
CommonAttribute.ChargeSize,
|
||||
CommonAttribute.UsedWithCharge1,
|
||||
CommonAttribute.UsedWithCharge2,
|
||||
];
|
||||
|
||||
const eWarAttrs = [CommonAttribute.MaxVelocityBonus];
|
||||
|
||||
const attrMap = {
|
||||
Weapon: weaponAttrs,
|
||||
Structure: structureAttrs,
|
||||
Armor: armorAttrs,
|
||||
Shield: shieldAttrs,
|
||||
'Cargo | Drones': droneAttrs,
|
||||
'Electronic Resistances': elResAttrs,
|
||||
Capacitor: capAttrs,
|
||||
Targeting: targetAttrs,
|
||||
'Jump Drive Systems': jumpAttrs,
|
||||
Propulsion: propAttrs,
|
||||
'Electronic Warfare': eWarAttrs,
|
||||
};
|
||||
|
||||
export function getAttributeNames(type: Type, ids: number[], locale: string = 'en') {
|
||||
return ids
|
||||
.map((id) => typeGetAttribute(type, id))
|
||||
.filter((attr) => !!attr)
|
||||
.map((attr) => `> ${attr.attribute.display_name[locale] ?? attr.attribute.display_name.en}`);
|
||||
}
|
||||
|
||||
export function getAttributeValues(type: Type, ids: number[], locale: string = 'en') {
|
||||
return ids
|
||||
.map((id) => typeGetAttribute(type, id))
|
||||
.filter((attr) => !!attr)
|
||||
.map(
|
||||
(attr) => `**${attr.attribute.unit_id ? renderUnit(getUnit(attr.attribute.unit_id), attr.value) : attr.value}**`,
|
||||
);
|
||||
}
|
||||
86
packages/eve-bot/src/commands/search/pages/_old_fitting.ts
Normal file
86
packages/eve-bot/src/commands/search/pages/_old_fitting.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { renderThreeColumns, type Page } from '@lib/discord';
|
||||
import { getAttributeNames, getAttributeValues } from './_old_attributes';
|
||||
import { PageKey, type TypeContext } from '../_old_ItemLookup';
|
||||
import {
|
||||
eveRefLink,
|
||||
getTypeIconUrl,
|
||||
getTypeVariants,
|
||||
typeHasAnyAttribute,
|
||||
CommonAttribute,
|
||||
renderTypeEveRefLink,
|
||||
} from 'star-kitten-lib/eve';
|
||||
|
||||
export function fittingPage(key: string = PageKey.FITTING, locale: string = 'en'): Page<TypeContext> {
|
||||
return {
|
||||
key,
|
||||
content: async (context: TypeContext) => {
|
||||
const type = context.type;
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(type.name[locale] ?? type.name.en)
|
||||
.setThumbnail(getTypeIconUrl(type))
|
||||
.setURL(eveRefLink(type.type_id))
|
||||
.setFooter({ text: `id: ${type.type_id}` })
|
||||
.setColor('Green');
|
||||
|
||||
const fields = [];
|
||||
|
||||
for (const [name, attrs] of Object.entries(attrMap)) {
|
||||
if (!typeHasAnyAttribute(type, attrs)) continue;
|
||||
fields.push(
|
||||
...renderThreeColumns(
|
||||
name,
|
||||
getAttributeNames(type, attrs, locale),
|
||||
[],
|
||||
getAttributeValues(type, attrs, locale),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// get variants
|
||||
{
|
||||
if (getTypeVariants(type).length > 0) {
|
||||
getTypeVariants(type).map((v) => {
|
||||
fields.push({
|
||||
name: `${v.metaGroup.name[locale] ?? v.metaGroup.name.en} variants`,
|
||||
value: v.types.map((t) => renderTypeEveRefLink(t, locale)).join('\n'),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (fields.length === 0) {
|
||||
return {
|
||||
type: 'page',
|
||||
embeds: [embed.setDescription('This item does not have any fitting attributes.')],
|
||||
components: [context.buildButtonRow(key, context)],
|
||||
};
|
||||
}
|
||||
|
||||
embed.addFields(fields);
|
||||
return {
|
||||
type: 'page',
|
||||
embeds: [embed],
|
||||
components: [context.buildButtonRow(key, context)],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const shipOutputAttrs = [CommonAttribute.PowergridOutput, CommonAttribute.CPUOutput];
|
||||
|
||||
const hardpointAttrs = [CommonAttribute.TurretHardpoints, CommonAttribute.LauncherHardpoints];
|
||||
|
||||
const moduleAttrs = [CommonAttribute.HighSlots, CommonAttribute.MediumSlots, CommonAttribute.LowSlots];
|
||||
|
||||
const rigAttrs = [CommonAttribute.RigSlots, CommonAttribute.RigSize, CommonAttribute.Calibration];
|
||||
|
||||
const moduleFittingAttrs = [CommonAttribute.CPUUsage, CommonAttribute.PowergridUsage, CommonAttribute.ActivationCost];
|
||||
|
||||
const attrMap = {
|
||||
'Ship Output': shipOutputAttrs,
|
||||
Hardpoints: hardpointAttrs,
|
||||
Modules: moduleAttrs,
|
||||
Rigs: rigAttrs,
|
||||
Fitting: moduleFittingAttrs,
|
||||
};
|
||||
115
packages/eve-bot/src/commands/search/pages/_old_industry.ts
Normal file
115
packages/eve-bot/src/commands/search/pages/_old_industry.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { renderThreeColumns, type Page } from '@lib/discord';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import {
|
||||
getBlueprint,
|
||||
type ManufacturingActivity,
|
||||
eveRefLink,
|
||||
getType,
|
||||
getSchematic,
|
||||
getTypeIconUrl,
|
||||
getTypeBlueprints,
|
||||
getTypeSchematics,
|
||||
} from 'star-kitten-lib/eve';
|
||||
import type { PageKey, TypeContext } from '../_old_ItemLookup';
|
||||
|
||||
export function industryPage(key: PageKey.INDUSTRY, locale: string = 'en'): Page<TypeContext> {
|
||||
return {
|
||||
key,
|
||||
content: async (context: TypeContext) => {
|
||||
const type = context.type;
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(type.name[locale] ?? type.name.en)
|
||||
.setThumbnail(getTypeIconUrl(type))
|
||||
.setURL(eveRefLink(type.type_id))
|
||||
.setFooter({ text: `id: ${type.type_id}` })
|
||||
.setColor('Green');
|
||||
|
||||
let description = '';
|
||||
|
||||
const fields = [];
|
||||
const bps = getTypeBlueprints(type);
|
||||
if (bps.length > 0) {
|
||||
bps.map((bp) => {
|
||||
const type = bp.blueprint;
|
||||
const blueprint = getBlueprint(bp.blueprint.type_id);
|
||||
const activity = blueprint.activities[bp.activity];
|
||||
|
||||
description += `### Blueprint\n`;
|
||||
description += `[${type.name[locale] ?? type.name.en}](${eveRefLink(type.type_id)})\n`;
|
||||
// fields.push({
|
||||
// name: 'Blueprints',
|
||||
// value: bps.map(bp => {
|
||||
// const type = bp.blueprint;
|
||||
// return `[${type.name[locale] ?? type.name.en}](${type.eveRefLink})`;
|
||||
// })
|
||||
// });
|
||||
|
||||
if (activity['materials']) {
|
||||
const manufacturing = activity as ManufacturingActivity;
|
||||
if (manufacturing.materials) {
|
||||
description += '### Materials\n```';
|
||||
description += Object.values(manufacturing.materials)
|
||||
.map((m) => {
|
||||
const t = getType(m.type_id);
|
||||
return `${t.name[locale] ?? t.name.en} ${m.quantity}`;
|
||||
})
|
||||
.join('\n');
|
||||
description += '```';
|
||||
|
||||
// fields.push(...renderThreeColumns(
|
||||
// 'Materials',
|
||||
// Object.values(manufacturing.materials).map(m => {
|
||||
// const t = getType(m.type_id);
|
||||
// return `[${t.name[locale] ?? t.name.en}](${t.eveRefLink})`;
|
||||
// }),
|
||||
// [],
|
||||
// Object.values(manufacturing.materials).map(m => {
|
||||
// const t = getType(m.type_id);
|
||||
// return `x**${m.quantity}**`;
|
||||
// }),
|
||||
// ));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const schematics = getTypeSchematics(type);
|
||||
if (schematics.length > 0) {
|
||||
schematics.map((type) => {
|
||||
const schematic = getSchematic(type.type_id);
|
||||
|
||||
fields.push({
|
||||
name: 'Schematic',
|
||||
value: `[${type.name[locale] ?? type.name.en}](${eveRefLink(type.type_id)})`,
|
||||
});
|
||||
|
||||
fields.push(
|
||||
...renderThreeColumns(
|
||||
'Materials',
|
||||
Object.values(schematic.materials).map((m) => {
|
||||
const t = getType(m.type_id);
|
||||
return `[${t.name[locale] ?? t.name.en}](${eveRefLink(t.type_id)})`;
|
||||
}),
|
||||
[],
|
||||
Object.values(schematic.materials).map((m) => {
|
||||
return `x**${m.quantity}**`;
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (description === '') {
|
||||
description = 'No blueprints or schematics found';
|
||||
}
|
||||
|
||||
embed.addFields(fields);
|
||||
embed.setDescription(description);
|
||||
return {
|
||||
type: 'page',
|
||||
embeds: [embed],
|
||||
components: [context.buildButtonRow(key, context)],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
134
packages/eve-bot/src/commands/search/pages/_old_skills.ts
Normal file
134
packages/eve-bot/src/commands/search/pages/_old_skills.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { coloredText, renderThreeColumns, WHITE_SPACE, type Page } from '@lib/discord';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import type { Type } from 'star-kitten-lib/eve';
|
||||
import { eveRefLink, getCharacterSkills, getGroup, getTypeIconUrl, getTypeSkills } from 'star-kitten-lib/eve';
|
||||
import { CommonCategory } from 'star-kitten-lib/eve';
|
||||
import type { PageKey, TypeContext } from '../_old_ItemLookup';
|
||||
import { CharacterHelper, UserHelper } from 'star-kitten-lib/db';
|
||||
|
||||
function canUseText(type: Type) {
|
||||
const category = getGroup(type.group_id).category_id;
|
||||
switch (category) {
|
||||
case CommonCategory.SHIP:
|
||||
return 'fly this ship';
|
||||
case CommonCategory.DRONE:
|
||||
return 'use this drone';
|
||||
case CommonCategory.MODULE:
|
||||
return 'use this module';
|
||||
default:
|
||||
return 'use this item';
|
||||
}
|
||||
}
|
||||
|
||||
export function skillsPage(key: PageKey.SKILLS, locale: string = 'en'): Page<TypeContext> {
|
||||
return {
|
||||
key: 'skills',
|
||||
content: async (context: TypeContext) => {
|
||||
const type = context.type;
|
||||
|
||||
if (!type.required_skills || type.required_skills.length === 0) {
|
||||
return {
|
||||
type: 'page',
|
||||
embeds: [
|
||||
new EmbedBuilder()
|
||||
.setTitle(type.name[locale] ?? type.name.en)
|
||||
.setDescription('This item does not require any skills to use.')
|
||||
.setThumbnail(getTypeIconUrl(type))
|
||||
.setURL(eveRefLink(type.type_id))
|
||||
.setFooter({ text: `id: ${type.type_id}` })
|
||||
.setColor('Green'),
|
||||
],
|
||||
components: [context.buildButtonRow(key, context)],
|
||||
};
|
||||
}
|
||||
|
||||
const user = UserHelper.findByDiscordId(context.interaction.user.id);
|
||||
const main = CharacterHelper.find(user.mainCharacterID);
|
||||
const skills = main && (await getCharacterSkills(main));
|
||||
const characterSkills: { [key: number]: number } =
|
||||
skills && skills?.skills.reduce((acc, skill) => ({ ...acc, [skill.skill_id]: skill.trained_skill_level }), {});
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(type.name[locale] ?? type.name.en)
|
||||
.setThumbnail(getTypeIconUrl(type))
|
||||
.setURL(eveRefLink(type.type_id))
|
||||
.setFooter({ text: `id: ${type.type_id} -- ◼ = trained | ☒ = required but not trained` });
|
||||
|
||||
let description = '';
|
||||
|
||||
description += '### Required Skills\n```\n';
|
||||
description += getTypeSkills(type)
|
||||
.map((skillLevel) => `${skillLevel.skill.name[locale] ?? skillLevel.skill.name.en} ${skillLevel.level}`)
|
||||
.join('\n');
|
||||
description += '```';
|
||||
|
||||
let canFly = true;
|
||||
if (characterSkills) {
|
||||
if (getTypeSkills(type).every((skillLevel) => characterSkills[skillLevel.skill.type_id] >= skillLevel.level)) {
|
||||
description += coloredText(`${main.name} can ${canUseText(type)}`, 'green');
|
||||
canFly = true;
|
||||
} else {
|
||||
description += coloredText(`${main.name} cannot ${canUseText(type)}`, 'red');
|
||||
canFly = false;
|
||||
}
|
||||
}
|
||||
embed.setDescription(description);
|
||||
embed.addFields(
|
||||
renderThreeColumns('', getSkillNames(type, locale), [], getSkillLevels(type, characterSkills).map(renderLevel)),
|
||||
);
|
||||
embed.setColor(canFly ? 'Green' : 'Red');
|
||||
return {
|
||||
type: 'page',
|
||||
embeds: [embed],
|
||||
components: [context.buildButtonRow(key, context)],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getSkillNames(type: Type, locale: string, depth: number = 0) {
|
||||
let spacing = '';
|
||||
for (let i = 0; i < depth; ++i) {
|
||||
spacing += WHITE_SPACE;
|
||||
}
|
||||
let names: string[] = [];
|
||||
getTypeSkills(type).forEach((skillLevel) => {
|
||||
names.push(
|
||||
`${spacing}[${skillLevel.skill.name[locale] ?? skillLevel.skill.name.en}](${skillLevel.skill.eveRefLink})`,
|
||||
);
|
||||
if (skillLevel.skill.skills.length > 0) {
|
||||
names.push(...getSkillNames(skillLevel.skill, locale, depth + 1));
|
||||
}
|
||||
});
|
||||
return names;
|
||||
}
|
||||
|
||||
interface RequiredLevel {
|
||||
required: number;
|
||||
have: number;
|
||||
}
|
||||
|
||||
// skills is a map of skill_id to trained_skill_level
|
||||
function getSkillLevels(type: Type, skills?: { [key: number]: number }): RequiredLevel[] {
|
||||
let levels: RequiredLevel[] = [];
|
||||
getTypeSkills(type).forEach((skillLevel) => {
|
||||
levels.push({
|
||||
required: skillLevel.level,
|
||||
have: skills ? skills[skillLevel.skill.type_id] || 0 : 0,
|
||||
});
|
||||
if (skillLevel.skill.skills.length > 0) {
|
||||
levels.push(...getSkillLevels(skillLevel.skill, skills));
|
||||
}
|
||||
});
|
||||
return levels;
|
||||
}
|
||||
|
||||
function renderLevel(level: RequiredLevel) {
|
||||
let str = '';
|
||||
for (let i = 1; i <= 5; ++i) {
|
||||
str += i <= level.required ? (level.have >= i ? '◼' : '☒') : level.have >= i ? '◼' : '▢';
|
||||
// shapes to test with:
|
||||
// '■' '▰' '▱' '▨' '▧' '◼' '▦' '▩' '▥' '▤' '▣' '▢' '◪' '◫' '◩' '◨' '◧'
|
||||
}
|
||||
return str + `${WHITE_SPACE}${level.have}/${level.required}`;
|
||||
}
|
||||
140
packages/eve-bot/src/commands/search/pages/attributes.ts
Normal file
140
packages/eve-bot/src/commands/search/pages/attributes.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { renderSubroutes, type Page } from '@star-kitten/discord/pages';
|
||||
import type { SearchState } from '../search.command';
|
||||
import {
|
||||
ButtonStyle,
|
||||
createContainer,
|
||||
createSection,
|
||||
createSeparator,
|
||||
createTextDisplay,
|
||||
createThumbnail,
|
||||
Padding,
|
||||
} from '@star-kitten/discord/components';
|
||||
import {
|
||||
getGroup,
|
||||
getType,
|
||||
getUnit,
|
||||
renderUnit,
|
||||
typeGetAttribute,
|
||||
typeHasAnyAttribute,
|
||||
type Type,
|
||||
} from '@star-kitten/eve/models';
|
||||
import { attributeOrdering } from '@star-kitten/eve';
|
||||
import { searchActionRow } from './helpers';
|
||||
import { toTitleCase } from '@star-kitten/util/text.js';
|
||||
|
||||
enum Images {
|
||||
ATTRIBUTES = 'https://iili.io/KTbaMR2.md.webp',
|
||||
DEFENSES = 'https://iili.io/KTbSVoX.md.webp',
|
||||
FITTING = 'https://iili.io/KufiFYG.md.webp',
|
||||
FACILITIES = 'https://iili.io/KufikGt.md.webp',
|
||||
}
|
||||
|
||||
const attributeCategoryMap = {
|
||||
structure: 'UI/Fitting/Structure',
|
||||
armor: 'UI/Common/Armor',
|
||||
shield: 'UI/Common/Shield',
|
||||
ewar: 'UI/Common/EWarResistances',
|
||||
capacitor: 'UI/Fitting/FittingWindow/Capacitor',
|
||||
targeting: 'UI/Fitting/FittingWindow/Targeting',
|
||||
facilities: 'UI/InfoWindow/SharedFacilities',
|
||||
fighters: 'UI/InfoWindow/FighterFacilities',
|
||||
on_death: 'UI/InfoWindow/OnDeath',
|
||||
jump_drive: 'UI/InfoWindow/JumpDriveSystems',
|
||||
propulsion: 'UI/Compare/Propulsion',
|
||||
};
|
||||
|
||||
const groupedCategories = [
|
||||
// defenses
|
||||
['shield', 'armor', 'structure', 'ewar'],
|
||||
// fittings
|
||||
['capacitor', 'targeting', 'propulsion'],
|
||||
// facilities
|
||||
['facilities', 'fighters', 'on_death', 'jump_drive'],
|
||||
];
|
||||
|
||||
function getAttributeOrdering(type: Type) {
|
||||
const group = getGroup(type.group_id);
|
||||
switch (group.category_id) {
|
||||
case 11:
|
||||
return attributeOrdering['11'];
|
||||
case 87:
|
||||
return attributeOrdering['87'];
|
||||
default:
|
||||
return attributeOrdering.default;
|
||||
}
|
||||
}
|
||||
|
||||
const bannerMap = {
|
||||
shield: Images.DEFENSES,
|
||||
armor: Images.DEFENSES,
|
||||
structure: Images.DEFENSES,
|
||||
ewar: Images.DEFENSES,
|
||||
|
||||
capacitor: Images.FITTING,
|
||||
targeting: Images.FITTING,
|
||||
propulsion: Images.FITTING,
|
||||
|
||||
facilities: Images.FACILITIES,
|
||||
fighters: Images.FACILITIES,
|
||||
on_death: Images.FACILITIES,
|
||||
jump_drive: Images.FACILITIES,
|
||||
};
|
||||
|
||||
const page: Page<SearchState> = {
|
||||
key: 'attributes',
|
||||
render: (context) => {
|
||||
const type = getType(context.state.data.type_id);
|
||||
const ordering = getAttributeOrdering(type);
|
||||
|
||||
return {
|
||||
components: [
|
||||
createContainer(
|
||||
{},
|
||||
createSection(
|
||||
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
createTextDisplay(`# [${type.name.en}](https://everef.net/types/${type.type_id})\n## Attributes`),
|
||||
),
|
||||
...renderSubroutes(
|
||||
context,
|
||||
'attributes',
|
||||
groupedCategories.map((group) =>
|
||||
group.map((cat) => {
|
||||
const attrCat = ordering[attributeCategoryMap[cat]];
|
||||
const attrs = attrCat.groupedCategories
|
||||
? attrCat.groupedCategories.map(([name, id]) => id).concat(attrCat.normalAttributes) || []
|
||||
: attrCat.normalAttributes;
|
||||
if (!typeHasAnyAttribute(type, attrs)) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
label: toTitleCase(cat.replace('_', ' ')),
|
||||
value: cat,
|
||||
banner: bannerMap[cat],
|
||||
};
|
||||
}),
|
||||
),
|
||||
(currentRoute) => {
|
||||
const lines: string[] = [];
|
||||
const attrCat = ordering[attributeCategoryMap[currentRoute]];
|
||||
const attrs = attrCat.groupedCategories
|
||||
? attrCat.groupedCategories.map(([name, id]) => id).concat(attrCat.normalAttributes) || []
|
||||
: attrCat.normalAttributes;
|
||||
attrs.map((attrId) => {
|
||||
const attr = typeGetAttribute(type, attrId);
|
||||
if (!attr) return;
|
||||
const unit = attr.attribute.unit_id ? renderUnit(getUnit(attr.attribute.unit_id), attr.value) : '';
|
||||
lines.push(`${attr.attribute.display_name.en.padEnd(24)} ${unit}`);
|
||||
});
|
||||
return createTextDisplay('```\n' + lines.join('\n') + '\n```');
|
||||
},
|
||||
{ style: ButtonStyle.SECONDARY },
|
||||
),
|
||||
createSeparator(Padding.LARGE),
|
||||
searchActionRow('attributes'),
|
||||
),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default page;
|
||||
11
packages/eve-bot/src/commands/search/pages/helpers.ts
Normal file
11
packages/eve-bot/src/commands/search/pages/helpers.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createActionRow, createButton } from '@star-kitten/discord/components';
|
||||
|
||||
export function searchActionRow(pageKey: string) {
|
||||
return createActionRow(
|
||||
createButton('Main', 'main', { disabled: pageKey === 'main' }),
|
||||
createButton('Attributes', 'attributes', { disabled: pageKey === 'attributes' }),
|
||||
createButton('Fittings', 'fittings', { disabled: pageKey === 'fittings' }),
|
||||
createButton('Skills', 'skills', { disabled: pageKey === 'skills' }),
|
||||
createButton('Industry', 'industry', { disabled: pageKey === 'industry' }),
|
||||
);
|
||||
}
|
||||
3
packages/eve-bot/src/commands/search/pages/index.ts
Normal file
3
packages/eve-bot/src/commands/search/pages/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './_old_industry';
|
||||
export * from './main';
|
||||
export * from './attributes';
|
||||
89
packages/eve-bot/src/commands/search/pages/main.ts
Normal file
89
packages/eve-bot/src/commands/search/pages/main.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { Page } from '@star-kitten/discord/pages';
|
||||
import type { SearchState } from '../search.command';
|
||||
import {
|
||||
createContainer,
|
||||
createMediaGallery,
|
||||
createSection,
|
||||
createTextDisplay,
|
||||
createThumbnail,
|
||||
createURLButton,
|
||||
} from '@star-kitten/discord/components';
|
||||
import { getRoleBonuses, getSkillBonuses, getType } from '@star-kitten/eve/models/type.js';
|
||||
import { cleanText } from '@star-kitten/eve/utils/markdown.js';
|
||||
import { typeSearch } from '@star-kitten/eve/utils/typeSearch.js';
|
||||
import { isApplicationCommand } from '@star-kitten/discord';
|
||||
import { fetchPrice } from '@star-kitten/eve/third-party/evetycoon.js';
|
||||
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
|
||||
import { searchActionRow } from './helpers';
|
||||
|
||||
const page: Page<SearchState> = {
|
||||
key: 'main',
|
||||
render: async (context) => {
|
||||
if (!context.state.data.type_id && isApplicationCommand(context.interaction)) {
|
||||
const typeName = context.interaction.data.options?.find((opt) => opt.name === 'name')?.value;
|
||||
const found = await typeSearch(typeName as string);
|
||||
|
||||
if (!found) {
|
||||
return {
|
||||
components: [createTextDisplay(`No item found for: ${typeName}`)],
|
||||
};
|
||||
}
|
||||
|
||||
context.state.data.type_id = found.type_id;
|
||||
}
|
||||
|
||||
const type = getType(context.state.data.type_id);
|
||||
|
||||
const skillBonuses = getSkillBonuses(type);
|
||||
const roleBonuses = getRoleBonuses(type);
|
||||
const price = await fetchPrice(type.type_id);
|
||||
|
||||
return {
|
||||
components: [
|
||||
createContainer(
|
||||
{},
|
||||
createSection(
|
||||
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
createTextDisplay(`
|
||||
# [${type.name.en}](https://everef.net/types/${type.type_id})
|
||||
|
||||
${skillBonuses
|
||||
.map((bonus) => {
|
||||
return `## Bonus per level of ${bonus.skill.name.en}
|
||||
${bonus.bonuses
|
||||
.sort((a, b) => a.importance - b.importance)
|
||||
.map((b) => `${b.bonus}${b.unit?.display_name ?? '-'} ${cleanText(b.bonus_text.en)}`)
|
||||
.join('\n')}`;
|
||||
})
|
||||
.join('\n')}
|
||||
${
|
||||
roleBonuses.length > 0
|
||||
? `\n## Role Bonuses
|
||||
${roleBonuses
|
||||
.sort((a, b) => a.importance - b.importance)
|
||||
.map((b) => `${b.bonus ?? ''}${b.unit?.display_name ?? '-'} ${cleanText(b.bonus_text.en)}`)
|
||||
.join('\n')}`
|
||||
: ''
|
||||
}
|
||||
`),
|
||||
),
|
||||
createMediaGallery({
|
||||
url: 'https://iili.io/KTPCFRt.md.webp',
|
||||
}),
|
||||
// createSeparator(Padding.LARGE),
|
||||
createSection(
|
||||
createURLButton('View on EVE Tycoon', `https://evetycoon.com/market/${type.type_id}`),
|
||||
createTextDisplay(
|
||||
`## Buy: ${price ? formatNumberToShortForm(price.buyAvgFivePercent) : '--'} ISK
|
||||
## Sell: ${price ? formatNumberToShortForm(price.sellAvgFivePercent) : '--'} ISK`,
|
||||
),
|
||||
),
|
||||
createTextDisplay(`-# Type Id: ${type.type_id}`),
|
||||
searchActionRow('main'),
|
||||
),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default page;
|
||||
69
packages/eve-bot/src/commands/search/search.command.ts
Normal file
69
packages/eve-bot/src/commands/search/search.command.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
createChatCommand,
|
||||
isAutocomplete,
|
||||
stringOption,
|
||||
type CommandContext,
|
||||
type ExecutableInteraction,
|
||||
} from '@star-kitten/discord';
|
||||
import { usePages } from '@star-kitten/discord/pages';
|
||||
import { initializeTypeSearch, typeSearchAutoComplete } from '@star-kitten/eve/utils/typeSearch.js';
|
||||
|
||||
import main from './pages/main';
|
||||
import attributes from './pages/attributes';
|
||||
|
||||
let now = Date.now();
|
||||
console.debug('Initializing type search...');
|
||||
await initializeTypeSearch().catch((e) => {
|
||||
console.error('Failed to initialize type search', e);
|
||||
process.exit(1);
|
||||
});
|
||||
console.debug(`Type search initialized. Took ${Date.now() - now}ms`);
|
||||
|
||||
export interface SearchState {
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
export default createChatCommand(
|
||||
{
|
||||
name: 'search',
|
||||
description: 'Search for a type',
|
||||
options: [
|
||||
stringOption({
|
||||
name: 'name',
|
||||
description: 'The type name to search for',
|
||||
autocomplete: true,
|
||||
required: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
execute,
|
||||
);
|
||||
|
||||
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
|
||||
if (isAutocomplete(interaction)) {
|
||||
const focusedOption = interaction.data.options?.find((opt) => opt.focused);
|
||||
if (focusedOption?.name === 'name') {
|
||||
const value = focusedOption.value as string;
|
||||
const results = await typeSearchAutoComplete(value);
|
||||
if (results) {
|
||||
await interaction.result(results);
|
||||
} else {
|
||||
await interaction.result([]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
usePages<SearchState>(
|
||||
{
|
||||
pages: {
|
||||
main,
|
||||
attributes,
|
||||
},
|
||||
initialPage: 'main',
|
||||
ephemeral: false,
|
||||
},
|
||||
interaction,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
3
packages/eve-bot/src/main.ts
Normal file
3
packages/eve-bot/src/main.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { startDiscordBot } from '@star-kitten/discord';
|
||||
|
||||
startDiscordBot();
|
||||
40
packages/eve-bot/tsconfig.json
Normal file
40
packages/eve-bot/tsconfig.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@star-kitten/discord",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": false,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
|
||||
// Paths
|
||||
"paths": {
|
||||
"@*": ["./src/*"]
|
||||
},
|
||||
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
|
||||
"typeRoots": ["src/types", "./node_modules/@types"]
|
||||
},
|
||||
"include": ["src", "types"],
|
||||
"exclude": ["node_modules", "dist", "build", "**/*.test.ts"]
|
||||
}
|
||||
30
packages/eve-web/.env.development
Normal file
30
packages/eve-web/.env.development
Normal file
@@ -0,0 +1,30 @@
|
||||
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
|
||||
#/ public-key encryption for .env files /
|
||||
#/ [how it works](https://dotenvx.com/encryption) /
|
||||
#/----------------------------------------------------------/
|
||||
DOTENV_PUBLIC_KEY_DEVELOPMENT="02572da3d4f3a844588a944214c0e142a5a01deaa6551456af146d34b574024416"
|
||||
|
||||
# .env.development
|
||||
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
|
||||
#/ public-key encryption for .env files /
|
||||
#/ [how it works](https://dotenvx.com/encryption) /
|
||||
#/----------------------------------------------------------/
|
||||
DOTENV_PUBLIC_KEY="02292a330aa041b5f7efc51504e0c208accba67a6877a217ab43cbb59c3c0c3e66"
|
||||
|
||||
# .env
|
||||
DEBUG="encrypted:BC7p62nrs3NV7XdxnBbO1WsHGm8IgDEbOS1RmgORHSqh05vGIv+hmqwau61FamrU/puT4btAsG+iLcSeypCQV5e7bBpr0qu0HQoVyMzunBvrN5ivzNY0Af800lNynsBXzq0cXTY="
|
||||
PORT="encrypted:BJpY7J2J+0z4LUNnKRr7HzcpETcdnWFuRAOC3hVl2cZyiCBl706vJqv+iY3BgA0mus73t9fwYjGRrPSXSQbcSEBzr+Jquj8Gkvy7loXKkKp4Gz1tqX554txfY0XjrgMO3oHATO8="
|
||||
NODE_ENV="encrypted:BJun6Kdf/kBSrIYUgw7pfnMwlrTjvUUq/w2yjqn+X5UgaxUxzLsI0JabYlxQCxoDMSEagQYWI5HkRaZvYuXHzyN2aXm6drC2bahg9aWZTVyWYu00FFwIah7l/tuMA/caeO7s5dwkuOgCvlOQ"
|
||||
LOG_LEVEL="encrypted:BKBvZDS7xkzgg5IqiTc9izmt4om4CjX7t4LA8gMY+0ru0NtVBpkSkchil4PvaRNhktcNjtIzfE6sduRoFw5T1tt88PjtKWIhORyACZk2ZtR3vuO/xecq2q1rIIp5kD1gcp4ujltK"
|
||||
BASE_URL="encrypted:BMueek9QzKR3k+Foe0xUruZUxwwIXBcrL7B6ksdBNFaF92nHHy2HLLOhOpKNebGfSwb9mBR5kOXb095+hsDEcqAiv5nc5BjWGfk/wkFpRfFcmyEGYlmrxLclbFdQFXIqYrzmN7ae/VV1VlRzNLBipO0smJj4LZY649oV2A=="
|
||||
EVE_CLIENT_ID="encrypted:BPOAohc2MmP7VvvLybwvcl7XgzYRqWC6IzBkOf+T73kN8YlHYKt611uYNjU27G6hkVRK3DSfgCuxTPzrQBPxvAY6pDbqFG8pVU9cKDmoeHYtiKh7KkHuBN/cEzll6x8hpIwwrY32nzQjxYMvyVO5UgG7OHK1T/jkeya2TW2DSgGn"
|
||||
EVE_CLIENT_SECRET="encrypted:BE0VYniO3JEFNt1R4iUrGA7W7cSp7gQtG7Y86VQeWnte+idjnqSFmv2lmz83rc7Idvi/VU+ipuY6RL2+49jAb/oUaXGUiwguBAnlFU+ypOVy2Ed29o7yggqiB2+dUuu4xDAsLAfSXErnw4gsDsEPAMqaKhCYz0LHEvJX5ZdwfAcfrpWoLeI/Vm0="
|
||||
EVE_CALLBACK_URL="encrypted:BIjACAnGtL06X0vkvmydydup9HZDcPA+DAYUAhlH3lsq8GJPD5XxlRwVx02VzZQuATfqm1JwGDyYbw8ceaWD2RLlcjSXPF7MWDpYzG1FExE7FZbFRBBO8XqGH9X8kfxYsuca/Td8KuIPjyS5BNkyM4GQcTlojKa15Hk4GXmpNP6Gyb45XNPCegRJL5aARQ=="
|
||||
ESI_USER_AGENT="encrypted:BEUaqMbwPNvJeF/d5q5LJ5Owd5wcQ8Jg/BTGn6qns4cwlX/e6QqLLmfp8E8SxQ8Z6h+qDLpZj0HROJIOK9Y2Xb6qjB+hCnjceRMTx1QWpNS6jXQ85TQiZfYzee57QFleau621B77KIuM5DjPUZZ02efAL+2Yk83amrh3vnzvwrnvM5mMGTzc2TbeQwB6MEdjAdvAz65VGX9DnCwbtP3bMKE7og4+sKMUrTYZpCsILug="
|
||||
DISCORD_APP_ID="encrypted:BM3yHCf9kTxcIQzzmNseT0/xol6ZLYTjZ3m3NKybW1oD2joZ/gTUIg2+mgfaeCqY1CPaSGppxguDPFgthMbWihAdeGuxiITiwLDulCTLcgjBsyT6IlsKUSsE5ZiEZl1A+ikNG1/8rxrF0MsIjVqfw+U+ev8="
|
||||
DISCORD_APP_SECRET="encrypted:BNVLmKb2ZJq1+iIYKwGaCtcM+hak5NPXLNgJnPzTlpd/5zUTKc8OtYZLhg2oqtOYv8rxf8sbjpXNWB6lZL5J5NUuPcQSfVOMr3U1BN6b9WsfWZ/2Pr4cm8kqqqjJVTF49/DasRQU5VlIXodvCI2XfWrcPFV20NvE8HzsJQz/g5cF"
|
||||
DISCORD_PUBLIC_KEY="encrypted:BEmxCcAqODfgukOza3EGzsXdrnXv3qVbzlLFYsD4Iba9mQFCaOwChIQNkN/+Ve5NV5sZeefU5sEXZRYbb/hRjJjMRX4NrhEZn2Pg3mI5/FG/+uCD0cWWs4JGzTRwUcBcG8FZ2Mw/kP1ymUqMRkJCYC+XdyYqcP8zNrQ6/aca3HLcqPma7j2/1lsbX4UJ3QcKDg3bsY+107MLLJJ4+TYiwcE="
|
||||
DISCORD_BOT_TOKEN="encrypted:BCLvoICUVOYz7pkV3f+dufxEZiWcKxQ7E56cCUXtLQjjorW5WsCftgoCdP5NXKSbhHYZBqrTCAHqPp/kLgaVjDXxXu2+61DiCNjB3t/kNIudkfsuzJ0vusKcVzgnQOmTmYnOZ+SYo7hKPrjLi2/Vk8r6K26TATp9t+iuSBMsEs7t9nnnySSYIXyHfXRXbRBg4NUFOmeDRUqBhhix9mgr+MI5SPmQgsXCvA=="
|
||||
DISCORD_TEST_GUILD_ID="encrypted:BKRJRRvQw2aoBrVoVZpPPYSzCy+2VXLLzBb1zNzUR5510qobPNDbUoIlJdm41moQS0ALG94l8miVLtkOKW6MGZyepq+gE1zSEu7eIJWHB8/eSUnGuNeqSghI4Kxr8kn04Bl4eDb1UgP1Fu+8XSXjVel85Q=="
|
||||
JANICE_KEY="encrypted:BPq+CJycBbmmoDHNmHHYiQ00PeISDQTqp2IdlLZD52V2wWPOmXnRnqgvoXff64ebUayySaW1sQtvoaMJE3Gt9E/FUquYiRTPUMT5+oW7Xo60IRgAhRW9n2m/YUDIORxR0J0qxKc9cdE75VWJNqLELKKwsriBdpqj0+4yM/Mn6IaU"
|
||||
PERPLEXITY_API_KEY="encrypted:BHxscbb6WwhFgt5Cp/WzrYGJ6nJvIFeoS3JrTaibPbQ7kiu3C/Zx8klgRhlBF25+HAA8chZHT5MeE99FSD59RqIQTcnKAyfUnxNq+ovoXMV7Nk2Fb/rrdLnvUf8VoeH7L4ZyOVF1wv0r4xm/7Qqc1JN50fME6Qa47KnnQekq/n6o2e5HVI153yJnlCQu/SvcyXCY6VW2"
|
||||
23
packages/eve-web/.env.production
Normal file
23
packages/eve-web/.env.production
Normal file
@@ -0,0 +1,23 @@
|
||||
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
|
||||
#/ public-key encryption for .env files /
|
||||
#/ [how it works](https://dotenvx.com/encryption) /
|
||||
#/----------------------------------------------------------/
|
||||
DOTENV_PUBLIC_KEY_PRODUCTION="02f0469506f6722d8fcc179c199ff159ca32f082000c8e7a1465891adb50a4c031"
|
||||
|
||||
# .env.production
|
||||
DEBUG="encrypted:BKnKyDjoQANl7bGi3568JTnA/7sUBdUVlc8nNznTwxs5Lu/4iMDu1cY0x0iE7b0z+RXEuA/w6bERB5uTmWkCdglEI5S6LhnXZzcV55iLBY8rHO/MIDBF39vn/PBsMiA86gBmtaIn"
|
||||
PORT="encrypted:BKaGvooBBBWB13491yjfYwYT2zG1MZiYi3+Y6wW8ZhuvMOEsfPuW94rv3cE2LoguALsBFXH9rn3lQJysZLJcYd9AxJoLqxmWHkEpbQ35PYDSvkJ0GsEGlrd74hVbnh57A7Dmqqc="
|
||||
NODE_ENV="encrypted:BIiY7XZ9vy9stbiNC2u+o4ibruGTtMuRXUJPs3lMxkHKlK6gksg0ddTPbia/qZkZudGjnEhmqYDUPfSWQDdmf8gFsuBgSgYGhR2GCNw7mCCmsDO/wE3ojNnlvuetnVfLeJa7ugvOGjX5QTs="
|
||||
LOG_LEVEL="encrypted:BEAJviH6nTAR4AdFEoiud4ZHV+dwvURoZys4M9KsYAn5MD+nlNEnzS+9vtE3NPwqzfkpK0Z/46xB+SUIXwhwaJ9Yzgz2WqLK1UXEB6fhQqHXdIqvW2ug/+hUxW9k0ueMu5I9btE="
|
||||
BASE_URL="encrypted:BJ6YYd2cX31HuTvGnNxLK33KQgzWxU9yRtlwAc79hhbuioHP5lMu7LxCu8NnXfcaEvevWsEt3cG4I7PNqzlBmU1WhTwsdJ4Kqi2cvs6hKwMeVWvtgdI+ymojG/GoglkbHjSc9737dsth2+erI4qbjkafqYuOC9S9"
|
||||
DISCORD_APP_ID="encrypted:BMqY+6wep/q7bWO9Yc/tJukPHH5H6vvItZpOFK5zaLP92Fx4S+qyZvH/LZKfUBSxZ1d1vbAqo4V+HPxNPyvwXaZyu/qlRf5ZTP/hkt1k9RoNb5UNOMSoD6GSZFS24/JEKuDPzQnvEOb0+prPHEJknvTMXmI="
|
||||
DISCORD_APP_SECRET="encrypted:BBBWkXA3zi8rCWEiMH66v1hFb6Jqw0Bn4H/6b5qWbVakm5UDckOmgQSjXosJLA1VX0vY+38s5fT1ICPIBv2b0rzpaQ09GpRIwryyTT+VLvMcZfrt1CP4ISC0uzlE2p9qceK9EG+6I7ge4pRkzpxotwnWAg9SCqVcNb04kRwPhUvj"
|
||||
DISCORD_PUBLIC_KEY="encrypted:BPqsvWPJ7/IBYynqFMHmEcpMBS+T5CXyfnzZU3flouPwcCaKkIc3xkLhtVco7nWdv2v/hw9ulysmVJhT6CiW9r1k0XRqRnv4q1PDDSTLrP0c1cXPg6pFLcOEv7e2CU+Gkj7UEFZz3xgrb6aWkkLRO0yATcA20xbdnOv3rbigOrJCfJwbbAWPQk7yun8oF6O/mGvWW9Blv8if54fp8vBOeUs="
|
||||
DISCORD_BOT_TOKEN="encrypted:BMfVvYAKvIsMW+dS1X5SZcXfUIxrsunb3q4iXM2ifTlSP8ZlFpA2jxvgYj16jKcexiyVUBhWMSyUC4eS2AuIY/6fKCuA/5JtvqMltzcxPYAi3VEYIz3AgURiMRNy2QsNnqEXxNiakNaPq5Tv4dqVB4z7YeQ16QdvpDWxD4XUQFtG8Q5jZQK/ISj4xJT9cmaxLUJB9XQcbAa8Oseghpk/A1i5JXO05Rx4vQ=="
|
||||
EVE_CLIENT_ID="encrypted:BCBtASu/2DN+EvLPM//WQjBdfwRscwSC5zKBHLTTTcXMfVur8GtDB6ZcBWstmC4YdiimjPobi1RJ+qdYndu1SM300g6UwOmmO2sNhOpG5nyP0mT2HNgwcJzl+Z7Ad1Vr/iByzYyqkc+uYr9NwhvJDPud+HP11dTjKvw+9Ht9/abA"
|
||||
EVE_CLIENT_SECRET="encrypted:BMccZ43R9rT33amzo0zfIgLM8hKDCMXLrj+5h0TNLH1RhwrsUxcKgl17MAVqV+8uPBbB171kRRnjKLaQDjjJM27Jv1SV5bn316qrIx35Tkl2Ocd5wjEs7TSAjf8HwzUhiH9F68+IrQ36Vm8w27+RmsaRtvTtiWWVmYvBXw4PFMprTE7SG0bFq3M="
|
||||
EVE_CALLBACK_URL="encrypted:BDsEZqRGXFzRigkBq50UYj14UvNjRM4Ao3PLSxVTeyc+2Fad1DQa9mfFE9yBnp3l1H5KMQcPJdWf/MxyAa9J0RvXm9l01lbmkgXu+C+HJXWHKJ7/b91NrQqngm2l76jp80WjtmdJ2D5WOUGrIxZatQaqgh8TexQAjVwjkTeQO97PJnbF0FyNQOlu"
|
||||
ESI_USER_AGENT="encrypted:BJ9Pib5a8/qxfROzBfjlAKr/fEvgepN8o6NCI0l3aiYvFuk5hczaA57TKPMP6P2Ct/Juj47YuU8bqF147y8C556NMiY2HDPbrnenXKdAh4xCerjXhkFqowPvEMVxoeuiyhRM7mPmUSAw7AbYi7AxDtCTw80/6S2/b9/32XBk4eCnSdJmM9kFxwHVFQNK83V0Sr5XEymT4S1kntvqlFsBel/5KxMMfNieqTiT+b5mVyM="
|
||||
AUTH_DB_PATH="encrypted:BAqssA/4tJHhxv+pQuSXln5reiqtIdaJzIakctW9fs3omlsZr8j7pXHvZEPQAyYnH2u396tXQxZLXSfdj68q5odUEXUDt6kxN70h3ikL/4gbkfpPkW24wd4NlVPA21GZR+rBpvfpZN1u57Lvp8Lm/QvUfTlka4H5"
|
||||
JANICE_KEY="encrypted:BFEsjnnZNfYFIXvGXKVtko5c8zh5sZze7hjFORfAb4QsHqHh/SqXVKClMCyEa8OMCjNtd8Zmz8LOckaOUYAh09Xi57KM6Eh33CirHipys0rdeURcwSkI9RSXPZOvmOfKZ9yDmhd3iov3AF6b+wwDQ9/rhYZrqh/NWETeHV98Xgv1"
|
||||
PERPLEXITY_API_KEY="encrypted:BIRn8UX4BgL/4QOCaz2cNZVfiJY3zR/Qclr2UI8FnDsUR8mu+hWi8SVWeaauzLSRwiWU1Ihc3/sWUxi8Jz/Ma1dGcDdPwPO7kiZuN2a1Tl3NKiSMmDlNYszLekhpESoUzVOa/605lcKsTemqC8SvVfq7rOPuC2QP7/7bIGSPGDShIfBjU7dUpvfpDX0/Vf++kLHkNiV1"
|
||||
8
packages/eve-web/.prettierrc.yaml
Normal file
8
packages/eve-web/.prettierrc.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
trailingComma: all
|
||||
tabWidth: 2
|
||||
useTabs: false
|
||||
semi: true
|
||||
singleQuote: true
|
||||
printWidth: 140
|
||||
experimentalTernaries: true
|
||||
quoteProps: consistent
|
||||
83
packages/eve-web/README.md
Normal file
83
packages/eve-web/README.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Star Kitten Web
|
||||
|
||||
Project created with [Brisa](https://github.com/brisa-build/brisa).
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
### Link the Library
|
||||
|
||||
`star-kitten-lib` has not been published, so link to it locally before running this web project.
|
||||
|
||||
```bash
|
||||
cd star-kitten-lib
|
||||
bun link
|
||||
cd ../web
|
||||
bun link star-kitten-lib
|
||||
```
|
||||
|
||||
### Download static eve reference data & Hoboleaks archive from [EVE Ref](https://everef.net/).
|
||||
|
||||
```bash
|
||||
cd star-kitten-lib
|
||||
bun get-data
|
||||
```
|
||||
|
||||
### Initialize the sqlite database
|
||||
|
||||
```bash
|
||||
cd star-kitten-lib
|
||||
bun generate-migrations
|
||||
bun migrate
|
||||
```
|
||||
Drizzle's migrations seems to fail on the first try sometimes, so just grab the .sql from the generation and run those against the kitten.db file to create the tables & indexes.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create a .env file in the root directory with the following values:
|
||||
|
||||
```yaml
|
||||
#General
|
||||
BASE_URL=http://localhost:3000
|
||||
DEBUG=true
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
LOG_LEVEL=debug
|
||||
|
||||
|
||||
# EVE - https://developers.eveonline.com/applications
|
||||
EVE_CLIENT_ID=YOUR_EVE_CLIENT_ID
|
||||
EVE_CLIENT_SECRET=YOUR_EVE_SECRET
|
||||
EVE_CALLBACK_URL=http://localhost:3000/auth/callback
|
||||
ESI_USER_AGENT=ADD_YOUR_USER_AGENT_INFO_HERE
|
||||
|
||||
# For using Janice's Appraisal API
|
||||
JANICE_KEY=XXX
|
||||
|
||||
# For using Perplexities AI API
|
||||
PERPLEXITY_API_KEY=XXX
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
bun dev
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
bun build
|
||||
```
|
||||
|
||||
### Start
|
||||
|
||||
```bash
|
||||
bun start
|
||||
```
|
||||
|
||||
7
packages/eve-web/brisa.config.ts
Normal file
7
packages/eve-web/brisa.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { Configuration } from "brisa";
|
||||
import tailwindcss from 'brisa-tailwindcss';
|
||||
|
||||
export default {
|
||||
integrations: [tailwindcss()],
|
||||
} as Configuration;
|
||||
|
||||
4
packages/eve-web/build/_brisa/types.ts
Normal file
4
packages/eve-web/build/_brisa/types.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface IntrinsicCustomElements {
|
||||
'counter-client': JSX.WebComponentAttributes<typeof import("D:\dev\@star-kitten\packages\eve-web\src\web-components\counter-client.tsx").default>;
|
||||
}
|
||||
export type PageRoute = "/" | "/about" | "/auth/error" | "/auth/success";
|
||||
18258
packages/eve-web/build/api/auth/callback.js
Normal file
18258
packages/eve-web/build/api/auth/callback.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,55 @@
|
||||
// @bun
|
||||
var __create = Object.create;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __toESM = (mod, isNodeMode, target) => {
|
||||
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
||||
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
||||
for (let key of __getOwnPropNames(mod))
|
||||
if (!__hasOwnProp.call(to, key))
|
||||
__defProp(to, key, {
|
||||
get: () => mod[key],
|
||||
enumerable: true
|
||||
});
|
||||
return to;
|
||||
};
|
||||
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
||||
var __require = import.meta.require;
|
||||
|
||||
// src/utils/cookies.ts
|
||||
function getCookies(headers) {
|
||||
if (!headers)
|
||||
return {};
|
||||
const cookieHeader = headers.get("Cookie");
|
||||
const cookies = {};
|
||||
if (cookieHeader === null)
|
||||
return {};
|
||||
for (const kv of cookieHeader.split(";")) {
|
||||
const [cookieKey, ...cookieVal] = kv.split("=");
|
||||
const key = cookieKey.trim();
|
||||
cookies[key] = cookieVal.join("=");
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
function setCookie(response, key, value, maxAge) {
|
||||
response.headers.append("Set-Cookie", `${key}=${value}${maxAge ? "; Path=/; Max-Age=" + maxAge : ""}`);
|
||||
}
|
||||
function removeCookie(response, key) {
|
||||
response.headers.append("Set-Cookie", `${key}=""; Path=/; Max-Age=-1;`);
|
||||
}
|
||||
// src/api/auth/discordID/[discordID]/characterID/[characterID]/scopes/[scopes].ts
|
||||
async function GET({ store, route: { params } }) {
|
||||
const eveauth = store.get("eveauth");
|
||||
const response = await eveauth.redirect(params["scopes"]);
|
||||
setCookie(response, "discordID", params["discordID"], 60 * 10);
|
||||
setCookie(response, "characterID", params["characterID"], 60 * 10);
|
||||
return response;
|
||||
}
|
||||
export {
|
||||
GET
|
||||
};
|
||||
|
||||
//# debugId=AEE66F1C816CC1AD64756E2164756E21
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi5cXHNyY1xcdXRpbHNcXGNvb2tpZXMudHMiLCAiLi5cXHNyY1xcYXBpXFxhdXRoXFxkaXNjb3JkSURcXFtkaXNjb3JkSURdXFxjaGFyYWN0ZXJJRFxcW2NoYXJhY3RlcklEXVxcc2NvcGVzXFxbc2NvcGVzXS50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsKICAgICJcbmV4cG9ydCBmdW5jdGlvbiBnZXRDb29raWVzKGhlYWRlcnM6IEhlYWRlcnMpIHtcbiAgaWYgKCFoZWFkZXJzKSByZXR1cm4ge307XG4gIGNvbnN0IGNvb2tpZUhlYWRlciA9IGhlYWRlcnMuZ2V0KFwiQ29va2llXCIpO1xuICBjb25zdCBjb29raWVzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge307XG5cbiAgaWYgKGNvb2tpZUhlYWRlciA9PT0gbnVsbCkgcmV0dXJuIHt9O1xuXG4gIGZvciAoY29uc3Qga3Ygb2YgY29va2llSGVhZGVyLnNwbGl0KFwiO1wiKSkge1xuICAgIGNvbnN0IFtjb29raWVLZXksIC4uLmNvb2tpZVZhbF0gPSBrdi5zcGxpdChcIj1cIik7XG4gICAgY29uc3Qga2V5ID0gY29va2llS2V5LnRyaW0oKTtcbiAgICBjb29raWVzW2tleV0gPSBjb29raWVWYWwuam9pbihcIj1cIik7XG4gIH1cblxuICByZXR1cm4gY29va2llcztcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHNldENvb2tpZShyZXNwb25zZTogUmVzcG9uc2UsIGtleTogc3RyaW5nLCB2YWx1ZTogc3RyaW5nLCBtYXhBZ2U/OiBudW1iZXIpIHtcbiAgcmVzcG9uc2UuaGVhZGVycy5hcHBlbmQoJ1NldC1Db29raWUnLCBgJHtrZXl9PSR7dmFsdWV9JHttYXhBZ2UgPyAnOyBQYXRoPS87IE1heC1BZ2U9JyArIG1heEFnZSA6ICcnfWApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcmVtb3ZlQ29va2llKHJlc3BvbnNlOiBSZXNwb25zZSwga2V5OiBzdHJpbmcpIHtcbiAgcmVzcG9uc2UuaGVhZGVycy5hcHBlbmQoJ1NldC1Db29raWUnLCBgJHtrZXl9PVwiXCI7IFBhdGg9LzsgTWF4LUFnZT0tMTtgKTtcbn0iLAogICAgImltcG9ydCB0eXBlIHsgRVZFQXV0aCB9IGZyb20gJ0AvbWlkZGxld2FyZSc7XG5pbXBvcnQgeyBzZXRDb29raWUgfSBmcm9tICdAL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgUmVxdWVzdENvbnRleHQgfSBmcm9tICdicmlzYSc7XG5cbi8vIEdFVCAvYXBpL2F1dGgvZGlzY29yZElELzpkaXNjb3JkSUQvY2hhcmFjdGVySUQvOmNoYXJhY3RlcklEL3Njb3Blcy86c2NvcGVzXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gR0VUKHsgc3RvcmUsIHJvdXRlOiB7IHBhcmFtcyB9IH06IFJlcXVlc3RDb250ZXh0KSB7XG4gIC8vIHRoaXMgaXMgdXNlZCB0byBzZXQgdGhlIHNjb3BlcyB0aGF0IHdlcmUgc2VudCwgc28ganVzdCBwYXNzIHRoZW0gYWxvbmcgdG8gYXV0aCBkaXJlY3RseSBcbiAgLy8gd2l0aCB0aGUgcHJvdmlkZWQgc2NvcGVzXG4gIGNvbnN0IGV2ZWF1dGg6IEVWRUF1dGggPSBzdG9yZS5nZXQoJ2V2ZWF1dGgnKTtcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBldmVhdXRoLnJlZGlyZWN0KHBhcmFtcyFbJ3Njb3BlcyddIGFzIHN0cmluZyk7XG4gIHNldENvb2tpZShyZXNwb25zZSwgJ2Rpc2NvcmRJRCcsIHBhcmFtcyFbJ2Rpc2NvcmRJRCddIGFzIHN0cmluZywgNjAgKiAxMCAvKiAxMCBtaW4gKi8pO1xuICBzZXRDb29raWUocmVzcG9uc2UsICdjaGFyYWN0ZXJJRCcsIHBhcmFtcyFbJ2NoYXJhY3RlcklEJ10gYXMgc3RyaW5nLCA2MCAqIDEwIC8qIDEwIG1pbiAqLyk7XG4gIHJldHVybiByZXNwb25zZTtcbn0iCiAgXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFDTyxTQUFTLFVBQVUsQ0FBQyxTQUFrQjtBQUFBLEVBQzNDLEtBQUs7QUFBQSxJQUFTLE9BQU8sQ0FBQztBQUFBLEVBQ3RCLE1BQU0sZUFBZSxRQUFRLElBQUksUUFBUTtBQUFBLEVBQ3pDLE1BQU0sVUFBa0MsQ0FBQztBQUFBLEVBRXpDLElBQUksaUJBQWlCO0FBQUEsSUFBTSxPQUFPLENBQUM7QUFBQSxFQUVuQyxXQUFXLE1BQU0sYUFBYSxNQUFNLEdBQUcsR0FBRztBQUFBLElBQ3hDLE9BQU8sY0FBYyxhQUFhLEdBQUcsTUFBTSxHQUFHO0FBQUEsSUFDOUMsTUFBTSxNQUFNLFVBQVUsS0FBSztBQUFBLElBQzNCLFFBQVEsT0FBTyxVQUFVLEtBQUssR0FBRztBQUFBLEVBQ25DO0FBQUEsRUFFQSxPQUFPO0FBQUE7QUFHRixTQUFTLFNBQVMsQ0FBQyxVQUFvQixLQUFhLE9BQWUsUUFBaUI7QUFBQSxFQUN6RixTQUFTLFFBQVEsT0FBTyxjQUFjLEdBQUcsT0FBTyxRQUFRLFNBQVMsdUJBQXVCLFNBQVMsSUFBSTtBQUFBO0FBR2hHLFNBQVMsWUFBWSxDQUFDLFVBQW9CLEtBQWE7QUFBQSxFQUM1RCxTQUFTLFFBQVEsT0FBTyxjQUFjLEdBQUcsNkJBQTZCO0FBQUE7O0FDakJ4RSxlQUFzQixHQUFHLEdBQUcsT0FBTyxTQUFTLFlBQTRCO0FBQUEsRUFHdEUsTUFBTSxVQUFtQixNQUFNLElBQUksU0FBUztBQUFBLEVBQzVDLE1BQU0sV0FBVyxNQUFNLFFBQVEsU0FBUyxPQUFRLFNBQW1CO0FBQUEsRUFDbkUsVUFBVSxVQUFVLGFBQWEsT0FBUSxjQUF3QixLQUFLLEVBQWU7QUFBQSxFQUNyRixVQUFVLFVBQVUsZUFBZSxPQUFRLGdCQUEwQixLQUFLLEVBQWU7QUFBQSxFQUN6RixPQUFPO0FBQUE7IiwKICAiZGVidWdJZCI6ICJBRUU2NkYxQzgxNkNDMUFENjQ3NTZFMjE2NDc1NkUyMSIsCiAgIm5hbWVzIjogW10KfQ==
|
||||
@@ -0,0 +1,54 @@
|
||||
// @bun
|
||||
var __create = Object.create;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __toESM = (mod, isNodeMode, target) => {
|
||||
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
||||
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
||||
for (let key of __getOwnPropNames(mod))
|
||||
if (!__hasOwnProp.call(to, key))
|
||||
__defProp(to, key, {
|
||||
get: () => mod[key],
|
||||
enumerable: true
|
||||
});
|
||||
return to;
|
||||
};
|
||||
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
||||
var __require = import.meta.require;
|
||||
|
||||
// src/utils/cookies.ts
|
||||
function getCookies(headers) {
|
||||
if (!headers)
|
||||
return {};
|
||||
const cookieHeader = headers.get("Cookie");
|
||||
const cookies = {};
|
||||
if (cookieHeader === null)
|
||||
return {};
|
||||
for (const kv of cookieHeader.split(";")) {
|
||||
const [cookieKey, ...cookieVal] = kv.split("=");
|
||||
const key = cookieKey.trim();
|
||||
cookies[key] = cookieVal.join("=");
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
function setCookie(response, key, value, maxAge) {
|
||||
response.headers.append("Set-Cookie", `${key}=${value}${maxAge ? "; Path=/; Max-Age=" + maxAge : ""}`);
|
||||
}
|
||||
function removeCookie(response, key) {
|
||||
response.headers.append("Set-Cookie", `${key}=""; Path=/; Max-Age=-1;`);
|
||||
}
|
||||
// src/api/auth/discordID/[discordID]/index.ts
|
||||
async function GET({ store, route: { params } }) {
|
||||
const eveauth = store.get("eveauth");
|
||||
const response = await eveauth.redirect("publicData");
|
||||
setCookie(response, "discordID", params["discordID"], 60 * 10);
|
||||
return response;
|
||||
}
|
||||
export {
|
||||
GET
|
||||
};
|
||||
|
||||
//# debugId=7AF70B8658B4D3D064756E2164756E21
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi5cXHNyY1xcdXRpbHNcXGNvb2tpZXMudHMiLCAiLi5cXHNyY1xcYXBpXFxhdXRoXFxkaXNjb3JkSURcXFtkaXNjb3JkSURdXFxpbmRleC50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsKICAgICJcbmV4cG9ydCBmdW5jdGlvbiBnZXRDb29raWVzKGhlYWRlcnM6IEhlYWRlcnMpIHtcbiAgaWYgKCFoZWFkZXJzKSByZXR1cm4ge307XG4gIGNvbnN0IGNvb2tpZUhlYWRlciA9IGhlYWRlcnMuZ2V0KFwiQ29va2llXCIpO1xuICBjb25zdCBjb29raWVzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge307XG5cbiAgaWYgKGNvb2tpZUhlYWRlciA9PT0gbnVsbCkgcmV0dXJuIHt9O1xuXG4gIGZvciAoY29uc3Qga3Ygb2YgY29va2llSGVhZGVyLnNwbGl0KFwiO1wiKSkge1xuICAgIGNvbnN0IFtjb29raWVLZXksIC4uLmNvb2tpZVZhbF0gPSBrdi5zcGxpdChcIj1cIik7XG4gICAgY29uc3Qga2V5ID0gY29va2llS2V5LnRyaW0oKTtcbiAgICBjb29raWVzW2tleV0gPSBjb29raWVWYWwuam9pbihcIj1cIik7XG4gIH1cblxuICByZXR1cm4gY29va2llcztcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHNldENvb2tpZShyZXNwb25zZTogUmVzcG9uc2UsIGtleTogc3RyaW5nLCB2YWx1ZTogc3RyaW5nLCBtYXhBZ2U/OiBudW1iZXIpIHtcbiAgcmVzcG9uc2UuaGVhZGVycy5hcHBlbmQoJ1NldC1Db29raWUnLCBgJHtrZXl9PSR7dmFsdWV9JHttYXhBZ2UgPyAnOyBQYXRoPS87IE1heC1BZ2U9JyArIG1heEFnZSA6ICcnfWApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcmVtb3ZlQ29va2llKHJlc3BvbnNlOiBSZXNwb25zZSwga2V5OiBzdHJpbmcpIHtcbiAgcmVzcG9uc2UuaGVhZGVycy5hcHBlbmQoJ1NldC1Db29raWUnLCBgJHtrZXl9PVwiXCI7IFBhdGg9LzsgTWF4LUFnZT0tMTtgKTtcbn0iLAogICAgImltcG9ydCB0eXBlIHsgRVZFQXV0aCB9IGZyb20gJ0AvbWlkZGxld2FyZSc7XG5pbXBvcnQgeyBzZXRDb29raWUgfSBmcm9tICdAL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgUmVxdWVzdENvbnRleHQgfSBmcm9tICdicmlzYSc7XG5cbi8vIEdFVCAvYXBpL2F1dGgvZGlzY29yZElELzpkaXNjb3JkSURcbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBHRVQoeyBzdG9yZSwgcm91dGU6IHsgcGFyYW1zIH19OiBSZXF1ZXN0Q29udGV4dCkge1xuICAvLyBjYWxsZWQgd2hlbiBhZGRpbmcgYSBuZXcgY2hhcmFjdGVyLCBzbyBqdXN0IHJlZGlyZWN0IHRvIGF1dGggYW5kIHNldCBjb29raWVzXG4gIGNvbnN0IGV2ZWF1dGg6IEVWRUF1dGggPSAgc3RvcmUuZ2V0KCdldmVhdXRoJyk7XG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZXZlYXV0aC5yZWRpcmVjdCgncHVibGljRGF0YScpO1xuICBzZXRDb29raWUocmVzcG9uc2UsICdkaXNjb3JkSUQnLCBwYXJhbXMhWydkaXNjb3JkSUQnXSBhcyBzdHJpbmcsIDYwICogMTAgLyogMTAgbWluICovKTtcbiAgcmV0dXJuIHJlc3BvbnNlO1xufSIKICBdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUNPLFNBQVMsVUFBVSxDQUFDLFNBQWtCO0FBQUEsRUFDM0MsS0FBSztBQUFBLElBQVMsT0FBTyxDQUFDO0FBQUEsRUFDdEIsTUFBTSxlQUFlLFFBQVEsSUFBSSxRQUFRO0FBQUEsRUFDekMsTUFBTSxVQUFrQyxDQUFDO0FBQUEsRUFFekMsSUFBSSxpQkFBaUI7QUFBQSxJQUFNLE9BQU8sQ0FBQztBQUFBLEVBRW5DLFdBQVcsTUFBTSxhQUFhLE1BQU0sR0FBRyxHQUFHO0FBQUEsSUFDeEMsT0FBTyxjQUFjLGFBQWEsR0FBRyxNQUFNLEdBQUc7QUFBQSxJQUM5QyxNQUFNLE1BQU0sVUFBVSxLQUFLO0FBQUEsSUFDM0IsUUFBUSxPQUFPLFVBQVUsS0FBSyxHQUFHO0FBQUEsRUFDbkM7QUFBQSxFQUVBLE9BQU87QUFBQTtBQUdGLFNBQVMsU0FBUyxDQUFDLFVBQW9CLEtBQWEsT0FBZSxRQUFpQjtBQUFBLEVBQ3pGLFNBQVMsUUFBUSxPQUFPLGNBQWMsR0FBRyxPQUFPLFFBQVEsU0FBUyx1QkFBdUIsU0FBUyxJQUFJO0FBQUE7QUFHaEcsU0FBUyxZQUFZLENBQUMsVUFBb0IsS0FBYTtBQUFBLEVBQzVELFNBQVMsUUFBUSxPQUFPLGNBQWMsR0FBRyw2QkFBNkI7QUFBQTs7QUNqQnhFLGVBQXNCLEdBQUcsR0FBRyxPQUFPLFNBQVMsWUFBMkI7QUFBQSxFQUVyRSxNQUFNLFVBQW9CLE1BQU0sSUFBSSxTQUFTO0FBQUEsRUFDN0MsTUFBTSxXQUFXLE1BQU0sUUFBUSxTQUFTLFlBQVk7QUFBQSxFQUNwRCxVQUFVLFVBQVUsYUFBYSxPQUFRLGNBQXdCLEtBQUssRUFBZTtBQUFBLEVBQ3JGLE9BQU87QUFBQTsiLAogICJkZWJ1Z0lkIjogIjdBRjcwQjg2NThCNEQzRDA2NDc1NkUyMTY0NzU2RTIxIiwKICAibmFtZXMiOiBbXQp9
|
||||
File diff suppressed because one or more lines are too long
31
packages/eve-web/build/api/auth/index.js
Normal file
31
packages/eve-web/build/api/auth/index.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// @bun
|
||||
var __create = Object.create;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __toESM = (mod, isNodeMode, target) => {
|
||||
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
||||
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
||||
for (let key of __getOwnPropNames(mod))
|
||||
if (!__hasOwnProp.call(to, key))
|
||||
__defProp(to, key, {
|
||||
get: () => mod[key],
|
||||
enumerable: true
|
||||
});
|
||||
return to;
|
||||
};
|
||||
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
||||
var __require = import.meta.require;
|
||||
|
||||
// src/api/auth/index.ts
|
||||
function GET(request) {
|
||||
const eveauth = request.store.get("eveauth");
|
||||
return eveauth.redirect();
|
||||
}
|
||||
export {
|
||||
GET
|
||||
};
|
||||
|
||||
//# debugId=2B9DFE71D16A520B64756E2164756E21
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi5cXHNyY1xcYXBpXFxhdXRoXFxpbmRleC50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsKICAgICJpbXBvcnQgdHlwZSB7IFJlcXVlc3RDb250ZXh0IH0gZnJvbSAnYnJpc2EnO1xuaW1wb3J0IHR5cGUgeyBFVkVBdXRoIH0gZnJvbSAnQC9taWRkbGV3YXJlJztcblxuLy8gR0VUIC9hcGkvYXV0aC9cbmV4cG9ydCBmdW5jdGlvbiBHRVQocmVxdWVzdDogUmVxdWVzdENvbnRleHQpIHtcbiAgY29uc3QgZXZlYXV0aDogRVZFQXV0aCA9ICByZXF1ZXN0LnN0b3JlLmdldCgnZXZlYXV0aCcpO1xuICByZXR1cm4gZXZlYXV0aC5yZWRpcmVjdCgpO1xufSIKICBdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUlPLFNBQVMsR0FBRyxDQUFDLFNBQXlCO0FBQUEsRUFDM0MsTUFBTSxVQUFvQixRQUFRLE1BQU0sSUFBSSxTQUFTO0FBQUEsRUFDckQsT0FBTyxRQUFRLFNBQVM7QUFBQTsiLAogICJkZWJ1Z0lkIjogIjJCOURGRTcxRDE2QTUyMEI2NDc1NkUyMTY0NzU2RTIxIiwKICAibmFtZXMiOiBbXQp9
|
||||
1
packages/eve-web/build/css-files.js
Normal file
1
packages/eve-web/build/css-files.js
Normal file
@@ -0,0 +1 @@
|
||||
export default ["style-4253422825010316650.css","style-4799912372787954844.css","style-16814944961881043919.css"]
|
||||
18297
packages/eve-web/build/layout/index.js
Normal file
18297
packages/eve-web/build/layout/index.js
Normal file
File diff suppressed because one or more lines are too long
18463
packages/eve-web/build/middleware.js
Normal file
18463
packages/eve-web/build/middleware.js
Normal file
File diff suppressed because one or more lines are too long
1
packages/eve-web/build/pages-client/_unsuspense-0.2.7.js
Normal file
1
packages/eve-web/build/pages-client/_unsuspense-0.2.7.js
Normal file
@@ -0,0 +1 @@
|
||||
l$=new Set;u$=(k)=>{let g=(f)=>document.getElementById(f);l$.add(k);for(let f of l$){let j=g(`S:${f}`),h=g(`U:${f}`);if(j&&h)l$.delete(f),j.replaceWith(h.content.cloneNode(!0)),h.remove(),g(`R:${f}`)?.remove()}};
|
||||
1
packages/eve-web/build/pages-client/_unsuspense.txt
Normal file
1
packages/eve-web/build/pages-client/_unsuspense.txt
Normal file
@@ -0,0 +1 @@
|
||||
\pages\index.js
|
||||
115
packages/eve-web/build/pages/about/index.js
Normal file
115
packages/eve-web/build/pages/about/index.js
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user