break up library, move bots to their own repositories

This commit is contained in:
JB
2026-02-12 21:05:15 -05:00
parent 415aa3dbfe
commit cb39431a11
289 changed files with 1931 additions and 6235 deletions

4
.gitignore vendored
View File

@@ -4,3 +4,7 @@ node_modules
packages/**/coverage
*.tsbuildinfo
.vscode/settings.json
data
db/
coverage/
logs/

3
.husky/pre-commit Normal file
View File

@@ -0,0 +1,3 @@
bun test
bun run encrypt
git add **/.env*

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
@star-kitten:registry=https://git.f302.me/api/packages/jb/npm/

574
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,23 @@
{
"name": "star-kitten",
"version": "0.0.0",
"description": "Star Kitten framework",
"name": "@star-kitten/monorepo",
"version": "0.0.1",
"description": "The Star Kitten framework",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"build": "bun --filter '*' build",
"co-dev": "bun --filter '!star-kitten' dev",
"sk-dev": "bun --filter '!concierge-bot' dev",
"link": "bun --filter '*' link",
"dev": "bun --filter '*' dev",
"test": "bun --filter '*' test",
"encrypt": "bun --filter '*' encrypt",
"decrypt": "bun --filter '*' decrypt"
"decrypt": "bun --filter '*' decrypt",
"typecheck": "bun --filter '*' typecheck",
"release": "bun --filter '*' release",
"prepare": "husky"
},
"devDependencies": {
"ghooks": "^2.0.4",
"typescript": "5.9.3"
},
"config": {
"ghooks": {
"pre-commit": "bun run encrypt && git add **/.env*"
}
"husky": "^9.1.7"
}
}

View File

@@ -1,30 +0,0 @@
#/-------------------[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
DISCORD_BOT_TOKEN="encrypted:BHWtrKfWm5sdv5MwMVnvwaqsgGlUXQ35/Dxhu+6NqWSdwa0pesu8EmXMZr64fLXEMzsSpbH0rDTj3NUe64jbGyxL2f9LRbMWExj8o7rz9qeM2vdXCBAiOWPYOqLiqqhKvBRvpGuFCZlMeA7dsLxr0t4RPzPbAAUINaLKMmyxTVRsLJO7NVcMK5u/0FczmjoLkz2+woka9RcVZoL2XRMfWM4uLKm8WMrccg=="
DEBUG="encrypted:BFqmdpn+2mZ4wU0KT5qhlRJ6J/TMKqhDjiCGEYIdOM9O8CBaSRO5dAduODmr3k/PR3/L9ruk81q0Na4DR0JnnArNB9+mUP9tUkCC/KKBWkQJenwaUpxxZ/Xs1WbTmKCiquTWRVE="
PORT="encrypted:BB7uSrGyN0EL1gv/JLl9ltjadMqLLnKg7p6XAS1z96Qt1z+/KuwQ8EQ4BQY+dx19Q4H443vtIWmNSLPWTbZI1Swj6xBwirZ8VmitlnazSYxotkz2nq2sdvALQH5mQlj0iZ0NHLQ="
NODE_ENV="encrypted:BFCK2qYG9T55sxJPTGx+xqeGCFx2EPSvnzwX5PM8CoHGmN+6X5Irj1/PZ9m1QbFtQAibDj0CH/i1yHQQ9he4vN0RUqq7uGXsIbSsApvRYSKPaIdi7DKOhMwYRMCd1a4CjHemt2w1n1BizUxf"
LOG_LEVEL="encrypted:BFYODyDKQ+uvLGCOk7eZQ8uZJdfI4iy/wUJeC2rNGHJYxIK/2icQ3LvV04DlD8a75kg5zUKC951U0nqT/8vmlp3BCso7CYvOAXISggfMBwFXIaBh2UrDHGOIWDMQWPqjcziCnucj"
BASE_URL="encrypted:BNJ+cwBs/2His4iT0aPhsTgxTqkOH28DNsZZ1ctf/Ru4TCr4Q28d9jE/5KSkBlYpWOBItRJ/PK28uJVSdwFuPiCPrpycniLccWS3GvcQFXrMLsfvc9Jo0PoXAFtpmbivlDa7rK6Zhe2y7/16V112hXdeWDGFWlYO9N/iQQM2Vg=="
EVE_CLIENT_ID="encrypted:BFv8tNHSS6tb8cWDpnhfC393UcKJZsRgvBTbx4l36+lN3m3oSNmouzRa/KPIXglqkAujt+OyzTyxQ59XxaQ/+ORi6nPNVMr7xDsWwpp24iz509qReOKlTqwk4y2PQ7xVbMskJD65GTg2ZyRwtqc0klBjvvF9frDzlitRpUiL06cS"
EVE_CLIENT_SECRET="encrypted:BJIPWclcu2Ok/PQSI8evLfxP7Ze3ULQGoluCCjTQORfzqb8IixKty6lpQurFmt6ez4p1rlf8s73zRjaI6jYKrnPhZ+4sFYfgrCCZUUObsVctFoR7REvYYXD/k/mr5ryRCkR1L7NWihPyQ9fgNViWb+sQPtayf+WrArbqxYRRI5vDD2MMEbakxRCEpg=="
EVE_CALLBACK_URL="encrypted:BGiF/m1Zd3jL4Q/p58mE9XUIGoL+Cc7wpxX6PHKt5L0uSat6X74pXC11y0IcO77jSKmpcQ1Zj/pXMz3YLCbHK7toqucZlvTatvka+ad0YAylrLwUBCYeamHmi2CU17qJSPqD9E6Q33yTvqFqf3zBiyZfI8L/PbH9+ok7e8RP+L4xsQ8Jx3piFjS0lFmOC3iznw=="
ESI_USER_AGENT="encrypted:BO8zPPwVUKscBZzw8RcePwvuWkYjdCgz9vGrvhFzNJNCNthij3rhN7F5hK1UGPl6RPLOssV1Ke681e6rjldFdi3S1BxWeKQo8QOdjkyHTjmGbOdHoDd6QesMIsx+K9bwJvPl+XFhh+LPnIDzRPEQnTmGOTbvMsxGW9IkJDrm0Oq/aNdO6Blyh4GKzMyW2elgt+s+39/UT5nD2FCe5MlMdLk1/2z8lVV363MfZP1k8qUrbKAvdnZOLJWoWG6Nlg0vDg=="
JANICE_KEY="encrypted:BPTKebIZjiHeXHafRCIi7fX2DVYPz5ZwIpPX2GtINQEfrI7DYHhG2NMsxlLaiSFmaGcYA7GAK0DGXt9yTJpIGUBkg73bTE1Pbh1DgtVbpK74xFDbfA7748toAWypDz0B+erh6k6hzz7TywMe+fc80r+OWmQkTpz5pgwg7LaqmT0x"
PERPLEXITY_API_KEY="encrypted:BOzm0zV+Nl0sHq9KvoazlqbpgFwj8Gy2crn64Qw5+t4X51OWi1HbCPR82hxuH3CDRKJyuTQMG06s0Z6ovV0N3kMheFlX9qK0SogYympxtVcPd8723IWy6Nr1IioNcajfNW4S38ruRhcrmU4/8rddpyEiWMPB8/Ewb6ycfrnfWHvul4ZqBg6H60IbLDxOQ6AZPWZjRAEy"
STAR_KITTEN_KV_DB_PATH="encrypted:BKCUDzngHkpO2Dtqzh++hCsEAAzWfabiF/NdtnP1BzwSN84Y2I+PjUp4mdAiDgPrUWgi3BFS74kZtsutzEF3h3rNi/doKZyOU1cY1DY6aBbQ+WVWPKDiAcENU1GgUp8sUezJNbZ7GXI3Qhnpkz4i"
SCOPES=encrypted:BNLqmvLSqQFAMExxaGcx9zUWErl/UO+cbQXOYeQVDgR9JJyo1D8aaPfy06uQDlR+Euv3hy/1NL6tx75KMVChR2p+2fhVcKEC43fx3rt1JxuOvoWck81r6kv/yu5v+YUkw1BVcQNvNp4iiqqOifrro1bylyZkOML6D1DYu7Ns+6srYI4MbJGTDacV6eyRFnGcPdbxCyIophCDEP2iXw==
CONCIERGE_DB_PATH="encrypted:BD4/iLtpn7+sqAm/Lm9CSA6ur1AjEZRaYCSy/Z9qCZMPCtoPIeHfCjYs+s+A9PBCxezqJikxbMS8IPhR0EKWQTDzeBsjHRpWGA9mJGKuUYBDtGySH+WQG6+3TEEMU/uqwpLwEUtaULKF9khJdnHlyr5znz5gSw=="
ADMIN_USER_ID="encrypted:BFM6XD3HUmF0to4PO8RlGG71OF5aDoV4N3E34/HGwV8Rq4NiIJ+wtxLag8tWjaeIYkTgbknlE8GHiXmJUJMc8eEVhIxNFCC0xmlSJe2NipTLSBbTilfSQPOexURnNXE2I/u+3+il2QvGP7ziReuNM5QiWQ=="

View File

@@ -1,23 +0,0 @@
#/-------------------[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:BK1CLEV0fBG5FnYN581CvgIftBf431wzAqxR6CTQdkpY/FSbLpNtm+7sBCD8pdfOGzqlWO6Vp0WPkd56kSYgFSWyChBiWipUIou6lva/1mzIPW0v/s53azEdPlo2k3VpxVcVSWC8"
PORT="encrypted:BIxJSVnlk4yf4r5Yrs3emdnRwO2MuH3YS8cRedHKOpTWtwmxFUyRpr7Psn8McOqhVX1pznNrqbnP8LYAjfmuesYZeon8khD0D2OXepb53J3y+7jf5uoOsNOgTHobH3B/djqKEzY="
NODE_ENV="encrypted:BGwvbXEi7Zx49wiBZNE+05VHntm/2zIM3mCQE31J4sthkvW3q5KRR5gPmjyzkzN02juqWC/6lCF6ar73nsIwLzDKZabZiYCxEMcn3B/QYPQ1AiTguu8lVPZWTEdRA2S6kFsiKoV9Rfokry8="
LOG_LEVEL="encrypted:BPyfUBe3+m9DP+S48GN9QUNedicrn0ICcUYzQeSfBO25NU60lGRLfyx+iPY1Cn3UmH6izR4V+fJ9UedowHRQYrivAzhk2GUa8p4wbld9HXp2kyo3jZpQXzzUOGtf1cWRSdGr0r4="
BASE_URL="encrypted:BAHgrTREjKsjKJCogVttpFTOV84iKFzz8+NAtW9+FEDyZlZPixPYoDYRg2qG+7aOs/YNK7GaxpE6gGyV5EIit9l3V8Fb+p157Ra4tdEevF4xmNZcjnjB6GN2fGDNtTddFwMl71X2NcopxwV5auMhQdC14hk9UIkO"
DISCORD_APP_ID="encrypted:BJE5LiHHvSEbz500t8+cew4UPf7khvKKFYy5rlfXeTKahQ/eM94e7MDO3nxMZqnlQ9OO0Ge7N3yOvJEb5x/hKlhqwLJo9sKbVHWS6bKnH9/Ft64rcN+g4pSZJeQBG1jFg/zyPJQq4tVyg7C4htnqQU4TiEI="
DISCORD_APP_SECRET="encrypted:BGPATPBLvmFcnZSlyUSkT4hrnM4cH56f92/kQgOHDN+5XvSYq5RFyu0soM6Q6/0wCLb92sdVfKbc5x//84k/im245OCmpsKc7RCMoJNlJ/72uEsFQEADxglYrpevy1gVMI7Rs621gEfaVuoJn5AcQDwpX685ydnlr6Ev0vRtzfLD"
DISCORD_PUBLIC_KEY="encrypted:BF3QEgKgm86nnO5LegRHlo/Jr/PNpBoY1BMFNgCJ4LdHTw+XkF//JISwQWxaU0aswwYa4duMSUYjc9DMs/mc1yLpf1DZ2R58X86vTcjCX0SiTiLFhYOpQf0JA+3HfIB/4j7GzM1D99DNK8ZsnzY/YXzHAc+R4aRwl1uu8AlGgtZpv7kgu0QjTNJshz9TUoGUROwbUD7RZABZwom0PYW2Wbw="
DISCORD_BOT_TOKEN="encrypted:BMGsG954WBMxPgMgeJX5B9FZmJk4oPrGkDJXPaOfXnqkrOaaSP8Oq2tD+m2Pzfwbkla3xoWp2a3863lumgIsOcjoyFT0jj51tyC7CL9pbLmhLqOmnUwGjl9Vv8MHA369WUFwqm0Sum7bUD4HnMrc5Q+qEKF8zut96hTYmZWn6ACqmoxHqxEp9393NFeUzXUD5tdUhkFCYkxFGvBoy1l/EX7x0aSCkB1iiQ=="
EVE_CLIENT_ID="encrypted:BK738Cb0M9aVcW49g7MR9YjjyLXn/if25nCjEBVoT5/+9y6seI2BwxsnXIidQ2253NyuZLzJCf8ual+HN5ouwO4r4ckqKYnk7SwDqj6yTuB47yAqgJDi8SnVManX+oRz6URNiSD7eUTYFxPHfC5XdCYFjMOtGDvLndEbqOiKSvGT"
EVE_CLIENT_SECRET="encrypted:BBZdQ+o77YtfV/ic+dfx9nORoFs8iIRwQxToUN65I310uhwMeBWbgjJFu3P1pkR5niHxjkQkyBmEWCBKFNsmNZh/zHyjMpXu5ORIsTpDc45ivu96Cuw5PbtnD+3DwAIdJQIjQR9ufYMIdI6DsrEscJg/XX7Yqk703g0s5e4ODm+jDVlLS6maRW8="
EVE_CALLBACK_URL="encrypted:BHrxPwPrv8WoHYPZI7nKyJCnggzxBS4JZKxoWXOKa70fglSvAuujGLWktt1LppJ2qvm8uU+J2qltJq0xCwFjzWB/qkRCQ4mK1tLc+cEh9S9mwKXK+55xntl/cxvfUvNt3dyHTL6Bwtxb1UfUxAkMtvPjtDfFKtJxtZBuXnk4Wv72SHZuGmUCp/Yd"
ESI_USER_AGENT="encrypted:BN2TE5wokSxMPBgfek0sxyg3sJHTdTm7vPYnMoyqAux93SzvH06wJ+8sVOLo9dmzHbKzSBn1rVNkoLiZV+fvvbrXMrTGL6Ai2jDzrFoIFTHOU9QNL2dk9LmklgmILhy/RPD5ZJlyFHFPffK9laBVws2aOMepoCgq0wPws5dKEd7SYw8jg/wtmw3EYb68wVghzpdnKIpYRqJAlduDfCJv9FRe5J7XFsZxhVxQSSc="
AUTH_DB_PATH="encrypted:BM9HDaWwoYJZrHIQtT2FCMQou8NPbom55bvnNdE916P20UlpDHLkopypRxrnFYn6+ufJVanOl6/1AiD7gHqUP/GfT9mrnnXT8qU9XT43hVtyr+N91t6r4fQkgEs31mFnzrCEvOO8VHx+Ps4Kn3tkhdgnoC798OZv"
JANICE_KEY="encrypted:BLrwoFguAtfDtopwRBkaM129x3kq8HLYDHddiNLV8unvaJPXPUHOzL+MySnK0lH2s43f1nqx759C2wT/95PHsU8mk29DUT2FbhwDCtqBccrjTZHJdXNZrzEXhyVUAdgoHdyyGugu/xwhaVqKYyTZJNCviceDcW4c/3iBiRBXpan8"
PERPLEXITY_API_KEY="encrypted:BMMVGTrkVOeh5a/iP9dIUMRXmcf0gIhj+KUQeyz4Cm0nKbAS/obENMn24NqmBRQtU/4M5Xj1lIpqV+C5MsBmdY/LW/9LX0z1okvaPRKjrudRyj24Mmj0HZLm+RCv0VTYBL2Yx4dszYCYkxBAEihDR/eQfGIMU7KoRNjMot0uF8BonC6fzSIWI0iwxsRm2k3ZAo/MoaRB"

View File

@@ -1,182 +0,0 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
.env.keys
.flaskenv*
!.env.project
!.env.vault
data/
db/
litefs/
brainstorming/
# Sentry Config File
.sentryclirc
cloudflared.exe

View File

@@ -1,5 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120
}

View File

@@ -1,4 +0,0 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@@ -1,29 +0,0 @@
{
"name": "concierge-bot",
"type": "module",
"module": "src/main.ts",
"private": true,
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"@dotenvx/dotenvx": "^1.49.0",
"@types/bun": "^1.2.21",
"prettier": "^3.6.2",
"typescript": "^5"
},
"dependencies": {
"@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev",
"@star-kitten/lib": "workspace:*"
},
"scripts": {
"dev": "bunx dotenvx run -f .env.development -- bun run --watch src/main.ts",
"start": "bunx @dotenvx/dotenvx run -f .env.production -- bun src/main.ts",
"build": "mkdirp ./db && bun build --minify --sourcemap src/main.ts --target bun --outdir ./dist",
"lint": "prettier --check .",
"format": "prettier --write .",
"test": "bun test",
"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"
}
}

View File

@@ -1,126 +0,0 @@
import { getDB } from '@/lib/db';
import { Constants } from '@projectdysnomia/dysnomia';
import {
createChatCommand,
integerOption,
stringOption,
subCommandGroupOption,
subCommandRouter,
type CommandContext,
type ExecutableInteraction,
} from '@star-kitten/lib/discord';
import { numberOption, subCommandOption } from '@star-kitten/lib/discord';
export default createChatCommand(
{
name: 'route',
description: 'Routes',
defaultMemberPermissions: Constants.Permissions.administrator,
options: [
subCommandGroupOption({
name: 'group',
description: 'a group',
options: [
subCommandOption({
name: 'add',
description: 'add a route',
options: [
stringOption({
name: 'start',
description: 'starting location',
autocomplete: true,
required: true,
}),
stringOption({
name: 'end',
description: 'end location',
autocomplete: true,
required: true,
}),
integerOption({
name: 'isk-per-m3',
description: 'ISK per m3',
required: true,
}),
numberOption({
name: 'collateral-percent',
description: 'percentage of collateral to add onto the cost.',
required: true,
}),
integerOption({
name: 'max-volume',
description: 'Maximum volume allowed for this route',
required: true,
}),
integerOption({
name: 'min-reward',
description: 'Minimum required reward for this route',
required: true,
}),
integerOption({
name: 'expiration',
description: 'Expiration the client should set on the contract',
required: true,
}),
integerOption({
name: 'completion',
description: 'Days to complete time the client should set on the contract',
required: true,
}),
integerOption({
name: 'max-collateral',
description: 'Maximum collateral allowed for this route',
}),
],
}),
subCommandOption({
name: 'remove',
description: 'remove a route',
options: [
integerOption({
name: 'id',
description: 'ID of the route to remove',
required: true,
}),
],
}),
],
}),
],
},
subCommandRouter({
group: {
add: async (interaction, ctx, data) => {
console.log(`in add`);
if (interaction.isAutocomplete()) {
const focused = data.options?.find((opt) => opt.focused);
console.log(`focused`, focused);
if (focused) {
switch (focused.name) {
case 'start': {
const locations = getDB().getAllLocations();
console.log(JSON.stringify(locations.length));
return await interaction.result(
locations.map((l) => ({ name: l.short_name, value: String(l.location_id) })),
);
}
case 'end': {
const locations = getDB().getAllLocations();
console.log(JSON.stringify(locations.length));
return await interaction.result(
locations.map((l) => ({ name: l.short_name, value: String(l.location_id) })),
);
}
}
}
}
},
remove: (interaction, ctx) => {
console.log('remove handler');
if (interaction.isApplicationCommand()) {
interaction.createMessage(`Thanks`);
}
},
},
}),
);

View File

@@ -1,4 +0,0 @@
import './locations/command';
import './quoute.command';
import './time.command';
import './create-route.command';

View File

@@ -1,48 +0,0 @@
import type { PageContext } from '@star-kitten/lib/discord';
import type { LocationsState } from './state';
import { Page } from './router';
import { StructureType } from '@/lib/db/types/routes';
export default async function (ctx: PageContext<LocationsState>) {
const isAdd = ctx.custom_id === Page.addLocationModal;
const location = ctx.state.data.selected;
return (
<modal
title={`${isAdd ? 'Add Location' : 'Edit: ' + ctx.state.data.selected?.short_name || ''}`}
customId={isAdd ? Page.addLocationModalSubmit : Page.editLocationModalSubmit}
>
{isAdd && (
<label label="Location ID">
<textInput customId="loc-id" placeholder="Enter the structure id" required />
</label>
)}
<label label="Location Name">
<textInput customId="loc-name" placeholder="Enter the location name" required value={location?.name} />
</label>
<label label="Location Short Name">
<textInput
customId="loc-short-name"
placeholder="Enter the location short name"
required
value={location?.short_name}
/>
</label>
<label label="System">
<textInput customId="loc-system" placeholder="Enter the system (e.g. Jita)" required value={location?.system} />
</label>
<label label="Structure Type">
<stringSelect
customId="loc-structure-type"
placeholder="Select Structure Type"
minValues={1}
maxValues={1}
required
>
{Object.values(StructureType).map((type) => (
<option label={type} value={type} default={location?.structure_type === type} />
))}
</stringSelect>
</label>
</modal>
);
}

View File

@@ -1,62 +0,0 @@
import {
createChatCommand,
type CommandContext,
type ExecutableInteraction,
PageType,
usePages,
hasUserId,
PermissionType,
} from '@star-kitten/lib/discord';
import mainPage from './main.page';
import addLocationModal from './add-location.modal';
import editServicesModal from './edit-services.modal';
import removeLocationModal from './remove-location.modal';
import type { LocationsState } from './state';
import router, { Page } from './router';
import { Constants } from '@projectdysnomia/dysnomia';
export default createChatCommand(
{
name: 'locations',
description: 'location management',
defaultMemberPermissions: Constants.Permissions.administrator,
},
async (interaction: ExecutableInteraction, commandCtx: CommandContext) => {
await usePages<LocationsState>(
{
pages: {
[Page.main]: {
key: Page.main,
type: PageType.MESSAGE,
render: mainPage,
},
[Page.addLocationModal]: {
key: Page.addLocationModal,
type: PageType.MODAL,
render: addLocationModal,
},
[Page.editLocationModal]: {
key: Page.editLocationModal,
type: PageType.MODAL,
render: addLocationModal,
},
[Page.editServicesModal]: {
key: Page.editServicesModal,
type: PageType.MODAL,
render: editServicesModal,
},
[Page.removeLocationModal]: {
key: Page.removeLocationModal,
type: PageType.MODAL,
render: removeLocationModal,
},
},
router,
initialPage: Page.main,
ephemeral: true,
},
interaction,
commandCtx,
);
},
);

View File

@@ -1,46 +0,0 @@
import type { PageContext } from '@star-kitten/lib/discord';
import type { LocationsState } from './state';
import { Page } from './router';
import { locationSupportsType, RouteType } from '@/lib/db/types/routes';
export default function (ctx: PageContext<LocationsState>) {
const location = ctx.state.data.selected!;
return (
<modal title={`Services: ${ctx.state.data.selected?.short_name || ''}`} customId={Page.editServicesModalSubmit}>
<label label="Can JF">
<stringSelect customId="can-jf" placeholder="Can JF" minValues={1} maxValues={1}>
<option label="Yes" value={RouteType.JF + ''} default={locationSupportsType(location, RouteType.JF)} />
<option label="No" value="0" default={!locationSupportsType(location, RouteType.JF)} />
</stringSelect>
</label>
<label label="Can DST">
<stringSelect customId="can-dst" placeholder="Can DST" minValues={1} maxValues={1}>
<option label="Yes" value={RouteType.DST + ''} default={locationSupportsType(location, RouteType.DST)} />
<option label="No" value="0" default={!locationSupportsType(location, RouteType.DST)} />
</stringSelect>
</label>
<label label="Can BR">
<stringSelect customId="can-br" placeholder="Can BR" minValues={1} maxValues={1}>
<option label="Yes" value={RouteType.BR + ''} default={locationSupportsType(location, RouteType.BR)} />
<option label="No" value="0" default={!locationSupportsType(location, RouteType.BR)} />
</stringSelect>
</label>
<label label="Can SMB">
<stringSelect customId="can-smb" placeholder="Can SMB" minValues={1} maxValues={1}>
<option label="Yes" value={RouteType.SMB + ''} default={locationSupportsType(location, RouteType.SMB)} />
<option label="No" value="0" default={!locationSupportsType(location, RouteType.SMB)} />
</stringSelect>
</label>
<label label="Can Bridge">
<stringSelect customId="can-bridge" placeholder="Can Bridge" minValues={1} maxValues={1}>
<option
label="Yes"
value={RouteType.BRIDGE + ''}
default={locationSupportsType(location, RouteType.BRIDGE)}
/>
<option label="No" value="0" default={!locationSupportsType(location, RouteType.BRIDGE)} />
</stringSelect>
</label>
</modal>
);
}

View File

@@ -1,78 +0,0 @@
import { ButtonStyle, type PageContext } from '@star-kitten/lib/discord';
import type { LocationsState } from './state';
import { getDB } from '@/lib/db';
import { Page } from './router';
import { locationSupportsType, RouteType } from '@/lib/db/types/routes';
export default function (ctx: PageContext<LocationsState>) {
const locations = getDB().getAllLocations();
const hasLocations = locations.length > 0;
const renderLocations = () => {
if (locations.length === 0) {
return 'No locations added yet.';
}
return locations
.map(
(loc) =>
`${loc.location_id}\t|\t${loc.short_name}\t|\t${locationSupportsType(loc, RouteType.JF) ? 'JF' : ''}${locationSupportsType(loc, RouteType.DST) ? ', DST' : ''}${locationSupportsType(loc, RouteType.BR) ? ', BR' : ''}${locationSupportsType(loc, RouteType.SMB) ? ', SMB' : ''}${locationSupportsType(loc, RouteType.BRIDGE) ? ', BRIDGE' : ''}`,
)
.join('\n');
};
if (!hasLocations) {
return (
<container accent={0x11cc33}>
<text>{`# Locations\nNo locations added yet.`}</text>
<actionRow>
<button customId={Page.addLocationModal} label="Add Location" style={ButtonStyle.PRIMARY} />
</actionRow>
</container>
);
}
const locationOptions = locations.map((loc) => <option label={loc.short_name} value={loc.location_id.toString()} />);
return (
<container accent={0x11cc33}>
<text>{`# Locations\n${renderLocations()}`}</text>
<actionRow>
<button customId={Page.addLocationModal} label="Add Location" style={ButtonStyle.PRIMARY} />
</actionRow>
<text>{`## Edit services at`}</text>
<actionRow>
<stringSelect
customId={Page.editServicesModal}
placeholder="Select location to edit services"
minValues={1}
maxValues={1}
>
{locationOptions}
</stringSelect>
</actionRow>
<text>{`## Edit location details`}</text>
<actionRow>
<stringSelect
customId={Page.editLocationModal}
placeholder="Select location to edit"
minValues={1}
maxValues={1}
>
{locationOptions}
</stringSelect>
</actionRow>
<text>{`## Remove location`}</text>
<actionRow>
<stringSelect
customId={Page.removeLocationModal}
placeholder="Select location to remove"
minValues={1}
maxValues={1}
>
{locationOptions}
</stringSelect>
</actionRow>
</container>
);
}

View File

@@ -1,11 +0,0 @@
import type { PageContext } from '@star-kitten/lib/discord';
import type { LocationsState } from './state';
import { Page } from './router';
export default function (ctx: PageContext<LocationsState>) {
return (
<modal title={`Remove ${ctx.state.data.selected?.short_name || ''}?`} customId={Page.removeLocationModalSubmit}>
<text>{`# Are you sure?\n\nConfrim that you want remove **${ctx.state.data.selected.name}**.\n\n*This action cannot be undone. Click submit to confirm.*`}</text>
</modal>
);
}

View File

@@ -1,140 +0,0 @@
import * as StarKitten from '@star-kitten/lib/discord';
import type { LocationsState } from './state';
import { getDB } from '@/lib/db';
import type { StructureType } from '@/lib/db/types/routes';
export enum Page {
main = 'main',
addLocationModal = 'add-location-modal',
addLocationModalSubmit = 'add-location-modal-submit',
editLocationModal = 'edit-location-modal',
editLocationModalSubmit = 'edit-location-modal-submit',
editServicesModal = 'edit-services-modal',
editServicesModalSubmit = 'edit-services-modal-submit',
removeLocationModal = 'remove-location-modal',
removeLocationModalSubmit = 'remove-location-modal-submit',
}
export default function (ctx: StarKitten.PageContext<LocationsState>): Page {
switch (ctx.custom_id) {
case Page.addLocationModal:
ctx.state.data.selected = undefined;
return Page.addLocationModal;
case Page.editLocationModalSubmit:
case Page.addLocationModalSubmit: {
if (!ctx.interaction.isModalSubmit()) {
throw new Error('Expected a modal submit interaction.');
}
const location: any = {};
for (const component of ctx.interaction.data.components) {
if (StarKitten.isModalLabel(component)) {
if (StarKitten.isModalTextInput(component.component)) {
if (StarKitten.componentHasIdPrefix(component.component, 'loc-id')) {
location.location_id = component.component.value.trim();
} else if (StarKitten.componentHasIdPrefix(component.component, 'loc-name')) {
location.name = component.component.value.trim();
} else if (StarKitten.componentHasIdPrefix(component.component, 'loc-short-name')) {
location.short_name = component.component.value.trim();
} else if (StarKitten.componentHasIdPrefix(component.component, 'loc-system')) {
location.system = component.component.value.trim();
}
} else if (
StarKitten.isStringSelectMenu(component.component) &&
StarKitten.componentHasIdPrefix(component.component, 'loc-structure-type')
) {
location.structure_type = component.component.values[0] as StructureType;
}
}
}
ctx.custom_id === Page.addLocationModalSubmit ? getDB().addLocation(location) : getDB().updateLocation(location);
ctx.state.data.selected = undefined;
return Page.main;
}
case Page.editLocationModal: {
if (!ctx.interaction.isSelectMenu()) {
console.error('Expected a message component interaction.');
return Page.main;
}
const data = ctx.interaction.data;
const locationId = Number.parseInt(data.values[0]);
const location = getDB().getLocationById(locationId);
if (location) {
ctx.state.data.selected = location;
return Page.editLocationModal;
}
return Page.main;
}
case Page.editServicesModal: {
if (!ctx.interaction.isSelectMenu()) {
console.error('Expected a message component interaction.');
return Page.main;
}
const data = ctx.interaction.data;
const locationId = Number.parseInt(data.values[0]);
const location = getDB().getLocationById(locationId);
if (location) {
ctx.state.data.selected = location;
return Page.editServicesModal;
}
return Page.main;
}
case Page.editServicesModalSubmit: {
if (ctx.interaction.isModalSubmit()) {
const location = ctx.state.data.selected;
if (!location) {
return Page.main;
}
let supported_route_types = 0;
for (const component of ctx.interaction.data.components) {
if (StarKitten.isModalLabel(component) && StarKitten.isStringSelectMenu(component.component)) {
supported_route_types = supported_route_types | parseInt(component.component.values[0]);
}
}
getDB().updateLocation({
...location,
supported_route_types,
});
}
ctx.state.data.selected = undefined;
return Page.main;
}
case Page.removeLocationModal: {
if (!ctx.interaction.isSelectMenu()) {
console.error('Expected a message component interaction.');
return Page.main;
}
const data = ctx.interaction.data;
const locationId = Number.parseInt(data.values[0]);
const location = getDB().getLocationById(locationId);
if (location) {
ctx.state.data.selected = location;
return Page.removeLocationModal;
}
return Page.main;
}
case Page.removeLocationModalSubmit: {
if (ctx.interaction.isModalSubmit()) {
const location = ctx.state.data.selected;
if (!location) {
return Page.main;
}
getDB().removeLocation(location.location_id);
}
ctx.state.data.selected = undefined;
return Page.main;
}
default:
return Page.main;
}
}

View File

@@ -1,5 +0,0 @@
import type { Location } from '@/lib/db';
export interface LocationsState {
selected?: Location;
}

View File

@@ -1,239 +0,0 @@
import {
Constants,
type ChatInputApplicationCommandStructure,
type ComponentInteractionSelectMenuData,
} from '@projectdysnomia/dysnomia';
import { appraiseItems, type Appraisal } from '@star-kitten/lib/eve/third-party/janice.js';
import { componentHasIdPrefix, isModalLabel, isModalTextInput } from '@star-kitten/lib/discord';
import { createChatCommand, type CommandContext, type 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: 'quote',
nameLocalizations: {
de: 'angebot',
'es-ES': 'cotización',
fr: 'devis',
ja: '見積もり',
ko: '견적',
ru: 'цитата',
'zh-CN': '报价',
},
description: 'Get a quote for moving your items',
descriptionLocalizations: {
de: 'Holen Sie Sie sich ein Angebot für den Umzug Ihrer Gegenstände',
'es-ES': 'Obtén una cotización para mover tus artículos',
fr: 'Obtenez un devis pour déplacer vos articles',
ja: 'アイテムを移動するための見積もりを取得します',
ko: '항목을 이동하기 위한 견적 받기',
ru: 'Получите предложение по перемещению ваших предметов',
'zh-CN': '获取移动您的物品的报价',
},
};
interface QuouteState {
serviceType: RouteType;
originId?: number;
destinationId?: number;
items?: string;
appraisal?: Appraisal;
}
// Hardcoded routes for now
interface RouteType {
id: number;
short: string;
label: string;
}
const routeTypes: Record<string, RouteType> = {
JF: { id: 1, short: 'JF', label: 'Jump Freighter' },
DST: { id: 2, short: 'DST', label: 'Deep Space Transport' },
SMB: { id: 3, short: 'SMB', label: 'Ship Maintenance Bay' },
BR: { id: 4, short: 'BR', label: 'Blockade Runner' },
CUSTOM: { id: 5, short: 'CUSTOM', label: 'Custom' },
};
interface Location {
id: number;
name: string;
supported_types: number[]; // RouteType IDs
}
const locations: Location[] = [
{ id: 1, name: 'Jita 4-4', supported_types: [1, 2, 3, 4, 5] },
{ id: 2, name: 'B-9', supported_types: [1, 2] },
{ id: 3, name: '3T7', supported_types: [1, 2, 3, 4, 5] },
{ id: 4, name: '4-H', supported_types: [1, 2] },
{ id: 5, name: 'Odebeinn', supported_types: [1, 2, 3, 4, 5] },
];
interface Route {
origin: number;
destination: number;
type: number; // RouteType ID
}
const routes: Route[] = [
{ origin: 1, destination: 2, type: routeTypes.JF.id },
{ origin: 2, destination: 1, type: routeTypes.JF.id },
{ origin: 2, destination: 3, type: routeTypes.JF.id },
{ origin: 3, destination: 2, type: routeTypes.JF.id },
{ origin: 3, destination: 4, type: routeTypes.JF.id },
{ origin: 4, destination: 3, type: routeTypes.JF.id },
{ origin: 4, destination: 5, type: routeTypes.JF.id },
{ origin: 5, destination: 4, type: routeTypes.JF.id },
{ origin: 5, destination: 2, type: routeTypes.JF.id },
{ origin: 2, destination: 3, type: routeTypes.JF.id },
{ origin: 3, destination: 4, type: routeTypes.JF.id },
{ origin: 4, destination: 5, type: routeTypes.JF.id },
{ origin: 5, destination: 2, type: routeTypes.JF.id },
{ origin: 3, destination: 2, type: routeTypes.DST.id },
{ origin: 2, destination: 4, type: routeTypes.SMB.id },
{ origin: 2, destination: 5, type: routeTypes.BR.id },
{ origin: 1, destination: 3, type: routeTypes.DST.id },
{ origin: 1, destination: 4, type: routeTypes.SMB.id },
{ origin: 1, destination: 5, type: routeTypes.BR.id },
{ origin: 2, destination: 1, type: routeTypes.JF.id },
{ origin: 3, destination: 1, type: routeTypes.DST.id },
{ origin: 4, destination: 1, type: routeTypes.SMB.id },
{ origin: 5, destination: 1, type: routeTypes.BR.id },
];
const defaultState: QuouteState = {
serviceType: routeTypes.JF,
originId: undefined,
destinationId: undefined,
items: undefined,
appraisal: undefined,
};
function uniqueDestinationForOriginAndType(typeId: number, originId?: number) {
if (!originId) {
locations.filter((loc) => loc.supported_types.includes(typeId));
}
const filtered = routes.filter((r) => r.origin === originId && r.type === typeId);
const locSet = new Set<Location>();
filtered.forEach((route, index) => {
locSet.add(locations.find((l) => l.id === route.destination)!);
});
return Array.from(locSet);
}
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
await usePages<QuouteState>(
{
pages: {
main: {
key: 'main',
type: PageType.MESSAGE,
render: (pageCtx) => {
console.log('Rendering main page with state:', pageCtx.state.data);
return (
<container accent={0x11cc33}>
<text>{`# Quote`}</text>
<text>{`### Service: ${pageCtx.state.data.serviceType?.label ?? ''}`}</text>
<actionRow>
{Object.keys(routeTypes).map((key) => (
<button
customId={`type-${key}`}
label={routeTypes[key].short}
style={Constants.ButtonStyles.SECONDARY}
/>
))}
</actionRow>
<text>{`### Origin: ${locations[pageCtx.state.data.originId - 1]?.name ?? ''}`}</text>
<actionRow>
<stringSelect customId="route-origin" placeholder="Select Origin">
{locations
.filter((loc) => loc.supported_types.includes(pageCtx.state.data.serviceType?.id ?? -1))
.map((loc) => {
return <option label={loc?.name ?? ''} value={loc?.id.toString() ?? ''} />;
})}
</stringSelect>
</actionRow>
<text>{`### Destination: ${locations[pageCtx.state.data.destinationId - 1]?.name ?? ''}`}</text>
<actionRow>
<stringSelect customId="route-destination" placeholder="Select Destination">
<option label="Select a destination" value="1" />
</stringSelect>
</actionRow>
<text>{`### Items:\n${pageCtx.state.data.items ?? ''}`}</text>
<actionRow>
<button customId="addItems" label="Add Items" style={Constants.ButtonStyles.PRIMARY} />
</actionRow>
</container>
);
},
},
addItems: {
key: 'add-items',
type: PageType.MODAL,
render: () => {
return (
<modal title="Add Items" customId="add-items-modal">
<label
label="Items"
description="Discord limits input to 4000 characters. Add more items by submitting multiple times."
>
<textInput
customId="items-input"
placeholder={`e.g. Tritanium 22222
Pyerite 8000
Mexallon 2444`}
isParagraph={true}
required
/>
</label>
</modal>
);
},
},
},
initialPage: 'main',
initialStateData: defaultState,
ephemeral: true,
router: (pageCtx) => {
if (pageCtx.custom_id.startsWith('type-')) {
const key = pageCtx.custom_id.replace('type-', '');
pageCtx.state.data.serviceType = routeTypes[key];
return 'main';
}
if (pageCtx.custom_id === 'route-origin' && pageCtx.interaction.isMessageComponent()) {
const data = pageCtx.interaction.data as ComponentInteractionSelectMenuData;
pageCtx.state.data.originId = Number.parseInt(data.values[0]);
return 'main';
}
if (pageCtx.custom_id === 'route-destination' && pageCtx.interaction.isMessageComponent()) {
const data = pageCtx.interaction.data as ComponentInteractionSelectMenuData;
pageCtx.state.data.destinationId = Number.parseInt(data.values[0]);
return 'main';
}
if (pageCtx.custom_id === 'addItems') {
return 'addItems';
}
if (pageCtx.custom_id === 'add-items-modal' && pageCtx.interaction.isModalSubmit()) {
let items = '';
pageCtx.interaction.data.components.forEach((comp) => {
if (isModalLabel(comp)) {
if (isModalTextInput(comp.component) && componentHasIdPrefix(comp.component, 'items-input')) {
items = comp.component.value || items;
}
}
});
pageCtx.state.data.items = pageCtx.state.data.items ? `${pageCtx.state.data.items}\n${items}` : items;
}
return 'main';
},
},
interaction,
ctx,
);
}
export default createChatCommand(definition, execute);

View File

@@ -1,61 +0,0 @@
import { type ExecutableInteraction, type CommandContext, Locale, createChatCommand } from '@star-kitten/lib/discord';
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时间',
};
export default createChatCommand(
{
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时间',
},
},
(interaction: ExecutableInteraction, ctx: CommandContext) => {
if (!interaction.isApplicationCommand()) return;
const now = new Date();
const eveTime = now.toISOString().split('T')[1].split('.')[0];
const eveDate = now.toLocaleDateString(interaction.locale, {
timeZone: 'UTC',
year: 'numeric',
month: 'long',
day: '2-digit',
weekday: 'long',
});
interaction.createJSXMessage(
<container>
<text>
{`### ${eveTimeText[interaction.locale] || eveTimeText[Locale.EN_US]}
${eveTime}
${eveDate}`}
</text>
</container>,
);
},
);

View File

@@ -1,61 +0,0 @@
import { Database } from 'bun:sqlite';
import locationTables, * as locationQueries from './location';
export * from './location';
let db: Database = undefined;
const queries = {
...locationQueries,
};
function createTables() {
locationTables.createTable(db!);
}
function dropTables() {
locationTables.dropTable(db!);
}
function close() {
if (db) {
db.close();
db = null;
}
}
function initializeDatabase(dbPath: string = process.env.CONCIERGE_DB_PATH || ':memory:') {
db = new Database(dbPath);
createTables();
Object.keys(queries).forEach((key) => {
if (typeof queries[key] === 'function') {
queries[key] = queries[key].bind(null, db);
}
});
}
type OmitFirstArg<F> = F extends (arg1: any, ...args: infer R) => infer Ret ? (...args: R) => Ret : never;
type CurriedObject<T, O> = {
[K in keyof T]: T[K] extends (arg1: O, ...args: infer R) => infer Ret ? OmitFirstArg<T[K]> : T[K];
};
export type DB = CurriedObject<Omit<typeof queries, 'default'>, Database> & {
db: Database;
createTables: () => void;
dropTables: () => void;
close: () => void;
};
export function getDB(): DB {
if (!db) {
initializeDatabase();
}
return {
...(queries as any),
default: undefined,
createTables,
dropTables,
close,
db,
} as DB;
}

View File

@@ -1,46 +0,0 @@
import type { Database } from 'bun:sqlite';
import { dynamicInsert, dynamicUpdate, remove, select, type QueryOptions } from '@star-kitten/lib/util/sqlite.js';
import type { Location } from './types/routes';
const TABLE_NAME = 'locations';
export default {
createTable: (db: Database) => {
db.run(
`CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
location_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
short_name TEXT NOT NULL,
structure_type TEXT NOT NULL,
system TEXT NOT NULL,
supported_route_types INT NOT NULL DEFAULT 0
)`,
);
},
dropTable: (db: Database) => {
db.run(`DROP TABLE IF EXISTS ${TABLE_NAME}`);
},
};
export function addLocation(db: Database, location: Location) {
return dynamicInsert(db, TABLE_NAME, location);
}
export function updateLocation(db: Database, location: Location) {
const id = location.location_id;
delete location.location_id;
return dynamicUpdate(db, TABLE_NAME, location, 'location_id', id);
}
export function getLocationById(db: Database, location_id: number) {
return select<Location>(db, TABLE_NAME, {}, '*', { location_id })?.[0];
}
export function getAllLocations(db: Database, options?: QueryOptions<Location>): Location[] {
return select<Location>(db, TABLE_NAME, options);
}
export function removeLocation(db: Database, location_id: number) {
return remove<Location>(db, TABLE_NAME, { location_id });
}

View File

@@ -1,51 +0,0 @@
import type { Database } from 'bun:sqlite';
import { dynamicInsert, dynamicUpdate, remove, select, type QueryOptions } from '@star-kitten/lib/util/sqlite.js';
import type { Route } from './types/routes';
const TABLE_NAME = 'routes';
export default {
createTable: (db: Database) => {
db.run(
`CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
route_id INTEGER PRIMARY KEY AUTOINCREMENT,
start_location_id INTEGER NOT NULL,
end_location_id INTEGER NOT NULL,
isk_per_m3 INTEGER NOT NULL,
collat_pct REAL NOT NULL,
max_volume INTEGER NOT NULL,
min_reward INTEGER NOT NULL,
expiration INTEGER NOT NULL,
completion INTEGER NOT NULL,
max_collateral INTEGER
)
`,
);
},
dropTable: (db: Database) => {
db.run(`DROP TABLE IF EXISTS ${TABLE_NAME}`);
},
};
export function addRoute(db: Database, route: Omit<Route, 'route_id'>) {
return dynamicInsert(db, TABLE_NAME, route);
}
export function updateLocation(db: Database, location: Route) {
const id = location.route_id;
delete location.route_id;
return dynamicUpdate(db, TABLE_NAME, location, 'route_id', id);
}
export function getRoutes(db: Database, options?: QueryOptions<Route>) {
return select<Route>(db, TABLE_NAME, options);
}
export function getRoute(db: Database, route_id: number) {
return select<Route>(db, TABLE_NAME, {}, '*', { route_id })?.[0];
}
export function removeRoute(db: Database, route_id: number) {
return remove<Route>(db, TABLE_NAME, { route_id });
}

View File

@@ -1,10 +0,0 @@
import type { ContractStatus } from '@star-kitten/lib/eve';
export interface CourierContract {
concierge_id: number; // internal id to track this db record
contract_id: number; // id of the contract for this courier
assigned_to: number;
received: string;
last_updated: string;
status: ContractStatus;
}

View File

@@ -1,45 +0,0 @@
export enum RouteType {
JF = 1 << 0,
DST = 1 << 1,
BR = 1 << 2,
SMB = 1 << 3,
BRIDGE = 1 << 4,
}
export enum StructureType {
NPC = 'NPC',
Keepstar = 'Keepstar',
Fortizar = 'Fortizar',
Astrahus = 'Astrahus',
Sotiyo = 'Sotiyo',
Azbel = 'Azbel',
Raitaru = 'Raitaru',
Athanor = 'Athanor',
Tatara = 'Tatara',
}
export interface Location {
location_id: number;
name: string;
short_name: string;
structure_type: StructureType;
system: string;
supported_route_types: number;
}
export interface Route {
route_id: number;
start_location_id: number;
end_location_id: number;
isk_per_m3: number;
collat_pct: number; // collateral percent as a float, 1.5 = 1.5%
max_volume: number;
min_reward: number;
expiration: number;
completion: number;
max_collateral: number;
}
export function locationSupportsType(loc: Location, rt: RouteType) {
return (loc.supported_route_types & rt) !== 0;
}

View File

@@ -1,4 +0,0 @@
import { startBot } from '@star-kitten/lib/discord';
import './commands';
startBot();

View File

@@ -1,16 +0,0 @@
{
"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"]
}

View File

@@ -1,4 +1,4 @@
# concierge-bot
# discord
To install dependencies:

View File

@@ -0,0 +1,7 @@
import { createChatCommand } from '@/commands';
export default createChatCommand({
name: 'test1', description: 'Test command 1' },
async () => {},
);

View File

@@ -0,0 +1,7 @@
import { createChatCommand } from '@/commands';
export default createChatCommand({
name: 'test2', description: 'Test command 2' },
async () => {},
);

View File

@@ -0,0 +1,74 @@
{
"name": "@star-kitten/discord",
"version": "0.0.1",
"description": "Star Kitten Discord Library.",
"type": "module",
"license": "MIT",
"homepage": "https://git.f302.me/jb/star-kitten#readme",
"bugs": {
"url": "https://git.f302.me/jb/star-kitten/issues"
},
"repository": {
"type": "git",
"url": "git+https://git.f302.me/jb/star-kitten.git"
},
"author": "JB <j-b-3.deviate267@passmail.net>",
"files": [
"dist"
],
"main": "./dist/index.js",
"module": "./dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index*.d.ts"
},
"./commands": {
"require": "./dist/commands/index.js",
"import": "./dist/commands/index.js",
"types": "./dist/types/commands/index.d.ts"
},
"./components": {
"types": "./dist/types/components/index.d.ts",
"require": "./dist/components/index.js",
"import": "./dist/components/index.js"
},
"./pages": {
"require": "./dist/pages/index.js",
"import": "./dist/pages/index.js",
"types": "./dist/types/pages/index.d.ts"
},
"./common": {
"require": "./dist/common/index.js",
"import": "./dist/common/index.js"
}
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/bun": "^1.3.5",
"@types/lodash": "^4.17.20",
"@vitest/coverage-v8": "^3.2.4",
"bumpp": "^10.1.0",
"prettier-plugin-multiline-arrays": "^4.0.3",
"tsdown": "^0.14.2",
"typescript": "beta"
},
"dependencies": {
"@star-kitten/util": "link:@star-kitten/util",
"@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev",
"date-fns": "^4.1.0",
"fp-filters": "^0.5.4",
"lodash": "^4.17.21"
},
"scripts": {
"build": "tsdown",
"dev": "tsdown --watch",
"link": "bun link",
"test": "bun test",
"typecheck": "tsc --noEmit",
"release": "bumpp && npm publish"
}
}

View File

@@ -13,9 +13,6 @@ export function injectInteraction(interaction: ExecutableInteraction, ctx: Parti
return _originalCreateModal(content);
};
interaction.createJSXModal = async (component) => {
return interaction.createModal(component as any);
};
}
if ('createMessage' in interaction) {
@@ -28,13 +25,6 @@ export function injectInteraction(interaction: ExecutableInteraction, ctx: Parti
return _originalCreateMessage(content);
};
interaction.createJSXMessage = async (component) => {
const messageContent = {
flags: Constants.MessageFlags.IS_COMPONENTS_V2,
components: [component],
};
return interaction.createMessage(messageContent as any);
};
}
if ('editMessage' in interaction) {
@@ -47,13 +37,6 @@ export function injectInteraction(interaction: ExecutableInteraction, ctx: Parti
return _originalEditMessage(messageID, content);
};
interaction.editJSXMessage = async (messageID, component) => {
const messageContent = {
flags: Constants.MessageFlags.IS_COMPONENTS_V2,
components: [component],
};
return interaction.editMessage(messageID, messageContent as any);
};
}
if ('createFollowup' in interaction) {
@@ -66,13 +49,6 @@ export function injectInteraction(interaction: ExecutableInteraction, ctx: Parti
return _originalCreateFollowup(content);
};
interaction.createJSXFollowup = async (component) => {
const messageContent = {
flags: Constants.MessageFlags.IS_COMPONENTS_V2,
components: [component],
};
return interaction.createFollowup(messageContent as any);
};
}
}
return [interaction, ctx as CommandContext];

View File

@@ -1,4 +1,4 @@
import { createReactiveState } from '@/util/reactive-state.js';
import { createReactiveState } from '@star-kitten/util/reactive-state.js';
import { isApplicationCommand, isAutocomplete } from './command-helpers';
import type { CommandState, ExecutableInteraction, PartialContext } from '../types';

View File

@@ -4,7 +4,7 @@ import { getCommandState } from './command-state';
import { type ExecutableInteraction } from '../types/interaction.type';
import type { CommandContext, CommandHandler, CommandOptions, PartialContext } from '../types';
import { augmentInteraction, getCommandName } from './command-helpers';
import { awaitMaybePromise } from '@/util/promise';
import { awaitMaybePromise } from '@star-kitten/util/promise';
export async function handleCommands(
interaction: ExecutableInteraction,

View File

@@ -1,3 +1,6 @@
// Exports that will be made publicly available outside this library
export * from './create';
export * from './option-builders';
import * as options from './option-builders';
export { options };

View File

@@ -1,339 +1,367 @@
import {
Constants,
type ActionRow,
type Button,
type ChannelSelectMenu,
type GuildChannelTypes,
type MentionableSelectMenu,
type PartialEmoji,
type RoleSelectMenu,
type StringSelectMenu,
type TextInput,
type UserSelectMenu,
type LabelComponent,
type ContainerComponent,
type TextDisplayComponent,
type SectionComponent,
type MediaGalleryComponent,
type SeparatorComponent,
type FileComponent,
type InteractionButton,
type URLButton,
type PremiumButton,
type ThumbnailComponent,
type ModalSubmitInteractionData,
type FileUploadComponent,
} from '@projectdysnomia/dysnomia';
export type ActionRowItem = Button | StringSelectMenu | UserSelectMenu | RoleSelectMenu | MentionableSelectMenu | ChannelSelectMenu;
export const actionRow = (...components: ActionRowItem[]): ActionRow => ({
type: Constants.ComponentTypes.ACTION_ROW,
components: components.filter((c) => c),
});
export enum ButtonStyle {
PRIMARY = 1,
SECONDARY = 2,
SUCCESS = 3,
DANGER = 4,
}
export interface ButtonOptions {
style?: ButtonStyle;
emoji?: PartialEmoji;
disabled?: boolean;
}
export const button = (label: string, custom_id: string, options?: ButtonOptions): InteractionButton => ({
type: Constants.ComponentTypes.BUTTON,
style: options?.style ?? Constants.ButtonStyles.PRIMARY,
label,
custom_id,
...options,
});
export interface URLButtonOptions {
emoji?: PartialEmoji;
disabled?: boolean;
}
export const urlButton = (label: string, url: string, options?: URLButtonOptions): URLButton => ({
type: Constants.ComponentTypes.BUTTON,
style: Constants.ButtonStyles.LINK,
label,
url,
...options,
});
export interface PremiumButtonOptions {
emoji?: PartialEmoji;
disabled?: boolean;
}
export const premiumButton = (sku_id: string, options?: PremiumButtonOptions): PremiumButton => ({
type: Constants.ComponentTypes.BUTTON,
style: Constants.ButtonStyles.PREMIUM,
sku_id,
...options,
});
export interface StringSelectOpts {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean; // Note: not actually a property of StringSelectMenu, but useful for modals
}
export interface StringSelectOption {
label: string;
value: string;
description?: string;
emoji?: PartialEmoji;
default?: boolean;
}
export interface StringSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean;
}
export const stringSelect = (custom_id: string, selectOpts: StringSelectOpts, ...options: StringSelectOption[]): StringSelectMenu => ({
type: Constants.ComponentTypes.STRING_SELECT,
custom_id,
options,
placeholder: selectOpts.placeholder,
min_values: selectOpts.min_values ?? 1,
max_values: selectOpts.max_values ?? 1,
disabled: selectOpts.disabled ?? false,
required: selectOpts.required ?? false, // Note: not actually a property of StringSelectMenu, but useful for modals
});
export interface InputOptions {
isParagraph?: boolean;
label?: string;
min_length?: number;
max_length?: number;
required?: boolean;
value?: string;
placeholder?: string;
}
export const input = (custom_id: string, options?: InputOptions): TextInput => ({
type: Constants.ComponentTypes.TEXT_INPUT,
custom_id,
style: options.isParagraph ? Constants.TextInputStyles.PARAGRAPH : Constants.TextInputStyles.SHORT,
label: options?.label,
min_length: options?.min_length ?? 0,
max_length: options?.max_length ?? 4000,
required: options?.required ?? false,
value: options?.value,
placeholder: options?.placeholder,
});
export interface UserSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean; // if on a modal
default_values?: Array<{ id: string; type: 'user' }>;
}
export const userSelect = (custom_id: string, options?: UserSelectOptions): UserSelectMenu => ({
type: Constants.ComponentTypes.USER_SELECT,
custom_id,
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface RoleSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean; // if on a modal
default_values?: Array<{ id: string; type: 'role' }>;
}
export const roleSelect = (custom_id: string, options?: RoleSelectOptions): RoleSelectMenu => ({
type: Constants.ComponentTypes.ROLE_SELECT,
custom_id,
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface MentionableSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean; // if on a modal
default_values?: Array<{ id: string; type: 'user' | 'role' }>;
}
export const mentionableSelect = (custom_id: string, options?: MentionableSelectOptions): MentionableSelectMenu => ({
type: Constants.ComponentTypes.MENTIONABLE_SELECT,
custom_id,
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface ChannelSelectOptions {
channel_types?: GuildChannelTypes[];
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean; // if on a modal
default_values?: Array<{ id: string; type: 'channel' }>;
}
export const channelSelect = (custom_id: string, options?: ChannelSelectOptions): ChannelSelectMenu => ({
type: Constants.ComponentTypes.CHANNEL_SELECT,
custom_id,
channel_types: options?.channel_types ?? [],
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface SectionOptions {
components: Array<TextDisplayComponent>;
accessory: Button | ThumbnailComponent;
}
export const section = (accessory: Button | ThumbnailComponent, ...components: Array<TextDisplayComponent>): SectionComponent => ({
type: Constants.ComponentTypes.SECTION,
accessory,
components: components.filter((c) => c),
});
/**
* Creates a text display component where the text will be displayed similar to a message: supports markdown
* @param content The text content to display.
* @returns The created text display component.
*/
export const text = (content: string) => ({
type: Constants.ComponentTypes.TEXT_DISPLAY,
content,
});
export interface ThumbnailOptions {
media: {
url: string; // Supports arbitrary urls and attachment://<filename> references
};
description?: string;
spoiler?: boolean;
}
export const thumbnail = (url: string, description?: string, spoiler?: boolean): ThumbnailComponent => ({
type: Constants.ComponentTypes.THUMBNAIL,
media: {
url,
},
description,
spoiler,
});
export interface MediaItem {
url: string; // Supports arbitrary urls and attachment://<filename> references
description?: string;
spoiler?: boolean;
}
export const gallery = (...items: MediaItem[]): MediaGalleryComponent => ({
type: Constants.ComponentTypes.MEDIA_GALLERY,
items: items.map((item) => ({
type: Constants.ComponentTypes.FILE,
media: { url: item.url },
description: item.description,
spoiler: item.spoiler,
})),
});
export interface FileOptions {
url: string; // Supports only attachment://<filename> references
spoiler?: boolean;
}
export const file = (url: string, spoiler?: boolean): FileComponent => ({
type: Constants.ComponentTypes.FILE,
file: {
url,
},
spoiler,
});
export enum Padding {
SMALL = 1,
LARGE = 2,
}
export interface SeparatorOptions {
divider?: boolean;
spacing?: Padding;
}
export const separator = (spacing?: Padding, divider?: boolean): SeparatorComponent => ({
type: Constants.ComponentTypes.SEPARATOR,
divider,
spacing: spacing ?? Padding.SMALL,
});
export interface ContainerOptions {
accent_color?: number;
spoiler?: boolean;
}
export type ContainerItems =
| ActionRow
| TextDisplayComponent
| SectionComponent
| MediaGalleryComponent
| SeparatorComponent
| FileComponent;
export const container = (options: ContainerOptions, ...components: ContainerItems[]): ContainerComponent => ({
type: Constants.ComponentTypes.CONTAINER,
...options,
components: components.filter((c) => c),
});
// Modals
export interface LabelOptions {
label: string;
description?: string;
}
export const label = (options: LabelOptions, component: LabelComponent['component']): LabelComponent => ({
type: Constants.ComponentTypes.LABEL,
label: options.label,
description: options.description,
component,
});
export const modal = (
options: { custom_id?: string; title?: string },
...components: Array<LabelComponent | ActionRow | TextDisplayComponent>
): ModalSubmitInteractionData =>
({
type: 9 as any, // Modal type
custom_id: options.custom_id ?? '',
title: options.title ?? '',
components: components.filter((c) => c),
} as any);
import {
Constants,
type ActionRow,
type Button,
type ChannelSelectMenu,
type GuildChannelTypes,
type MentionableSelectMenu,
type PartialEmoji,
type RoleSelectMenu,
type StringSelectMenu,
type TextInput,
type UserSelectMenu,
type LabelComponent,
type ContainerComponent,
type TextDisplayComponent,
type SectionComponent,
type MediaGalleryComponent,
type SeparatorComponent,
type FileComponent,
type InteractionButton,
type URLButton,
type PremiumButton,
type ThumbnailComponent,
type ModalSubmitInteractionData,
type FileUploadComponent,
} from '@projectdysnomia/dysnomia';
export type ActionRowItem = Button | StringSelectMenu | UserSelectMenu | RoleSelectMenu | MentionableSelectMenu | ChannelSelectMenu;
export const actionRow = (...components: ActionRowItem[]): ActionRow => ({
type: Constants.ComponentTypes.ACTION_ROW,
components: components.filter((c) => c),
});
export enum ButtonStyle {
PRIMARY = 1,
SECONDARY = 2,
SUCCESS = 3,
DANGER = 4,
}
export interface ButtonOptions {
style?: ButtonStyle;
emoji?: PartialEmoji;
disabled?: boolean;
}
export const button = (label: string, custom_id: string, options?: ButtonOptions): InteractionButton => ({
type: Constants.ComponentTypes.BUTTON,
style: options?.style ?? Constants.ButtonStyles.PRIMARY,
label,
custom_id,
...options,
});
export interface URLButtonOptions {
emoji?: PartialEmoji;
disabled?: boolean;
}
export const urlButton = (label: string, url: string, options?: URLButtonOptions): URLButton => ({
type: Constants.ComponentTypes.BUTTON,
style: Constants.ButtonStyles.LINK,
label,
url,
...options,
});
export interface PremiumButtonOptions {
emoji?: PartialEmoji;
disabled?: boolean;
}
export const premiumButton = (sku_id: string, options?: PremiumButtonOptions): PremiumButton => ({
type: Constants.ComponentTypes.BUTTON,
style: Constants.ButtonStyles.PREMIUM,
sku_id,
...options,
});
export interface StringSelectOpts {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean; // Note: not actually a property of StringSelectMenu, but useful for modals
}
export interface StringSelectOption {
label: string;
value: string;
description?: string;
emoji?: PartialEmoji;
default?: boolean;
}
export const option = (option: StringSelectOption): StringSelectOption => option;
export interface StringSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean;
}
export const stringSelect = (custom_id: string, selectOpts: StringSelectOpts, ...options: StringSelectOption[]): StringSelectMenu => ({
type: Constants.ComponentTypes.STRING_SELECT,
custom_id,
options,
placeholder: selectOpts.placeholder,
min_values: selectOpts.min_values ?? 1,
max_values: selectOpts.max_values ?? 1,
disabled: selectOpts.disabled ?? false,
required: selectOpts.required ?? false, // Note: not actually a property of StringSelectMenu, but useful for modals
});
export interface InputOptions {
isParagraph?: boolean;
label?: string;
min_length?: number;
max_length?: number;
required?: boolean;
value?: string;
placeholder?: string;
}
export const input = (custom_id: string, options?: InputOptions): TextInput => ({
type: Constants.ComponentTypes.TEXT_INPUT,
custom_id,
style: options.isParagraph ? Constants.TextInputStyles.PARAGRAPH : Constants.TextInputStyles.SHORT,
label: options?.label,
min_length: options?.min_length ?? 0,
max_length: options?.max_length ?? 4000,
required: options?.required ?? false,
value: options?.value,
placeholder: options?.placeholder,
});
export interface UserSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean; // if on a modal
default_values?: Array<{ id: string; type: 'user' }>;
}
export const userSelect = (custom_id: string, options?: UserSelectOptions): UserSelectMenu => ({
type: Constants.ComponentTypes.USER_SELECT,
custom_id,
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface RoleSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean; // if on a modal
default_values?: Array<{ id: string; type: 'role' }>;
}
export const roleSelect = (custom_id: string, options?: RoleSelectOptions): RoleSelectMenu => ({
type: Constants.ComponentTypes.ROLE_SELECT,
custom_id,
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface MentionableSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean; // if on a modal
default_values?: Array<{ id: string; type: 'user' | 'role' }>;
}
export const mentionableSelect = (custom_id: string, options?: MentionableSelectOptions): MentionableSelectMenu => ({
type: Constants.ComponentTypes.MENTIONABLE_SELECT,
custom_id,
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface ChannelSelectOptions {
channel_types?: GuildChannelTypes[];
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean; // if on a modal
default_values?: Array<{ id: string; type: 'channel' }>;
}
export const channelSelect = (custom_id: string, options?: ChannelSelectOptions): ChannelSelectMenu => ({
type: Constants.ComponentTypes.CHANNEL_SELECT,
custom_id,
channel_types: options?.channel_types ?? [],
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface SectionOptions {
components: Array<TextDisplayComponent>;
accessory: Button | ThumbnailComponent;
}
export const section = (accessory: Button | ThumbnailComponent, ...components: Array<TextDisplayComponent>): SectionComponent => ({
type: Constants.ComponentTypes.SECTION,
accessory,
components: components.filter((c) => c),
});
/**
* Creates a text display component where the text will be displayed similar to a message: supports markdown
* @param content The text content to display.
* @returns The created text display component.
*/
export const text = (content: string) => ({
type: Constants.ComponentTypes.TEXT_DISPLAY,
content,
});
export interface ThumbnailOptions {
media: {
url: string; // Supports arbitrary urls and attachment://<filename> references
};
description?: string;
spoiler?: boolean;
}
export const thumbnail = (url: string, description?: string, spoiler?: boolean): ThumbnailComponent => ({
type: Constants.ComponentTypes.THUMBNAIL,
media: {
url,
},
description,
spoiler,
});
export interface MediaItem {
url: string; // Supports arbitrary urls and attachment://<filename> references
description?: string;
spoiler?: boolean;
}
export const gallery = (...items: MediaItem[]): MediaGalleryComponent => ({
type: Constants.ComponentTypes.MEDIA_GALLERY,
items: items.map((item) => ({
type: Constants.ComponentTypes.FILE,
media: { url: item.url },
description: item.description,
spoiler: item.spoiler,
})),
});
export interface FileOptions {
url: string; // Supports only attachment://<filename> references
spoiler?: boolean;
}
export const file = (url: string, spoiler?: boolean): FileComponent => ({
type: Constants.ComponentTypes.FILE,
file: {
url,
},
spoiler,
});
export enum Padding {
SMALL = 1,
LARGE = 2,
}
export interface SeparatorOptions {
divider?: boolean;
spacing?: Padding;
}
export const separator = (spacing?: Padding, divider?: boolean): SeparatorComponent => ({
type: Constants.ComponentTypes.SEPARATOR,
divider,
spacing: spacing ?? Padding.SMALL,
});
export interface ContainerOptions {
accent_color?: number;
spoiler?: boolean;
}
export type ContainerItems =
| ActionRow
| TextDisplayComponent
| SectionComponent
| MediaGalleryComponent
| SeparatorComponent
| FileComponent;
export const container = (options: ContainerOptions, ...components: ContainerItems[]): ContainerComponent => ({
type: Constants.ComponentTypes.CONTAINER,
...options,
components: components.filter((c) => c),
});
// Modals
export interface LabelOptions {
label: string;
description?: string;
}
export const label = (options: LabelOptions, component: LabelComponent['component']): LabelComponent => ({
type: Constants.ComponentTypes.LABEL,
label: options.label,
description: options.description,
component,
});
export const modal = (
options: { custom_id?: string; title?: string },
...components: Array<LabelComponent | ActionRow | TextDisplayComponent>
): ModalSubmitInteractionData =>
({
type: 9 as any, // Modal type
custom_id: options.custom_id ?? '',
title: options.title ?? '',
components: components.filter((c) => c),
} as any);
export interface ComponentsV2Options {
ephemeral?: boolean;
}
export const componentsV2 = (
options: ComponentsV2Options,
...components: Array<
| ContainerComponent
| ActionRow
| TextDisplayComponent
| Button
| SectionComponent
| MediaGalleryComponent
| SeparatorComponent
| FileComponent
| UserSelectMenu
| RoleSelectMenu
| MentionableSelectMenu
| ChannelSelectMenu
| StringSelectMenu
>
) => ({
flags: Constants.MessageFlags.IS_COMPONENTS_V2 | (options.ephemeral ? Constants.MessageFlags.EPHEMERAL : 0),
components: components.filter((c) => c),
});

View File

@@ -0,0 +1,5 @@
export * from './helpers';
export * from './builders';
import * as components from './builders';
export { components };

View File

@@ -1,5 +1,5 @@
import { Client as DJSClient } from '@projectdysnomia/dysnomia';
import kv, { asyncKV } from '@/util/kv.js';
import kv, { asyncKV } from '@star-kitten/util/kv.js';
import type { KVStore } from './kv-store.type.ts.ts';
import type { Cache } from './cache.type.ts';
import { registerCommands } from '../commands/handle-commands.ts';

View File

@@ -1,7 +1,6 @@
export * from './constants';
export * from './commands';
export * from './core';
export * from './jsx';
export * from './components';
export * from './pages';
export * from './types';

View File

@@ -1,6 +1,5 @@
import { Constants, type InteractionContentEdit, type InteractionModalContent } from '@projectdysnomia/dysnomia';
import type { CommandContext, ExecutableInteraction } from '../types';
import type { StarKittenElement } from '../jsx';
export enum PageType {
MODAL = 'modal',
@@ -15,8 +14,8 @@ export interface Page<T> {
render: (
ctx: PageContext<T>,
) =>
| (InteractionModalContent | InteractionContentEdit | StarKittenElement)
| Promise<InteractionModalContent | InteractionContentEdit | StarKittenElement>;
| (InteractionModalContent | InteractionContentEdit)
| Promise<InteractionModalContent | InteractionContentEdit>;
}
export interface PagesOptions<T> {
@@ -132,7 +131,7 @@ export async function usePages<T>(options: PagesOptions<T>, interaction: Executa
const content = isPromise(maybePromise) ? await maybePromise : maybePromise;
return await pagesInteraction.createFollowup({
flags,
...wrapJSXContent(content),
...content as any,
});
}
@@ -140,7 +139,7 @@ export async function usePages<T>(options: PagesOptions<T>, interaction: Executa
await pagesInteraction.deferUpdate();
const maybePromise = page.render(pageContext);
const content = isPromise(maybePromise) ? await maybePromise : maybePromise;
return await pagesInteraction.editMessage(pageState.messageId, wrapJSXContent(content));
return await pagesInteraction.editMessage(pageState.messageId, content as any);
}
{
@@ -149,7 +148,7 @@ export async function usePages<T>(options: PagesOptions<T>, interaction: Executa
const content = isPromise(maybePromise) ? await maybePromise : maybePromise;
const message = await pagesInteraction.createFollowup({
flags: getFlags(options),
...wrapJSXContent(content),
...content as any,
});
pageState.messageId = message.id;
pageState.channelId = message.channel?.id;
@@ -161,9 +160,3 @@ function isPromise<T>(value: T | Promise<T>): value is Promise<T> {
return typeof (value as Promise<T>)?.then === 'function';
}
function wrapJSXContent(content: any) {
if ('type' in content) {
return { components: [content] };
}
return content;
}

View File

@@ -1,5 +1,5 @@
import type { PartialEmoji } from '@projectdysnomia/dysnomia';
import { actionRow, button, gallery, type ButtonOptions, type ContainerItems } from '@/discord/components';
import { actionRow, button, gallery, type ButtonOptions, type ContainerItems } from '@/components';
import type { PageContext } from './pages';
export function getSubrouteKey(prefix: string, subroutes: string[]) {

View File

@@ -1,8 +1,8 @@
import type { ChatInputApplicationCommandStructure, ApplicationCommandStructure } from '@projectdysnomia/dysnomia';
import type { ExecutableInteraction } from './interaction.type';
import type { Cache } from '@/discord/core/cache.type';
import type { KVStore } from '@/discord/core/kv-store.type.ts';
import type { Client } from '@/discord/core/client';
import type { Cache } from '@/core/cache.type';
import type { KVStore } from '@/core/kv-store.type.ts';
import type { Client } from '@/core/client';
export interface CommandState<T = any> {
id: string; // unique id for this command instance

View File

@@ -1,5 +1,4 @@
import type Dysnomia from '@projectdysnomia/dysnomia';
import type { StarKittenElement } from '../jsx';
export type Interaction = CommandInteraction | ModalSubmitInteraction | ComponentInteraction | AutocompleteInteraction | PingInteraction;
@@ -14,10 +13,6 @@ export interface InteractionAugments {
isAutocomplete: () => this is Dysnomia.AutocompleteInteraction;
isPing: () => this is Dysnomia.PingInteraction;
isExecutable: () => this is ExecutableInteraction;
createJSXMessage: (component: StarKittenElement) => Promise<Dysnomia.Message>;
editJSXMessage: (messageID: string, component: StarKittenElement) => Promise<Dysnomia.Message>;
createJSXFollowup: (component: StarKittenElement) => Promise<Dysnomia.Message>;
createJSXModal: (component: StarKittenElement) => Promise<void>;
}
export type CommandInteraction = Dysnomia.CommandInteraction & InteractionAugments;

View File

@@ -0,0 +1,23 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"strict": false,
"noImplicitAny": false,
"skipLibCheck": true,
"lib": ["ESNext"],
"typeRoots": ["src/types", "./node_modules/@types"],
"paths": {
"@/*": ["./src/*"],
},
"emitDeclarationOnly": true,
"noEmit": false,
"noEmitOnError": false,
"declaration": true,
"outDir": "dist/types",
"rootDir": ".",
"allowImportingTsExtensions": true
},
"include": ["src", "fixtures"],
"exclude": ["node_modules", "build", "**/*.test.ts"]
}

View File

@@ -0,0 +1,19 @@
import { defineConfig } from 'tsdown';
export default defineConfig([
{
entry: [
'./src/index.ts',
'./src/commands/index.ts',
'./src/components/index.ts',
'./src/pages/index.ts',
'./src/common/index.ts',
],
platform: 'node',
dts: true,
minify: false,
sourcemap: true,
unbundle: true,
external: ['bun:sqlite', 'bun'],
},
]);

View File

@@ -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:BL9a2Dle847wiqvjnB1ryC8L5vJy0pn/5BZJdEMSWBIzcxPN3MhXoHsQ4KQcRXW4xuWLpHq00aAH5kNda3Bu7kPPbnltvhcYoqQSz3giItL8th1T/qm4zc+gM7lNHl/+5G0xJFE="
PORT="encrypted:BKVA322kVuBfcDbptjVrvp9O8+1w0iCNT7Sv777MfCuLHTdJdUZr5jZbcKBD8yxPGpkc6sNdB4j6n2flC5NwExHdO/mdGD1VEBpdcZQCcSO7MYOYt1j0pfLNkvEqpuHnD1tkxT8="
NODE_ENV="encrypted:BLNNdnKOW0f8d614oe1ur73es5z/2MhyJIXmeMNvU/VlOBL1dg/mT4vHosXG908eCyM2Nx1v7GxAfXmdpVSzWltfODwJzPw/lPZV3JFB2yIt4f8zJIHvkHKPMFLupK9bDUUS2m63uvZ4IE0v"
LOG_LEVEL="encrypted:BOZXADbkv9vkzwx7/JC/EcBC00W5li6rbluRVGwy5t0fIj2WaoWXkwlTmHf5NiczQLPCgB+HD6LJxAeef8uEMgN0hwDCBXVGl53HWwDjkmxmmDAdfBmOeldJhDQbFNXumyrmKIOu"
BASE_URL="encrypted:BFjoUMvbNCwqcd8j74dNe+4MZn1STXKTVq8LoP+CPuunNfL9QQkjyR+ryH0rgOH0W0LVLQBszEEpEiFFOohq3/uZlN/UTsqkQXntINvBMvT2S2dTTTafB4jgSoNrS6VDSx3mipO15IzQZ/QlADQrqVI5Y+iB7Y2lAx7M"
EVE_CLIENT_ID="encrypted:BPpft3gC8xuWAg5SzhnoG009FhlJ9K65Ku7QoRrjZFAm7VaR2WDbH7lPMXoXY1/tu2sFln2+iCA0J8VMwqnvmHgocQtE2qPmRHdZUUSuGph76uIU4CNaIRICpXyn3jOfPIjsyBXcsKrMwLEdfeEwkxazDJ8ZAw/r+WHre9xr+iwK"
EVE_CLIENT_SECRET="encrypted:BNXhx7jO6z2IfGU0TLUxMnONUXYWlQvM+RxUYDuOb+Tsncr32aJ4j6pFMHMeUYcqrQIogads6GDNb73trhk9XvpPu8kBgrksdkCtErp47u5++6yRGLkqTmkdpZeja2KCaGx0Bcu4EfP5DS/H9Mc6nvb6+wctj8s/w2MguF6O5gg/2LNDNT3tbrI="
EVE_CALLBACK_URL="encrypted:BPzYFYlfnfLhPQuwRyUZs3F3wxjy6TEHQKNjbu+XnztSS6rJh8eAuW0ThYxee8lBAzLSGgKmjIt1PkYljnTekrR2CKyp3hMhag6W08tzxJDs4qyAsi+X91mEHXrd+ekmr4d5iAZu+sr/rHiK9jkzAsVWA8e8ttAVtW5Us7sb6ovZ1bjWM20oXk+4e5Gg"
ESI_USER_AGENT="encrypted:BI/g/h+eCwT468Io0A9DhVU+z1iWiJwOI75mNDqZMJB1LWyhd/+pneYlcEAJJEsAPP4+YN66qAZ/8PzgH6rVcXS5H27FqwC+/mJFsMfC9jAkFDl38nxDogpKJUAimpG53ERM7LegMMcxAsnr085JduloijL5ylCwEwpiewzWwN+xqOOcQrW6UcQgsgbyonIlqaYstN4NdplvdSC2zvrYN5OnToEoQ2YFiAtU7dTxSx0="
DISCORD_APP_ID="encrypted:BIiCdiy6ypeM3LK9pvkqoNIn0Js7XS5rUQqBNzZcywm9eH21gSit2rb+cwYGBGzYmkv/xG6Zf/CnUA7STrtRFfOD57vF46DVam0now4IVrTWr7X3CZLgBa0TBJgph72rTSTg8HKz8OheH/y7u730uN0OLhw="
DISCORD_APP_SECRET="encrypted:BHNfy9BO/85OpyEQgehnwAByFz0jyE3b/YiK69aVFFKotBjeh9rj/0eg2/LsJvPsAHW3NDHmSWIvo+0gqTMuFbbA52wyLk4HeZmX5yNV6sQfGLitwJVCiyfXWpW81dkwTWF4bPNu/zTDlN7hJVuOTz9o6QN5WRSlbCiuP3n7Ojk8"
DISCORD_PUBLIC_KEY="encrypted:BMzCllTpNrIQy9DkWGqE6tW7XxIEdui58eV5PqmMtwizEptjZVGp+IRtin9SUXBOtugzUUn51KP4+8AQauyFP4/oiEpiCa04lPLy2OF0pTzF61zC6jBb+gGL6fbvA2DWORXkR98sK6IAO5+OaGXy1SnHU1qjM7J5GFBaAywdPmDdLpuD4zJLvXggpooepaHHKer4ViyBAev9QU2Zb9/Ants="
DISCORD_BOT_TOKEN="encrypted:BKJPnAvriqdaKV5eirAAs6TgtmtJMj7ZMjf8Y8sbD+T3WK/tbh8mgNNlyPk+ztre5mQD46vuWMa9F9uYCZcBmlk3XcRiYFwtyIGeaUPw2hT5kswk+jcaj66HKTknF09KwOhjvg3O2394d9p0jNFsx6ioT1ddzoSEKcQkyeBuJAsOTGnPVQKkfUNCRCWXMf6C3pxqVmFP/azfFFQNy8yevXnhZU97Zk9Ohg=="
DISCORD_TEST_GUILD_ID="encrypted:BP+e5/IUX1gJP+/ZtG1LxwWcLeYsYwhM6boSbJv1YDhg3FF/qDM6A3OXsneSBP3PTDwW7e14M5U0jRIMT9YemyuF9r7895fMgBnFbPfSY+WaRDruibFI6QW1RvEcTuFjoU2/kRT6s69C/I1frYEMja2rfw=="
JANICE_KEY="encrypted:BIb85AGnz4hgENGxJYhDmtEE6rEPjfBKZBtJ4KuDNtAjyIEI4kRKQNkOudVxc4BXQ6MXIqhIPIuTE6DdCN03olEfk3Fo5/FmwOr1bWF6pYFbwMxdVgjc9Cj9Lie3+tAnr3MaOfT0y+P+zUiAMWRJELpLv0TzSNhmTAF1LS+9zjUV"
PERPLEXITY_API_KEY="encrypted:BNy5gd/Ku1Dilcfc9VftzIJn5xxDCT0NapIw5i+4h1srz2nNVgDc8w6GvmdBBz/NiPut9x+7Wv5hKVuXh2N07HaO67bTugzZ7dwabQdDJGlRp8mo/TEJMIN9Pm1ztI5HZW4UGfMY4xNILHRJb+JRBNdI4GZSh13i6vyc8ym9BgOXLVTR3ZSCJwFqzeDTgR9R3NhIjUa3"
STAR_KITTEN_KV_DB_PATH="encrypted:BPZyMGyF1A+mQQ+OKNtHJLM+EGyrgkfhO6hHs1YHaoifLsMbJz2NDZmOzDg+ubPTUyjOJ+XbjcVdEFLn23QoOqKtW/WSk6WPPNAgWnWrTl+5XJI7bfGKaaZ1LR1PaB4ftPyMLFyoWVZ2iPHBP1xv"
POSTGRES_URL="encrypted:BLUN+6NZpCkc+8WvJkhB3v8sYoX88maPYzkBi2WXItAOtA2r/1a65yZThcumddnba7WzaaN7xPv0CUA6LZpszP8t59LJ2ewKNJJyi9Z5bHINoedLh57kwlu0TcGsoO/EUPS81V0FZAlxLnmuVvMn/xYxmXJWiTeQRCe4vxLSctMNWgvcWXuQivGrLFh6AMtgfi+FxtkaH5zYXIjDn1Q/U2gKTwyLxmPBrXxFnYx8aGI9Leios605d/4lugra"
NEO4J_URL="encrypted:BGphrNITLIGqF5aVoY9F2IaDVP+IRXuPvtfTRKtNqL8u5JIqkbEBpRknfB3wA2XoYiDsQ2ADfwwSuw5uvj1IWU2/ZS9AEukGJGYa6RP1dI/R0XvRAqS7O9waOH8nuH3KPaTSiEfeLqd+c+3J8MAKgpaysNp1nKGsZa6RzjhYEWxsKw3Yp4LY5d3yRloqxWsAJsd3WnH8cJWvJi0gT04PEJ73gX4TyHlPuduH37uo6os="

View 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:BK1eaaKoyGmDyiuLd+fIdaEdCGyYgLyUv5tsNcXvZ8CTMMXi7SBcNSy0K5hI0VC0ptEIX7iO/sZQcQ1K0V9gBFCoNDpuTvO+c3bFKU2nKQ0BBULlXhJNDoETmZ2p9L7KLNDFYQ6d"
PORT="encrypted:BOpbRvBpbB3ra5St/8dv2h/93aL1vu1rVbc9u/V0YZyvP6nJv88rToz2/zfyNDeqsjBA2IO4s3+v3ZtxSJ6XXwd3+or3++yGs7A8t7bGoxVRyzb0uluHh6hWx+DKdxZE7HIYAh0="
NODE_ENV="encrypted:BORW/YY5Y5EV7Lh5L3fSOfKsbyH5/ZBtMH/iuurGnJZlvFhBsZTBm5ms02dLJgFN/F1HFYskR8xLyEnnCCndK3YpdzrPYbVYSPr5EuWsVeX6DbCROi7RY1Zd27kYBSA5QjxHJOxWCIL0djY="
LOG_LEVEL="encrypted:BHAO8Gg4Y3bo9Cwi1+YOvtc0dSgYUS6+UWNTcHfWISsrv4XX1sj/T9wjEGb8Y6dCmenqPSSI0DaBaEoLXDG3S31mdhTPmMfNBWRQQFbIxrz17WtqIDN6MGiI7/6ki5qryzup6ME="
BASE_URL="encrypted:BAAw6UxbwTW7snM4pjPLGsYtZGbyOSDR9BwIo2oPDGPernDYD5YS80ffc7N1cRpLt807Vg41YoDsuj5PnwjmU9Zg7rl6cUnFKnLcvuFHNWdiULcl7bPQjHgZq1/wXwmYRt8p1DOI1nAgZIaxf58hi/c/9pUy5IpE"
DISCORD_APP_ID="encrypted:BEneta1lCVd+uZCdIEOCtilukJb8W/57BZRvSzg7a6MnFkNxqlT2CXiZH6kvO9k+s3qj8zCUdsb6vQP0BzLlmDmmFLQGMn6q/NOlYArn0WlIUz+81JvMOZnv1YH6uFnJczJugtPoBy8d9Jjvn1u6vT+6pfM="
DISCORD_APP_SECRET="encrypted:BG4R0eH2AHda/W+EkwT82dhO1UAjQZAbrXfn0+vOeNT/E3EQZ3VH/o5N1Ed3Hjg/mf90jv+ey3HBimt747dN+IBgkzkYpbs44h/TVZukSPwN5wJR14nVRwtU+ZZYX9UUsb/wjWFn7n5DCCOIs2N5Z/bqbRw4mPruKwBzJ5SD8JgD"
DISCORD_PUBLIC_KEY="encrypted:BBTLdwJa7/tbzH4bddef00+PpJN9ZKCnSKxIWLuc10oKkg5g5TRSt9cnTRZvAsWGeacKxrECyl2WjoPYYI0qqDPZv/OHRejQ1EwDpfEVJVVoL7Xdm3c5Ncs0Vog03al3gEMbc2Zs6X3Teyt4K96PybwZ/fyhqsoHwmJ0mSvafD+2ASYii3tP23TkFewtnOenlZTBblXGkLy5C9Vkvodu1Qg="
DISCORD_BOT_TOKEN="encrypted:BKIDa8iPhX1BKarlpoJOd/qhn/wLz0LsSI3joaCKWcEWpRynWAU1e1lfAA85j1EzVsX2BE2AcUbDS+FDzuacRooOgXk1fcywf2RSA+skDqdSGwNXDUQvOlptTdGyb8Eq10MkQOtUCHn50npULOCFkoGVHunzHCOtLAKzq0n758X8SOswfdWgDLQP8vtB9BF6SfkCu93bDDeZU+gJvYU3x/GwLqlaaJdF0A=="
EVE_CLIENT_ID="encrypted:BA6dxhE8M8b3pDjcXDFBPdMw8aIBd1CUlN+NqnzC9XCZUc5Y7g4n2le0NPk6BwlgbrM9iRRdxzNkfmXiopJ1C33N4t095F/qd2cHAmya05//XAEOnOjA8MYA889rA/Jn1TXY2dSrLfp8UAMFo2pdu7r8QDAXdRUWoefAW/CmD0vR"
EVE_CLIENT_SECRET="encrypted:BP7JP9rGeAWdVBashqAcGmB9v2WstOu7jJiB72jyzEK8PtSXA8eZK4MdFjwkfilZJr9k0kQcIYH4E/koKEW6BANDr5Ph8pYRz+r/lS11fsoJ1HZhujPKyuKIaIVEHxGb94FU7bJb6tAE0ozEZPZzeEZFGKJe5x9dR8qbAuD3oRC9JbidH4Lu2kQ="
EVE_CALLBACK_URL="encrypted:BBOyw5Un+FeDukVE387oHdUZ6k3rMn69WX0Wi8qVWhyW1koCKTAot5jgZA24tKq4JPI71kYb1/5Z7baZCNekfOSUQsVbuFH8g9I9KffWeIIW7AhKA0TWPZCVxGCEFIj/x54DqmXtCnd7YRxYxXaWaSVrCos7lZAR5mnoTN2hBEEQFCWOhA1cX4iP"
ESI_USER_AGENT="encrypted:BADtioS3QPCdtq+RvA90WTk61cnHPadpaeTE4JJDEaWuFygvtbouvrcbPhp6MmkXTrVb1E6Fs6dS74ocCMP+4wSfE4UuJpDos2lTo5eHp/Y65ERqdByQbAUVPQ1tf1vuqrDqBYFiM3LfiQUnulASsmhGsDUgroxPTv0g4hv+4m5/DMR4mClgn5Cb+UoguJRRptWyZi5jCvXwxQzTtRQ1NYqr8WNNMA65oiGCkw=="
AUTH_DB_PATH="encrypted:BOgXzNmmct1BCPLRgrKbrCtERDEUCxyv1gJPEQupSgVmxyjoDCZLu+7dgfMvB7AeDfy2BtelFDpT3geWg+zazyUZO2aN/EvEv56YShh6iTDFONXHWx6l+8uu+l7Dmj9saL8MCJRcTBv4AtYseu7Y4AErK4wbNAEF"
JANICE_KEY="encrypted:BKdgDHUyDpAx5e72lhaOA33hszd6OTCz5p3cDk8AkkLK4JumLtKYJSelkKeHsl+jSSpYgLbwDgS42RL5bYBOJZUXmTCSn01PlxQguoseHzjtyd4HqdRtXtK3AucQOX27ZbY0tEZFzW467suq9NpDKie2cB3mdSJrfS/CJX58VgRi"
PERPLEXITY_API_KEY="encrypted:BL8MWGlcOic+n/V0yWSWai2Pwo8lbYbjcRkD2N7uDf38ogp7dUUdye/1dNWYzjm+ICsWXi5SQNEDkSdysegsJmoUtxBtEnX0Y0GJrQZaY5MEFobSicV5sjSdlh2y1gDcRrGPmvgsDXL/ehs3PWzz2DI0J71BnsziR4CmumkzwSxqot/mtTQMJQaO3UKFXB0RL5bwCWwv"

View File

@@ -0,0 +1,15 @@
# eve-utils
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.3.5. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.

View File

@@ -0,0 +1,7 @@
[test]
coverage = true
coverageSkipTestFiles = true
coverageReporter = ["text", "lcov"]
[run]
bun = true

View File

@@ -0,0 +1,29 @@
{
"name": "@star-kitten/eve-data",
"module": "src/main.ts",
"type": "module",
"private": true,
"devDependencies": {
"@dotenvx/dotenvx": "^1.52.0",
"@types/bun": "^1.3.9"
},
"peerDependencies": {
"typescript": "^5"
},
"dependencies": {
"@duckdb/node-api": "^1.4.4-r.1",
"neo4j-driver": "^6.0.1",
"pg": "^8.18.0",
"redis": "^5.10.0"
},
"scripts": {
"link": "bun link",
"test": "bun test",
"typecheck": "tsc --noEmit",
"release": "bumpp && npm publish",
"dev": "bunx @dotenvx/dotenvx run -f .env.development -- bun run src/main.ts && tsdown --watch",
"build": "bunx @dotenvx/dotenvx run -f .env.production -- bun src/main.ts && tsdown",
"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"
}
}

View File

@@ -0,0 +1,72 @@
import fs from "fs";
import path from "path";
import { Readable } from "stream";
import { exec } from "child_process";
export async function downloadAndExtract(
url: string,
outputDir: string
): Promise<void> {
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
console.log(`Starting download from ${url}...`);
const response = await fetch(url);
if (!response.ok || !response.body)
throw new Error(`Failed to download ${url}`);
const nodeStream = Readable.fromWeb(response.body as any);
const compressedFilePath = path.join(outputDir, "archive.tar.xz");
const fileStream = fs.createWriteStream(compressedFilePath);
nodeStream.pipe(fileStream);
return new Promise((resolve, reject) => {
fileStream.on("finish", () => {
// Use native tar command to extract files
exec(
`tar -xJf ${compressedFilePath} -C ${outputDir}`,
(error, stdout, stderr) => {
if (error) {
console.error(`Extraction error: ${stderr}`);
reject(error);
} else {
console.log("Extraction complete");
// Clean up the archive file
fs.unlink(compressedFilePath, (err) => {
if (err) {
console.error(`Error removing archive: ${err.message}`);
reject(err);
} else {
console.log("Archive cleaned up");
resolve();
}
});
}
}
);
});
fileStream.on("error", (err) => {
console.error("File stream error", err);
reject(err);
});
});
}
// CLI execution (only runs when file is executed directly)
if (import.meta.main) {
const args = process.argv.slice(2);
if (args.length !== 2) {
console.error("Usage: bun run downloadAndExtract.ts <url> <outputDir>");
process.exit(1);
}
const [url, outputDir] = args;
downloadAndExtract(url, outputDir).catch((err) =>
console.error("Download failed", err)
);
}

View File

@@ -0,0 +1,26 @@
import { downloadAndExtract } from "./download-and-extract";
async function main() {
const referenceDataUrl =
"https://data.everef.net/reference-data/reference-data-latest.tar.xz";
const hoboleaksSDEUrl =
"https://data.everef.net/hoboleaks-sde/hoboleaks-sde-latest.tar.xz";
const referenceDataOutputDir = "./data/reference-data";
const hoboleaksSDEOutputDir = "./data/hoboleaks-sde";
try {
await downloadAndExtract(referenceDataUrl, referenceDataOutputDir);
console.log("Reference data downloaded and extracted successfully");
} catch (err) {
console.error("Error during download and extraction", err);
}
try {
await downloadAndExtract(hoboleaksSDEUrl, hoboleaksSDEOutputDir);
console.log("Hoboleaks SDE downloaded and extracted successfully");
} catch (err) {
console.error("Error during download and extraction", err);
}
}
main();

View File

@@ -5,9 +5,7 @@
"strict": false,
"noImplicitAny": false,
"skipLibCheck": true,
"jsx": "react-jsx",
"jsxImportSource": "@star-kitten/lib/discord",
"lib": ["ESNext", "DOM"],
"lib": ["ESNext"],
"typeRoots": ["src/types", "./node_modules/@types"],
"paths": {
"@/*": ["./src/*"],
@@ -22,6 +20,6 @@
"rootDir": ".",
"allowImportingTsExtensions": true
},
"include": ["src", "types", "src/jsx/types.d.ts", "scripts"],
"include": ["src"],
"exclude": ["node_modules", "build", "**/*.test.ts"]
}

View File

@@ -0,0 +1,22 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"strict": false,
"noImplicitAny": false,
"skipLibCheck": true,
"lib": ["ESNext"],
"typeRoots": ["src/types", "./node_modules/@types"],
"paths": {
"@/*": ["./src/*"]
},
"emitDeclarationOnly": true,
"noEmit": false,
"noEmitOnError": false,
"declaration": true,
"rootDir": "src",
"allowImportingTsExtensions": true
},
"include": ["src"],
"exclude": ["node_modules", "build", "**/*.test.ts"]
}

View File

@@ -0,0 +1,16 @@
import { defineConfig } from 'tsdown';
export default defineConfig([
{
entry: [
'./src/**/*.ts',
'!./src/**/*.test.ts',
],
platform: 'node',
dts: true,
minify: false,
sourcemap: true,
unbundle: true,
external: ['bun:sqlite', 'bun'],
},
]);

View File

@@ -0,0 +1,23 @@
# tsdown-starter
A starter for creating a TypeScript package.
## Development
- Install dependencies:
```bash
npm install
```
- Run the unit tests:
```bash
npm run test
```
- Build the library:
```bash
npm run build
```

View File

@@ -0,0 +1,59 @@
{
"name": "@star-kitten/eve-discord",
"version": "0.0.1",
"description": "Star Kitten EVE Discord Library.",
"type": "module",
"license": "MIT",
"homepage": "https://git.f302.me/jb/star-kitten#readme",
"bugs": {
"url": "https://git.f302.me/jb/star-kitten/issues"
},
"repository": {
"type": "git",
"url": "git+https://git.f302.me/jb/star-kitten.git"
},
"author": "JB <j-b-3.deviate267@passmail.net>",
"files": [
"dist"
],
"main": "./dist/index.js",
"module": "./dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index*.d.ts"
},
"./commands/*.js": {
"require": "./dist/commands/*.js",
"import": "./dist/commands/*.js",
"types": "./dist/types/commands/*.d.ts"
},
"./package.json": "./package.json"
},
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev"
},
"devDependencies": {
"@types/bun": "^1.3.5",
"prettier-plugin-multiline-arrays": "^4.0.3",
"tsdown": "^0.14.2",
"typescript": "beta"
},
"dependencies": {
"@star-kitten/util": "link:@star-kitten/util",
"@star-kitten/eve": "link:@star-kitten/eve",
"@star-kitten/discord": "link:@star-kitten/discord"
},
"scripts": {
"build": "tsdown",
"dev": "tsdown --watch",
"link": "bun link",
"test": "bun test",
"typecheck": "tsc --noEmit",
"release": "bumpp && npm publish"
}
}

View File

@@ -0,0 +1,136 @@
import { appraiseItems, type Appraisal } from '@star-kitten/eve/third-party/janice.js';
import {
componentHasIdPrefix,
isModalLabel,
isModalSelect,
isModalTextInput,
} from '@star-kitten/discord/components';
import { createChatCommand, type CommandContext, type ExecutableInteraction, components as c, type PageContext, ButtonStyle } from '@star-kitten/discord';
import { PageType, usePages } from '@star-kitten/discord/pages';
import { Constants } from '@projectdysnomia/dysnomia';
import { markets } from '@star-kitten/eve/third-party/janice.js';
import { formatNumberToShortForm } from '@star-kitten/util';
export interface AppraisalState {
appraisal?: Appraisal;
}
function renderAppraisalModal() {
return c.modal({ custom_id: 'appraisalResult', title: 'Appraise Items' },
c.label({ label: 'Select a market' },
c.stringSelect('market', { placeholder: 'Select a market', min_values: 1, max_values: 1 },
...markets.map((m) => c.option({ label: m.name, value: m.id.toString(), default: m.id === 2 }))
),
),
c.label({ label: 'Enter items to appraise' },
c.input('input',{
isParagraph: true,
placeholder: `e.g. Tritanium 22222\nPyerite 8000\nMexallon 2444`,
}),
),
);
}
function renderAppraisalFollowUp(appraisal: Appraisal,
pageCtx: PageContext<AppraisalState>,
interaction: ExecutableInteraction
) {
const content = `
# [Appraisal ${appraisal.id} @ ${appraisal.market.name}](https://janice.e-351.com/a/${appraisal.id})
### Buy: \`${formatNumberToShortForm(appraisal.effectivePrices.totalBuyPrice)}\` ISK
### Split: \`${formatNumberToShortForm(appraisal.effectivePrices.totalSplitPrice)}\` ISK
### Sell: \`${formatNumberToShortForm(appraisal.effectivePrices.totalSellPrice)}\` ISK
-# Volume: ${formatNumberToShortForm(appraisal.totalPackagedVolume)}
\`\`\`
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}
`;
return c.container({ accent: 0x1da57a },
c.text(content),
pageCtx.state.currentPage !== 'share' && c.actionRow(
c.button({ custom_id: 'share', label: 'Share in Channel', disabled: !interaction.channel?.id, style: ButtonStyle.PRIMARY }),
)
);
}
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
return await usePages<AppraisalState>(
{
pages: {
appraiseModal: {
key: 'appraiseModal',
type: PageType.MODAL,
render: () => renderAppraisalModal(),
},
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 renderAppraisalFollowUp(appraisal, pageCtx, interaction);
},
},
share: {
key: 'share',
type: PageType.FOLLOWUP,
followUpFlags: Constants.MessageFlags.IS_COMPONENTS_V2,
render: (pageCtx) => {
const rendered = renderAppraisalFollowUp(pageCtx.state.data.appraisal!, pageCtx, interaction);
return rendered;
},
},
},
initialPage: 'appraiseModal',
timeout: 300, // 5 minutes
ephemeral: true,
},
interaction,
ctx,
);
}
export default createChatCommand({
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': '评估您宇宙垃圾的价值',
},
}, execute);

View File

@@ -0,0 +1,78 @@
import {
type ExecutableInteraction,
type CommandContext,
createChatCommand,
components,
options,
} from '@star-kitten/discord';
async function execute(interaction: ExecutableInteraction, context: CommandContext) {
if (!interaction.isApplicationCommand()) return;
const days = (interaction.data.options.find((option) => option.name === 'days')?.value || 0) as number;
const hours = (interaction.data.options.find((option) => option.name === 'hours')?.value || 0) as number;
const minutes = (interaction.data.options.find((option) => option.name === 'minutes')?.value || 0) as number;
const ephemeral = !(interaction.data.options.find((option) => option.name === 'public')?.value || false) as boolean;
const totalSeconds = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60;
const now = new Date();
const futureTime = new Date(now.getTime() + totalSeconds * 1000);
const eveTime = futureTime.toISOString().split('T')[1].split('.')[0];
const eveDate = futureTime.toLocaleDateString(interaction.locale, {
timeZone: 'UTC',
year: 'numeric',
month: 'long',
day: '2-digit',
weekday: 'long',
});
const discordTimestamp = Math.floor(futureTime.getTime() / 1000);
const timestampCodes = [
`<t:${discordTimestamp}:f>`, // Full date and time
`<t:${discordTimestamp}:F>`, // Long date and time
`<t:${discordTimestamp}:T>`, // 24-hour time
`<t:${discordTimestamp}:R>`, // Relative time
];
const textContent = `In ${days ? `**${days}** day${days !== 1 ? 's' : ''}, ` : ''}${hours ? `**${hours}** hour${hours !== 1 ? 's' : ''}, ` : ''}${minutes ? `**${minutes}** minute${minutes !== 1 ? 's' : ''} ` : ''}from now, the EVE time will be:
${eveDate} at ${eveTime}
### Discord timestamp codes:
${timestampCodes.map((code) => `${code}\n\`\`\`${code}\`\`\``).join('\n')}
`;
interaction.createMessage(
components.componentsV2({ ephemeral }, components.container({}, components.text(textContent))),
);
}
export default createChatCommand(
{
name: 'time_from_now',
description: 'Calculate time from now in EVE format with Discord timestamp codes',
options: [
options.numberOption({
name: 'days',
description: 'Number of days to add',
required: false,
}),
options.numberOption({
name: 'hours',
description: 'Number of hours to add',
required: false,
}),
options.numberOption({
name: 'minutes',
description: 'Number of minutes to add',
required: false,
}),
options.booleanOption({
name: 'public',
description: 'Whether the response should be public (visible to everyone)',
required: false,
}),
],
},
execute,
);

View File

@@ -1,13 +1,44 @@
import { Constants, type ChatInputApplicationCommandStructure } from '@projectdysnomia/dysnomia';
import {
type ExecutableInteraction,
type CommandContext,
isApplicationCommand,
Locale,
} from '@star-kitten/lib/discord';
componentsV2,
container,
text,
createChatCommand,
} from '@star-kitten/discord';
const definition: ChatInputApplicationCommandStructure = {
type: Constants.ApplicationCommandTypes.CHAT_INPUT,
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时间',
};
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
if (!interaction.isApplicationCommand()) return;
const now = new Date();
const eveTime = now.toISOString().split('T')[1].split('.')[0];
const eveDate = now.toLocaleDateString(interaction.locale, {
timeZone: 'UTC',
year: 'numeric',
month: 'long',
day: '2-digit',
weekday: 'long',
});
interaction.createMessage(componentsV2({}, container({}, text(`### ${eveTimeText[interaction.locale] || eveTimeText[Locale.EN_US]}
${eveTime}
${eveDate}`))));
}
export default createChatCommand({
name: 'time',
nameLocalizations: {
[Locale.DE]: 'zeit',
@@ -28,51 +59,4 @@ const definition: ChatInputApplicationCommandStructure = {
[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,
};
}, execute);

View File

@@ -0,0 +1,3 @@
export default function() {
console.log('Hello from eve-discord!');
}

View File

@@ -0,0 +1,21 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"strict": false,
"noImplicitAny": false,
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"],
"@data/*": ["./data/*"],
},
"emitDeclarationOnly": true,
"noEmit": false,
"noEmitOnError": false,
"declaration": true,
"rootDir": ".",
"allowImportingTsExtensions": true
},
"include": ["src"],
"exclude": ["node_modules", "build", "**/*.test.ts"]
}

View File

@@ -0,0 +1,16 @@
import { defineConfig } from 'tsdown';
export default defineConfig([
{
entry: [
'./src/**/*.ts',
'!./src/**/*.test.ts',
],
platform: 'node',
dts: true,
minify: false,
sourcemap: true,
unbundle: true,
external: ['bun:sqlite', 'bun'],
},
]);

23
packages/eve/README.md Normal file
View File

@@ -0,0 +1,23 @@
# tsdown-starter
A starter for creating a TypeScript package.
## Development
- Install dependencies:
```bash
npm install
```
- Run the unit tests:
```bash
npm run test
```
- Build the library:
```bash
npm run build
```

115
packages/eve/package.json Normal file
View File

@@ -0,0 +1,115 @@
{
"name": "@star-kitten/eve",
"version": "0.0.1",
"description": "Star Kitten EVE Library.",
"type": "module",
"license": "MIT",
"homepage": "https://git.f302.me/jb/star-kitten#readme",
"bugs": {
"url": "https://git.f302.me/jb/star-kitten/issues"
},
"repository": {
"type": "git",
"url": "git+https://git.f302.me/jb/star-kitten.git"
},
"author": "JB <j-b-3.deviate267@passmail.net>",
"files": [
"dist"
],
"main": "./dist/index.js",
"module": "./dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index*.d.ts"
},
"./esi": {
"import": "./dist/esi/index.js",
"types": "./dist/esi/index*.d.ts",
"require": "./dist/esi/index.js"
},
"./db": {
"import": "./dist/db/index.js",
"types": "./dist/db/index*.d.ts",
"require": "./dist/db/index.js"
},
"./ref": {
"import": "./dist/ref/index.js",
"types": "./dist/ref/index*.d.ts",
"require": "./dist/ref/index.js"
},
"./third-party/janice.js": {
"import": "./dist/third-party/janice.js",
"types": "./dist/types/third-party/janice.d.ts",
"require": "./dist/third-party/janice.js"
},
"./models": {
"import": "./dist/models/index.js",
"types": "./dist/models/index*.d.ts",
"require": "./dist/models/index.js"
},
"./data/*": "./data/*",
"./discord": {
"import": "./dist/discord/index.js",
"require": "./dist/discord/index.js",
"types": "./dist/types/discord/index.d.ts"
}
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/bun": "^1.3.5",
"@types/jsonwebtoken": "^9.0.10",
"@types/jwk-to-pem": "^2.0.3",
"@types/lodash": "^4.17.20",
"@types/node": "^22.15.17",
"@types/node-cache": "^4.2.5",
"@types/stream-chain": "^2.1.0",
"@types/stream-json": "^1.7.8",
"@vitest/coverage-v8": "^3.2.4",
"bumpp": "^10.1.0",
"drizzle-kit": "^0.31.4",
"openapi-fetch": "^0.15.0",
"openapi-typescript": "^7.10.1",
"prettier-plugin-multiline-arrays": "^4.0.3",
"tsdown": "^0.14.2",
"typescript": "beta"
},
"dependencies": {
"@star-kitten/util": "link:@star-kitten/util",
"@orama/orama": "^3.1.13",
"@oslojs/encoding": "^1.1.0",
"cron-parser": "^5.3.1",
"date-fns": "^4.1.0",
"domhandler": "^5.0.3",
"drizzle-orm": "^0.44.5",
"elysia": "^1.4.20",
"fp-filters": "^0.5.4",
"html-dom-parser": "^5.1.1",
"jsonwebtoken": "^9.0.2",
"jwk-to-pem": "^2.0.7",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"node-cache": "^5.1.2",
"stream-chain": "^3.4.0",
"stream-json": "^1.9.1",
"winston": "^3.17.0"
},
"scripts": {
"build": "tsdown",
"dev": "tsdown --watch",
"link": "bun link",
"test": "bun test",
"typecheck": "tsc --noEmit",
"release": "bumpp && npm publish",
"generate-migrations": "bunx drizzle-kit generate --dialect sqlite --schema ./src/db/schema.ts",
"migrate": "bun run ./src/db/migrate.ts",
"everef-api": "bunx openapi-typescript https://raw.githubusercontent.com/autonomouslogic/eve-ref/refs/heads/main/spec/eve-ref-api.yaml -o src/eve/everef/schema.d.ts",
"get-data": "bun run refresh:reference-data && bun run refresh:hoboleaks",
"refresh:reference-data": "bun run scripts/download-and-extract.ts https://data.everef.net/reference-data/reference-data-latest.tar.xz ./data/reference-data",
"refresh:hoboleaks": "bun run scripts/download-and-extract.ts https://data.everef.net/hoboleaks-sde/hoboleaks-sde-latest.tar.xz ./data/hoboleaks",
"static-export": "bun run scripts/export-solar-systems.ts"
}
}

Some files were not shown because too many files have changed in this diff Show More