Initial commit
This commit is contained in:
31
packages/star-kitten-bot/.env.development
Normal file
31
packages/star-kitten-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="encrypted:BH4VZPx8vYDz9+wOLYTG3iyzwjd0CPrQKsa13qqZlL56VuP+fn3inwc4CgIGR9+GZ3yChV/RYGAnH2aTyda70ndm//c4YN1xe51YquEgmLHHxAHFAf8+deqetbADQc/OyOp8QRs="
|
||||
PORT="encrypted:BInhSdlEztIkFKZQXKdNIrr+JtotI3WkdZ8COeGB8+Ihhe+8QiVfeUjqX+AGPg5EW88xE8afebum/OUpt/NGVQBqRmxc8DzRi02ShKtF/JqEKczSipOFZLXctZm/OWp5PTlrvSY="
|
||||
NODE_ENV="encrypted:BMoifBpI9mSASWO1Mj6UcLwc3HR9Y/GNV4Ac/iJzoWgED/qkm+5XLdwIho+FTWXdqOTmpMn77JUjmlPctVEQz2qFQXeBV6n4va/3sZ5NDQq6WIFaaxU4Ru0Ab4JsopYnqHs+txP17iqH18rK"
|
||||
LOG_LEVEL="encrypted:BBeNG+hfeuUeBqu9XPdywtmh14W6Ng3jjob1uXPtQDlpTyykFmTsjQRyN49qB32hP/Dxl9LlDBFBYoDvsRR70ykXv9qjxvtwgcwPTxfKAD4F1fIkg324NKBFKI+uOppRsCQYJ7HG"
|
||||
BASE_URL="encrypted:BFDXxzQnyyy/d6N9e5GPP3K0a3N3wvLYVeR5G0AYz9eqi+5Ubb6DfoNIrNqLHXoOYRSJPBgDgvHwU8M3Xdv7bNckQjrkhkKQTuz5CPMK3rp0TkHb5ccaWCKc8f65CQ65z7dchdFKm/oTlzshPObtGkWQnV9ZSB0VJXx4"
|
||||
EVE_CLIENT_ID="encrypted:BCRo2KZwwBpXcURQFnUn6coiprzBjXW1OakelETLS6aQZaO9awf4GCWZ8djDanBcaEfAnRBfNp2Jlr6IhA4MaGwDEbF/ehaI1rT2bQHD3ALFezyXcQnG+S9GgHCwrRNXa32Vw4OQjxAvEAYJP0hQa3CMkxpdN1sg6N2Q66e/H+Bo"
|
||||
EVE_CLIENT_SECRET="encrypted:BJcXRi1NzC2BMYh+nsGy5AyudGvaG1pxDXuCXLRIbAYRT9rQWG64Ev4eHY0Hee7eAKR85YsH8xEY2vXcTGST0FVwCrwNkOT3pxF6vSwjQgkJAB0QAyONjdjrmxJYPIx6n1+AR2dRGQ/NDTwHltDbX3l+rta71Glb+Cpy5iuzHMXJQCbifVn4tVI="
|
||||
EVE_CALLBACK_URL="encrypted:BOoYseMLToFnaG000LxCWO6V4Nlrv3S+ubuBsqtskBeUoO36odLvmRzLGgTJ69AQW5CaK1WzO8Agwiy88KcgcpbHqklfuCuWuuL8dVa5sQt573IKkIMHgyt8TtYVIZkbh5KM/eMgGru9aLEF5FlhPMv/s2yLHufHN+RLnuVzP0PVk9FWLUcavWKuqSwC"
|
||||
ESI_USER_AGENT="encrypted:BCXSJs69AMhQPwVejRA7sD7YAvIphM+aoOqpDphOk08aSxeuYpynYcEwZlvuwQZhFIL0rI5DhRIhkK4299VT7tmMV5Yopx6LbMiVJ+moCK1EIoguUqIj+Yadq5Kwp8bSPa/GGNdFwoOirI9oJp6zmBndzTzGLwGPCbSRUsa0dxjp+53FFNWLIUcdjlO1Pe5/GLWcUkXIZd8M2lLQ/a5mD+ewbGPl6MroSGq2zGgWMGSNqpFilg=="
|
||||
DISCORD_APP_ID="encrypted:BL21AyCTN6+iGCSyHk34+izYNBFCtGgkvjjDtdsD955WKobm7RcFRUrvLieZXeePC7g2gm6m9A5FjNwlX6j/SuNejnZPuX4Rn+zfOp831dX0mtKiaac0CMTbIpOKIOVYMgIugHH1GJGja+plw45w+eYH+zg="
|
||||
DISCORD_APP_SECRET="encrypted:BLcXsfZaFtEpzPeyKR2xXOYhnFpEpVRI4m/xBpFcjcDBUiKpcSi0QwQXjXBlUfwT35C/5TpaBa8WtQxGh/0n49DaGOhjhVlbAKaWnoqeGVvYgpZKBWo/BXnaLOncF5wy2/GgBwmbxNA0VxLtl2cJCAzlUW1nvF/xAh6B7gxUdNQq"
|
||||
DISCORD_PUBLIC_KEY="encrypted:BHd+GM5GIZ65jRuniQU2nFA3qYTStzADiEO0EQFMLvVsRsg+PmrJr7udgdSHMFRr6zXMdiD2MyZi55xZN6qTz/ShrWhtvyV8zTuKNH0/CUxiEFaaBdQQIpYNvmAUrEON7Dt/ajm+rFeo5HFydTiOqoE2OUEf7VLjmt4jBb3Na+3EZ0PFdHhNM5Hen8vyZisjVs4AFzV5H0OgtLZYu4xcy18="
|
||||
DISCORD_BOT_TOKEN="encrypted:BP7NGOn1JhkSjK0j9bGx7ywNsLzRiZBtKg8EIHF0AzjDh9JGt3tJOZX2vYaen824sWFJGDPOyWUae1jknVx6URl26g3pXRISetowtOFhtOH4yC/lS9qJ7f4MTYr29smKB9klDv+Z7JtKcjLju1u4sFYizC0pCl+fOFFX4616l0RQaYVHqCalWLPrr2jiipwGzGPg+Rctd5maA4GlHNUmQCcB5/rIrlxmtg=="
|
||||
DISCORD_TEST_GUILD_ID="encrypted:BGVm9myDnJYQm1zGvEJAaRyY9hnEVGU2fNWd2EmHm3CspMjc26e1sBwp+Cz/2QDxnQxARNQBXModvAK913NdAYcb9HWO57Xg7yC6a6M4p8p73P5foJfs7l78YMyjgO+Fh/HrHwcGeBM+V6cmVaY8nI/Mvw=="
|
||||
JANICE_KEY="encrypted:BOizj0f3hpQrP6elDX1Mo0wnOaNQRWhFDFnTJSxVCPbjkv4O/mmTx9+PwSJA/2Shrq3/+ea/PvMP76YkRpPv6EitlfZl3A6d4YhEdHGvD1nhH8dIvDOwoeCLXvlVnsb6S/O7NM+RnJVvH6xmyjP5bJVHFuLgm9+YBCWNnb+AWu9v"
|
||||
PERPLEXITY_API_KEY="encrypted:BB1oGMElNVwXvcrkPUbHLOg4wf43Kobdyo+BptnfuRuQJUQYvUYQ+ALLWshk/hvJNTbQcOA0czr+lLnwgZwQlephTsDz9tea5qS/0NqVOq/jeFn2O7P4e9vzVrD2XP7avhzVSVyVDnORHrAuCXLKOiM87DxLMj7xaYv745xateDKVOG3+MwJVWgj31coS0q5y0ICw2RB"
|
||||
STAR_KITTEN_KV_DB_PATH="encrypted:BIg9Hiq4Oc/InaGyQD92V1ghIzoY2TDYyvFlyyom8luHvsCwtwCYCLkM0T564HZ9BUjgG9x+NPZ1htw5x1aGkV4frBLA4q/afhx/Ad2Z20SUPudF8c2K9ICaW+HvJZUuFQzRfz6f9w0u+R7RCRio"
|
||||
23
packages/star-kitten-bot/.env.production
Normal file
23
packages/star-kitten-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:BLRJy/AWcYEX8ZS6P4+igHMZ4TZpKibq84Zb9wS7lW1GGC8g31hn0z5+AqMvl4mVKbmlvd1dOESSES85TjcxdlTWDB0R0lx25cLX/ueX3ZCvjuHOeZOkdFeRnWvHsB587Q6tYxac"
|
||||
PORT="encrypted:BDBqBCPqHTNjJKOZyuPnmWn+DY1DPSUN7mZRZu/vHhlafoa0Niuy4sjC6DAg25uc0DojvNyytxlAv/F5ko7vuWrQm27dliuYxQxk8gsNybtibCsOdS+2PXtppUgzcF4qPl538oo="
|
||||
NODE_ENV="encrypted:BCCLAZZJrDcWeG71xVyu29GG+Af1ytHgBc4/R8QT6au+rtxM1vM32gW3Whbap5+Bt+GVAR86QonADSZmeVfYCZ+sjqdOBokg9Vbk8ivs8hHNX2V4v9XwAvF1BwchTalUTSdtnfCv3qthg8Y="
|
||||
LOG_LEVEL="encrypted:BPvgXocX/WBBbK+CaYrfMZnnnU2o2NCrAtNy5sQNBETp9h8MGisyHG5RYIPWGHDiIAZOH3V95ulf6/2edUngHYtU9lEZcDoBHxNpW0a80KoZ+iYUif4eRUTRbG4HWi3Jwhs7eTo="
|
||||
BASE_URL="encrypted:BF3lujdQPVb6Q/s/oZT0xrziMdE0hPqSy3McLNDOCQb+cWx9mqZDuYpVBLxs2T19zGdP6yjNX8jLjEYPYmh9OyYGRItLRK3gMgVYZQTEjf+B1SNtOeF4QN5Ct0cpipdVD81iTPdQUyCQ0mvYHJhtpWFWUbmyDkTD"
|
||||
DISCORD_APP_ID="encrypted:BExz43HXZ9g0m/QLu0iuI/qSt3OaGYVw2g0f5nZel1ys0mpqAuci2cS8fCcVzfwMJn/6p1RYsmB3A7fflNy7YeiBX8Ma/cHdOF3Zyoj2yNoqqehNC2KTK0C9tbnSY6E9bhuVQJTMb35OzAMj7WcREwbZM/U="
|
||||
DISCORD_APP_SECRET="encrypted:BIv5jEjpZ9J4BKKJ22edzaNoud4PqJ5gc4QY57aObxPZS+9lb+SGvfCDjUIQ8nQSJveKbHXuZLtIg3xzvPA9zhRZmSzWQuZDmcqSrgUIS0t/lxz1gMmlT3pE2puWdvM8EWPTLBCR/n5B//rCCyNuVhQ7kWhyw1f9f1H+o1NkLhYx"
|
||||
DISCORD_PUBLIC_KEY="encrypted:BN+q3uWIyoKRi0gBnbzUPuoam/8q1t2Q7/YtaWkQzf1pauzp/WvjpLnHCBuVoCZ8a0MBSb/LInXLB9QtkerfBw1ssp1DHV2zwY6ll+AfqXK86N54dpWFPB/M0sGodNh6f5eldSn+Xxlhbni9iXYTvS91hv5WzoD9v9NNx17rMrTl7Gp4dpRVdcx+AakIa4GjLSfve/rNQW6zLngpE1LaDF4="
|
||||
DISCORD_BOT_TOKEN="encrypted:BHXawD07KwH77vcFbVnrIJ3Fkl+RuZnKIkRo7RttPAUSVlXOdCECujXLudAcKMH5WiiexdGIj0K0xabtR0z/fGPTyaF0knggrPQBYHkc8MSQVVY504xpnseV/8rer/yk5KTVlOP9Aa6fwXs68pKvJwhl6Rg8jAffxxEtCfVbJ5uDLIgOyCYGn8UkGph3hc9cMFNQ3zMTHe0zJmDMpotByvqSSP4zG95qPA=="
|
||||
EVE_CLIENT_ID="encrypted:BGz8McWi5toM40GAa8fJmTEi2L+r8rvkB5A88GCryIRt71f/C9bmaOZXPMu8lhn+JqlwmH7UJ/mabQkBuyKPl5ION11veeQRERjw3murhX1wHJM0P/C9dQiDGMP//yqUWv3UbRG5DVZYkz1N8wngqfajQQ9rbglA4SuZYyjUhGNO"
|
||||
EVE_CLIENT_SECRET="encrypted:BNW8BEjqWVBHQa07rlJdjExy8ANCvc+tCEXeLMDleTpQOLnAZCuiv1ONMm8ytz7toD3bZkqWcSzoqSJTr8uSl+Nfle72nbxpofYbbBh/wGJkKScmiAXTLz+UJB6a7Wck8eX8TTKEgxo9JDOElz5qR7AgSoy9kU1CJyzsOkFfqgQV+uD8z2+8lvs="
|
||||
EVE_CALLBACK_URL="encrypted:BEigQd6FfoyqzO9QJfyFkYFDJ1QnAIXIMj1/ScKP5BqE20l7eaitRAbdXnUtRAFtRxgA1HU6kmBfMzYm9XQLbM/Umm9yStyaKR2AC7Bkyw5xJ7IfTJP3hhrGbWroQtxse4GOddOF4SE99QpPt7f13ABRvycJOBDjKSQH7yUxFChjAHtHq33iNLgn"
|
||||
ESI_USER_AGENT="encrypted:BEvfNDuhTzHfJlNokl1gh7wMVGoGxfVvCjFnMJupDBwG0GPFlnJmMBKPR7uii9+ML2aerxLNPPlR+OvVRtQy9pbTDsKE4G+1oyjQ72DNDk37HybPNR7ATQtqdz35HxOJeia38N3XgylC3sH16VnVtY9mD5DuKoMKh3XIX843FnbrEsQGJL0pIEWGbHC5wkkGtaxfs1cBu3fDDjbIt6/2Ggx7zzlDmtzujZRxaq7ksWvvMdT2pXLRvrA="
|
||||
AUTH_DB_PATH="encrypted:BBNfPkks5tw/h0obJDuDSCPoCbFZFOZUX3Br7GKytMVkeltG7xOqiuT6RBMYffzNnfdlJKqseWP5EG7shoBB20spTPRHnmHFAfNi6SBEB93oP5s9TtpTMGAGD5+BMV2aBOv0mHwGcyfY49haXly+T8fKW5A0k8C5"
|
||||
JANICE_KEY="encrypted:BBhnf2qamAD7CdVDEGFtJkBKbg9m/U/KMqBE0Ws01HLF+ZF58xTRhREiPgeOPE0E8ZJslxgYJ8BxdAhXCfCuKk/T4qE56rZNTFidSOXdycfI/T3TxaZc1/B5PFrcyUDXwCgmySx8vSZ5V8slqdjIHZSI+rSt6cFrLW/R37cY+RIu"
|
||||
PERPLEXITY_API_KEY="encrypted:BDImj/MShxH3jECidjrfU1VbRytCICiNz6uOhouZSB5K4QhPC/UlSl83XWqOSLEsl5wyX0dk+ca10Low0HBPJMALLZuao195oiEjReyXp6RC4L5gqK/JCeJiTiJOqhZ0ERYsBlbJB9b/hyq6MZULh0cd5am8vYzWprqdJ8op/poDWPtAGeOSU5P3/niPQ/tKa3f+Pnz5"
|
||||
5
packages/star-kitten-bot/.prettierrc
Normal file
5
packages/star-kitten-bot/.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120
|
||||
}
|
||||
8
packages/star-kitten-bot/.prettierrc.yaml
Normal file
8
packages/star-kitten-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
|
||||
67
packages/star-kitten-bot/README.md
Normal file
67
packages/star-kitten-bot/README.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Star Kitten Discord Bot
|
||||
|
||||
A Discord bot for [EVE Online](https://www.eveonline.com/) built with [bun](https://bun.sh/) and [@star-kitten/lib](https://git.f302.me/jb/star-kitten)
|
||||
|
||||
# [Install Star Kitten](https://discord.com/oauth2/authorize?client_id=1288711114388930601)
|
||||
|
||||
## Running the Bot
|
||||
|
||||
This bot runs on [Bun](https://bun.sh/)! To install Bun, 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 bot dependencies
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
### Download static eve reference data & Hoboleaks archive from [EVE Ref](https://everef.net/)
|
||||
|
||||
```bash
|
||||
bun get-data
|
||||
```
|
||||
|
||||
### Run the bot
|
||||
|
||||
```bash
|
||||
bun dev
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create a .env file in the root directory with the following values:
|
||||
|
||||
```yaml
|
||||
# Discord - https://discord.com/developers/applications
|
||||
DISCORD_BOT_TOKEN=YOUR_BOT_TOKEN
|
||||
|
||||
#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
|
||||
|
||||
```
|
||||
121
packages/star-kitten-bot/bun.lock
Normal file
121
packages/star-kitten-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=="],
|
||||
}
|
||||
}
|
||||
4
packages/star-kitten-bot/data/.gitignore
vendored
Normal file
4
packages/star-kitten-bot/data/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
||||
34
packages/star-kitten-bot/package.json
Normal file
34
packages/star-kitten-bot/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"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",
|
||||
"mkdirp": "^3.0.1",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev",
|
||||
"@star-kitten/lib": "workspace:^0.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bunx dotenvx run -f .env.development -- bun --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 ../util/dist/download-and-extract.js https://data.everef.net/reference-data/reference-data-latest.tar.xz ./data/reference-data",
|
||||
"refresh:hoboleaks": "bun ../util/dist/download-and-extract.js https://data.everef.net/hoboleaks-sde/hoboleaks-sde-latest.tar.xz ./data/hoboleaks",
|
||||
"static-export": "bun ../eve/scripts/export-solar-systems.ts",
|
||||
"encrypt": "bunx dotenvx encrypt -f .env.development && bunx dotenvx encrypt -f .env.production",
|
||||
"decrypt": "bunx dotenvx decrypt -f .env.development && bunx dotenvx decrypt -f .env.production"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { Constants, type ChatInputApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import { appraiseItems, type Appraisal } from '@star-kitten/lib/eve/third-party/janice.js';
|
||||
import {
|
||||
componentHasIdPrefix,
|
||||
isModalLabel,
|
||||
isModalSelect,
|
||||
isModalTextInput,
|
||||
} from '@star-kitten/lib/discord/components';
|
||||
import type { CommandContext, ExecutableInteraction } from '@star-kitten/lib/discord';
|
||||
import { PageType, usePages } from '@star-kitten/lib/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: () => renderAppraisalModal(interaction),
|
||||
},
|
||||
appraisalResult: {
|
||||
key: 'appraisalResult',
|
||||
render: async (pageCtx) => {
|
||||
if (!interaction.isModalSubmit()) {
|
||||
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: (pageCtx) => {
|
||||
const rendered = renderAppraisal(pageCtx.state.data.appraisal!, pageCtx, interaction);
|
||||
return rendered;
|
||||
},
|
||||
},
|
||||
},
|
||||
initialPage: 'appraiseModal',
|
||||
timeout: 300, // 5 minutes
|
||||
ephemeral: true,
|
||||
},
|
||||
interaction,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
definition,
|
||||
execute,
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { ExecutableInteraction } from '@star-kitten/lib/discord';
|
||||
import * as StarKitten from '@star-kitten/lib/discord';
|
||||
import type { PageContext } from '@star-kitten/lib/discord/pages';
|
||||
import { type Appraisal } from '@star-kitten/lib/eve/third-party/janice.js';
|
||||
import { formatNumberToShortForm } from '@star-kitten/lib/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,
|
||||
});
|
||||
return (
|
||||
<container accent={0x1da57a}>
|
||||
<text>
|
||||
{`
|
||||
# [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
|
||||
`}
|
||||
</text>
|
||||
{pageCtx.state.currentPage !== 'share' && (
|
||||
<actionRow>
|
||||
<button
|
||||
customId="share"
|
||||
label="Share in Channel"
|
||||
disabled={!interaction.channel?.id}
|
||||
style={StarKitten.ButtonStyle.PRIMARY}
|
||||
/>
|
||||
</actionRow>
|
||||
)}
|
||||
</container>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { markets } from '@star-kitten/lib/eve/third-party/janice.js';
|
||||
import type { ExecutableInteraction } from '@star-kitten/lib/discord';
|
||||
|
||||
export function renderAppraisalModal(interaction: ExecutableInteraction) {
|
||||
return (
|
||||
<modal customId="appraisalResult" title="Appraise Items">
|
||||
<label label="Select a market">
|
||||
<stringSelect customId="market" placeholder="Select a market" minValues={1} maxValues={1}>
|
||||
{markets.map((m) => (
|
||||
<option
|
||||
label={m.name}
|
||||
value={m.id.toString()}
|
||||
default={m.id === 2} // Jita
|
||||
/>
|
||||
))}
|
||||
</stringSelect>
|
||||
</label>
|
||||
<label label="Enter items to appraise">
|
||||
<textInput
|
||||
customId="input"
|
||||
isParagraph={true}
|
||||
placeholder={`e.g. Tritanium 22222
|
||||
Pyerite 8000
|
||||
Mexallon 2444`}
|
||||
/>
|
||||
</label>
|
||||
</modal>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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}**`,
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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)],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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/star-kitten-bot/src/commands/search/pages/attributes.ts
Normal file
140
packages/star-kitten-bot/src/commands/search/pages/attributes.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { renderSubroutes, type Page } from '@star-kitten/lib/discord/pages';
|
||||
import type { SearchState } from '../search.command';
|
||||
import {
|
||||
ButtonStyle,
|
||||
container,
|
||||
section,
|
||||
separator,
|
||||
text,
|
||||
thumbnail,
|
||||
Padding,
|
||||
} from '@star-kitten/lib/discord/components';
|
||||
import {
|
||||
getGroup,
|
||||
getType,
|
||||
getUnit,
|
||||
renderUnit,
|
||||
typeGetAttribute,
|
||||
typeHasAnyAttribute,
|
||||
type Type,
|
||||
} from '@star-kitten/lib/eve/models';
|
||||
import { attributeOrdering } from '@star-kitten/lib/eve';
|
||||
import { searchActionRow } from './helpers';
|
||||
import { toTitleCase } from '@star-kitten/lib/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: [
|
||||
container(
|
||||
{},
|
||||
section(
|
||||
thumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
text(`# [${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 text('```\n' + lines.join('\n') + '\n```');
|
||||
},
|
||||
{ style: ButtonStyle.SECONDARY },
|
||||
),
|
||||
separator(Padding.LARGE),
|
||||
searchActionRow('attributes'),
|
||||
),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default page;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { actionRow, button } from '@star-kitten/lib/discord/components';
|
||||
|
||||
export function searchActionRow(pageKey: string) {
|
||||
return actionRow(
|
||||
button('Main', 'main', { disabled: pageKey === 'main' }),
|
||||
button('Attributes', 'attributes', { disabled: pageKey === 'attributes' }),
|
||||
button('Fittings', 'fittings', { disabled: pageKey === 'fittings' }),
|
||||
button('Skills', 'skills', { disabled: pageKey === 'skills' }),
|
||||
button('Industry', 'industry', { disabled: pageKey === 'industry' }),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './_old_industry';
|
||||
export * from './main';
|
||||
export * from './attributes';
|
||||
82
packages/star-kitten-bot/src/commands/search/pages/main.ts
Normal file
82
packages/star-kitten-bot/src/commands/search/pages/main.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { Page } from '@star-kitten/lib/discord/pages';
|
||||
import type { SearchState } from '../search.command';
|
||||
import { container, gallery, section, text, thumbnail, urlButton } from '@star-kitten/lib/discord/components';
|
||||
import { getRoleBonuses, getSkillBonuses, getType } from '@star-kitten/lib/eve/models/type.js';
|
||||
import { cleanText } from '@star-kitten/lib/eve/utils/markdown.js';
|
||||
import { typeSearch } from '@star-kitten/lib/eve/utils/typeSearch.js';
|
||||
import { isApplicationCommand } from '@star-kitten/lib/discord';
|
||||
import { fetchPrice } from '@star-kitten/lib/eve/third-party/evetycoon.js';
|
||||
import { formatNumberToShortForm } from '@star-kitten/lib/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: [text(`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: [
|
||||
container(
|
||||
{},
|
||||
section(
|
||||
thumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
text(`
|
||||
# [${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')}`
|
||||
: ''
|
||||
}
|
||||
`),
|
||||
),
|
||||
gallery({
|
||||
url: 'https://iili.io/KTPCFRt.md.webp',
|
||||
}),
|
||||
// createSeparator(Padding.LARGE),
|
||||
section(
|
||||
urlButton('View on EVE Tycoon', `https://evetycoon.com/market/${type.type_id}`),
|
||||
text(
|
||||
`## Buy: ${price ? formatNumberToShortForm(price.buyAvgFivePercent) : '--'} ISK
|
||||
## Sell: ${price ? formatNumberToShortForm(price.sellAvgFivePercent) : '--'} ISK`,
|
||||
),
|
||||
),
|
||||
text(`-# Type Id: ${type.type_id}`),
|
||||
searchActionRow('main'),
|
||||
),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default page;
|
||||
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
createChatCommand,
|
||||
isAutocomplete,
|
||||
stringOption,
|
||||
type CommandContext,
|
||||
type ExecutableInteraction,
|
||||
} from '@star-kitten/lib/discord';
|
||||
import { usePages } from '@star-kitten/lib/discord/pages';
|
||||
import { initializeTypeSearch, typeSearchAutoComplete } from '@star-kitten/lib/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,
|
||||
);
|
||||
}
|
||||
78
packages/star-kitten-bot/src/commands/time.command.tsx
Normal file
78
packages/star-kitten-bot/src/commands/time.command.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Constants, type ChatInputApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import {
|
||||
type ExecutableInteraction,
|
||||
type CommandContext,
|
||||
isApplicationCommand,
|
||||
Locale,
|
||||
} from '@star-kitten/lib/discord';
|
||||
|
||||
const definition: ChatInputApplicationCommandStructure = {
|
||||
type: Constants.ApplicationCommandTypes.CHAT_INPUT,
|
||||
name: 'time',
|
||||
nameLocalizations: {
|
||||
[Locale.DE]: 'zeit',
|
||||
[Locale.ES_ES]: 'hora',
|
||||
[Locale.FR]: 'heure',
|
||||
[Locale.JA]: '時間',
|
||||
[Locale.KO]: '시간',
|
||||
[Locale.RU]: 'время',
|
||||
[Locale.ZH_CN]: '时间',
|
||||
},
|
||||
description: 'Get the current EVE time',
|
||||
descriptionLocalizations: {
|
||||
[Locale.DE]: 'Holen Sie sich die aktuelle EVE-Zeit',
|
||||
[Locale.ES_ES]: 'Obtén la hora actual de EVE',
|
||||
[Locale.FR]: "Obtenez l'heure actuelle d'EVE",
|
||||
[Locale.JA]: '現在のEVE時間を取得します',
|
||||
[Locale.KO]: '현재 EVE 시간을 가져옵니다',
|
||||
[Locale.RU]: 'Получите текущее время EVE',
|
||||
[Locale.ZH_CN]: '获取当前的EVE时间',
|
||||
},
|
||||
};
|
||||
|
||||
const eveTimeText = {
|
||||
[Locale.EN_US]: 'EVE Time',
|
||||
[Locale.EN_GB]: 'EVE Time',
|
||||
[Locale.DE]: 'EVE-Zeit',
|
||||
[Locale.ES_ES]: 'Hora EVE',
|
||||
[Locale.FR]: "Heure d'EVE",
|
||||
[Locale.JA]: 'EVE時間',
|
||||
[Locale.KO]: 'EVE 시간',
|
||||
[Locale.RU]: 'Время EVE',
|
||||
[Locale.ZH_CN]: 'EVE时间',
|
||||
};
|
||||
|
||||
function renderTimeDisplay(locale: string = 'en-US') {
|
||||
const now = new Date();
|
||||
const eveTime = now.toISOString().split('T')[1].split('.')[0];
|
||||
const eveDate = now.toLocaleDateString(locale, {
|
||||
timeZone: 'UTC',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: '2-digit',
|
||||
weekday: 'long',
|
||||
});
|
||||
return {
|
||||
flags: Constants.MessageFlags.IS_COMPONENTS_V2,
|
||||
components: [
|
||||
<container>
|
||||
<text>
|
||||
{`### ${eveTimeText[locale] || eveTimeText[Locale.EN_US]}
|
||||
${eveTime}
|
||||
${eveDate}`}
|
||||
</text>
|
||||
</container>,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
|
||||
if (!isApplicationCommand(interaction)) return;
|
||||
|
||||
interaction.createMessage(renderTimeDisplay(interaction.locale));
|
||||
}
|
||||
|
||||
export default {
|
||||
definition,
|
||||
execute,
|
||||
};
|
||||
3
packages/star-kitten-bot/src/main.ts
Normal file
3
packages/star-kitten-bot/src/main.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { startBot } from '@star-kitten/lib/discord';
|
||||
|
||||
startBot();
|
||||
16
packages/star-kitten-bot/tsconfig.json
Normal file
16
packages/star-kitten-bot/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@star-kitten/lib/discord",
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"typeRoots": ["src/types", "./node_modules/@types"]
|
||||
},
|
||||
"references": [{ "path": "../lib" }],
|
||||
"include": ["src", "types"],
|
||||
"exclude": ["node_modules", "dist", "build", "**/*.test.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user