commit 9023b68e66d749a142ee0de386604603c5a03539 Author: JB Date: Thu Feb 12 21:12:11 2026 -0500 Initial commit diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..25141fc --- /dev/null +++ b/.env.development @@ -0,0 +1,33 @@ +#/-------------------[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:BDJ8/K1Qm9bAcGm1D9etyuk0bQqoXQU3nidRnJPz5gTF9xPMhaPpBQ3hOmLKZBOJoKnydVRz2XFywq0W6S8bo6pC1dV6gNYGQMcD88fIVNF9Mi2zE4L1B0AjQC/h/QgQ58ihdRc=" +PORT="encrypted:BN/VGuk3dAZsQKKjRiKTQpWJtgRrtVYNYXr3CahoOUIYUvrVwIS8ATXpKqDwUZoyEuLv5Ypgg4FVkPWiJ8EP/jpG3mcHY58gFYrNOyPVBSbBt0gprJLsQtZ8hzoYCg0OyrhVnoY=" +NODE_ENV="encrypted:BF8jXFkdSedzxHjyIixwWxowwHLqEBKxv8xPNYY6HquRAL70hd7Z43hsLTX9qSa+VINVUkZhKt+swYJ9GYI0RsGXDGNlOofTtdxMmtOLIjsd3OVZbQ89ycW0LES1/x2rPsRoZcBi8CwuUYvp" +LOG_LEVEL="encrypted:BCnKxBPtZLil3M8cexphmxIV7ods5JopnzMee2pH4MD+t1XTxRMPL2gWdrdHYssSrClQxUz6uX5wnKouhJ8i5WqDa3yczz9qv1ITtV9KQhYEdU0vRQ22IU8+y2BMQF257vNJIZg4" +BASE_URL="encrypted:BKc9lOSo7zFrkUcH4qEr2QGcVKbXAVB0mvnkdSvzNxW8JFpfOGUdToxMOmSngSxEOlJ4awPiaFLkeXEY/iGWZYTCmd3OiHHr8ZYdwpesh5OqYxdeJ1kD3WT/nihjUFvMcaVRmMFw6Mg4q0TvOaXq95Rh1na39eteLicv" +EVE_CLIENT_ID="encrypted:BFHLubJYNIBg7wzfvWxe8sElN4MglmxHSbzuPls8/F25GwPTPBovlrZNNhGAIidTlm05fRh3ciFYFa//i6KgB2kidqB14nPMU899vS49SAZpL7KBTuCkWiURjAFxhPcgG0j2546SUxxIRYzb8lC9/0p7+Sj3JTeEvm/7vDxZ9NOf" +EVE_CLIENT_SECRET="encrypted:BAX72LsKPglufcVruKveB5cbMFRImFa7TKEjPTaSbzkAERkDdV10fJ4HsBTWS9crTM5Kp14A8ghg8FAjnWaozgH3re3eiz+/BaFT+EiOY8C8LCSKyulEqZH0sbA1NitaTRk6dX3QUoIcrCXJMTlJVgFnYkKikCdHNy2DPbVktY0Fg4+y1oZcklY=" +EVE_CALLBACK_URL="encrypted:BHvXeR5TFcpYzwyySIi79OMljtILmgPObAsn4Wbr89hdoRgrS65kq71XFTGb4ZmKpaFVIwyodZYAMQCRZ+sFFKyPlA6jopxOw3Pxb6epjtXxtQSYpYIrsgczFBg+XuWODOzxcd4HGcGzibIIPB5DE0MLm/OkD0An+KyFoHLhuA1Kb53c7NEGHeobb5p0" +ESI_USER_AGENT="encrypted:BGHEVuvVn2YLjnFTjHzPBt/PWc+/eNIEPC5zs6BvlRB3lganokRPY4U0749mW4ZEvSQVm/QAA43wstL+gvtjkog0WnkCEifjCrUgv40flhJEGb3Y/BeMt7StTDEamUAIgdso7sdDuRZE/EwSTf3fuVlu1jJWKuYs/G8Z9U/DL1KqBKV8AUz0paPz+AdFlJx0tQE5IRZdB1DVtOgvPTVzrzXKD9JqMNRKPkIXy7Puib0=" +DISCORD_APP_ID="encrypted:BOdRJjYl0STTWJsvJr72WYTVPrhS0kwrSgE7xdZkrK2eNsQ2yloy2MmpUBgZNSbtI0R+lbX7tcTROoCgnwQIakGzBjmD9MD5HsrNDuJjSqmNt9m14MVnSwRlS3qk4S8/f6q2sFS2S+WYtWQ/7gIXKAAWRAU=" +DISCORD_APP_SECRET="encrypted:BDC8e7tOq0KaFQ/L/7rQuDcxe9S6IMQkXTliJVTvDqBsUS45ogpYY8LMPJR2c4IectLUFPgeG1JFcgVLGYLR5wm134DIqargWXkD4Fu8yjscoNaX/8yGgjWATJDcpoByBB+OichgRe+2Fl2UyuEEOm3+WrrXt3PSJTAd1uESGYkd" +DISCORD_PUBLIC_KEY="encrypted:BIBoYvkwIo+o+YaXgi713zvnS0whOwO3BLcesMEOqgB1h59YRgOB96g+geNtbX31u2RUf8su3fgo/zDY2xz+yNzk0UdEPKBT1N7kQlrUiMRyWhJBwL44Jgy/cUmjcq4VlYeYUe7pMxOeUILIkEzNzIWy/3QAwBKtQv0OnAqWBlkRNaP8xQvtDbQOTAhj655e919vuGwSaUT5EMBVW6MEKSg=" +DISCORD_BOT_TOKEN="encrypted:BFjKo3ysXlVOkddHVpRC5c+4ICQTDQftROckpQ/icS233VldpcPM1oRE7Qzh9P872gA5kUpHG4QH1A3TMGNcA1ESTEIDEf7Z9/H0dRxmEVINo9PkfXyDLa334LAXXj4EzaeMoikNyoDBiWdaH7YWVVbLdah8o+8DEf1sGPkngfsf5gdx4/+ht7a5O9VdNInTw+0DiqPyPhCaZIvbfoHdyjwrPwmWpG5i5Q==" +DISCORD_TEST_GUILD_ID="encrypted:BHC82XO6i5pmt8VPuOlz/1M8vSmQMP5EElZIiooRLx1Dbb5CR9XGyvopk/PLLhtFKz43g8PCTCj0436fZErs7EhVGFM9e2besr5Ncyg/zXm6+GJ8LMRr0rh+R6jSMJswXC1ZC4eYYW/KWO9RO0/5b1MRpw==" +JANICE_KEY="encrypted:BL/i1VVvrx++M36yvTjVxiwwxK56bDb3MvZnU8jo6aI9v7wcXM4x4UbvvIopXjvbZnDbwbBMQQDTQm1dvrGVicqmzeExcL3BwvQEmSJqitArxo0cDcLBai4HxaotdbCCtQ2HY4dXrsPU7VSK/5hDolCKjVXrdBjZdW4pl9IlFdtS" +PERPLEXITY_API_KEY="encrypted:BAen20ADUNSpGeJsU1fiH+UfLZJPG5rLNlT6GnkLwZNoITM3PyPFGzY1IewrBtP/jST0y8XYB3sR5VZ2NlZ/ksPs102oRw3xq8RVtmPPfQ0hwcc1dCdRs9CStjW0ecpP4DSKImzMBGkzXptLzyY+L8yTrUNA6hpWWRZVb3d2Qc+DoJs43SlLHg2WF5PQ5zm1GDFGY4XX" +STAR_KITTEN_KV_DB_PATH="encrypted:BBVO77V74cqJCRZnRfRBPCxwEzHB2y0Zlu0MYZWOvta7WXTUrjf+2R6OqRGAr1ufivjlxcAnI7pd+C1Z5p3Kso/o8JLW7tLtfIp8qYBnI6GvwHNd2n64A7jyrRY9q3QgGeFrJwC7ri9WfMVB8Hg4" +POSTGRES_URL="encrypted:BDDpo6rOefNrh98gSuNRBadPmKkSz0WhOauh0Yc/Rh2VgbnRCeDWsb2Ps7FjwtTbUedapBCiNNX9P43voCVWyYAqWxWQUJYByI4LEgPb39VkXlcXkaToytDSTmKOxgHZPIGkSkG+5GxU8rFxBVe4gbT6P9wVnV8E6SX/0YtdFs7NrbLUrYRstomI8mMR3yTrWnBe2TwlRkZSe2b41Zc3Pl4rabIFaeZhyjqmZkksrvW8d+vU0k/ZOee+updl" +NEO4J_URL="encrypted:BFZGXaev/GzYvKfFGOXR08jhe42qIPLG6VDbuWADT7ulH2i80lvG9j79kTQoI+XQhyiZqG5mr0w8N8/WGyutCvVLDOxRChPVV+XfuXoYCihWiR8apO7z+8qClxloAvfsSel4DssmP1lFvKoyjgmqfjd1Ks3y4FLHBQrt9HlHGpQEFTTWMQuVn/vwg38IYMgjGKXAc9puzP7vyGhmfUbpmI9dwJPox5jKue69DifecHw=" diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..c033f4f --- /dev/null +++ b/.env.production @@ -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:BJ7+kItX/nGaAYi54Ovt0fI9uWkl7AaIc6MXfSFNkHOkjAYH6UsTJvRWfNIsCN6Jd1F458BcNXQIEv4BS/PMEq7hdtqd5KHZH2aIlPlonmetBD3w7sNhm+mFxrCLSnQK8Oam3SJ6" +PORT="encrypted:BAro5r3E/TSgYas47yFGjSVCWj3xW1myqtBIK6AA49b9E8I2H1b1UTtlYwdS9w64ylRuavk1YpxGeQWBJsaMVpsnhMJzADL/V3mHFpkL7MzXvyklS1BGyXPWzgHkjoikS+wnCd8=" +NODE_ENV="encrypted:BEfswbi+NKMs0ZwOIalYRDm8RPRAh0Y8WxgbfUw6eMU8rw9LtOpIvMIHhn9uV7iqcRMx6Ku4hXEtT+Kk2HEr/Yk7JF0m2gUJMaZFEaOlw/oFp6W70pNAIw2bLo+ZrHplsd4RSCz+j/BuYh4=" +LOG_LEVEL="encrypted:BPNoOdNxMMESi9WfCFO9aFQLp4H7MoJcvwh0DRexiL8/38Rj2h8+fp9apnPv5fFPZDJFAOA6CsQI5deVMIXYwcCnqqGcVNX9UHpATH8JmpXxg1K5D9uB9hrn6u8CVnaunpq6E6w=" +BASE_URL="encrypted:BHqtFdukWFfbuh8fog7fE2D42jOlgcb9tTySZwBa9SWA5Z3+XLvvJq/VPox1gJTh/ouP4txRYlpTt6lv5C3NLzZzhfmOSjgkJ/2p6yZXKTmDL2uNRSFyVtorb9UsGn1C80hsxr1MWivbBJ8OuQQwZAVQEsERHBZL" +DISCORD_APP_ID="encrypted:BC8cpOQW8JvbEgG50vfoZ9qqHlSxvn0Yfw+1actEv83cd7of+R5r/TlZYwfwqeIOnyUyllH/qi7w2RQr3Ll+L4Z7EL7e2P1rkHqKgGOs5iynaVCrFh2nS2mWFQQtGa0WmVhXNUIOV8jx65zqOMhJ5/3GyFs=" +DISCORD_APP_SECRET="encrypted:BD4cRqwdkXfWlITxbomEbVJecHvSTvCl2s1K25dxsUltTchdG+XDwNG8FgqpOwIrNkJgd3g3FuPRw7YTul6idnh6Sk83k0HSKQfRhAK1Ch/52Er4aD+DPzQxfK2jNSrBlI+QhNlqKTrSQmZws8Np57sfsKhjdr2hwyPN99MDfBA+" +DISCORD_PUBLIC_KEY="encrypted:BCXkhk8GO8oml62ribVt21KZzj345URPDbyHS+WuJLc/1hO0pBEIMts7nUAgJJn4uk9nJLzkEtmgZ+9ZXn7ujIuyao+djAYKltgmK5KJoDeFiXzK1q6x1jnJQL7wf0EVY7+/4MIWZPs18uYv2wjuHpvyg0Vxo/vc+rpZsQHIAUqxE6hCcTxEYPF9pzTi/75bQcvswuOnz9a5o+GfRodcnVk=" +DISCORD_BOT_TOKEN="encrypted:BCGnL0T+6rwwq7wEFIv54A/npnnZbeGYnqIYmvdETPyHXfceTI3S2tBqHLaug0r3JdqVTTUtWsI+JlXFRw7qV/bWJ1nEFU7C9z3Q7N1vNz6CWuVpySJcS+0/7y2JYuy98uWKnMPQUVrX8E68UX4mt2J/0haTogBSbntJhoIZzGPjELbKoRtNPt8bzuHgrjzaspPWeoDF+MnCxnw0D3YhFffoqEMSeYuEzA==" +EVE_CLIENT_ID="encrypted:BIdohc0Ho58IPhM1V/lUYe//ksqpv6WRVbjTQUN6YPPHq4tk9+sPTJFYwH3BcA47nj2HRQN5LAH2+YVKGz5qhqLaQihIhGzycyQJxmidewYq6Ov4ernE82MeQwSTxZS7zaa1VESkE9t6zh8vW4OllJ1Oz+iv5KfqZ+WeGGmNUIrd" +EVE_CLIENT_SECRET="encrypted:BLy/RFWr4FYiFFXjkap97EKnoJB7f1hGWxW03nEgLrrWBc+eCVO4L915keUp9w6bC34bwUkD1Xwx2fGFM8TNykLYcnxFlgh8dTRjTrJBTuxZZ4rcIqQAiIaW9pkI02S34fhoXdeDT3rAF74l3PlZSXWVJ3YhdxD8hyOnORK9ckxLfvGA0acrqAU=" +EVE_CALLBACK_URL="encrypted:BA8NpXsxrafYFRaEgZ+M7t5a2U6TQMh2D7HNfCcCvVhYDK4eagXnaUkwGVMaUMjef2yOmNreCWk9yT7Kpmc+zUsaGfz9my7CbkuNXUMqLcx0PqIlIlGUlewc2ckb0Xm0Y62GadbkoDSwzM39G6xqwd71vIKQq35TcxgHe0SyJi/RGy6XAn/k/JXa" +ESI_USER_AGENT="encrypted:BEgeNZzEAtym7QUMfEX+I+OtPTQZo2hXk2wPGSScluPUMrwzC6ewCGRa5zPkQ7jG9SwGLTkEfYn5PvBg3bwTEw5EEnzguOdCHoDEa1lCzTSgWwSVIWDbRBnwsbLTVfhJZ54KM/pJItW5LNsQasoWuh9pCnbQAnRgs3MIYwGo1lIAs2bvkbdf6AbiBEHBnGXsADwVEINyJx/K6cRQlcSMB+87QMbnjkXuqWgAm9r3bbdlDvpdbNikAbU=" +AUTH_DB_PATH="encrypted:BAAhDiyG8vHhnKJX8o7g3kSziMwImr5PqjVf4K6P0LnWxaqYSvHlexMTKMtd17mmk5GJ3jZOEp3L9iWqhWZuhb9wL/HhrkixyATwQwJfa+hHBZCJFOFiC55/zbXT+mJKNFqvMcs39YFCzPgrNqOEiwwAGop0Mlco" +JANICE_KEY="encrypted:BAS4U3jmYPhGjuaxFX5ZZYvRp7zHuoFwriTK4y36SoZp/LOLvZAIiZu07QNMEbNTmEIR4RgEyS1g6ztO41yHNZUgaB5yZa0/fSTnsZv0boVMABmX8y3z3QlXE2PqORkWM2vFsllkL1F8Czyt3cJw7CTLvvvXQHJOV4iPj9Oo6dFb" +PERPLEXITY_API_KEY="encrypted:BOh0av8o7bYMRBmbyvx63oBOeMee4ceMXRI6lBRenvZnDgI8AfNq6DmPWJ+upb+gat/FADqQWqKobclIDRbBTfMa3RlhehHtjtQnVQl4zqecAQ/Yv4bisanhUO9I7h2zyf+3BOx5vE5+OnI+gOHZ1Kru5Gal1DVXQrxMgjrscVW1SLxrWBTs5Nn0cT8evqb2z4X398um" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ebe0c1d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +dist +node_modules +.env.keys +packages/**/coverage +*.tsbuildinfo +.vscode/settings.json +db/ +data/ \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..ea21004 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +bun run encrypt +git add .env.development .env.production \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..ecbf261 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@star-kitten:registry=https://git.f302.me/api/packages/jb/npm/ \ No newline at end of file diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..1a5a004 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,8 @@ +trailingComma: all +tabWidth: 2 +useTabs: false +semi: true +singleQuote: true +printWidth: 140 +experimentalTernaries: true +quoteProps: consistent diff --git a/README.md b/README.md new file mode 100644 index 0000000..05c2bc8 --- /dev/null +++ b/README.md @@ -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 + +``` diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..5a315fa --- /dev/null +++ b/bun.lock @@ -0,0 +1,142 @@ +{ + "lockfileVersion": 1, + "configVersion": 0, + "workspaces": { + "": { + "name": "star-kitten", + "dependencies": { + "@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev", + "@star-kitten/discord": "link:@star-kitten/discord", + "@star-kitten/eve": "link:@star-kitten/eve", + "@star-kitten/eve-discord": "link:@star-kitten/eve-discord", + "@star-kitten/util": "link:@star-kitten/util", + }, + "devDependencies": { + "@dotenvx/dotenvx": "^1.49.0", + "@types/bun": "^1.2.21", + "@types/node": "^24.3.1", + "husky": "^9.1.7", + "mkdirp": "^3.0.1", + "prettier": "^3.6.2", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "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=="], + + "@star-kitten/discord": ["@star-kitten/discord@link:@star-kitten/discord", {}], + + "@star-kitten/eve": ["@star-kitten/eve@link:@star-kitten/eve", {}], + + "@star-kitten/eve-discord": ["@star-kitten/eve-discord@link:@star-kitten/eve-discord", {}], + + "@star-kitten/util": ["@star-kitten/util@link:@star-kitten/util", {}], + + "@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=="], + + "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], + + "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=="], + + "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + + "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=="], + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f69ec7c --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "star-kitten", + "version": "0.0.1", + "description": "A Discord bot for Eve Online", + "author": "j-b-3", + "type": "module", + "module": "src/main.ts", + "private": true, + "peerDependencies": { + "typescript": "^5" + }, + "devDependencies": { + "@dotenvx/dotenvx": "^1.49.0", + "@types/bun": "^1.2.21", + "@types/node": "^24.3.1", + "husky": "^9.1.7", + "mkdirp": "^3.0.1", + "prettier": "^3.6.2" + }, + "dependencies": { + "@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev", + "@star-kitten/util": "link:@star-kitten/util", + "@star-kitten/discord": "link:@star-kitten/discord", + "@star-kitten/eve": "link:@star-kitten/eve", + "@star-kitten/eve-discord": "link:@star-kitten/eve-discord" + }, + "scripts": { + "dev": "bunx @dotenvx/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", + "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", + "prepare": "husky" + } +} diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 0000000..1074b84 --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,5 @@ +import '@star-kitten/eve-discord/commands/time.command.js'; +import '@star-kitten/eve-discord/commands/time-from-now.command.js'; +import '@star-kitten/eve-discord/commands/appraise.command.js'; + +import './search/search.command'; diff --git a/src/commands/search/_old_ItemLookup.ts b/src/commands/search/_old_ItemLookup.ts new file mode 100644 index 0000000..298a842 --- /dev/null +++ b/src/commands/search/_old_ItemLookup.ts @@ -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, + }); +} diff --git a/src/commands/search/pages/_old_attributes.ts b/src/commands/search/pages/_old_attributes.ts new file mode 100644 index 0000000..70c091d --- /dev/null +++ b/src/commands/search/pages/_old_attributes.ts @@ -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 { + 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}**`, + ); +} diff --git a/src/commands/search/pages/_old_fitting.ts b/src/commands/search/pages/_old_fitting.ts new file mode 100644 index 0000000..65ca002 --- /dev/null +++ b/src/commands/search/pages/_old_fitting.ts @@ -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 { + 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, +}; diff --git a/src/commands/search/pages/_old_industry.ts b/src/commands/search/pages/_old_industry.ts new file mode 100644 index 0000000..5ca067d --- /dev/null +++ b/src/commands/search/pages/_old_industry.ts @@ -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 { + 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)], + }; + }, + }; +} diff --git a/src/commands/search/pages/_old_skills.ts b/src/commands/search/pages/_old_skills.ts new file mode 100644 index 0000000..00bcf99 --- /dev/null +++ b/src/commands/search/pages/_old_skills.ts @@ -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 { + 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}`; +} diff --git a/src/commands/search/pages/attributes.ts b/src/commands/search/pages/attributes.ts new file mode 100644 index 0000000..3364f98 --- /dev/null +++ b/src/commands/search/pages/attributes.ts @@ -0,0 +1,140 @@ +import { renderSubroutes, type Page } from '@star-kitten/discord/pages'; +import type { SearchState } from '../search.command'; +import { + ButtonStyle, + container, + section, + separator, + text, + thumbnail, + 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 = { + 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; diff --git a/src/commands/search/pages/helpers.ts b/src/commands/search/pages/helpers.ts new file mode 100644 index 0000000..f2ef85e --- /dev/null +++ b/src/commands/search/pages/helpers.ts @@ -0,0 +1,11 @@ +import { actionRow, button } from '@star-kitten/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' }), + ); +} diff --git a/src/commands/search/pages/index.ts b/src/commands/search/pages/index.ts new file mode 100644 index 0000000..d7ae153 --- /dev/null +++ b/src/commands/search/pages/index.ts @@ -0,0 +1,3 @@ +export * from './_old_industry'; +export * from './main'; +export * from './attributes'; diff --git a/src/commands/search/pages/main.ts b/src/commands/search/pages/main.ts new file mode 100644 index 0000000..f1524d9 --- /dev/null +++ b/src/commands/search/pages/main.ts @@ -0,0 +1,80 @@ +import type { Page } from '@star-kitten/discord/pages'; +import type { SearchState } from '../search.command'; +import { container, gallery, section, text, thumbnail, urlButton } from '@star-kitten/discord/components'; +import { formatNumberToShortForm } from '@star-kitten/util/text.js'; +import { searchActionRow } from './helpers'; +import { cleanText, typeSearch } from '@star-kitten/eve'; +import { getRoleBonuses, getSkillBonuses, getType } from '@star-kitten/eve/models'; +import { fetchPrice } from '@star-kitten/eve/third-party/janice.js'; + +const page: Page = { + key: 'main', + render: async (context) => { + if (!context.state.data.type_id && context.interaction.isApplicationCommand()) { + 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; diff --git a/src/commands/search/search.command.ts b/src/commands/search/search.command.ts new file mode 100644 index 0000000..261863f --- /dev/null +++ b/src/commands/search/search.command.ts @@ -0,0 +1,68 @@ +import { + createChatCommand, + stringOption, + type CommandContext, + type ExecutableInteraction, +} from '@star-kitten/discord'; +import { usePages } from '@star-kitten/discord/pages'; +import { initializeTypeSearch, typeSearchAutoComplete } from '@star-kitten/eve'; + +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 (interaction.isAutocomplete()) { + 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( + { + pages: { + main, + attributes, + }, + initialPage: 'main', + ephemeral: false, + }, + interaction, + ctx, + ); +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..85f7d98 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,4 @@ +import { startBot } from '@star-kitten/discord'; +import './commands'; + +startBot(); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0211736 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,36 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "typeRoots": ["src/types", "node_modules/@types"], + + // 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, + + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + + "paths": { + "@/*": ["./src/*"], + }, + "composite": true, + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "build", "**/*.test.ts"] +}