mirror of
https://github.com/artiemis/artemis.js.git
synced 2026-02-14 02:11:55 +00:00
initial
This commit is contained in:
commit
c0da4083da
177
.gitignore
vendored
Normal file
177
.gitignore
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
# 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
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# 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
|
||||
|
||||
data/temp/*
|
||||
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# artemis
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.2.2. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||
198
bun.lock
Normal file
198
bun.lock
Normal file
@ -0,0 +1,198 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "artemis",
|
||||
"dependencies": {
|
||||
"@discordjs/core": "^2.0.1",
|
||||
"@sapphire/discord.js-utilities": "^7.3.2",
|
||||
"cheerio": "^1.0.0",
|
||||
"discord.js": "^14.17.3",
|
||||
"ky": "^1.7.4",
|
||||
"winston": "^3.17.0",
|
||||
"zod": "^3.24.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="],
|
||||
|
||||
"@dabh/diagnostics": ["@dabh/diagnostics@2.0.3", "", { "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA=="],
|
||||
|
||||
"@discordjs/builders": ["@discordjs/builders@1.10.0", "", { "dependencies": { "@discordjs/formatters": "^0.6.0", "@discordjs/util": "^1.1.1", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.37.114", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-ikVZsZP+3shmVJ5S1oM+7SveUCK3L9fTyfA8aJ7uD9cNQlTqF+3Irbk2Y22KXTb3C3RNUahRkSInClJMkHrINg=="],
|
||||
|
||||
"@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="],
|
||||
|
||||
"@discordjs/core": ["@discordjs/core@2.0.1", "", { "dependencies": { "@discordjs/rest": "^2.4.1", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^2.0.1", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.37.114" } }, "sha512-tj//rnPhdniq+Clxq0tq5wI7QTMH4IgyXkUFI32+ashIRpFJ1+J1btNG3FA74oATFu2sYy/zT9CKLWW4jp2RGQ=="],
|
||||
|
||||
"@discordjs/formatters": ["@discordjs/formatters@0.6.0", "", { "dependencies": { "discord-api-types": "^0.37.114" } }, "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw=="],
|
||||
|
||||
"@discordjs/rest": ["@discordjs/rest@2.4.2", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.37.114", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.19.8" } }, "sha512-9bOvXYLQd5IBg/kKGuEFq3cstVxAMJ6wMxO2U3wjrgO+lHv8oNCT+BBRpuzVQh7BoXKvk/gpajceGvQUiRoJ8g=="],
|
||||
|
||||
"@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="],
|
||||
|
||||
"@discordjs/ws": ["@discordjs/ws@2.0.1", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/rest": "^2.4.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@types/ws": "^8.5.12", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.37.114", "tslib": "^2.6.3", "ws": "^8.18.0" } }, "sha512-5etVbXdwThIT5+KfU+uiBh358Ql58f2vf1W9B5ZXxaZuIlpiX/VC/G0Lr/xuEcs6cL+/HZtEIUf0p9dQfEDang=="],
|
||||
|
||||
"@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="],
|
||||
|
||||
"@sapphire/discord-utilities": ["@sapphire/discord-utilities@3.4.4", "", { "dependencies": { "discord-api-types": "^0.37.114" } }, "sha512-P7bCL5U2s+U5oy9OFpC4lr4Y0bGoNrSCPu2bgxP9AaqK90hcwpoLWWKouvGgaY9tCbUZmaf8E98/SlZZoakhAQ=="],
|
||||
|
||||
"@sapphire/discord.js-utilities": ["@sapphire/discord.js-utilities@7.3.2", "", { "dependencies": { "@sapphire/discord-utilities": "^3.4.4", "@sapphire/duration": "^1.1.4", "@sapphire/utilities": "^3.18.1", "tslib": "^2.8.1" } }, "sha512-eEs6SjZghc7SlSRfirXzfJNy7sT9VxBtX32Zz33u8sxVq2X2/W6++klmDuIi6LSLrcsAlo9xWxcR+lL0pdaIEQ=="],
|
||||
|
||||
"@sapphire/duration": ["@sapphire/duration@1.1.4", "", {}, "sha512-hxtuE8HvmWcRok2A10lJ+ic8qY0oYGTTn44XmESUYJYYSVJWmqlCH1LnNYi6Ul+LRjxNUfFQEL/TJS0GJ+8kew=="],
|
||||
|
||||
"@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="],
|
||||
|
||||
"@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],
|
||||
|
||||
"@sapphire/utilities": ["@sapphire/utilities@3.18.2", "", {}, "sha512-QGLdC9+pT74Zd7aaObqn0EUfq40c4dyTL65pFnkM6WO1QYN7Yg/s4CdH+CXmx0Zcu6wcfCWILSftXPMosJHP5A=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="],
|
||||
|
||||
"@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
|
||||
|
||||
"@types/triple-beam": ["@types/triple-beam@1.3.5", "", {}, "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="],
|
||||
|
||||
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
|
||||
|
||||
"@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.6", "", {}, "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="],
|
||||
|
||||
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="],
|
||||
|
||||
"cheerio": ["cheerio@1.0.0", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "encoding-sniffer": "^0.2.0", "htmlparser2": "^9.1.0", "parse5": "^7.1.2", "parse5-htmlparser2-tree-adapter": "^7.0.0", "parse5-parser-stream": "^7.1.2", "undici": "^6.19.5", "whatwg-mimetype": "^4.0.0" } }, "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww=="],
|
||||
|
||||
"cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
|
||||
|
||||
"color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="],
|
||||
|
||||
"color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
|
||||
|
||||
"color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
||||
|
||||
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
|
||||
|
||||
"colorspace": ["colorspace@1.1.4", "", { "dependencies": { "color": "^3.1.3", "text-hex": "1.0.x" } }, "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w=="],
|
||||
|
||||
"css-select": ["css-select@5.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg=="],
|
||||
|
||||
"css-what": ["css-what@6.1.0", "", {}, "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="],
|
||||
|
||||
"discord-api-types": ["discord-api-types@0.37.119", "", {}, "sha512-WasbGFXEB+VQWXlo6IpW3oUv73Yuau1Ig4AZF/m13tXcTKnMpc/mHjpztIlz4+BM9FG9BHQkEXiPto3bKduQUg=="],
|
||||
|
||||
"discord.js": ["discord.js@14.17.3", "", { "dependencies": { "@discordjs/builders": "^1.10.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.0", "@discordjs/rest": "^2.4.2", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.0", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.37.114", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "tslib": "^2.6.3", "undici": "6.19.8" } }, "sha512-8/j8udc3CU7dz3Eqch64UaSHoJtUT6IXK4da5ixjbav4NAXJicloWswD/iwn1ImZEMoAV3LscsdO0zhBh6H+0Q=="],
|
||||
|
||||
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||
|
||||
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||
|
||||
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||
|
||||
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||
|
||||
"enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="],
|
||||
|
||||
"encoding-sniffer": ["encoding-sniffer@0.2.0", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg=="],
|
||||
|
||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="],
|
||||
|
||||
"fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="],
|
||||
|
||||
"htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
|
||||
|
||||
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="],
|
||||
|
||||
"ky": ["ky@1.7.4", "", {}, "sha512-zYEr/gh7uLW2l4su11bmQ2M9xLgQLjyvx58UyNM/6nuqyWFHPX5ktMjvpev3F8QWdjSsHUpnWew4PBCswBNuMQ=="],
|
||||
|
||||
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
||||
|
||||
"lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="],
|
||||
|
||||
"logform": ["logform@2.7.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ=="],
|
||||
|
||||
"magic-bytes.js": ["magic-bytes.js@1.10.0", "", {}, "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||
|
||||
"one-time": ["one-time@1.0.0", "", { "dependencies": { "fn.name": "1.x.x" } }, "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="],
|
||||
|
||||
"parse5": ["parse5@7.2.1", "", { "dependencies": { "entities": "^4.5.0" } }, "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ=="],
|
||||
|
||||
"parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="],
|
||||
|
||||
"parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="],
|
||||
|
||||
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
|
||||
|
||||
"stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="],
|
||||
|
||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||
|
||||
"text-hex": ["text-hex@1.0.0", "", {}, "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="],
|
||||
|
||||
"triple-beam": ["triple-beam@1.4.1", "", {}, "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="],
|
||||
|
||||
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
|
||||
"undici": ["undici@6.19.8", "", {}, "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g=="],
|
||||
|
||||
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
|
||||
|
||||
"whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
|
||||
|
||||
"winston": ["winston@3.17.0", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.9.0" } }, "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw=="],
|
||||
|
||||
"winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="],
|
||||
|
||||
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
||||
|
||||
"zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="],
|
||||
|
||||
"@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
|
||||
|
||||
"@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
|
||||
|
||||
"discord.js/@discordjs/ws": ["@discordjs/ws@1.2.0", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.4.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.37.114", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-QH5CAFe3wHDiedbO+EI3OOiyipwWd+Q6BdoFZUw/Wf2fw5Cv2fgU/9UEtJRmJa9RecI+TAhdGPadMaEIur5yJg=="],
|
||||
|
||||
"discord.js/@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
|
||||
}
|
||||
}
|
||||
27
package.json
Normal file
27
package.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "artemis",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"dev": "bun --watch src/index.ts",
|
||||
"start": "bun run src/index.ts",
|
||||
"sync": "bun run src/scripts/sync.ts",
|
||||
"test": "bun run src/scripts/test.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/core": "^2.0.1",
|
||||
"@sapphire/discord.js-utilities": "^7.3.2",
|
||||
"cheerio": "^1.0.0",
|
||||
"discord.js": "^14.17.3",
|
||||
"ky": "^1.7.4",
|
||||
"winston": "^3.17.0",
|
||||
"zod": "^3.24.1"
|
||||
}
|
||||
}
|
||||
6
src/api.ts
Normal file
6
src/api.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { env } from "./env";
|
||||
import { API } from "@discordjs/core";
|
||||
import { REST } from "discord.js";
|
||||
|
||||
const rest = new REST().setToken(env.DISCORD_TOKEN);
|
||||
export const api = new API(rest);
|
||||
130
src/client.ts
Normal file
130
src/client.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import { Client, Collection, GatewayIntentBits } from "discord.js";
|
||||
import { env } from "./env";
|
||||
import { ActivityType, API, InteractionContextType } from "@discordjs/core";
|
||||
import { api } from "./api";
|
||||
import type { Command } from "./types/command";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import { log } from "./utils/logger";
|
||||
import { pluralize } from "./utils/functions";
|
||||
import { DEV } from "./utils/constants";
|
||||
|
||||
export class ArtemisClient extends Client {
|
||||
public api: API;
|
||||
public ownerId = env.OWNER_ID;
|
||||
public commands = new Collection<string, Command>();
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.DirectMessages,
|
||||
],
|
||||
allowedMentions: {
|
||||
parse: [],
|
||||
},
|
||||
presence: {
|
||||
activities: [{ name: "🩷", type: ActivityType.Custom }],
|
||||
},
|
||||
});
|
||||
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
async setup() {
|
||||
Promise.all([this.loadCommands(), this.registerEvents()]);
|
||||
}
|
||||
|
||||
async loadCommands() {
|
||||
const commandsDir = path.join(import.meta.dir, "commands");
|
||||
const categories = await fs
|
||||
.readdir(commandsDir)
|
||||
.then((categories) =>
|
||||
categories.filter((category) => !category.includes("."))
|
||||
);
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
for (const category of categories) {
|
||||
const files = await fs
|
||||
.readdir(path.join(commandsDir, category))
|
||||
.then((files) => files.filter((file) => file.endsWith(".ts")));
|
||||
|
||||
for (const file of files) {
|
||||
promises.push(
|
||||
import(path.join(commandsDir, category, file)).then(
|
||||
({ default: command }: { default: Command }) => {
|
||||
this.commands.set(command.data.name, {
|
||||
...command,
|
||||
category,
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
log.info(`Loaded ${pluralize(this.commands.size, "application command")}`);
|
||||
}
|
||||
|
||||
async registerEvents() {
|
||||
const eventsDir = path.join(import.meta.dir, "events");
|
||||
const files = await fs
|
||||
.readdir(eventsDir)
|
||||
.then((files) => files.filter((file) => file !== "index.ts"));
|
||||
|
||||
for (const file of files) {
|
||||
const { default: event } = await import(path.join(eventsDir, file));
|
||||
this[event.once ? "once" : "on"](event.name, event.execute);
|
||||
}
|
||||
|
||||
log.info(`Registered ${pluralize(files.length, "event")}`);
|
||||
}
|
||||
|
||||
async syncCommands() {
|
||||
if (!this.commands.size) {
|
||||
log.warn("No commands were loaded, skipping registration");
|
||||
return;
|
||||
}
|
||||
|
||||
const publicCommands = this.commands
|
||||
.filter((command) => !command.isOwnerOnly)
|
||||
.map((command) => command.data.toJSON());
|
||||
const ownerCommands = this.commands
|
||||
.filter((command) => command.isOwnerOnly)
|
||||
.map((command) => command.data.toJSON());
|
||||
|
||||
let guildCount = 0;
|
||||
let globalCount = 0;
|
||||
|
||||
if (DEV) {
|
||||
await this.api.applicationCommands
|
||||
.bulkOverwriteGuildCommands(env.APPLICATION_ID, env.DEV_GUILD_ID, [
|
||||
...publicCommands,
|
||||
...ownerCommands,
|
||||
])
|
||||
.then((res) => (guildCount += res.length));
|
||||
} else {
|
||||
await this.api.applicationCommands
|
||||
.bulkOverwriteGuildCommands(
|
||||
env.APPLICATION_ID,
|
||||
env.DEV_GUILD_ID,
|
||||
ownerCommands
|
||||
)
|
||||
.then((res) => (guildCount += res.length));
|
||||
await this.api.applicationCommands
|
||||
.bulkOverwriteGlobalCommands(env.APPLICATION_ID, publicCommands)
|
||||
.then((res) => (globalCount += res.length));
|
||||
}
|
||||
|
||||
log.info(
|
||||
`Successfully synced ${guildCount} guild and ${globalCount} global application commands`
|
||||
);
|
||||
return { guildCount, globalCount };
|
||||
}
|
||||
}
|
||||
|
||||
export const client = new ArtemisClient();
|
||||
5
src/commands/index.ts
Normal file
5
src/commands/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import type { Command } from "../types/command";
|
||||
|
||||
export function defineCommand(command: Command) {
|
||||
return command;
|
||||
}
|
||||
131
src/commands/language/wiktionary.ts
Normal file
131
src/commands/language/wiktionary.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import {
|
||||
bold,
|
||||
EmbedBuilder,
|
||||
inlineCode,
|
||||
SlashCommandBuilder,
|
||||
} from "discord.js";
|
||||
import { defineCommand } from "..";
|
||||
import { getDefinitions } from "../../utils/wiktionary";
|
||||
import { stripHtml } from "../../utils/functions";
|
||||
|
||||
export default defineCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("wiktionary")
|
||||
.setDescription("Looks up a term on Wiktionary")
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("term")
|
||||
.setDescription("The term to look up")
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true)
|
||||
),
|
||||
|
||||
async autocomplete(interaction) {
|
||||
let language: string | undefined;
|
||||
let term = interaction.options.getFocused().trim();
|
||||
if (term.length < 3) {
|
||||
await interaction.respond([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = term.split(":");
|
||||
if (parsed.length === 2) {
|
||||
term = parsed[0].trim();
|
||||
language = parsed[1].trim();
|
||||
if (!language) {
|
||||
await interaction.respond([]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const definitions = await getDefinitions(term);
|
||||
if (!definitions) {
|
||||
await interaction.respond([]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (language) {
|
||||
const choices = definitions
|
||||
.filter((definition) =>
|
||||
definition.language.toLowerCase().startsWith(language.toLowerCase())
|
||||
)
|
||||
.map((definition) => ({
|
||||
name: `${term} (${definition.language})`,
|
||||
value: `:${term}:${definition.languageCode}:`,
|
||||
}))
|
||||
.slice(0, 25);
|
||||
|
||||
await interaction.respond(choices);
|
||||
return;
|
||||
}
|
||||
|
||||
const choices = definitions
|
||||
.map((definition) => ({
|
||||
name: `${term} (${definition.language})`,
|
||||
value: `:${term}:${definition.languageCode}:`,
|
||||
}))
|
||||
.slice(0, 25);
|
||||
|
||||
await interaction.respond(choices);
|
||||
},
|
||||
|
||||
async execute(interaction) {
|
||||
let term = interaction.options.getString("term", true);
|
||||
let languageCode: string | undefined;
|
||||
|
||||
const parsed = term.match(/^:(?<term>.+):(?<languageCode>.+):$/);
|
||||
if (parsed?.groups) {
|
||||
term = parsed.groups.term;
|
||||
languageCode = parsed.groups.languageCode;
|
||||
}
|
||||
|
||||
const definitions = await getDefinitions(term);
|
||||
if (!definitions) {
|
||||
await interaction.reply({
|
||||
content: "No definitions found",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const definition = languageCode
|
||||
? definitions.find((def) => def.languageCode === languageCode)
|
||||
: definitions[0];
|
||||
if (!definition) {
|
||||
await interaction.reply({
|
||||
content: "No definitions found",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let description = definition.entries
|
||||
.map((entry) => {
|
||||
const name = entry.partOfSpeech;
|
||||
const definitions = entry.definitions
|
||||
.filter((def) => def.definition)
|
||||
.map((def, i) => {
|
||||
const prefix = inlineCode(`${i + 1}.`);
|
||||
const definition = stripHtml(def.definition);
|
||||
return `${prefix} ${definition.trim()}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return `${bold(name)}\n${definitions}`;
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setAuthor({
|
||||
name: `Wiktionary - ${definition.language}`,
|
||||
iconURL:
|
||||
"https://en.wiktionary.org/static/apple-touch/wiktionary/en.png",
|
||||
url: `https://${
|
||||
definition.languageCode
|
||||
}.wiktionary.org/wiki/${encodeURIComponent(term)}`,
|
||||
})
|
||||
.setTitle(term)
|
||||
.setColor(0xfefefe)
|
||||
.setDescription(description);
|
||||
|
||||
await interaction.reply({ embeds: [embed] });
|
||||
},
|
||||
});
|
||||
19
src/commands/owner/restart.ts
Normal file
19
src/commands/owner/restart.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { MessageFlags, SlashCommandBuilder } from "discord.js";
|
||||
import { defineCommand } from "..";
|
||||
import { restart } from "../../utils/restart";
|
||||
|
||||
export default defineCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("restart")
|
||||
.setDescription("Restarts the bot"),
|
||||
isOwnerOnly: true,
|
||||
|
||||
async execute(interaction) {
|
||||
await interaction.reply({
|
||||
content: "Restarting...",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
|
||||
await restart(interaction.token);
|
||||
},
|
||||
});
|
||||
28
src/commands/owner/sync.ts
Normal file
28
src/commands/owner/sync.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { MessageFlags, SlashCommandBuilder } from "discord.js";
|
||||
import { client } from "../../client";
|
||||
import { defineCommand } from "..";
|
||||
|
||||
export default defineCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("sync")
|
||||
.setDescription("Sync application commands"),
|
||||
isOwnerOnly: true,
|
||||
|
||||
async execute(interaction) {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
const counts = await client.syncCommands();
|
||||
|
||||
if (!counts) {
|
||||
await interaction.followUp({
|
||||
content: "No commands to sync",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { guildCount, globalCount } = counts;
|
||||
|
||||
await interaction.followUp({
|
||||
content: `Successfully synced ${guildCount} guild and ${globalCount} global application commands`,
|
||||
});
|
||||
},
|
||||
});
|
||||
33
src/commands/utility/ping.ts
Normal file
33
src/commands/utility/ping.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { inlineCode, SlashCommandBuilder } from "discord.js";
|
||||
import { client } from "../../client";
|
||||
import { defineCommand } from "..";
|
||||
|
||||
export default defineCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("ping")
|
||||
.setDescription("Useful latency data"),
|
||||
|
||||
async execute(interaction) {
|
||||
if (client.ws.ping < 1) {
|
||||
await interaction.reply(
|
||||
":ping_pong: Pong!\nThe bot is still starting up, accurate latency will be available shortly."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const msg = (
|
||||
await interaction.reply({
|
||||
content: `:ping_pong: Pong!\nWebSocket latency is ${inlineCode(
|
||||
Math.round(client.ws.ping).toString()
|
||||
)} ms.`,
|
||||
withResponse: true,
|
||||
})
|
||||
).resource?.message!;
|
||||
|
||||
await msg.edit(
|
||||
`${msg.content}\nAPI roundtrip latency is ${inlineCode(
|
||||
(msg.createdTimestamp - interaction.createdTimestamp).toString()
|
||||
)}ms.`
|
||||
);
|
||||
},
|
||||
});
|
||||
14
src/env.ts
Normal file
14
src/env.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const envSchema = z.object({
|
||||
DISCORD_TOKEN: z.string(),
|
||||
OWNER_ID: z.string(),
|
||||
APPLICATION_ID: z.string(),
|
||||
NODE_ENV: z
|
||||
.enum(["development", "production"])
|
||||
.optional()
|
||||
.default("development"),
|
||||
DEV_GUILD_ID: z.string(),
|
||||
});
|
||||
|
||||
export const env = envSchema.parse(process.env);
|
||||
20
src/events/clientReady.ts
Normal file
20
src/events/clientReady.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { env } from "../env";
|
||||
import { Events } from "discord.js";
|
||||
import { log } from "../utils/logger";
|
||||
import { defineEvent } from ".";
|
||||
import { maybeSendRestarted } from "../utils/restart";
|
||||
|
||||
export default defineEvent({
|
||||
name: Events.ClientReady,
|
||||
once: true,
|
||||
|
||||
async execute(client) {
|
||||
log.info("Logged in", {
|
||||
tag: client.user.tag,
|
||||
id: client.user.id,
|
||||
env: env.NODE_ENV,
|
||||
});
|
||||
|
||||
await maybeSendRestarted();
|
||||
},
|
||||
});
|
||||
6
src/events/index.ts
Normal file
6
src/events/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type { ClientEvents } from "discord.js";
|
||||
import type { Event } from "../types/event";
|
||||
|
||||
export function defineEvent<E extends keyof ClientEvents>(event: Event<E>) {
|
||||
return event;
|
||||
}
|
||||
81
src/events/interactionCreate.ts
Normal file
81
src/events/interactionCreate.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import {
|
||||
AutocompleteInteraction,
|
||||
ChatInputCommandInteraction,
|
||||
Events,
|
||||
MessageFlags,
|
||||
} from "discord.js";
|
||||
import { client } from "../client";
|
||||
import { log } from "../utils/logger";
|
||||
import { defineEvent } from ".";
|
||||
|
||||
const running = new Map<string, number>();
|
||||
const getRunning = (command: string) => running.get(command) ?? 0;
|
||||
|
||||
export default defineEvent({
|
||||
name: Events.InteractionCreate,
|
||||
|
||||
async execute(interaction) {
|
||||
if (interaction.isChatInputCommand()) {
|
||||
await handleChatInputCommand(interaction);
|
||||
} else if (interaction.isAutocomplete()) {
|
||||
await handleAutocomplete(interaction);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
async function handleChatInputCommand(
|
||||
interaction: ChatInputCommandInteraction
|
||||
) {
|
||||
const command = client.commands.get(interaction.commandName);
|
||||
if (!command) return;
|
||||
|
||||
if (command.isOwnerOnly && interaction.user.id !== client.ownerId) {
|
||||
await interaction.reply({
|
||||
content: "You do not have permission to use this command!",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.maxConcurrency) {
|
||||
if (getRunning(command.data.name) >= command.maxConcurrency) {
|
||||
await interaction.reply({
|
||||
content: `This command can only be run ${command.maxConcurrency} times at a time.`,
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
return;
|
||||
}
|
||||
running.set(command.data.name, getRunning(command.data.name) + 1);
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
await interaction[
|
||||
interaction.replied || interaction.deferred ? "followUp" : "reply"
|
||||
]({
|
||||
content: "There was an error while executing this command!",
|
||||
});
|
||||
} finally {
|
||||
if (command.maxConcurrency) {
|
||||
running.set(
|
||||
command.data.name,
|
||||
Math.max(0, getRunning(command.data.name) - 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAutocomplete(interaction: AutocompleteInteraction) {
|
||||
const command = client.commands.get(interaction.commandName);
|
||||
if (!command) return;
|
||||
|
||||
if (command.autocomplete) {
|
||||
try {
|
||||
await command.autocomplete(interaction);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/index.ts
Normal file
5
src/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { env } from "./env";
|
||||
import { client } from "./client";
|
||||
|
||||
await client.setup();
|
||||
client.login(env.DISCORD_TOKEN);
|
||||
9
src/scripts/sync.ts
Normal file
9
src/scripts/sync.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { client } from "../client";
|
||||
import { log } from "../utils/logger";
|
||||
|
||||
async function main() {
|
||||
await client.loadCommands();
|
||||
await client.syncCommands();
|
||||
}
|
||||
|
||||
main().catch(log.error);
|
||||
7
src/scripts/test.ts
Normal file
7
src/scripts/test.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { getDefinitions } from "../utils/wiktionary";
|
||||
|
||||
const definitions = await getDefinitions("appl");
|
||||
|
||||
if (!definitions) process.exit(1);
|
||||
|
||||
console.dir(definitions, { depth: null });
|
||||
15
src/types/command.ts
Normal file
15
src/types/command.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type {
|
||||
AutocompleteInteraction,
|
||||
ChatInputCommandInteraction,
|
||||
SlashCommandBuilder,
|
||||
SlashCommandOptionsOnlyBuilder,
|
||||
} from "discord.js";
|
||||
|
||||
export interface Command {
|
||||
data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder;
|
||||
execute(interaction: ChatInputCommandInteraction): Promise<void>;
|
||||
autocomplete?(interaction: AutocompleteInteraction): Promise<void>;
|
||||
category?: string;
|
||||
maxConcurrency?: number;
|
||||
isOwnerOnly?: boolean;
|
||||
}
|
||||
7
src/types/event.ts
Normal file
7
src/types/event.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { ClientEvents } from "discord.js";
|
||||
|
||||
export interface Event<Event extends keyof ClientEvents> {
|
||||
name: Event;
|
||||
once?: boolean;
|
||||
execute(...args: ClientEvents[Event]): Promise<void>;
|
||||
}
|
||||
48
src/utils/components.ts
Normal file
48
src/utils/components.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
ChatInputCommandInteraction,
|
||||
ComponentType,
|
||||
} from "discord.js";
|
||||
|
||||
export async function confirmPrompt(
|
||||
interaction: ChatInputCommandInteraction,
|
||||
message: string
|
||||
) {
|
||||
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId("confirm")
|
||||
.setLabel("Confirm")
|
||||
.setStyle(ButtonStyle.Success),
|
||||
new ButtonBuilder()
|
||||
.setCustomId("cancel")
|
||||
.setLabel("Cancel")
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
);
|
||||
|
||||
const msg = (
|
||||
await interaction.reply({
|
||||
content: message,
|
||||
components: [row],
|
||||
withResponse: true,
|
||||
})
|
||||
).resource?.message!;
|
||||
|
||||
const confirmation = await msg
|
||||
.awaitMessageComponent({
|
||||
componentType: ComponentType.Button,
|
||||
time: 60000,
|
||||
filter: (i) => i.user.id === interaction.user.id,
|
||||
dispose: true,
|
||||
})
|
||||
.catch(() => {
|
||||
interaction.editReply({
|
||||
content: "You took too long to respond.",
|
||||
components: [],
|
||||
});
|
||||
});
|
||||
|
||||
msg.delete();
|
||||
return confirmation?.customId === "confirm";
|
||||
}
|
||||
4
src/utils/constants.ts
Normal file
4
src/utils/constants.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { env } from "../env";
|
||||
|
||||
export const DEV = env.NODE_ENV === "development";
|
||||
export const PROD = env.NODE_ENV === "production";
|
||||
41
src/utils/functions.ts
Normal file
41
src/utils/functions.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import * as cheerio from "cheerio";
|
||||
|
||||
export function noop() {}
|
||||
|
||||
export function pickRandom<T>(arr: T[]): T {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
export function sleep(ms: number) {
|
||||
return new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function chunk<T>(arr: T[], size: number): T[][];
|
||||
export function chunk(arr: string, size: number): string[];
|
||||
export function chunk(arr: any, size: number): any[] {
|
||||
return Array.from({ length: Math.ceil(arr.length / size) }, (_, i) =>
|
||||
arr.slice(i * size, i * size + size)
|
||||
);
|
||||
}
|
||||
|
||||
export function stripHtml(html: string) {
|
||||
return cheerio.load(html).text();
|
||||
}
|
||||
|
||||
export function pluralize(
|
||||
value: number,
|
||||
singular: string,
|
||||
plural = singular + "s"
|
||||
) {
|
||||
return value === 1 ? `${value} ${singular}` : `${value} ${plural}`;
|
||||
}
|
||||
|
||||
export function run<T>(fn: () => T): T {
|
||||
return fn();
|
||||
}
|
||||
|
||||
export async function silently<T extends Promise<any>>(p?: T) {
|
||||
try {
|
||||
return await p;
|
||||
} catch {}
|
||||
}
|
||||
11
src/utils/logger.ts
Normal file
11
src/utils/logger.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { createLogger, format, transports } from "winston";
|
||||
import { DEV } from "./constants";
|
||||
const { combine, simple, errors, prettyPrint } = format;
|
||||
|
||||
export const log = createLogger({
|
||||
level: "info",
|
||||
format: DEV
|
||||
? combine(errors({ stack: true }), prettyPrint({ colorize: true }))
|
||||
: combine(errors({ stack: true }), simple()),
|
||||
transports: [new transports.Console()],
|
||||
});
|
||||
34
src/utils/restart.ts
Normal file
34
src/utils/restart.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Client, InteractionWebhook, MessageFlags } from "discord.js";
|
||||
import { silently } from "./functions";
|
||||
import { client } from "../client";
|
||||
import { env } from "../env";
|
||||
import { readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { log } from "./logger";
|
||||
|
||||
export async function restart(state: string) {
|
||||
log.info("Shutting down...");
|
||||
|
||||
await writeFile("./data/temp/restart", state);
|
||||
|
||||
await client.destroy();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
export async function maybeSendRestarted() {
|
||||
const restartToken = await silently(readFile("./data/temp/restart", "utf-8"));
|
||||
|
||||
if (restartToken) {
|
||||
const webhook = new InteractionWebhook(
|
||||
client as Client<true>,
|
||||
env.APPLICATION_ID,
|
||||
restartToken
|
||||
);
|
||||
|
||||
await webhook.send({
|
||||
content: "Successfully restarted!",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
|
||||
await silently(rm("./data/temp/restart"));
|
||||
}
|
||||
}
|
||||
37
src/utils/wiktionary.ts
Normal file
37
src/utils/wiktionary.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import ky from "ky";
|
||||
|
||||
type ParsedExample = {
|
||||
example: string;
|
||||
};
|
||||
|
||||
type Definition = {
|
||||
definition: string;
|
||||
parsedExamples?: ParsedExample[];
|
||||
};
|
||||
|
||||
type Entry = {
|
||||
partOfSpeech: string;
|
||||
language: string;
|
||||
definitions: Definition[];
|
||||
};
|
||||
|
||||
type DefinitionsResponse = {
|
||||
[key: string]: Entry[];
|
||||
};
|
||||
|
||||
const client = ky.create({
|
||||
prefixUrl: "https://en.wiktionary.org/api/rest_v1",
|
||||
throwHttpErrors: false,
|
||||
});
|
||||
|
||||
export async function getDefinitions(word: string) {
|
||||
const res = await client.get("page/definition/" + encodeURIComponent(word));
|
||||
const data = await res.json<DefinitionsResponse>();
|
||||
if (!res.ok || !data) return null;
|
||||
|
||||
return Object.entries(data).map(([languageCode, entries]) => ({
|
||||
language: entries[0].language,
|
||||
languageCode,
|
||||
entries,
|
||||
}));
|
||||
}
|
||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"allowJs": true,
|
||||
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user