Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

17 changed files with 468 additions and 478 deletions

View File

@ -1,34 +0,0 @@
name: Code Analysis
on:
push:
branches:
- main
jobs:
sonar:
name: SonarQube
steps:
- name: Checkout Source Files
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: SonarCube Scan
uses: SonarSource/sonarqube-scan-action@v4
timeout-minutes: 10
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: "https://quality.nhcarrigan.com"
with:
args: >
-Dsonar.sources=.
-Dsonar.projectKey=cordelia-taryne
- name: SonarQube Quality Gate check
uses: sonarsource/sonarqube-quality-gate-action@v1
with:
pollingTimeoutSec: 600
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: "https://quality.nhcarrigan.com"

View File

@ -1,6 +1,6 @@
{ {
"name": "cordelia-taryne", "name": "cordelia-taryne",
"version": "1.1.0", "version": "1.0.0",
"description": "An AI-powered multi-purpose assistant for Discord.", "description": "An AI-powered multi-purpose assistant for Discord.",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
@ -22,8 +22,8 @@
}, },
"dependencies": { "dependencies": {
"@anthropic-ai/sdk": "0.36.3", "@anthropic-ai/sdk": "0.36.3",
"@nhcarrigan/logger": "1.0.0",
"discord.js": "14.18.0", "discord.js": "14.18.0",
"fastify": "5.2.1" "fastify": "5.2.1",
"winston": "3.17.0"
} }
} }

206
pnpm-lock.yaml generated
View File

@ -11,15 +11,15 @@ importers:
'@anthropic-ai/sdk': '@anthropic-ai/sdk':
specifier: 0.36.3 specifier: 0.36.3
version: 0.36.3 version: 0.36.3
'@nhcarrigan/logger':
specifier: 1.0.0
version: 1.0.0
discord.js: discord.js:
specifier: 14.18.0 specifier: 14.18.0
version: 14.18.0 version: 14.18.0
fastify: fastify:
specifier: 5.2.1 specifier: 5.2.1
version: 5.2.1 version: 5.2.1
winston:
specifier: 3.17.0
version: 3.17.0
devDependencies: devDependencies:
'@nhcarrigan/eslint-config': '@nhcarrigan/eslint-config':
specifier: 5.1.0 specifier: 5.1.0
@ -50,6 +50,13 @@ packages:
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@colors/colors@1.6.0':
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
engines: {node: '>=0.1.90'}
'@dabh/diagnostics@2.0.3':
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
'@discordjs/builders@1.10.1': '@discordjs/builders@1.10.1':
resolution: {integrity: sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==} resolution: {integrity: sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==}
engines: {node: '>=16.11.0'} engines: {node: '>=16.11.0'}
@ -340,9 +347,6 @@ packages:
typescript: '>=5' typescript: '>=5'
vitest: '>=2' vitest: '>=2'
'@nhcarrigan/logger@1.0.0':
resolution: {integrity: sha512-2e19Bie+ZKb6yKPKjhawqsENkhHatYkvBAmFZx9eToOXdOca+CYi51tldRMtejg6e0+4hOOf2bo5zdBQKmH0dw==}
'@nhcarrigan/typescript-config@4.0.0': '@nhcarrigan/typescript-config@4.0.0':
resolution: {integrity: sha512-969HVha7A/Sg77fuMwOm6p14a+7C5iE6g55OD71srqwKIgksQl+Ex/hAI/pyzTQFDQ/FBJbpnHlR4Ov25QV/rw==} resolution: {integrity: sha512-969HVha7A/Sg77fuMwOm6p14a+7C5iE6g55OD71srqwKIgksQl+Ex/hAI/pyzTQFDQ/FBJbpnHlR4Ov25QV/rw==}
engines: {node: '20', pnpm: '9'} engines: {node: '20', pnpm: '9'}
@ -502,6 +506,9 @@ packages:
'@types/normalize-package-data@2.4.4': '@types/normalize-package-data@2.4.4':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
'@types/triple-beam@1.3.5':
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
'@types/ws@8.5.14': '@types/ws@8.5.14':
resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==} resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==}
@ -745,6 +752,9 @@ packages:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
async@3.2.6:
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
asynckit@0.4.0: asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@ -824,13 +834,28 @@ packages:
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
engines: {node: '>=4'} engines: {node: '>=4'}
color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
color-convert@2.0.1: color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'} engines: {node: '>=7.0.0'}
color-name@1.1.3:
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
color-name@1.1.4: color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
color@3.2.1:
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
colorspace@1.1.4:
resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
combined-stream@1.0.8: combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -927,6 +952,9 @@ packages:
electron-to-chromium@1.5.97: electron-to-chromium@1.5.97:
resolution: {integrity: sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ==} resolution: {integrity: sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ==}
enabled@2.0.0:
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
error-ex@1.3.2: error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
@ -1145,6 +1173,9 @@ packages:
fastq@1.19.0: fastq@1.19.0:
resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==}
fecha@4.2.3:
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
file-entry-cache@8.0.0: file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'} engines: {node: '>=16.0.0'}
@ -1172,6 +1203,9 @@ packages:
flatted@3.3.2: flatted@3.3.2:
resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
fn.name@1.1.0:
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
for-each@0.3.4: for-each@0.3.4:
resolution: {integrity: sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==} resolution: {integrity: sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -1303,6 +1337,9 @@ packages:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'} engines: {node: '>=8'}
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
internal-slot@1.1.0: internal-slot@1.1.0:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -1318,6 +1355,9 @@ packages:
is-arrayish@0.2.1: is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
is-async-function@2.1.1: is-async-function@2.1.1:
resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -1390,6 +1430,10 @@ packages:
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
is-string@1.1.1: is-string@1.1.1:
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -1473,6 +1517,9 @@ packages:
keyv@4.5.4: keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
kuler@2.0.0:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
levn@0.4.1: levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -1500,6 +1547,10 @@ packages:
lodash@4.17.21: lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
logform@2.7.0:
resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
engines: {node: '>= 12.0.0'}
loose-envify@1.4.0: loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true hasBin: true
@ -1613,6 +1664,9 @@ packages:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
one-time@1.0.0:
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
optionator@0.9.4: optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -1753,6 +1807,10 @@ packages:
resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==}
engines: {node: '>=8'} engines: {node: '>=8'}
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
real-require@0.2.0: real-require@0.2.0:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
engines: {node: '>= 12.13.0'} engines: {node: '>= 12.13.0'}
@ -1817,6 +1875,9 @@ packages:
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
engines: {node: '>=0.4'} engines: {node: '>=0.4'}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safe-push-apply@1.0.0: safe-push-apply@1.0.0:
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -1890,6 +1951,9 @@ packages:
siginfo@2.0.0: siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
slash@3.0.0: slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -1923,6 +1987,9 @@ packages:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'} engines: {node: '>= 10.x'}
stack-trace@0.0.10:
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
stackback@0.0.2: stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
@ -1948,6 +2015,9 @@ packages:
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
strip-bom@3.0.0: strip-bom@3.0.0:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -1972,6 +2042,9 @@ packages:
resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
text-hex@1.0.0:
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
thread-stream@3.1.0: thread-stream@3.1.0:
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
@ -2004,6 +2077,10 @@ packages:
tr46@0.0.3: tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
triple-beam@1.4.1:
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
engines: {node: '>= 14.0.0'}
ts-api-utils@1.4.3: ts-api-utils@1.4.3:
resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
engines: {node: '>=16'} engines: {node: '>=16'}
@ -2085,6 +2162,9 @@ packages:
uri-js@4.4.1: uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
validate-npm-package-license@3.0.4: validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
@ -2197,6 +2277,14 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
hasBin: true hasBin: true
winston-transport@4.9.0:
resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==}
engines: {node: '>= 12.0.0'}
winston@3.17.0:
resolution: {integrity: sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==}
engines: {node: '>= 12.0.0'}
word-wrap@1.2.5: word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2239,6 +2327,14 @@ snapshots:
'@babel/helper-validator-identifier@7.25.9': {} '@babel/helper-validator-identifier@7.25.9': {}
'@colors/colors@1.6.0': {}
'@dabh/diagnostics@2.0.3':
dependencies:
colorspace: 1.1.4
enabled: 2.0.0
kuler: 2.0.0
'@discordjs/builders@1.10.1': '@discordjs/builders@1.10.1':
dependencies: dependencies:
'@discordjs/formatters': 0.6.0 '@discordjs/formatters': 0.6.0
@ -2492,8 +2588,6 @@ snapshots:
- eslint-import-resolver-webpack - eslint-import-resolver-webpack
- supports-color - supports-color
'@nhcarrigan/logger@1.0.0': {}
'@nhcarrigan/typescript-config@4.0.0(typescript@5.7.3)': '@nhcarrigan/typescript-config@4.0.0(typescript@5.7.3)':
dependencies: dependencies:
typescript: 5.7.3 typescript: 5.7.3
@ -2613,6 +2707,8 @@ snapshots:
'@types/normalize-package-data@2.4.4': {} '@types/normalize-package-data@2.4.4': {}
'@types/triple-beam@1.3.5': {}
'@types/ws@8.5.14': '@types/ws@8.5.14':
dependencies: dependencies:
'@types/node': 22.13.1 '@types/node': 22.13.1
@ -2937,6 +3033,8 @@ snapshots:
async-function@1.0.0: {} async-function@1.0.0: {}
async@3.2.6: {}
asynckit@0.4.0: {} asynckit@0.4.0: {}
atomic-sleep@1.0.0: {} atomic-sleep@1.0.0: {}
@ -3018,12 +3116,33 @@ snapshots:
dependencies: dependencies:
escape-string-regexp: 1.0.5 escape-string-regexp: 1.0.5
color-convert@1.9.3:
dependencies:
color-name: 1.1.3
color-convert@2.0.1: color-convert@2.0.1:
dependencies: dependencies:
color-name: 1.1.4 color-name: 1.1.4
color-name@1.1.3: {}
color-name@1.1.4: {} color-name@1.1.4: {}
color-string@1.9.1:
dependencies:
color-name: 1.1.4
simple-swizzle: 0.2.2
color@3.2.1:
dependencies:
color-convert: 1.9.3
color-string: 1.9.1
colorspace@1.1.4:
dependencies:
color: 3.2.1
text-hex: 1.0.0
combined-stream@1.0.8: combined-stream@1.0.8:
dependencies: dependencies:
delayed-stream: 1.0.0 delayed-stream: 1.0.0
@ -3126,6 +3245,8 @@ snapshots:
electron-to-chromium@1.5.97: {} electron-to-chromium@1.5.97: {}
enabled@2.0.0: {}
error-ex@1.3.2: error-ex@1.3.2:
dependencies: dependencies:
is-arrayish: 0.2.1 is-arrayish: 0.2.1
@ -3529,6 +3650,8 @@ snapshots:
dependencies: dependencies:
reusify: 1.0.4 reusify: 1.0.4
fecha@4.2.3: {}
file-entry-cache@8.0.0: file-entry-cache@8.0.0:
dependencies: dependencies:
flat-cache: 4.0.1 flat-cache: 4.0.1
@ -3560,6 +3683,8 @@ snapshots:
flatted@3.3.2: {} flatted@3.3.2: {}
fn.name@1.1.0: {}
for-each@0.3.4: for-each@0.3.4:
dependencies: dependencies:
is-callable: 1.2.7 is-callable: 1.2.7
@ -3693,6 +3818,8 @@ snapshots:
indent-string@4.0.0: {} indent-string@4.0.0: {}
inherits@2.0.4: {}
internal-slot@1.1.0: internal-slot@1.1.0:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
@ -3709,6 +3836,8 @@ snapshots:
is-arrayish@0.2.1: {} is-arrayish@0.2.1: {}
is-arrayish@0.3.2: {}
is-async-function@2.1.1: is-async-function@2.1.1:
dependencies: dependencies:
async-function: 1.0.0 async-function: 1.0.0
@ -3786,6 +3915,8 @@ snapshots:
dependencies: dependencies:
call-bound: 1.0.3 call-bound: 1.0.3
is-stream@2.0.1: {}
is-string@1.1.1: is-string@1.1.1:
dependencies: dependencies:
call-bound: 1.0.3 call-bound: 1.0.3
@ -3866,6 +3997,8 @@ snapshots:
dependencies: dependencies:
json-buffer: 3.0.1 json-buffer: 3.0.1
kuler@2.0.0: {}
levn@0.4.1: levn@0.4.1:
dependencies: dependencies:
prelude-ls: 1.2.1 prelude-ls: 1.2.1
@ -3893,6 +4026,15 @@ snapshots:
lodash@4.17.21: {} lodash@4.17.21: {}
logform@2.7.0:
dependencies:
'@colors/colors': 1.6.0
'@types/triple-beam': 1.3.5
fecha: 4.2.3
ms: 2.1.3
safe-stable-stringify: 2.5.0
triple-beam: 1.4.1
loose-envify@1.4.0: loose-envify@1.4.0:
dependencies: dependencies:
js-tokens: 4.0.0 js-tokens: 4.0.0
@ -3996,6 +4138,10 @@ snapshots:
on-exit-leak-free@2.1.2: {} on-exit-leak-free@2.1.2: {}
one-time@1.0.0:
dependencies:
fn.name: 1.1.0
optionator@0.9.4: optionator@0.9.4:
dependencies: dependencies:
deep-is: 0.1.4 deep-is: 0.1.4
@ -4134,6 +4280,12 @@ snapshots:
parse-json: 5.2.0 parse-json: 5.2.0
type-fest: 0.6.0 type-fest: 0.6.0
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
real-require@0.2.0: {} real-require@0.2.0: {}
reflect.getprototypeof@1.0.10: reflect.getprototypeof@1.0.10:
@ -4223,6 +4375,8 @@ snapshots:
has-symbols: 1.1.0 has-symbols: 1.1.0
isarray: 2.0.5 isarray: 2.0.5
safe-buffer@5.2.1: {}
safe-push-apply@1.0.0: safe-push-apply@1.0.0:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
@ -4308,6 +4462,10 @@ snapshots:
siginfo@2.0.0: {} siginfo@2.0.0: {}
simple-swizzle@0.2.2:
dependencies:
is-arrayish: 0.3.2
slash@3.0.0: {} slash@3.0.0: {}
slashes@3.0.12: {} slashes@3.0.12: {}
@ -4339,6 +4497,8 @@ snapshots:
split2@4.2.0: {} split2@4.2.0: {}
stack-trace@0.0.10: {}
stackback@0.0.2: {} stackback@0.0.2: {}
std-env@3.8.0: {} std-env@3.8.0: {}
@ -4387,6 +4547,10 @@ snapshots:
define-properties: 1.2.1 define-properties: 1.2.1
es-object-atoms: 1.1.1 es-object-atoms: 1.1.1
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
strip-bom@3.0.0: {} strip-bom@3.0.0: {}
strip-indent@3.0.0: strip-indent@3.0.0:
@ -4406,6 +4570,8 @@ snapshots:
'@pkgr/core': 0.1.1 '@pkgr/core': 0.1.1
tslib: 2.8.1 tslib: 2.8.1
text-hex@1.0.0: {}
thread-stream@3.1.0: thread-stream@3.1.0:
dependencies: dependencies:
real-require: 0.2.0 real-require: 0.2.0
@ -4428,6 +4594,8 @@ snapshots:
tr46@0.0.3: {} tr46@0.0.3: {}
triple-beam@1.4.1: {}
ts-api-utils@1.4.3(typescript@5.7.3): ts-api-utils@1.4.3(typescript@5.7.3):
dependencies: dependencies:
typescript: 5.7.3 typescript: 5.7.3
@ -4515,6 +4683,8 @@ snapshots:
dependencies: dependencies:
punycode: 2.3.1 punycode: 2.3.1
util-deprecate@1.0.2: {}
validate-npm-package-license@3.0.4: validate-npm-package-license@3.0.4:
dependencies: dependencies:
spdx-correct: 3.2.0 spdx-correct: 3.2.0
@ -4646,6 +4816,26 @@ snapshots:
siginfo: 2.0.0 siginfo: 2.0.0
stackback: 0.0.2 stackback: 0.0.2
winston-transport@4.9.0:
dependencies:
logform: 2.7.0
readable-stream: 3.6.2
triple-beam: 1.4.1
winston@3.17.0:
dependencies:
'@colors/colors': 1.6.0
'@dabh/diagnostics': 2.0.3
async: 3.2.6
is-stream: 2.0.1
logform: 2.7.0
one-time: 1.0.0
readable-stream: 3.6.2
safe-stable-stringify: 2.5.0
stack-trace: 0.0.10
triple-beam: 1.4.1
winston-transport: 4.9.0
word-wrap@1.2.5: {} word-wrap@1.2.5: {}
ws@8.18.0: {} ws@8.18.0: {}

View File

@ -1,3 +1,2 @@
DISCORD_TOKEN="op://Environment Variables - Naomi/Cordelia Taryne/discord_token" DISCORD_TOKEN="op://Environment Variables - Naomi/Cordelia Taryne/discord_token"
AI_TOKEN="op://Environment Variables - Naomi/Cordelia Taryne/ai_token" AI_TOKEN="op://Environment Variables - Naomi/Cordelia Taryne/ai_token"
LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth"

View File

@ -12,7 +12,7 @@ import { proofread } from "./modules/proofread.js";
import { query } from "./modules/query.js"; import { query } from "./modules/query.js";
import { summarise } from "./modules/summarise.js"; import { summarise } from "./modules/summarise.js";
import { instantiateServer } from "./server/serve.js"; import { instantiateServer } from "./server/serve.js";
import { logger } from "./utils/logger.js"; import { logHandler } from "./utils/logHandler.js";
const commands: Record< const commands: Record<
string, string,
@ -28,22 +28,6 @@ const commands: Record<
"summarise": summarise, "summarise": summarise,
}; };
process.on("unhandledRejection", (error) => {
if (error instanceof Error) {
void logger.error("Unhandled Rejection", error);
return;
}
void logger.error("unhandled rejection", new Error(String(error)));
});
process.on("uncaughtException", (error) => {
if (error instanceof Error) {
void logger.error("Uncaught Exception", error);
return;
}
void logger.error("uncaught exception", new Error(String(error)));
});
const client = new Client({ const client = new Client({
intents: [], intents: [],
}); });
@ -57,16 +41,8 @@ client.on(Events.InteractionCreate, (interaction) => {
} }
}); });
client.on(Events.EntitlementCreate, (entitlement) => {
void logger.log("info", `User ${entitlement.userId} has subscribed!`);
});
client.on(Events.EntitlementDelete, (entitlement) => {
void logger.log("info", `User ${entitlement.userId} has unsubscribed... :c`);
});
client.on(Events.ClientReady, () => { client.on(Events.ClientReady, () => {
void logger.log("debug", "Bot is ready."); logHandler.info("Bot is ready.");
}); });
instantiateServer(); instantiateServer();

View File

@ -13,66 +13,56 @@ import {
MessageFlags, MessageFlags,
type ChatInputCommandInteraction, type ChatInputCommandInteraction,
} from "discord.js"; } from "discord.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/** /**
* Responds with information about the bot. * Responds with information about the bot.
* @param interaction -- The interaction payload from Discord. * @param interaction -- The interaction payload from Discord.
*/ */
// eslint-disable-next-line max-lines-per-function -- Refactor at a later time.
export const about = async( export const about = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
try { await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
const version = process.env.npm_package_version ?? "Unknown"; const version = process.env.npm_package_version ?? "Unknown";
const commit = execSync("git rev-parse --short HEAD").toString(). const commit = execSync("git rev-parse --short HEAD").toString().
trim(); trim();
const embed = new EmbedBuilder(); const embed = new EmbedBuilder();
embed.setTitle("About Cordelia Taryne"); embed.setTitle("About Cordelia Taryne");
embed.setDescription( embed.setDescription(
// eslint-disable-next-line stylistic/max-len -- It's a long string. // eslint-disable-next-line stylistic/max-len -- It's a long string.
"Cordelia Taryne is a Discord bot that uses Anthropic to provide assistive features. She is developed by NHCarrigan. To use the bot, type `/` and select one of her commands!", "Cordelia Taryne is a Discord bot that uses Anthropic to provide assistive features. She is developed by NHCarrigan. To use the bot, type `/` and select one of her commands!",
); );
embed.addFields( embed.addFields(
{ {
name: "Running Version", name: "Running Version",
value: version, value: version,
}, },
{ {
name: "Current Commit", name: "Current Commit",
value: commit, value: commit,
}, },
); );
const supportButton = new ButtonBuilder(). const supportButton = new ButtonBuilder().
setLabel("Need help?"). setLabel("Need help?").
setStyle(ButtonStyle.Link). setStyle(ButtonStyle.Link).
setURL("https://chat.nhcarrigan.com"); setURL("https://chat.nhcarrigan.com");
const sourceButton = new ButtonBuilder(). const sourceButton = new ButtonBuilder().
setLabel("Source Code"). setLabel("Source Code").
setStyle(ButtonStyle.Link). setStyle(ButtonStyle.Link).
setURL("https://git.nhcarrigan.com/nhcarrigan/aria-iuvo"); setURL("https://git.nhcarrigan.com/nhcarrigan/aria-iuvo");
const subscribeButton = new ButtonBuilder(). const subscribeButton = new ButtonBuilder().
setStyle(ButtonStyle.Premium). setStyle(ButtonStyle.Premium).
setSKUId("1338672773261951026"); setSKUId("1338672773261951026");
const row = new ActionRowBuilder<ButtonBuilder>().addComponents( const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
supportButton, supportButton,
sourceButton, sourceButton,
subscribeButton, subscribeButton,
); );
await interaction.editReply({ await interaction.editReply({
components: [ row ], components: [ row ],
embeds: [ embed ], embeds: [ embed ],
}); });
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("about command", error);
}
}
}; };

View File

@ -6,10 +6,7 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js"; import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js"; import { isSubscribed } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
import type { ImageBlockParam } from "@anthropic-ai/sdk/resources/index.js"; import type { ImageBlockParam } from "@anthropic-ai/sdk/resources/index.js";
const isValidContentType = ( const isValidContentType = (
@ -33,93 +30,83 @@ const isValidContentType = (
export const alt = async( export const alt = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
try { await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); const sub = await isSubscribed(interaction);
const sub = await isSubscribed(interaction); if (!sub) {
if (!sub) { return;
return; }
}
const image = interaction.options.getAttachment("image", true); const image = interaction.options.getAttachment("image", true);
const { contentType, height, width, size, url } = image; const { contentType, height, width, size, url } = image;
// Claude supports JPG, PNG, GIF, WEBP // Claude supports JPG, PNG, GIF, WEBP
if ( if (
contentType === null contentType === null
|| !isValidContentType(contentType) || !isValidContentType(contentType)
|| height === null || height === null
|| width === null || width === null
) { ) {
await interaction.editReply({ await interaction.editReply({
content: "That does not appear to be a valid image.", content: "That does not appear to be a valid image.",
}); });
return; return;
} }
// Max file size is 5MB // Max file size is 5MB
if (size > 5 * 1024 * 1024) { if (size > 5 * 1024 * 1024) {
await interaction.editReply({ await interaction.editReply({
content: content:
// eslint-disable-next-line stylistic/max-len -- It's a long string. // eslint-disable-next-line stylistic/max-len -- It's a long string.
"That image is too large. Please provide an image that is less than 5MB.", "That image is too large. Please provide an image that is less than 5MB.",
}); });
return; return;
} }
// Max dimensions are 8000px // Max dimensions are 8000px
if (height > 8000 || width > 8000) { if (height > 8000 || width > 8000) {
await interaction.editReply({ await interaction.editReply({
content: content:
// eslint-disable-next-line stylistic/max-len -- It's a long string. // eslint-disable-next-line stylistic/max-len -- It's a long string.
"That image is too large. Please provide an image that is less than 8000 pixels high or wide.", "That image is too large. Please provide an image that is less than 8000 pixels high or wide.",
});
return;
}
const downloadRequest = await fetch(url);
const blob = await downloadRequest.arrayBuffer();
const base64 = Buffer.from(blob).toString("base64");
const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000,
messages: [
{
content: [
{
source: {
data: base64,
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required property syntax for SDK.
media_type: contentType,
type: "base64",
},
type: "image",
},
],
role: "user",
},
],
model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to generate descriptive and accessible alt-text for the user's image. Be as descriptive as possible. Do not include ANYTHING in your response EXCEPT the actual alt-text. Wrap the text in a multi-line code block for easy copying.`,
temperature: 1,
}); });
return;
const response = messages.content.find((message) => {
return message.type === "text";
});
await interaction.editReply(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "alt-text");
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("alt-text command", error);
}
} }
const downloadRequest = await fetch(url);
const blob = await downloadRequest.arrayBuffer();
const base64 = Buffer.from(blob).toString("base64");
const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000,
messages: [
{
content: [
{
source: {
data: base64,
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required property syntax for SDK.
media_type: contentType,
type: "base64",
},
type: "image",
},
],
role: "user",
},
],
model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to generate descriptive and accessible alt-text for the user's image. Be as descriptive as possible. Do not include ANYTHING in your response EXCEPT the actual alt-text. Wrap the text in a multi-line code block for easy copying.`,
temperature: 1,
});
const response = messages.content.find((message) => {
return message.type === "text";
});
await interaction.editReply(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
}; };

View File

@ -6,10 +6,7 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js"; import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js"; import { isSubscribed } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/** /**
* Accepts an arbitrary code snippet from the user, then sends * Accepts an arbitrary code snippet from the user, then sends
@ -19,38 +16,28 @@ import { replyToError } from "../utils/replyToError.js";
export const evaluate = async( export const evaluate = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
try { await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); const sub = await isSubscribed(interaction);
const sub = await isSubscribed(interaction); if (!sub) {
if (!sub) { return;
return;
}
const code = interaction.options.getString("code", true);
const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000,
messages: [ { content: code, role: "user" } ],
model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to evaluate the user's code and provide the result. Wrap ONLY THE CODE RESULT in a multi-line code block for easy copying.`,
temperature: 1,
});
const response = messages.content.find((message) => {
return message.type === "text";
});
await interaction.editReply(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "evaluate");
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("evaluate command", error);
}
} }
const code = interaction.options.getString("code", true);
const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000,
messages: [ { content: code, role: "user" } ],
model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to evaluate the user's code and provide the result. Wrap ONLY THE CODE RESULT in a multi-line code block for easy copying.`,
temperature: 1,
});
const response = messages.content.find((message) => {
return message.type === "text";
});
await interaction.editReply(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
}; };

View File

@ -6,10 +6,7 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js"; import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js"; import { isSubscribed } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/** /**
* Accepts a text snippet from the user. Submits it to Anthropic * Accepts a text snippet from the user. Submits it to Anthropic
@ -19,39 +16,29 @@ import { replyToError } from "../utils/replyToError.js";
export const mood = async( export const mood = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
try { await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); const sub = await isSubscribed(interaction);
const sub = await isSubscribed(interaction); if (!sub) {
if (!sub) { return;
return;
}
const prompt = interaction.options.getString("text", true);
const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000,
messages: [ { content: prompt, role: "user" } ],
model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to analyse the text the user provides for the overall sentiment and mood of the author.`,
temperature: 1,
});
const response = messages.content.find((message) => {
return message.type === "text";
});
await interaction.editReply(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "mood");
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("mood command", error);
}
} }
const prompt = interaction.options.getString("text", true);
const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000,
messages: [ { content: prompt, role: "user" } ],
model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to analyse the text the user provides for the overall sentiment and mood of the author.`,
temperature: 1,
});
const response = messages.content.find((message) => {
return message.type === "text";
});
await interaction.editReply(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
}; };

View File

@ -6,10 +6,7 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js"; import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js"; import { isSubscribed } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/** /**
* Accepts a text snippet from the user. Submits it to Anthropic * Accepts a text snippet from the user. Submits it to Anthropic
@ -19,39 +16,29 @@ import { replyToError } from "../utils/replyToError.js";
export const proofread = async( export const proofread = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
try { await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); const sub = await isSubscribed(interaction);
const sub = await isSubscribed(interaction); if (!sub) {
if (!sub) { return;
return;
}
const prompt = interaction.options.getString("text", true);
const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000,
messages: [ { content: prompt, role: "user" } ],
model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to proofread the text the user has provided. You should identify spelling and grammatical errors using British English.`,
temperature: 1,
});
const response = messages.content.find((message) => {
return message.type === "text";
});
await interaction.editReply(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "proofread");
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("proofread command", error);
}
} }
const prompt = interaction.options.getString("text", true);
const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000,
messages: [ { content: prompt, role: "user" } ],
model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to proofread the text the user has provided. You should identify spelling and grammatical errors using British English.`,
temperature: 1,
});
const response = messages.content.find((message) => {
return message.type === "text";
});
await interaction.editReply(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
}; };

View File

@ -6,10 +6,7 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js"; import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js"; import { isSubscribed } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/** /**
* Accepts an arbitrary question from the user, then sends it to Anthropic * Accepts an arbitrary question from the user, then sends it to Anthropic
@ -19,39 +16,29 @@ import { replyToError } from "../utils/replyToError.js";
export const query = async( export const query = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
try { await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); const sub = await isSubscribed(interaction);
const sub = await isSubscribed(interaction); if (!sub) {
if (!sub) { return;
return;
}
const prompt = interaction.options.getString("prompt", true);
const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000,
messages: [ { content: prompt, role: "user" } ],
model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to answer the user's question to the best of your abilities. When possible, include links to relevant sources.`,
temperature: 1,
});
const response = messages.content.find((message) => {
return message.type === "text";
});
await interaction.editReply(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "query");
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("query command", error);
}
} }
const prompt = interaction.options.getString("prompt", true);
const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000,
messages: [ { content: prompt, role: "user" } ],
model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to answer the user's question to the best of your abilities. When possible, include links to relevant sources.`,
temperature: 1,
});
const response = messages.content.find((message) => {
return message.type === "text";
});
await interaction.editReply(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
}; };

View File

@ -6,10 +6,7 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js"; import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js"; import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js"; import { isSubscribed } from "../utils/isSubscribed.js";
import { logger } from "../utils/logger.js";
import { replyToError } from "../utils/replyToError.js";
/** /**
* Accepts a text snippet from the user. Submits it to Anthropic * Accepts a text snippet from the user. Submits it to Anthropic
@ -19,39 +16,29 @@ import { replyToError } from "../utils/replyToError.js";
export const summarise = async( export const summarise = async(
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
): Promise<void> => { ): Promise<void> => {
try { await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] });
await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); const sub = await isSubscribed(interaction);
const sub = await isSubscribed(interaction); if (!sub) {
if (!sub) { return;
return;
}
const prompt = interaction.options.getString("text", true);
const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000,
messages: [ { content: prompt, role: "user" } ],
model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to summarise the text the user has provided. Your goal is to reach 250 words or less. Wrap ONLY THE SUMMARY in multi-line code block so it is easy to copy.`,
temperature: 1,
});
const response = messages.content.find((message) => {
return message.type === "text";
});
await interaction.editReply(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "summarise");
} catch (error) {
await replyToError(interaction);
if (error instanceof Error) {
await logger.error("summarise command", error);
}
} }
const prompt = interaction.options.getString("text", true);
const messages = await ai.messages.create({
// eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK.
max_tokens: 2000,
messages: [ { content: prompt, role: "user" } ],
model: "claude-3-5-sonnet-latest",
system: `${personality} Your role in this conversation is to summarise the text the user has provided. Your goal is to reach 250 words or less. Wrap ONLY THE SUMMARY in multi-line code block so it is easy to copy.`,
temperature: 1,
});
const response = messages.content.find((message) => {
return message.type === "text";
});
await interaction.editReply(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
}; };

View File

@ -5,27 +5,27 @@
*/ */
import fastify from "fastify"; import fastify from "fastify";
import { logger } from "../utils/logger.js"; import { logHandler } from "../utils/logHandler.js";
const html = `<!DOCTYPE html> const html = `<!DOCTYPE html>
<html> <html>
<head> <head>
<title>Cordelia Taryne</title> <title>Aria Iuvo</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="AI-powered multi-purpose assistant for Discord!" /> <meta name="description" content="Bot to translate your messages on Discord!" />
<script src="https://cdn.nhcarrigan.com/headers/index.js" async defer></script> <script src="https://cdn.nhcarrigan.com/headers/index.js" async defer></script>
</head> </head>
<body> <body>
<main> <main>
<h1>Cordelia Taryne</h1> <h1>Aria Iuvo</h1>
<section> <section>
<p>AI-powered multi-purpose assistant for Discord!</p> <p>Bot to translate your messages on Discord!</p>
</section> </section>
<section> <section>
<h2>Links</h2> <h2>Links</h2>
<p> <p>
<a href="https://git.nhcarrigan.com/nhcarrigan/cordelia-taryne"> <a href="https://git.nhcarrigan.com/nhcarrigan/aria-iuvo">
<i class="fa-solid fa-code"></i> Source Code <i class="fa-solid fa-code"></i> Source Code
</a> </a>
</p> </p>
@ -60,16 +60,12 @@ export const instantiateServer = (): void => {
server.listen({ port: 5002 }, (error) => { server.listen({ port: 5002 }, (error) => {
if (error) { if (error) {
void logger.error("instantiate server", error); logHandler.error(error);
return; return;
} }
void logger.log("debug", "Server listening on port 5002."); logHandler.info("Server listening on port 5002.");
}); });
} catch (error) { } catch (error) {
if (error instanceof Error) { logHandler.error(error);
void logger.error("instantiate server", error);
return;
}
void logger.error("instantiate server", new Error("Unknown error"));
} }
}; };

View File

@ -1,30 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { logger } from "./logger.js";
import type { Usage } from "@anthropic-ai/sdk/resources/index.js";
/**
* Calculates the cost of a command run by a user, and sends to
* our logging service.
* @param usage -- The usage payload from Anthropic.
* @param uuid -- The Discord ID of the user who ran the command.
* @param command -- The command that was run.
*/
export const calculateCost = async(
usage: Usage,
uuid: string,
command: string,
): Promise<void> => {
const inputCost = usage.input_tokens * (3 / 1_000_000);
const outputCost = usage.output_tokens * (15 / 1_000_000);
const totalCost = inputCost + outputCost;
await logger.log(
"info",
`User ${uuid} ran \`${command}\` which accepted ${usage.input_tokens.toString()} and generated ${usage.output_tokens.toString()}.
Total cost: ${totalCost.toLocaleString("en-GB", { currency: "USD", style: "currency" })}`,
);
};

31
src/utils/logHandler.ts Normal file
View File

@ -0,0 +1,31 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { createLogger, format, transports, config } from "winston";
const { combine, timestamp, colorize, printf } = format;
/**
* Standard log handler, using winston to wrap and format
* messages. Call with `logHandler.log(level, message)`.
* @param {string} level - The log level to use.
* @param {string} message - The message to log.
*/
export const logHandler = createLogger({
exitOnError: false,
format: combine(
timestamp({
format: "YYYY-MM-DD HH:mm:ss",
}),
colorize(),
printf((info) => {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- Winston properties...
return `${info.level}: ${info.timestamp}: ${info.message}`;
}),
),
level: "silly",
levels: config.npm.levels,
transports: [ new transports.Console() ],
});

View File

@ -1,12 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Logger } from "@nhcarrigan/logger";
export const logger = new Logger(
"Cordelia Taryne",
process.env.LOG_TOKEN ?? "",
);

View File

@ -1,38 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
type ChatInputCommandInteraction,
type MessageContextMenuCommandInteraction,
} from "discord.js";
/**
* Responds to an interaction with a generic error message.
* @param interaction -- The interaction payload from Discord.
*/
export const replyToError = async(
interaction:
| ChatInputCommandInteraction
| MessageContextMenuCommandInteraction,
): Promise<void> => {
const button = new ButtonBuilder().setLabel("Need help?").
setStyle(ButtonStyle.Link).
setURL("https://chat.nhcarrigan.com");
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(button);
if (interaction.deferred || interaction.replied) {
await interaction.editReply({
components: [ row ],
content: "An error occurred while running this command.",
});
return;
}
await interaction.reply({
components: [ row ],
content: "An error occurred while running this command.",
});
};