Compare commits

..

14 Commits

52 changed files with 712 additions and 260 deletions

View File

@@ -19,6 +19,7 @@ Invulns don't need moderator or vip status in the channel.
The chatterbot and streamer always are invuln and cannot be stripped of this status. The chatterbot and streamer always are invuln and cannot be stripped of this status.
Moderators can add and remove invulns. Moderators can add and remove invulns.
On your first message in chat you will recieve 10 minutes of invuln status. On your first message in chat you will recieve 10 minutes of invuln status.
When a moderator adds you as invuln you will lose all items and qbucks.
### Bots ### Bots
@@ -121,6 +122,25 @@ The enable and disable redeem commands have a way to enable/disable all sound al
When running the development database and twitch api application, the redeems will not get created. This is because twitch only allows editing or deleting rewards when the same application created the reward. (fucking stupid) When running the development database and twitch api application, the redeems will not get created. This is because twitch only allows editing or deleting rewards when the same application created the reward. (fucking stupid)
### Welcome message
Welcome message in the context of this project means 2 things:
- The message the bot says when it's the first time you're chatting in the channel.
- The personalized message the bot says when you chat for the first time during a stream.
The personalized message can be set by using the channel point redemption. The message cannot be longer than 200 characters.
### TTS
There are 2 types of TTS supported:
- Microsoft Sam
- DecTalk
Microsoft s(c)am is currently only used by `a_n_i_v` and `a_n_e_e_v`. Whenever the aniv clankers say something MS Sam will pronounce their message.
Dectalk is available for 25k channel points. If your message is longer than 2 minutes your message will get scammed. I'm not sorry, 100% deserved.
### Chatterbot/streamerbot ### Chatterbot/streamerbot
This depends on if the `CHATTER_IS_STREAMER` environment variable is set. This depends on if the `CHATTER_IS_STREAMER` environment variable is set.
@@ -150,6 +170,7 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
`anivtimeouts`|Get the amount of timeouts, dodges and dodge percentage from aniv timeouts [(info)](#aniv-timeouts)|anyone|`anivtimeouts` `anivtimeout`|:white_check_mark: `anivtimeouts`|Get the amount of timeouts, dodges and dodge percentage from aniv timeouts [(info)](#aniv-timeouts)|anyone|`anivtimeouts` `anivtimeout`|:white_check_mark:
`racetime`|Get the racetime.gg room the streamer is currently in. Needs to have twitch linked to racetime account|anyone|`racetime` `raceroom`|:white_check_mark: `racetime`|Get the racetime.gg room the streamer is currently in. Needs to have twitch linked to racetime account|anyone|`racetime` `raceroom`|:white_check_mark:
`randomchatter`|Get a random chatter for whatever reason|moderators|`randomchatter`|:white_check_mark: `randomchatter`|Get a random chatter for whatever reason|moderators|`randomchatter`|:white_check_mark:
`economy`|Get a count of all items in circulation|anyone|`economy` `eco`|:white_check_mark:
### Qweribucks/Item commands ### Qweribucks/Item commands
@@ -186,7 +207,7 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
`getadmins`|Get a list of every admin in the channel|anyone|`getadmins`|:x: `getadmins`|Get a list of every admin in the channel|anyone|`getadmins`|:x:
`itemlock {target}`|Toggle the itemlock on the specified target|moderator|`itemlock`|:x: `itemlock {target}`|Toggle the itemlock on the specified target|moderator|`itemlock`|:x:
`testcheer {amount} [args]`|Create a fake cheering event|streamer/chatterbot|`testcheer`|:x: `testcheer {amount} [args]`|Create a fake cheering event|streamer/chatterbot|`testcheer`|:x:
`addinvuln {target}`|Adds an invuln user|moderator|`addinvuln`|:x: `addinvuln {target}`|Adds an invuln user and wipes the user's inventory and wallet|moderator|`addinvuln`|:x:
`removeinvuln {target}`|Removes an invuln user|moderator|`removeinvuln`|:x: `removeinvuln {target}`|Removes an invuln user|moderator|`removeinvuln`|:x:
`addbot {target}`|Adds bot status to a specific chatter|streamer/chatterbot|`addbot`|:x: `addbot {target}`|Adds bot status to a specific chatter|streamer/chatterbot|`addbot`|:x:
`removebot {target}`|Removes bot status from a specific chatter|streamer/chatterbot|`removebot`|:x: `removebot {target}`|Removes bot status from a specific chatter|streamer/chatterbot|`removebot`|:x:
@@ -205,7 +226,7 @@ NAME|COMMAND|FUNCTION|ALIASES|COST
-|-|-|-|- -|-|-|-|-
Blaster|`blaster {target}`|Times targeted user out for 60 seconds|`blaster` `blast`|100 Blaster|`blaster {target}`|Times targeted user out for 60 seconds|`blaster` `blast`|100
Grenade|`grenade`|Times a random vulnerable chatter out for 60 seconds|`grenade`|99 Grenade|`grenade`|Times a random vulnerable chatter out for 60 seconds|`grenade`|99
Silver Bullet|`silverbullet [target]`|Times targeted or random vulnerable user out for 30 minutes|`silverbullet` `execute` `{blastin}`|666 Silver Bullet|`silverbullet [target]`|Times targeted or random vulnerable user out for 30 minutes|`silverbullet` `execute` `{blastin}` `{fuck}`|666
TNT|`tnt`|Give 5-10 random chatters 60 second timeouts|`tnt`|1000 TNT|`tnt`|Give 5-10 random chatters 60 second timeouts|`tnt`|1000
## Cheers ## Cheers
@@ -223,9 +244,11 @@ NAME|AMOUNT|USAGE|FUNCTION
NAME|COST|DESCRIPTION|INTERNALNAME NAME|COST|DESCRIPTION|INTERNALNAME
-|-|-|- -|-|-|-
`Dectalk TTS`|25000|Play a custom dectalk TTS. If the sound is too long you WILL get scammed.|`dectalk`
`Set welcome message`|15000|Set the message the bot will say when you first chat during a stream (character limit is 200)|`setwelcomemsg`
`FREE MONEY`|1000|Get 100 qbucks|`qbucksredeem` `FREE MONEY`|1000|Get 100 qbucks|`qbucksredeem`
`RIPBOZO`|500|Sound: Coffeezilla calls me a conman [(source)](https://www.youtube.com/watch?v=QRvEGn7i-wM)|`sfxripbozo` `RIPBOZO`|500|Sound: Coffeezilla calls me a conman [(source)](https://www.youtube.com/watch?v=QRvEGn7i-wM)|`sfxripbozo`
`Welcome to the Madhouse`|100|Sound: mrockstar20 says: "Welcome to the Madhouse"|`sfxmrockmadhouse` `Welcome to the Madhouse`|100|Sound: mrockstar20 says: "Welcome to the Madhouse"|`sfxmrockmadhouse`
`Eddie Scream`|100|Sound: Eddie screams|`sfxeddiescream` `Eddie Scream`|100|Sound: Eddie screams|`sfxeddiescream`
`Factorio Building Destroyed`|100|Sound: Factorio Building Destroyed alert|`sfxfactorioalert` `Factorio Building Destroyed`|100|Sound: Factorio Building Destroyed alert|`sfxfactorioalert`
`Fail`|100|Sound: Either the sad trumpet or trombone meme sound|`sfxFail` `Fail`|100|Sound: Either the sad trumpet or trombone meme sound|`sfxfail`

View File

@@ -1,5 +1,5 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json", "$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
"clientKind": "git", "clientKind": "git",

131
bun.lock
View File

@@ -6,58 +6,51 @@
"name": "qweribot", "name": "qweribot",
"dependencies": { "dependencies": {
"@fontsource/jersey-15": "^5.2.8", "@fontsource/jersey-15": "^5.2.8",
"@twurple/api": "7.4.0", "@twurple/api": "8.0.3",
"@twurple/auth": "^7.4.0", "@twurple/auth": "^8.0.3",
"@twurple/eventsub-http": "^7.4.0", "@twurple/eventsub-http": "^8.0.3",
"discord.js": "^14.24.0", "discord.js": "^14.25.1",
"drizzle-orm": "^0.44.6", "drizzle-orm": "^0.45.1",
"kleur": "^4.1.5",
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.3.8", "@biomejs/biome": "^2.3.13",
"@twurple/eventsub-ngrok": "^7.4.0", "@twurple/eventsub-ngrok": "^8.0.3",
"@types/bun": "latest", "@types/bun": "latest",
"drizzle-kit": "^0.31.5", "drizzle-kit": "^0.31.8",
"pg": "^8.16.3", "pg": "^8.17.2",
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.8.3", "typescript": "^5.9.3",
}, },
}, },
}, },
"packages": { "packages": {
"@biomejs/biome": ["@biomejs/biome@2.3.8", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.8", "@biomejs/cli-darwin-x64": "2.3.8", "@biomejs/cli-linux-arm64": "2.3.8", "@biomejs/cli-linux-arm64-musl": "2.3.8", "@biomejs/cli-linux-x64": "2.3.8", "@biomejs/cli-linux-x64-musl": "2.3.8", "@biomejs/cli-win32-arm64": "2.3.8", "@biomejs/cli-win32-x64": "2.3.8" }, "bin": { "biome": "bin/biome" } }, "sha512-Qjsgoe6FEBxWAUzwFGFrB+1+M8y/y5kwmg5CHac+GSVOdmOIqsAiXM5QMVGZJ1eCUCLlPZtq4aFAQ0eawEUuUA=="], "@biomejs/biome": ["@biomejs/biome@2.3.13", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.13", "@biomejs/cli-darwin-x64": "2.3.13", "@biomejs/cli-linux-arm64": "2.3.13", "@biomejs/cli-linux-arm64-musl": "2.3.13", "@biomejs/cli-linux-x64": "2.3.13", "@biomejs/cli-linux-x64-musl": "2.3.13", "@biomejs/cli-win32-arm64": "2.3.13", "@biomejs/cli-win32-x64": "2.3.13" }, "bin": { "biome": "bin/biome" } }, "sha512-Fw7UsV0UAtWIBIm0M7g5CRerpu1eKyKAXIazzxhbXYUyMkwNrkX/KLkGI7b+uVDQ5cLUMfOC9vR60q9IDYDstA=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HM4Zg9CGQ3txTPflxD19n8MFPrmUAjaC7PQdLkugeeC0cQ+PiVrd7i09gaBS/11QKsTDBJhVg85CEIK9f50Qww=="], "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0OCwP0/BoKzyJHnFdaTk/i7hIP9JHH9oJJq6hrSCPmJPo8JWcJhprK4gQlhFzrwdTBAW4Bjt/RmCf3ZZe59gwQ=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-lUDQ03D7y/qEao7RgdjWVGCu+BLYadhKTm40HkpJIi6kn8LSv5PAwRlew/DmwP4YZ9ke9XXoTIQDO1vAnbRZlA=="], "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-AGr8OoemT/ejynbIu56qeil2+F2WLkIjn2d8jGK1JkchxnMUhYOfnqc9sVzcRxpG9Ycvw4weQ5sprRvtb7Yhcw=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-Uo1OJnIkJgSgF+USx970fsM/drtPcQ39I+JO+Fjsaa9ZdCN1oysQmy6oAGbyESlouz+rzEckLTF6DS7cWse95g=="], "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-xvOiFkrDNu607MPMBUQ6huHmBG1PZLOrqhtK6pXJW3GjfVqJg0Z/qpTdhXfcqWdSZHcT+Nct2fOgewZvytESkw=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-PShR4mM0sjksUMyxbyPNMxoKFPVF48fU8Qe8Sfx6w6F42verbwRLbz+QiKNiDPRJwUoMG1nPM50OBL3aOnTevA=="], "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-TUdDCSY+Eo/EHjhJz7P2GnWwfqet+lFxBZzGHldrvULr59AgahamLs/N85SC4+bdF86EhqDuuw9rYLvLFWWlXA=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.8", "", { "os": "linux", "cpu": "x64" }, "sha512-QDPMD5bQz6qOVb3kiBui0zKZXASLo0NIQ9JVJio5RveBEFgDgsvJFUvZIbMbUZT3T00M/1wdzwWXk4GIh0KaAw=="], "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.13", "", { "os": "linux", "cpu": "x64" }, "sha512-s+YsZlgiXNq8XkgHs6xdvKDFOj/bwTEevqEY6rC2I3cBHbxXYU1LOZstH3Ffw9hE5tE1sqT7U23C00MzkXztMw=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.8", "", { "os": "linux", "cpu": "x64" }, "sha512-YGLkqU91r1276uwSjiUD/xaVikdxgV1QpsicT0bIA1TaieM6E5ibMZeSyjQ/izBn4tKQthUSsVZacmoJfa3pDA=="], "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.13", "", { "os": "linux", "cpu": "x64" }, "sha512-0bdwFVSbbM//Sds6OjtnmQGp4eUjOTt6kHvR/1P0ieR9GcTUAlPNvPC3DiavTqq302W34Ae2T6u5VVNGuQtGlQ=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-H4IoCHvL1fXKDrTALeTKMiE7GGWFAraDwBYFquE/L/5r1927Te0mYIGseXi4F+lrrwhSWbSGt5qPFswNoBaCxg=="], "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-QweDxY89fq0VvrxME+wS/BXKmqMrOTZlN9SqQ79kQSIc3FrEwvW/PvUegQF6XIVaekncDykB5dzPqjbwSKs9DA=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.8", "", { "os": "win32", "cpu": "x64" }, "sha512-RguzimPoZWtBapfKhKjcWXBVI91tiSprqdBYu7tWhgN8pKRZhw24rFeNZTNf6UiBfjCYCi9eFQs/JzJZIhuK4w=="], "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.13", "", { "os": "win32", "cpu": "x64" }, "sha512-trDw2ogdM2lyav9WFQsdsfdVy1dvZALymRpgmWsvSez0BJzBjulhOT/t+wyKeh3pZWvwP3VMs1SoOKwO3wecMQ=="],
"@d-fischer/cache-decorators": ["@d-fischer/cache-decorators@4.0.1", "", { "dependencies": { "@d-fischer/shared-utils": "^3.6.3", "tslib": "^2.6.2" } }, "sha512-HNYLBLWs/t28GFZZeqdIBqq8f37mqDIFO6xNPof94VjpKvuP6ROqCZGafx88dk5zZUlBfViV9jD8iNNlXfc4CA=="], "@d-fischer/cache-decorators": ["@d-fischer/cache-decorators@4.0.1", "", { "dependencies": { "@d-fischer/shared-utils": "^3.6.3", "tslib": "^2.6.2" } }, "sha512-HNYLBLWs/t28GFZZeqdIBqq8f37mqDIFO6xNPof94VjpKvuP6ROqCZGafx88dk5zZUlBfViV9jD8iNNlXfc4CA=="],
"@d-fischer/cross-fetch": ["@d-fischer/cross-fetch@5.0.5", "", { "dependencies": { "node-fetch": "^2.6.12" } }, "sha512-symjDUPInTrkfIsZc2n2mo9hiAJLcTJsZkNICjZajEWnWpJ3s3zn50/FY8xpNUAf5w3eFuQii2wxztTGpvG1Xg=="],
"@d-fischer/detect-node": ["@d-fischer/detect-node@3.0.1", "", {}, "sha512-0Rf3XwTzuTh8+oPZW9SfxTIiL+26RRJ0BRPwj5oVjZFyFKmsj9RGfN2zuTRjOuA3FCK/jYm06HOhwNK+8Pfv8w=="], "@d-fischer/detect-node": ["@d-fischer/detect-node@3.0.1", "", {}, "sha512-0Rf3XwTzuTh8+oPZW9SfxTIiL+26RRJ0BRPwj5oVjZFyFKmsj9RGfN2zuTRjOuA3FCK/jYm06HOhwNK+8Pfv8w=="],
"@d-fischer/logger": ["@d-fischer/logger@4.2.3", "", { "dependencies": { "@d-fischer/detect-node": "^3.0.1", "@d-fischer/shared-utils": "^3.2.0", "tslib": "^2.0.3" } }, "sha512-mJUx9OgjrNVLQa4od/+bqnmD164VTCKnK5B4WOW8TX5y/3w2i58p+PMRE45gUuFjk2BVtOZUg55JQM3d619fdw=="], "@d-fischer/logger": ["@d-fischer/logger@4.2.3", "", { "dependencies": { "@d-fischer/detect-node": "^3.0.1", "@d-fischer/shared-utils": "^3.2.0", "tslib": "^2.0.3" } }, "sha512-mJUx9OgjrNVLQa4od/+bqnmD164VTCKnK5B4WOW8TX5y/3w2i58p+PMRE45gUuFjk2BVtOZUg55JQM3d619fdw=="],
"@d-fischer/qs": ["@d-fischer/qs@7.0.2", "", {}, "sha512-yAu3xDooiL+ef84Jo8nLjDjWBRk7RXk163Y6aTvRB7FauYd3spQD/dWvgT7R4CrN54Juhrrc3dMY7mc+jZGurQ=="],
"@d-fischer/rate-limiter": ["@d-fischer/rate-limiter@1.1.0", "", { "dependencies": { "@d-fischer/logger": "^4.2.3", "@d-fischer/shared-utils": "^3.6.3", "tslib": "^2.6.2" } }, "sha512-O5HgACwApyCZhp4JTEBEtbv/W3eAwEkrARFvgWnEsDmXgCMWjIHwohWoHre5BW6IYXFSHBGsuZB/EvNL3942kQ=="], "@d-fischer/rate-limiter": ["@d-fischer/rate-limiter@1.1.0", "", { "dependencies": { "@d-fischer/logger": "^4.2.3", "@d-fischer/shared-utils": "^3.6.3", "tslib": "^2.6.2" } }, "sha512-O5HgACwApyCZhp4JTEBEtbv/W3eAwEkrARFvgWnEsDmXgCMWjIHwohWoHre5BW6IYXFSHBGsuZB/EvNL3942kQ=="],
"@d-fischer/raw-body": ["@d-fischer/raw-body@2.4.3", "", { "dependencies": { "bytes": "3.1.0", "http-errors": "1.7.3", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-rtPTezQLROnTDdRij0Vo5OJ41aGvfKj9pQ7CkzFssQy+Jyc9BUVLV/DXLIGgvEGUaWt09Jq3im4WgvvPYqTomw=="],
"@d-fischer/shared-utils": ["@d-fischer/shared-utils@3.6.4", "", { "dependencies": { "tslib": "^2.4.1" } }, "sha512-BPkVLHfn2Lbyo/ENDBwtEB8JVQ+9OzkjJhUunLaxkw4k59YFlQxUUwlDBejVSFcpQT0t+D3CQlX+ySZnQj0wxw=="], "@d-fischer/shared-utils": ["@d-fischer/shared-utils@3.6.4", "", { "dependencies": { "tslib": "^2.4.1" } }, "sha512-BPkVLHfn2Lbyo/ENDBwtEB8JVQ+9OzkjJhUunLaxkw4k59YFlQxUUwlDBejVSFcpQT0t+D3CQlX+ySZnQj0wxw=="],
"@d-fischer/typed-event-emitter": ["@d-fischer/typed-event-emitter@3.3.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-OvSEOa8icfdWDqcRtjSEZtgJTFOFNgTjje7zaL0+nAtu2/kZtRCSK5wUMrI/aXtCH8o0Qz2vA8UqkhWUTARFQQ=="], "@d-fischer/typed-event-emitter": ["@d-fischer/typed-event-emitter@3.3.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-OvSEOa8icfdWDqcRtjSEZtgJTFOFNgTjje7zaL0+nAtu2/kZtRCSK5wUMrI/aXtCH8o0Qz2vA8UqkhWUTARFQQ=="],
@@ -66,11 +59,11 @@
"@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="], "@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="],
"@discordjs/formatters": ["@discordjs/formatters@0.6.1", "", { "dependencies": { "discord-api-types": "^0.38.1" } }, "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg=="], "@discordjs/formatters": ["@discordjs/formatters@0.6.2", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ=="],
"@discordjs/rest": ["@discordjs/rest@2.6.0", "", { "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.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w=="], "@discordjs/rest": ["@discordjs/rest@2.6.0", "", { "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.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w=="],
"@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="], "@discordjs/util": ["@discordjs/util@1.2.0", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg=="],
"@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.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.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="], "@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.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.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="],
@@ -168,23 +161,23 @@
"@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="], "@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],
"@twurple/api": ["@twurple/api@7.4.0", "", { "dependencies": { "@d-fischer/cache-decorators": "^4.0.0", "@d-fischer/cross-fetch": "^5.0.1", "@d-fischer/detect-node": "^3.0.1", "@d-fischer/logger": "^4.2.1", "@d-fischer/rate-limiter": "^1.1.0", "@d-fischer/shared-utils": "^3.6.1", "@d-fischer/typed-event-emitter": "^3.3.1", "@twurple/api-call": "7.4.0", "@twurple/common": "7.4.0", "retry": "^0.13.1", "tslib": "^2.0.3" }, "peerDependencies": { "@twurple/auth": "7.4.0" } }, "sha512-RlXLs4ZvS8n0+iIk7YyVDwrjhlwpn+N+h7fX5Q61HoxlmzoCShmnnFo03abYw9i8Cc3deGpbQATOSVmigXM4qg=="], "@twurple/api": ["@twurple/api@8.0.3", "", { "dependencies": { "@d-fischer/cache-decorators": "^4.0.0", "@d-fischer/detect-node": "^3.0.1", "@d-fischer/logger": "^4.2.1", "@d-fischer/rate-limiter": "^1.1.0", "@d-fischer/shared-utils": "^3.6.1", "@d-fischer/typed-event-emitter": "^3.3.1", "@twurple/api-call": "8.0.3", "@twurple/common": "8.0.3", "retry": "^0.13.1", "tslib": "^2.0.3" }, "peerDependencies": { "@twurple/auth": "8.0.3" } }, "sha512-vnqVi9YlNDbCqgpUUvTIq4sDitKCY0dkTw9zPluZvRNqUB1eCsuoaRNW96HQDhKtA9P4pRzwZ8xU7v/1KU2ytg=="],
"@twurple/api-call": ["@twurple/api-call@7.4.0", "", { "dependencies": { "@d-fischer/cross-fetch": "^5.0.1", "@d-fischer/qs": "^7.0.2", "@d-fischer/shared-utils": "^3.6.1", "@twurple/common": "7.4.0", "tslib": "^2.0.3" } }, "sha512-WNxvjp/hMqZREElbvE4rHMyUIrHdGY5cbG8xbqgSM9CESFvJ1wm5BubhyANOyKd1TxABacLddbfbO//Fz9YHgA=="], "@twurple/api-call": ["@twurple/api-call@8.0.3", "", { "dependencies": { "@d-fischer/shared-utils": "^3.6.1", "@twurple/common": "8.0.3", "tslib": "^2.0.3" } }, "sha512-/5DBTqFjpYB+qqOkkFzoTWE79a7+I8uLXmBIIIYjGoq/CIPxKcHnlemXlU8cQhTr87PVa3th8zJXGYiNkpRx8w=="],
"@twurple/auth": ["@twurple/auth@7.4.0", "", { "dependencies": { "@d-fischer/logger": "^4.2.1", "@d-fischer/shared-utils": "^3.6.1", "@d-fischer/typed-event-emitter": "^3.3.1", "@twurple/api-call": "7.4.0", "@twurple/common": "7.4.0", "tslib": "^2.0.3" } }, "sha512-WAQV6nJGkfY7r2BkRYhnzUpdfozLvjNsCxkyNVprl4dCWdJzccnTvqkKTdDRJc5ZJxDVaB9Drzwx9/fCp/gRDA=="], "@twurple/auth": ["@twurple/auth@8.0.3", "", { "dependencies": { "@d-fischer/logger": "^4.2.1", "@d-fischer/shared-utils": "^3.6.1", "@d-fischer/typed-event-emitter": "^3.3.1", "@twurple/api-call": "8.0.3", "@twurple/common": "8.0.3", "tslib": "^2.0.3" } }, "sha512-Xlv+WNXmGQir4aBXYeRCqdno5XurA6jzYTIovSEHa7FZf3AMHMFqtzW7yqTCUn4iOahfUSA2TIIxmxFM0wis0g=="],
"@twurple/common": ["@twurple/common@7.4.0", "", { "dependencies": { "@d-fischer/shared-utils": "^3.6.1", "klona": "^2.0.4", "tslib": "^2.0.3" } }, "sha512-lX5cVkYar6jGvni6iLmMYjhxH1oPSl2v7XVeZ4C7U1GbLz/Jwk0L0uldQNGUIf9gpRHPY+TXRlk0UIpz2yo8DA=="], "@twurple/common": ["@twurple/common@8.0.3", "", { "dependencies": { "@d-fischer/shared-utils": "^3.6.1", "klona": "^2.0.4", "tslib": "^2.0.3" } }, "sha512-JQ2lb5qSFT21Y9qMfIouAILb94ppedLHASq49Fe/AP8oq0k3IC9Q7tX2n6tiMzGWqn+n8MnONUpMSZ6FhulMXA=="],
"@twurple/eventsub-base": ["@twurple/eventsub-base@7.4.0", "", { "dependencies": { "@d-fischer/logger": "^4.2.1", "@d-fischer/shared-utils": "^3.6.1", "@d-fischer/typed-event-emitter": "^3.3.0", "@twurple/api": "7.4.0", "@twurple/auth": "7.4.0", "@twurple/common": "7.4.0", "tslib": "^2.0.3" } }, "sha512-Umx0kNZKxBUTF2/MHAlnnCuNPs8Tl1Aw8EzDJI2AW10tOiWvgeCR889fKCFBPlHXvcMYSEvsItkX+pXeZ8GkeQ=="], "@twurple/eventsub-base": ["@twurple/eventsub-base@8.0.3", "", { "dependencies": { "@d-fischer/logger": "^4.2.1", "@d-fischer/shared-utils": "^3.6.1", "@d-fischer/typed-event-emitter": "^3.3.0", "@twurple/api": "8.0.3", "@twurple/auth": "8.0.3", "@twurple/common": "8.0.3", "tslib": "^2.0.3" } }, "sha512-59G5xJbHWLTSO6NAgwtkHPfIlmdjrABgiEumFnHhNusMbLM9qdA+kLcW5NB2NImNliytl6zZtqY92FInzUE6NA=="],
"@twurple/eventsub-http": ["@twurple/eventsub-http@7.4.0", "", { "dependencies": { "@d-fischer/logger": "^4.2.1", "@d-fischer/raw-body": "^2.4.3", "@d-fischer/shared-utils": "^3.6.1", "@d-fischer/typed-event-emitter": "^3.3.0", "@twurple/auth": "7.4.0", "@twurple/common": "7.4.0", "@twurple/eventsub-base": "7.4.0", "@types/express-serve-static-core": "^4.17.24", "httpanda": "^0.4.6", "tslib": "^2.0.3" }, "peerDependencies": { "@twurple/api": "7.4.0" } }, "sha512-Ua8cP4OPfgyUxlNG2BSJ2Ck02Axk3YXIBoQqoTURlSI0wix8+kTK0X4QuDvxicPxn9iRV1prNimOSvt4HXSkrQ=="], "@twurple/eventsub-http": ["@twurple/eventsub-http@8.0.3", "", { "dependencies": { "@d-fischer/logger": "^4.2.1", "@d-fischer/shared-utils": "^3.6.1", "@d-fischer/typed-event-emitter": "^3.3.0", "@twurple/auth": "8.0.3", "@twurple/common": "8.0.3", "@twurple/eventsub-base": "8.0.3", "@types/express-serve-static-core": "^5.1.0", "httpanda": "^0.4.6", "raw-body": "^3.0.2", "tslib": "^2.0.3" }, "peerDependencies": { "@twurple/api": "8.0.3" } }, "sha512-ds8l01GfsIC0hhILepv/UUn/Ix8s0wLg9aGy10xWaG9/Hlfe82NPI8gAg0LYsmlCsOADPwJZSckMTGPJrpw1Iw=="],
"@twurple/eventsub-ngrok": ["@twurple/eventsub-ngrok@7.4.0", "", { "dependencies": { "@d-fischer/shared-utils": "^3.6.1", "@ngrok/ngrok": "^0.5.1", "tslib": "^2.0.3" }, "peerDependencies": { "@twurple/api": "7.4.0", "@twurple/eventsub-http": "7.4.0" } }, "sha512-YPk4TtYmCFQwBuIUgEpd6D29wqhJJQq6fxjWG83E86lp3vfcaY6aq7kE39kSqTz8TDkx62xnSH9lsMmZImIQ0w=="], "@twurple/eventsub-ngrok": ["@twurple/eventsub-ngrok@8.0.3", "", { "dependencies": { "@d-fischer/shared-utils": "^3.6.1", "@ngrok/ngrok": "^0.5.1", "tslib": "^2.0.3" }, "peerDependencies": { "@twurple/api": "8.0.3", "@twurple/eventsub-http": "8.0.3" } }, "sha512-wt4keLIivnEpv0EpQw1zgBD6tinaDmVf5VhvQqr8NABCpL4TuZNQAIveIUelHmY+phlISIX/42mvXqHNfmMTwg=="],
"@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="], "@types/bun": ["@types/bun@1.3.7", "", { "dependencies": { "bun-types": "1.3.7" } }, "sha512-lmNuMda+Z9b7tmhA0tohwy8ZWFSnmQm1UDWXtH5r9F7wZCfkeO3Jx7wKQ1EOiKq43yHts7ky6r8SDJQWRNupkA=="],
"@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.7", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg=="], "@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.1", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A=="],
"@types/node": ["@types/node@22.15.18", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg=="], "@types/node": ["@types/node@22.15.18", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg=="],
@@ -192,8 +185,6 @@
"@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="], "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
@@ -202,23 +193,21 @@
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="], "bun-types": ["bun-types@1.3.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-qyschsA03Qz+gou+apt6HNl6HnI+sJJLL4wLDke4iugsE6584CMupOtTY1n+2YC9nGVrEKUlTs99jjRLKgWnjQ=="],
"bytes": ["bytes@3.1.0", "", {}, "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"depd": ["depd@1.1.2", "", {}, "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"discord-api-types": ["discord-api-types@0.38.31", "", {}, "sha512-kC94ANsk8ackj8ENTuO8joTNEL0KtymVhHy9dyEC/s4QAZ7GCx40dYEzQaadyo8w+oP0X8QydE/nzAWRylTGtQ=="], "discord-api-types": ["discord-api-types@0.38.36", "", {}, "sha512-qrbUbjjwtyeBg5HsAlm1C859epfOyiLjPqAOzkdWlCNsZCWJrertnETF/NwM8H+waMFU58xGSc5eXUfXah+WTQ=="],
"discord.js": ["discord.js@14.24.0", "", { "dependencies": { "@discordjs/builders": "^1.13.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.1", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.31", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-KNq/ekT8bsmT3ZAfVre8cPbl+DfVYSdlLnDmGZPoz7Cw21LYeWHllRA9MivqNq5b1GPGAxGvyUN1vxbTb/PQWw=="], "discord.js": ["discord.js@14.25.1", "", { "dependencies": { "@discordjs/builders": "^1.13.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.33", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g=="],
"drizzle-kit": ["drizzle-kit@0.31.5", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-+CHgPFzuoTQTt7cOYCV6MOw2w8vqEn/ap1yv4bpZOWL03u7rlVRQhUY0WYT3rHsgVTXwYQDZaSUJSQrMBUKuWg=="], "drizzle-kit": ["drizzle-kit@0.31.8", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg=="],
"drizzle-orm": ["drizzle-orm@0.44.6", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-uy6uarrrEOc9K1u5/uhBFJbdF5VJ5xQ/Yzbecw3eAYOunv5FDeYkR2m8iitocdHBOHbvorviKOW5GVw0U1j4LQ=="], "drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="],
"esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="], "esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="],
@@ -228,16 +217,14 @@
"get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="], "get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="],
"http-errors": ["http-errors@1.7.3", "", { "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.1.1", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" } }, "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw=="], "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
"httpanda": ["httpanda@0.4.7", "", { "dependencies": { "@types/node": "^14.11.2", "tslib": "^2.0.3" } }, "sha512-NieTiR7kfOheL9OeEi6+JKFmJ2JP9ZRqUQ4tiXZ9J+EMMKxApHUQlEM5l4gZ+l67lxE9Er6oigZnujmhlodNCg=="], "httpanda": ["httpanda@0.4.7", "", { "dependencies": { "@types/node": "^14.11.2", "tslib": "^2.0.3" } }, "sha512-NieTiR7kfOheL9OeEi6+JKFmJ2JP9ZRqUQ4tiXZ9J+EMMKxApHUQlEM5l4gZ+l67lxE9Er6oigZnujmhlodNCg=="],
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
"klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="], "klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
@@ -248,19 +235,17 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], "pg": ["pg@8.17.2", "", { "dependencies": { "pg-connection-string": "^2.10.1", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw=="],
"pg": ["pg@8.16.3", "", { "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw=="], "pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="],
"pg-cloudflare": ["pg-cloudflare@1.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="], "pg-connection-string": ["pg-connection-string@2.10.1", "", {}, "sha512-iNzslsoeSH2/gmDDKiyMqF64DATUCWj3YJ0wP14kqcsf2TUklwimd+66yYojKwZCA7h2yRNLGug71hCBA2a4sw=="],
"pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="],
"pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
"pg-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="], "pg-pool": ["pg-pool@3.11.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w=="],
"pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="], "pg-protocol": ["pg-protocol@1.11.0", "", {}, "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="],
"pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
@@ -274,13 +259,15 @@
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
"retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"setprototypeof": ["setprototypeof@1.1.1", "", {}, "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="], "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@@ -288,11 +275,9 @@
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
"statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="], "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
"toidentifier": ["toidentifier@1.0.0", "", {}, "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="], "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="], "ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
@@ -306,18 +291,28 @@
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
"@discordjs/builders/@discordjs/formatters": ["@discordjs/formatters@0.6.1", "", { "dependencies": { "discord-api-types": "^0.38.1" } }, "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg=="],
"@discordjs/builders/@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="],
"@discordjs/builders/discord-api-types": ["discord-api-types@0.38.31", "", {}, "sha512-kC94ANsk8ackj8ENTuO8joTNEL0KtymVhHy9dyEC/s4QAZ7GCx40dYEzQaadyo8w+oP0X8QydE/nzAWRylTGtQ=="],
"@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
"@discordjs/rest/@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="],
"@discordjs/rest/discord-api-types": ["discord-api-types@0.38.31", "", {}, "sha512-kC94ANsk8ackj8ENTuO8joTNEL0KtymVhHy9dyEC/s4QAZ7GCx40dYEzQaadyo8w+oP0X8QydE/nzAWRylTGtQ=="],
"@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
"@discordjs/ws/@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="],
"@discordjs/ws/discord-api-types": ["discord-api-types@0.38.31", "", {}, "sha512-kC94ANsk8ackj8ENTuO8joTNEL0KtymVhHy9dyEC/s4QAZ7GCx40dYEzQaadyo8w+oP0X8QydE/nzAWRylTGtQ=="],
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
"httpanda/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], "httpanda/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="],

View File

@@ -8,7 +8,7 @@
in { in {
devShells."${system}" = { devShells."${system}" = {
default = pkgs.mkShell { default = pkgs.mkShell {
packages = with pkgs; [ bun nodejs deno ]; packages = with pkgs; [ bun biome ];
shellHook = '' shellHook = ''
echo Loaded the qweribot dev shell echo Loaded the qweribot dev shell
''; '';

View File

@@ -2,11 +2,11 @@
"name": "qweribot", "name": "qweribot",
"module": "src/index.ts", "module": "src/index.ts",
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.3.8", "@biomejs/biome": "^2.3.13",
"@twurple/eventsub-ngrok": "^7.4.0", "@twurple/eventsub-ngrok": "^8.0.3",
"@types/bun": "latest", "@types/bun": "latest",
"drizzle-kit": "^0.31.5", "drizzle-kit": "^0.31.8",
"pg": "^8.16.3" "pg": "^8.17.2"
}, },
"scripts": { "scripts": {
"check": "biome check && tsc -b", "check": "biome check && tsc -b",
@@ -26,11 +26,10 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@fontsource/jersey-15": "^5.2.8", "@fontsource/jersey-15": "^5.2.8",
"@twurple/api": "7.4.0", "@twurple/api": "8.0.3",
"@twurple/auth": "^7.4.0", "@twurple/auth": "^8.0.3",
"@twurple/eventsub-http": "^7.4.0", "@twurple/eventsub-http": "^8.0.3",
"discord.js": "^14.24.0", "discord.js": "^14.25.1",
"drizzle-orm": "^0.44.6", "drizzle-orm": "^0.45.1"
"kleur": "^4.1.5"
} }
} }

View File

@@ -9,7 +9,6 @@ import {
getAuthRecord, getAuthRecord,
updateAuthRecord, updateAuthRecord,
} from "db/dbAuth"; } from "db/dbAuth";
import kleur from "kleur";
import logger from "lib/logger"; import logger from "lib/logger";
async function initAuth( async function initAuth(
@@ -26,15 +25,10 @@ async function initAuth(
const state = Bun.randomUUIDv7().replace(/-/g, "").slice(0, 32).toUpperCase(); const state = Bun.randomUUIDv7().replace(/-/g, "").slice(0, 32).toUpperCase();
// Generate random state variable to prevent cross-site-scripting attacks // Generate random state variable to prevent cross-site-scripting attacks
const instruction = `Visit this URL as ${kleur const instruction = `Visit this URL as \x1b[3;4;1;95m${streamer ? "the streamer" : "the chatter"}\x1b[0;97m to authenticate the bot.`;
.red()
.underline()
.italic(
streamer ? "the streamer" : "the chatter",
)} to authenticate the bot.`;
logger.info(instruction); logger.info(instruction);
logger.info( logger.info(
`https://id.twitch.tv/oauth2/authorize?client_id=${clientId}&redirect_uri=${redirectURL}&response_type=code&scope=${requestedIntents.join("+")}&state=${state}`, `\x1b[3;4;1;95mhttps://id.twitch.tv/oauth2/authorize?client_id=${clientId}&redirect_uri=${redirectURL}&response_type=code&scope=${requestedIntents.join("+")}&state=${state}\x1b[0;97m`,
); );
const createCodePromise = () => { const createCodePromise = () => {
@@ -61,7 +55,7 @@ async function initAuth(
return new Response("Successfully authenticated!"); return new Response("Successfully authenticated!");
} else { } else {
return new Response( return new Response(
`Authentication attempt unsuccessful, please make sure the redirect url in the twitch developer console is set to ${redirectURL} and that the bot is listening to that url & port.`, `Authentication attempt unsuccessful, please make sure the redirect url in the twitch developer console is set to \x1b[3;4;1;95m${redirectURL}\x1b[0;97m and that the bot is listening to that url & port.`,
{ status: 400 }, { status: 400 },
); );
} }
@@ -112,13 +106,15 @@ export async function createAuthProvider(
}); });
authData.onRefresh(async (user, token) => { authData.onRefresh(async (user, token) => {
logger.ok(`Successfully refreshed auth for user ${user}`); logger.ok(
`Successfully refreshed auth for user \x1b[3;4;1;95m${user}\x1b[0;97m`,
);
await updateAuthRecord(user, token); await updateAuthRecord(user, token);
}); });
authData.onRefreshFailure((user, err) => { authData.onRefreshFailure((user, err) => {
logger.err( logger.err(
`Failed to refresh auth for user ${user}: ${err.name} ${err.message}`, `Failed to refresh auth for user \x1b[3;4;1;95m${user}\x1b[0;97m: ${err.name} ${err.message}`,
); );
}); });
@@ -149,7 +145,7 @@ export async function createAuthProvider(
await authData.refreshAccessTokenForUser(user.userId); await authData.refreshAccessTokenForUser(user.userId);
} catch (_err) { } catch (_err) {
logger.err( logger.err(
`Failed to refresh user ${user.userId}. Please restart the bot and re-authenticate it. Make sure the user that auths the bot and the user that's defined in .env are the same.`, `Failed to refresh user \x1b[3;4;1;95m${user.userId}\x1b[0;97m. Please restart the bot and re-authenticate it. Make sure the user that auths the bot and the user that's defined in .env are the same.`,
); );
await deleteAuthRecord(user.userId); await deleteAuthRecord(user.userId);
process.exit(1); process.exit(1);

View File

@@ -1,6 +1,8 @@
import { Cheer, handleNoTarget } from "cheers"; import { Cheer, handleNoTarget } from "cheers";
import { createCheerEventRecord } from "db/dbCheerEvents"; import {
import { createTimeoutRecord } from "db/dbTimeouts"; createCompensatedItemCheer,
createTimeoutEventCheer,
} from "db/CheerEvents";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { sendMessage } from "lib/commandUtils"; import { sendMessage } from "lib/commandUtils";
import { parseCheerArgs } from "lib/parseCommandArgs"; import { parseCheerArgs } from "lib/parseCommandArgs";
@@ -30,7 +32,8 @@ export default new Cheer({
} }
if (users.length === 0) { if (users.length === 0) {
await sendMessage("No vulnerable chatters"); await sendMessage("No vulnerable chatters");
await handleNoTarget(msg, user, ITEMNAME, true); const compensated = await handleNoTarget(msg, user, ITEMNAME, true);
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
return; return;
} }
target = users[Math.floor(Math.random() * users.length)]!; target = users[Math.floor(Math.random() * users.length)]!;
@@ -45,7 +48,8 @@ export default new Cheer({
target = await User.initUsername(args[0].toLowerCase()); target = await User.initUsername(args[0].toLowerCase());
} }
if (!target) { if (!target) {
await handleNoTarget(msg, user, ITEMNAME, false); const compensated = await handleNoTarget(msg, user, ITEMNAME);
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
return; return;
} }
await getUserRecord(target); await getUserRecord(target);
@@ -60,8 +64,7 @@ export default new Cheer({
sendMessage( sendMessage(
`KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`, `KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`,
), ),
createTimeoutRecord(user, target, ITEMNAME), createTimeoutEventCheer(user, target, "execute"),
createCheerEventRecord(user, ITEMNAME),
playAlert({ playAlert({
name: "userExecution", name: "userExecution",
user: user.displayName, user: user.displayName,
@@ -69,7 +72,8 @@ export default new Cheer({
}), }),
]); ]);
else { else {
await handleNoTarget(msg, user, ITEMNAME); const compensated = await handleNoTarget(msg, user, ITEMNAME);
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
switch (result.reason) { switch (result.reason) {
case "banned": case "banned":
await sendMessage( await sendMessage(

View File

@@ -1,6 +1,8 @@
import { Cheer, handleNoTarget } from "cheers"; import { Cheer, handleNoTarget } from "cheers";
import { createCheerEventRecord } from "db/dbCheerEvents"; import {
import { createTimeoutRecord } from "db/dbTimeouts"; createCompensatedItemCheer,
createTimeoutEventCheer,
} from "db/CheerEvents";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { sendMessage } from "lib/commandUtils"; import { sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
@@ -18,7 +20,8 @@ export default new Cheer({
const targets = await redis.keys(`user:*:vulnerable`); const targets = await redis.keys(`user:*:vulnerable`);
if (targets.length === 0) { if (targets.length === 0) {
await sendMessage("No vulnerable chatters to blow up!", msg.messageId); await sendMessage("No vulnerable chatters to blow up!", msg.messageId);
await handleNoTarget(msg, user, ITEMNAME); const compensated = await handleNoTarget(msg, user, ITEMNAME, true);
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
return; return;
} }
const selection = targets[Math.floor(Math.random() * targets.length)]!; const selection = targets[Math.floor(Math.random() * targets.length)]!;
@@ -32,8 +35,7 @@ export default new Cheer({
sendMessage( sendMessage(
`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`, `wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`,
), ),
createTimeoutRecord(user, target!, ITEMNAME), createTimeoutEventCheer(user, target!, "grenade"),
createCheerEventRecord(user, ITEMNAME),
playAlert({ playAlert({
name: "grenadeExplosion", name: "grenadeExplosion",
user: user.displayName, user: user.displayName,

View File

@@ -63,13 +63,13 @@ export async function handleNoTarget(
user: User, user: User,
itemname: items, itemname: items,
silent = true, silent = true,
) { ): Promise<boolean> {
if (await user.itemLock()) { if (await user.itemLock()) {
await sendMessage( await sendMessage(
`Cannot give ${user.displayName} a ${itemname} (itemlock)`, `Cannot give ${user.displayName} a ${itemname} (itemlock)`,
msg.messageId, msg.messageId,
); );
return; return false;
} }
await user.setLock(); await user.setLock();
const userRecord = await getUserRecord(user); const userRecord = await getUserRecord(user);
@@ -80,4 +80,5 @@ export async function handleNoTarget(
); );
await changeItemCount(user, userRecord, itemname, 1); await changeItemCount(user, userRecord, itemname, 1);
await user.clearLock(); await user.clearLock();
return true;
} }

View File

@@ -1,6 +1,5 @@
import { Cheer } from "cheers"; import { Cheer } from "cheers";
import { createCheerEventRecord } from "db/dbCheerEvents"; import { createTimeoutEventCheer } from "db/CheerEvents";
import { createTimeoutRecord } from "db/dbTimeouts";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { sendMessage } from "lib/commandUtils"; import { sendMessage } from "lib/commandUtils";
import { parseCheerArgs } from "lib/parseCommandArgs"; import { parseCheerArgs } from "lib/parseCommandArgs";
@@ -57,8 +56,7 @@ export default new Cheer({
sendMessage( sendMessage(
`KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`, `KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`,
), ),
createTimeoutRecord(user, target, "realsilverbullet"), createTimeoutEventCheer(user, target, "realsilverbullet"),
createCheerEventRecord(user, "realsilverbullet"),
playAlert({ playAlert({
name: "userExecution", name: "userExecution",
user: user.displayName, user: user.displayName,

View File

@@ -1,6 +1,5 @@
import { Cheer } from "cheers"; import { Cheer } from "cheers";
import { createCheerEventRecord } from "db/dbCheerEvents"; import { createSuperLootEvent } from "db/CheerEvents";
import { createGetLootRecord } from "db/dbGetLoot";
import { getUserRecord, updateUserRecord } from "db/dbUser"; import { getUserRecord, updateUserRecord } from "db/dbUser";
import itemMap, { type inventory, type items } from "items"; import itemMap, { type inventory, type items } from "items";
import { sendMessage } from "lib/commandUtils"; import { sendMessage } from "lib/commandUtils";
@@ -12,10 +11,10 @@ export default new Cheer({
amount: 150, amount: 150,
isItem: true, isItem: true,
async execute(msg, user) { async execute(msg, user) {
if (!(await redis.exists("streamIsLive"))) { // if (!(await redis.exists("streamIsLive"))) {
await sendMessage(`No loot while stream is offline`, msg.messageId); // await sendMessage(`No loot while stream is offline`, msg.messageId);
return; // return;
} // }
if (await user.itemLock()) { if (await user.itemLock()) {
await sendMessage(`Cannot get loot (itemlock)`, msg.messageId); await sendMessage(`Cannot get loot (itemlock)`, msg.messageId);
return; return;
@@ -81,8 +80,7 @@ export default new Cheer({
await Promise.all([ await Promise.all([
updateUserRecord(user, userData), updateUserRecord(user, userData),
sendMessage(message, msg.messageId), sendMessage(message, msg.messageId),
createCheerEventRecord(user, "superloot"), createSuperLootEvent(user, gainedqbucks, itemDiff),
createGetLootRecord(user, gainedqbucks, itemDiff, "superloot"),
user.clearLock(), user.clearLock(),
]); ]);
}, },

View File

@@ -1,6 +1,8 @@
import { Cheer, handleNoTarget } from "cheers"; import { Cheer, handleNoTarget } from "cheers";
import { createCheerEventRecord } from "db/dbCheerEvents"; import {
import { createTimeoutRecord } from "db/dbTimeouts"; createTimeoutEventCheer,
createCompensatedItemCheer,
} from "db/CheerEvents";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { sendMessage } from "lib/commandUtils"; import { sendMessage } from "lib/commandUtils";
import { parseCheerArgs } from "lib/parseCommandArgs"; import { parseCheerArgs } from "lib/parseCommandArgs";
@@ -17,12 +19,14 @@ export default new Cheer({
async execute(msg, user) { async execute(msg, user) {
const args = parseCheerArgs(msg.messageText); const args = parseCheerArgs(msg.messageText);
if (!args[0]) { if (!args[0]) {
await handleNoTarget(msg, user, ITEMNAME, false); const compensated = await handleNoTarget(msg, user, ITEMNAME, false);
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
return; return;
} }
const target = await User.initUsername(args[0].toLowerCase()); const target = await User.initUsername(args[0].toLowerCase());
if (!target) { if (!target) {
await handleNoTarget(msg, user, ITEMNAME, false); const compensated = await handleNoTarget(msg, user, ITEMNAME, false);
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
return; return;
} }
await getUserRecord(target); await getUserRecord(target);
@@ -37,8 +41,7 @@ export default new Cheer({
sendMessage( sendMessage(
`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`, `GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`,
), ),
createTimeoutRecord(user, target, ITEMNAME), createTimeoutEventCheer(user, target, "timeout"),
createCheerEventRecord(user, ITEMNAME),
playAlert({ playAlert({
name: "userBlast", name: "userBlast",
user: user.displayName, user: user.displayName,
@@ -46,7 +49,8 @@ export default new Cheer({
}), }),
]); ]);
else { else {
await handleNoTarget(msg, user, ITEMNAME); const compensated = await handleNoTarget(msg, user, ITEMNAME);
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
switch (result.reason) { switch (result.reason) {
case "banned": case "banned":
await sendMessage( await sendMessage(

View File

@@ -1,6 +1,8 @@
import { Cheer, handleNoTarget } from "cheers"; import { Cheer, handleNoTarget } from "cheers";
import { createCheerEventRecord } from "db/dbCheerEvents"; import {
import { createTimeoutRecord } from "db/dbTimeouts"; createTimeoutEventCheer,
createCompensatedItemCheer,
} from "db/CheerEvents";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { getTNTTargets } from "items/tnt"; import { getTNTTargets } from "items/tnt";
import { sendMessage } from "lib/commandUtils"; import { sendMessage } from "lib/commandUtils";
@@ -21,7 +23,8 @@ export default new Cheer({
.then((a) => a.map((b) => b.slice(5, -11))); .then((a) => a.map((b) => b.slice(5, -11)));
if (vulntargets.length === 0) { if (vulntargets.length === 0) {
await sendMessage("No vulnerable chatters to blow up", msg.messageId); await sendMessage("No vulnerable chatters to blow up", msg.messageId);
await handleNoTarget(msg, user, ITEMNAME); const compensated = await handleNoTarget(msg, user, ITEMNAME, true);
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
return; return;
} }
const targets = getTNTTargets(vulntargets); const targets = getTNTTargets(vulntargets);
@@ -36,13 +39,12 @@ export default new Cheer({
sendMessage( sendMessage(
`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`, `wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`,
), ),
createTimeoutRecord(user, target!, ITEMNAME),
]); ]);
}), }),
); );
await Promise.all([ await Promise.all([
createCheerEventRecord(user, ITEMNAME), createTimeoutEventCheer(user, targets, "tnt"),
playAlert({ playAlert({
name: "tntExplosion", name: "tntExplosion",
user: user.displayName, user: user.displayName,

View File

@@ -1,3 +1,5 @@
import { getUserRecord, updateUserRecord } from "db/dbUser";
import { emptyInventory } from "items";
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import { addInvuln } from "lib/invuln"; import { addInvuln } from "lib/invuln";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
@@ -20,12 +22,16 @@ export default new Command({
return; return;
} }
const data = await addInvuln(target.id); const data = await addInvuln(target.id);
if (data === "OK") if (data === "OK") {
const userRecord = await getUserRecord(target);
userRecord.inventory = emptyInventory;
userRecord.balance = 0;
await updateUserRecord(target, userRecord);
await sendMessage( await sendMessage(
`${target.displayName} is now an invuln`, `${target.displayName} is now an invuln. Their inventory and wallet have been wiped`,
msg.messageId, msg.messageId,
); );
else } else
await sendMessage( await sendMessage(
`${target.displayName} is already an invuln`, `${target.displayName} is already an invuln`,
msg.messageId, msg.messageId,

View File

@@ -40,7 +40,7 @@ export default new Command({
const selection = namedRedeems.get(args[0]); const selection = namedRedeems.get(args[0]);
if (!selection) { if (!selection) {
await sendMessage( await sendMessage(
`Redeem ${args[0]} doesn't exist. The internal names for redeems are here: https://github.com/qwerinope/qweribot#point-redeems`, `Redeem ${args[0]} doesn't exist. The internal names for redeems are here: https://gitlab.com/qwerinope/qweribot#point-redeems`,
msg.messageId, msg.messageId,
); );
return; return;

22
src/commands/economy.ts Normal file
View File

@@ -0,0 +1,22 @@
import { getTotalItemCounts } from "db/dbUser";
import itemAliasMap from "items";
import { Command, sendMessage } from "lib/commandUtils";
export default new Command({
name: "economy",
aliases: ["economy", "eco"],
usertype: "chatter",
execution: async (msg) => {
const allitems = await getTotalItemCounts();
const itemList = Object.entries(allitems)
.sort(([, a], [, b]) => b - a)
.map(([item, count]) => {
const itemobj = itemAliasMap.get(item);
if (itemobj) return `${itemobj.prettyName}: ${count}`;
return `${item}: ${count}`; // Fallback if an item doesn't have their name as an alias
})
.join(" | ");
await sendMessage(`Total Items in circulation: ${itemList}`, msg.messageId);
},
});

View File

@@ -39,7 +39,7 @@ export default new Command({
const selection = namedRedeems.get(args[0]); const selection = namedRedeems.get(args[0]);
if (!selection) { if (!selection) {
await sendMessage( await sendMessage(
`Redeem ${args[0]} doesn't exist. The internal names for redeems are here: https://github.com/qwerinope/qweribot#point-redeems`, `Redeem ${args[0]} doesn't exist. The internal names for redeems are here: https://gitlab.com/qwerinope/qweribot#point-redeems`,
msg.messageId, msg.messageId,
); );
return; return;

View File

@@ -12,7 +12,7 @@ export default new Command({
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { if (!args[0]) {
await sendMessage( await sendMessage(
`A full list of cheers can be found here: https://github.com/qwerinope/qweribot#cheers`, `A full list of cheers can be found here: https://gitlab.com/qwerinope/qweribot#cheers`,
msg.messageId, msg.messageId,
); );
return; return;

View File

@@ -12,7 +12,7 @@ export default new Command({
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { if (!args[0]) {
await sendMessage( await sendMessage(
`A full list of commands can be found here: https://github.com/qwerinope/qweribot#commands-1`, `A full list of commands can be found here: https://gitlab.com/qwerinope/qweribot#commands-1`,
msg.messageId, msg.messageId,
); );
return; return;

View File

@@ -1,5 +1,5 @@
import { createGetLootRecord } from "db/dbGetLoot";
import { getUserRecord, updateUserRecord } from "db/dbUser"; import { getUserRecord, updateUserRecord } from "db/dbUser";
import { createGetLootEvent } from "db/LootEvents";
import itemMap, { type inventory, type items } from "items"; import itemMap, { type inventory, type items } from "items";
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import { buildTimeString } from "lib/dateManager"; import { buildTimeString } from "lib/dateManager";
@@ -132,8 +132,11 @@ export default new Command({
await Promise.all([ await Promise.all([
updateUserRecord(user, userData), updateUserRecord(user, userData),
sendMessage(message, msg.messageId), sendMessage(message, msg.messageId),
createGetLootRecord(user, gainedqbucks, itemDiff, "getloot"), createGetLootEvent(user, gainedqbucks, itemDiff, "getloot"),
user.clearLock(), user.clearLock(),
]); ]);
if (itemstrings.length === 0 && gainedqbucks < 100)
await sendMessage("YEOP THAT'S A SCAMBOX YEOP");
}, },
}); });

View File

@@ -1,8 +1,10 @@
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import items, { changeItemCount } from "items"; import items, { changeItemCount } from "items";
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import { ANIVNAMES } from "lib/handleAnivMessage";
import logger from "lib/logger"; import logger from "lib/logger";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { timeout } from "lib/timeout";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
@@ -10,6 +12,11 @@ export default new Command({
aliases: ["give"], aliases: ["give"],
usertype: "chatter", usertype: "chatter",
execution: async (msg, user) => { execution: async (msg, user) => {
if (Array.from<string>(ANIVNAMES).includes(msg.chatterName)) {
await sendMessage("CLANKERS CAN'T GIVE ITEMS UltraMad UltraMad UltraMad");
await timeout(user, "STUPID CLANKER", 30);
return;
}
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { if (!args[0]) {
await sendMessage("Please specify a user", msg.messageId); await sendMessage("Please specify a user", msg.messageId);

View File

@@ -25,6 +25,10 @@ export default new Command({
} }
const selection = items.get(messagequery[0].toLowerCase()); const selection = items.get(messagequery[0].toLowerCase());
if (messagequery[0].toLowerCase() === "lootbox") { if (messagequery[0].toLowerCase() === "lootbox") {
if (await redis.sismember("disabledcommands", "getloot")) {
await sendMessage("Lootboxes are currently disabled", msg.messageId);
return;
}
await getloot.execute(msg, user); await getloot.execute(msg, user);
return; return;
} }

View File

@@ -19,7 +19,7 @@ export async function connectionCheck() {
redisstatus = true; redisstatus = true;
} catch {} } catch {}
logger.info( logger.info(
`Currently using the "${process.env.NODE_ENV ?? "production"}" database`, `Currently using the \x1b[3;4;1;95m"${process.env.NODE_ENV ?? "production"}"\x1b[0;97m database`,
); );
pgstatus pgstatus
? logger.ok(`Postgresql status: good`) ? logger.ok(`Postgresql status: good`)

151
src/db/CheerEvents.ts Normal file
View File

@@ -0,0 +1,151 @@
import type { cheers } from "cheers";
import db from "db/connection";
import { cheerEvents, events, timeouts } from "db/schema";
import type { inventory, items } from "items";
import type User from "user";
import { createGetLootEvent } from "./LootEvents";
import { eq } from "drizzle-orm";
/**
* Use this function to create a cheer event with timeouts
* This can only be used if the cheer succeeded
*
* The target can either be a single User object or an array of targetIDs
*/
export async function createTimeoutEventCheer(
user: User,
target: User | string[],
event: cheers,
) {
const userInt = parseInt(user.id, 10);
return await db.transaction(async (tx) => {
const cheerEventRecord = await tx
.insert(cheerEvents)
.values({
user: userInt,
event,
status: "success",
})
.returning();
if (Array.isArray(target))
target.map(
async (ripbozo) =>
await tx.insert(timeouts).values({
user: userInt,
target: parseInt(ripbozo, 10),
item: event,
cheer: cheerEventRecord[0]?.id,
}),
);
else
await tx.insert(timeouts).values({
user: userInt,
target: parseInt(target.id, 10),
item: event,
cheer: cheerEventRecord[0]?.id,
});
await tx.insert(events).values({
user: userInt,
cheer: cheerEventRecord[0]?.id,
});
if (!cheerEventRecord[0]) {
tx.rollback();
return false;
}
});
}
/**
* Use this function to create a cheer event without timeouts
* This can only be used if the cheer succeeded
*/
export async function createRegularEventCheer(
user: User,
event: cheers | items,
) {
const userInt = parseInt(user.id, 10);
return await db.transaction(async (tx) => {
const cheerEventRecord = await tx
.insert(cheerEvents)
.values({
user: userInt,
event,
status: "success",
})
.returning();
await tx.insert(events).values({
user: userInt,
cheer: cheerEventRecord[0]?.id,
});
if (!cheerEventRecord[0]) {
tx.rollback();
return false;
}
});
}
/**
* Use this function to create a cheer event where the user got an item after the cheer failed
*/
export async function createCompensatedItemCheer(user: User, item: items) {
const userInt = parseInt(user.id, 10);
return await db.transaction(async (tx) => {
const cheerEventRecord = await tx
.insert(cheerEvents)
.values({ user: userInt, event: item, status: "compensated" })
.returning();
await tx.insert(events).values({
user: userInt,
cheer: cheerEventRecord[0]?.id,
});
if (!cheerEventRecord[0]) {
tx.rollback();
return false;
}
});
}
/**
* Because superloot is a special case for cheers, as the event needs to link to the getLoot table and the cheerEvents table, we have this special function
*/
export async function createSuperLootEvent(
user: User,
qbucks: number,
inventory: inventory,
) {
const eventRecord = await createGetLootEvent(
user,
qbucks,
inventory,
"superloot",
);
if (eventRecord === false) return;
return await db.transaction(async (tx) => {
const cheerEventRecord = await tx
.insert(cheerEvents)
.values({
user: parseInt(user.id, 10),
event: "superloot",
})
.returning();
await tx
.update(events)
.set({ cheer: cheerEventRecord[0]?.id })
.where(eq(events.id, eventRecord.id));
if (!cheerEventRecord[0]) {
tx.rollback();
return false;
}
});
}

72
src/db/ItemEvents.ts Normal file
View File

@@ -0,0 +1,72 @@
import db from "db/connection";
import { events, timeouts, usedItems } from "db/schema";
import type { items } from "items";
import type User from "user";
/**
* Use this function for doing all item usages with timeouts
*/
export async function createTimeoutEventItem(
user: User,
target: User | string[],
item: items,
) {
const userInt = parseInt(user.id, 10);
return await db.transaction(async (tx) => {
const usedItemRecord = await tx
.insert(usedItems)
.values({ user: userInt, item })
.returning();
if (Array.isArray(target))
target.map(
async (ripbozo) =>
await tx.insert(timeouts).values({
user: userInt,
target: parseInt(ripbozo, 10),
item,
usedItem: usedItemRecord[0]?.id,
}),
);
else
await tx.insert(timeouts).values({
user: userInt,
target: parseInt(target.id, 10),
item,
usedItem: usedItemRecord[0]?.id,
});
await tx.insert(events).values({
user: userInt,
usedItem: usedItemRecord[0]?.id,
});
if (!usedItemRecord[0]) {
tx.rollback();
return false;
}
});
}
/**
* Use this function for doing all regular item usages (no timeouts)
*/
export async function createNormalEventItem(user: User, item: items) {
const userInt = parseInt(user.id, 10);
return await db.transaction(async (tx) => {
const usedItemRecord = await tx
.insert(usedItems)
.values({ user: userInt, item })
.returning();
await tx.insert(events).values({
user: userInt,
usedItem: usedItemRecord[0]?.id,
});
if (!usedItemRecord[0]) {
tx.rollback();
return false;
}
});
}

39
src/db/LootEvents.ts Normal file
View File

@@ -0,0 +1,39 @@
import db from "db/connection";
import type { lootTriggers } from "db/schema";
import { events, getLoots } from "db/schema";
import type { inventory } from "items";
import type User from "user";
export async function createGetLootEvent(
user: User,
qbucks: number,
inventory: inventory,
trigger: lootTriggers,
) {
return await db.transaction(async (tx) => {
const glRecord = await tx
.insert(getLoots)
.values({
user: parseInt(user.id, 10),
qbucks: qbucks,
items: inventory,
trigger,
})
.returning();
const eventRecord = await tx
.insert(events)
.values({
user: parseInt(user.id, 10),
getLoot: glRecord[0]?.id,
})
.returning();
if (!glRecord[0]) {
tx.rollback();
return false;
}
return eventRecord[0]!;
});
}

4
src/db/UndoEvent.ts Normal file
View File

@@ -0,0 +1,4 @@
import db from "db/connection";
import { events } from "db/schema";
import type User from "user";
import { desc, eq, and } from "drizzle-orm";

View File

@@ -1,19 +1,8 @@
import type { cheers } from "cheers";
import db from "db/connection"; import db from "db/connection";
import { cheerEvents } from "db/schema"; import { cheerEvents } from "db/schema";
import { and, between, eq, type SQL } from "drizzle-orm"; import { and, between, eq, type SQL } from "drizzle-orm";
import type { items } from "items";
import type User from "user"; import type User from "user";
export async function createCheerEventRecord(
user: User,
cheer: items | cheers,
): Promise<void> {
await db
.insert(cheerEvents)
.values({ user: parseInt(user.id, 10), event: cheer });
}
export async function getCheerEvents(user: User, monthData?: string) { export async function getCheerEvents(user: User, monthData?: string) {
let condition: SQL<unknown> | undefined = eq( let condition: SQL<unknown> | undefined = eq(
cheerEvents.user, cheerEvents.user,

View File

@@ -1,18 +0,0 @@
import db from "db/connection";
import { getLoots, type lootTriggers } from "db/schema";
import type { inventory } from "items";
import type User from "user";
export async function createGetLootRecord(
user: User,
qbucks: number,
inventory: inventory,
trigger: lootTriggers,
) {
await db.insert(getLoots).values({
user: parseInt(user.id, 10),
qbucks: qbucks,
items: inventory,
trigger,
});
}

View File

@@ -1,22 +1,8 @@
import type { cheers } from "cheers";
import db from "db/connection"; import db from "db/connection";
import { timeouts } from "db/schema"; import { timeouts } from "db/schema";
import { and, between, eq, type SQL } from "drizzle-orm"; import { and, between, eq, type SQL } from "drizzle-orm";
import type { items } from "items";
import type User from "user"; import type User from "user";
export async function createTimeoutRecord(
user: User,
target: User,
item: items | cheers,
): Promise<void> {
await db.insert(timeouts).values({
user: parseInt(user.id, 10),
target: parseInt(target.id, 10),
item,
});
}
export async function getTimeoutsAsUser(user: User, monthData?: string) { export async function getTimeoutsAsUser(user: User, monthData?: string) {
let condition: SQL<unknown> | undefined = eq( let condition: SQL<unknown> | undefined = eq(
timeouts.user, timeouts.user,

View File

@@ -1,16 +1,8 @@
import db from "db/connection"; import db from "db/connection";
import { usedItems } from "db/schema"; import { usedItems } from "db/schema";
import { and, between, eq, type SQL } from "drizzle-orm"; import { and, between, eq, type SQL } from "drizzle-orm";
import type { items } from "items";
import type User from "user"; import type User from "user";
export async function createUsedItemRecord(
user: User,
item: items,
): Promise<void> {
await db.insert(usedItems).values({ user: parseInt(user.id, 10), item });
}
export async function getItemsUsed(user: User, monthData?: string) { export async function getItemsUsed(user: User, monthData?: string) {
let condition: SQL<unknown> | undefined = eq( let condition: SQL<unknown> | undefined = eq(
usedItems.user, usedItems.user,

View File

@@ -12,7 +12,8 @@ import {
type SQL, type SQL,
sql, sql,
} from "drizzle-orm"; } from "drizzle-orm";
import { itemarray } from "items"; import { itemarray, type items } from "items";
import { ANIVNAMES } from "lib/handleAnivMessage";
import type User from "user"; import type User from "user";
/** Use this function to both ensure existance and to retreive data */ /** Use this function to both ensure existance and to retreive data */
@@ -120,3 +121,25 @@ export async function getKDLeaderboard(monthData?: string) {
return result; return result;
} }
type ItemCounts = Record<items, number>;
export async function getTotalItemCounts(): Promise<ItemCounts> {
const allUsers = await db
.select({ username: users.username, inventory: users.inventory })
.from(users);
const filteredUsers = allUsers.filter(
(user) =>
!Array.from<string>(ANIVNAMES).includes(user.username.toLowerCase()),
);
const counts = itemarray.reduce((acc, item) => {
acc[item] = filteredUsers.reduce((sum, user) => {
return sum + (user.inventory[item] || 0);
}, 0);
return acc;
}, {} as ItemCounts);
return counts;
}

View File

@@ -33,6 +33,7 @@ export const usersRelations = relations(users, ({ many }) => ({
cheers: many(cheers), cheers: many(cheers),
anivTimeouts: many(anivTimeouts), anivTimeouts: many(anivTimeouts),
getLoots: many(getLoots), getLoots: many(getLoots),
events: many(events),
})); }));
export const timeouts = pgTable("timeouts", { export const timeouts = pgTable("timeouts", {
@@ -45,6 +46,8 @@ export const timeouts = pgTable("timeouts", {
.references(() => users.id), .references(() => users.id),
item: varchar().$type<items | cheertypes>().notNull(), item: varchar().$type<items | cheertypes>().notNull(),
created: timestamp().defaultNow().notNull(), created: timestamp().defaultNow().notNull(),
cheer: uuid().references(() => cheerEvents.id),
usedItem: uuid().references(() => usedItems.id),
}); });
export const timeoutsRelations = relations(timeouts, ({ one }) => ({ export const timeoutsRelations = relations(timeouts, ({ one }) => ({
@@ -58,6 +61,14 @@ export const timeoutsRelations = relations(timeouts, ({ one }) => ({
references: [users.id], references: [users.id],
relationName: "target", relationName: "target",
}), }),
cheer: one(cheerEvents, {
fields: [timeouts.cheer],
references: [cheerEvents.id],
}),
usedItem: one(usedItems, {
fields: [timeouts.usedItem],
references: [usedItems.id],
}),
})); }));
export const usedItems = pgTable("usedItems", { export const usedItems = pgTable("usedItems", {
@@ -69,27 +80,36 @@ export const usedItems = pgTable("usedItems", {
created: timestamp().defaultNow().notNull(), created: timestamp().defaultNow().notNull(),
}); });
export const usedItemsRelations = relations(usedItems, ({ one }) => ({ export const usedItemsRelations = relations(usedItems, ({ one, many }) => ({
user: one(users, { user: one(users, {
fields: [usedItems.user], fields: [usedItems.user],
references: [users.id], references: [users.id],
}), }),
timeouts: many(timeouts),
})); }));
/**
* "success" when everything works
* "compensated" when the user gets an item in their inventory for a cheer
*/
export type cheerEventStatus = "success" | "compensated";
export const cheerEvents = pgTable("cheerEvents", { export const cheerEvents = pgTable("cheerEvents", {
id: uuid().defaultRandom().primaryKey(), id: uuid().defaultRandom().primaryKey(),
user: integer() user: integer()
.notNull() .notNull()
.references(() => users.id), .references(() => users.id),
event: varchar().$type<items | cheertypes>().notNull(), event: varchar().$type<items | cheertypes>().notNull(),
status: varchar().$type<cheerEventStatus>().default("success").notNull(),
created: timestamp().defaultNow().notNull(), created: timestamp().defaultNow().notNull(),
}); });
export const cheerEventsRelations = relations(cheerEvents, ({ one }) => ({ export const cheerEventsRelations = relations(cheerEvents, ({ one, many }) => ({
user: one(users, { user: one(users, {
fields: [cheerEvents.user], fields: [cheerEvents.user],
references: [users.id], references: [users.id],
}), }),
timeouts: many(timeouts),
})); }));
export const cheers = pgTable("cheers", { export const cheers = pgTable("cheers", {
@@ -146,3 +166,33 @@ export const getLootsRelations = relations(getLoots, ({ one }) => ({
references: [users.id], references: [users.id],
}), }),
})); }));
export const events = pgTable("events", {
id: uuid().defaultRandom().primaryKey(),
user: integer()
.notNull()
.references(() => users.id),
created: timestamp().defaultNow().notNull(),
usedItem: uuid().references(() => usedItems.id),
cheer: uuid().references(() => cheerEvents.id),
getLoot: uuid().references(() => getLoots.id),
});
export const eventsRelations = relations(events, ({ one }) => ({
user: one(users, {
fields: [events.user],
references: [users.id],
}),
usedItem: one(usedItems, {
fields: [events.usedItem],
references: [usedItems.id],
}),
cheer: one(cheerEvents, {
fields: [events.cheer],
references: [cheerEvents.id],
}),
getLoot: one(getLoots, {
fields: [events.getLoot],
references: [getLoots.id],
}),
}));

View File

@@ -14,8 +14,6 @@ eventSub.onChannelRedemptionAdd(streamerId, async (msg) => {
const user = await User.initUsername(msg.userName); const user = await User.initUsername(msg.userName);
try { try {
await selection.execute(msg, user!); await selection.execute(msg, user!);
if (process.env.NODE_ENV === "production")
await msg.updateStatus("FULFILLED"); // only on prod
} catch (err) { } catch (err) {
await sendMessage( await sendMessage(
`[ERROR]: Something went wrong with ${user?.displayName}'s redeem!`, `[ERROR]: Something went wrong with ${user?.displayName}'s redeem!`,

View File

@@ -1,34 +1,33 @@
import { api, eventSub } from "index"; import { api, eventSub } from "index";
import kleur from "kleur";
import logger from "lib/logger"; import logger from "lib/logger";
eventSub.onRevoke((event) => { eventSub.onRevoke((event) => {
logger.ok( logger.ok(
`Successfully revoked EventSub subscription: ${kleur.underline(event.id)}`, `Successfully revoked EventSub subscription: \x1b[3;4;1;95m${event.id}`,
); );
}); });
eventSub.onSubscriptionCreateSuccess((event) => { eventSub.onSubscriptionCreateSuccess((event) => {
logger.ok( logger.ok(
`Successfully created EventSub subscription: ${kleur.underline(event.id)}`, `Successfully created EventSub subscription: \x1b[3;4;1;95m${event.id}`,
); );
}); });
eventSub.onSubscriptionCreateFailure((event) => { eventSub.onSubscriptionCreateFailure((event) => {
logger.err( logger.err(
`Failed to create EventSub subscription: ${kleur.underline(event.id)}`, `Failed to create EventSub subscription: \x1b[3;4;4;95m${event.id}`,
); );
}); });
eventSub.onSubscriptionDeleteSuccess((event) => { eventSub.onSubscriptionDeleteSuccess((event) => {
logger.ok( logger.ok(
`Successfully deleted EventSub subscription: ${kleur.underline(event.id)}`, `Successfully deleted EventSub subscription: \x1b[3;4;1;95m${event.id}`,
); );
}); });
eventSub.onSubscriptionDeleteFailure((event) => { eventSub.onSubscriptionDeleteFailure((event) => {
logger.err( logger.err(
`Failed to delete EventSub subscription: ${kleur.underline(event.id)}`, `Failed to delete EventSub subscription: \x1b[3;4;1;95m${event.id}`,
); );
}); });

View File

@@ -38,7 +38,7 @@ async function parseChatMessage(msg: EventSubChannelChatMessageEvent) {
) { ) {
// The msg.sourceMessageId checks if the message is from shared chat. shared chat should be ignored // The msg.sourceMessageId checks if the message is from shared chat. shared chat should be ignored
const message = await sendMessage( const message = await sendMessage(
`Welcome ${user?.displayName}. Please note: This chat has PvP, if you get timed out that's part of the qwerinope experience. You have 10 minutes of invincibility. A full list of commands and items can be found here: https://github.com/qwerinope/qweribot/#qweribot`, `Welcome ${user?.displayName}. Please note: This chat has PvP, if you get timed out that's part of the qwerinope experience. You have 10 minutes of invincibility. A full list of commands and items can be found here: https://gitlab.com/qwerinope/qweribot/#qweribot`,
); );
await redis.set(`user:${user?.id}:haschatted`, "1"); await redis.set(`user:${user?.id}:haschatted`, "1");
await redis.set(`user:${user?.id}:welcomemessageid`, message.id); await redis.set(`user:${user?.id}:welcomemessageid`, message.id);
@@ -48,6 +48,18 @@ async function parseChatMessage(msg: EventSubChannelChatMessageEvent) {
if (!(await isInvuln(user?.id!))) user?.setVulnerable(); // Make the user vulnerable to explosions if not marked as invuln if (!(await isInvuln(user?.id!))) user?.setVulnerable(); // Make the user vulnerable to explosions if not marked as invuln
// Custom welcome messages
const wcmessage = await redis.get(`user:${user?.id}:welcomemessagetext`);
if (
process.env.NODE_ENV === "production" && // when running prod DB
wcmessage && // when chatter has a welcome message set
(await redis.exists(`streamIsLive`)) && // when the stream is active
!(await redis.exists(`user:${user?.id}:haschattedthisstream`)) // when the user hasn't chatted this stream
)
await sendMessage(wcmessage);
await redis.set(`user:${user?.id}:haschattedthisstream`, "1");
if (!msg.isCheer && !msg.isRedemption) await handleChatMessage(msg, user!); if (!msg.isCheer && !msg.isRedemption) await handleChatMessage(msg, user!);
else if (msg.isCheer && !msg.isRedemption) else if (msg.isCheer && !msg.isRedemption)
await handleCheer(msg, msg.bits, user!); await handleCheer(msg, msg.bits, user!);

View File

@@ -5,16 +5,23 @@ import { streamerId } from "main";
import { sendDiscordMessage } from "web/discordConnection"; import { sendDiscordMessage } from "web/discordConnection";
eventSub.onStreamOnline(streamerId, async (msg) => { eventSub.onStreamOnline(streamerId, async (msg) => {
await redis.set("streamIsLive", "1"); await Promise.all([
await sendMessage( redis.set("streamIsLive", "1"),
sendMessage(
`${msg.broadcasterDisplayName.toUpperCase()} IS LIVE! START DIGGING!`, `${msg.broadcasterDisplayName.toUpperCase()} IS LIVE! START DIGGING!`,
); ),
await sendDiscordMessage({ message: "live" }); sendDiscordMessage({ message: "live" }),
redis
.keys("user:*:haschattedthisstream")
.then((a) => a.map(async (b) => await redis.del(b))),
]);
}); });
eventSub.onStreamOffline(streamerId, async (msg) => { eventSub.onStreamOffline(streamerId, async (msg) => {
await redis.del("streamIsLive"); await Promise.all([
await sendMessage( redis.del("streamIsLive"),
sendMessage(
`${msg.broadcasterDisplayName.toUpperCase()} IS OFFLINE! NO MORE FREE LOOT!`, `${msg.broadcasterDisplayName.toUpperCase()} IS OFFLINE! NO MORE FREE LOOT!`,
); ),
]);
}); });

View File

@@ -48,7 +48,7 @@ eventSub.onChannelSubscription(streamerId, async (msg) => {
}); });
eventSub.onChannelSubscriptionGift(streamerId, async (msg) => { eventSub.onChannelSubscriptionGift(streamerId, async (msg) => {
if (msg.isAnonymous) { if (msg.gifterName === null) {
switch (msg.tier) { switch (msg.tier) {
case "1000": case "1000":
await sendMessage( await sendMessage(

View File

@@ -10,7 +10,7 @@ eventSub.onUserWhisperMessage(chatterId, async (msg) => {
if (!msg.messageText.startsWith(commandPrefix)) { if (!msg.messageText.startsWith(commandPrefix)) {
await whisper( await whisper(
msg.senderUserId, msg.senderUserId,
`Whisper commands start with '${commandPrefix}'. All whisper commands can be found here: https://github.com/qwerinope/qweribot#whisper-commands-1`, `Whisper commands start with '${commandPrefix}'. All whisper commands can be found here: https://gitlab.com/qwerinope/qweribot#whisper-commands-1`,
); );
return; return;
} }
@@ -25,7 +25,7 @@ eventSub.onUserWhisperMessage(chatterId, async (msg) => {
case "h": case "h":
await whisper( await whisper(
msg.senderUserId, msg.senderUserId,
`All whisper commands can be found here: https://github.com/qwerinope/qweribot#whisper-commands-1`, `All whisper commands can be found here: https://gitlab.com/qwerinope/qweribot#whisper-commands-1`,
); );
break; break;
case "ghostwhisper": case "ghostwhisper":

View File

@@ -144,6 +144,11 @@ streamerUsers.forEach(
]), ]),
); );
// Deleting all timeouts to prevent ghosts while bot was off
await redis
.keys("user:*:timeout")
.then(async (a) => a.map(async (b) => await redis.del(b)));
const banned = await api.moderation const banned = await api.moderation
.getBannedUsers(streamerId) .getBannedUsers(streamerId)
.then((a) => a.data); .then((a) => a.data);
@@ -156,7 +161,7 @@ for (const ban of banned) {
Math.floor((ban.expiryDate.getTime() - Date.now()) / 1000) + 1, Math.floor((ban.expiryDate.getTime() - Date.now()) / 1000) + 1,
); );
logger.info( logger.info(
`Set the timeout of ${ban.userDisplayName} in the Redis/Valkey database.`, `Set the timeout of \x1b[3;4;1;95m${ban.userDisplayName}\x1b[0;97m in the Redis/Valkey database.`,
); );
} }
} }
@@ -165,7 +170,7 @@ const mods = await api.moderation.getModerators(streamerId).then((a) => a.data);
for (const mod of mods) { for (const mod of mods) {
await redis.set(`user:${mod.userId}:mod`, "1"); await redis.set(`user:${mod.userId}:mod`, "1");
logger.info( logger.info(
`Set the mod status of ${mod.userDisplayName} in the Redis/Valkey database.`, `Set the mod status of \x1b[3;4;1;95m${mod.userDisplayName}\x1b[0;97m in the Redis/Valkey database.`,
); );
} }
@@ -180,7 +185,7 @@ for (const remod of bannedmods) {
duration = Math.floor((durationdata * 1000 - Date.now()) / 1000); duration = Math.floor((durationdata * 1000 - Date.now()) / 1000);
remodMod(target!, duration); remodMod(target!, duration);
logger.info( logger.info(
`Set the remod timer for ${target?.displayName} to ${duration} seconds.`, `Set the remod timer for \x1b[3;4;1;95m${target?.displayName}\x1b[0;97m to \x1b[3;4;1;95m${duration}\x1b[0;97m seconds.`,
); );
} }

View File

@@ -1,6 +1,5 @@
import { createTimeoutRecord } from "db/dbTimeouts";
import { createUsedItemRecord } from "db/dbUsedItems";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { createTimeoutEventItem } from "db/ItemEvents";
import { changeItemCount, Item } from "items"; import { changeItemCount, Item } from "items";
import { sendMessage } from "lib/commandUtils"; import { sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
@@ -28,7 +27,7 @@ export default new Item({
} }
const target = await User.initUsername(messagequery[0].toLowerCase()); const target = await User.initUsername(messagequery[0].toLowerCase());
if (!target) { if (!target) {
await sendMessage(`${messagequery[0]} doesn't exist`); await sendMessage(`${messagequery[0]} doesn't exist`, msg.messageId);
return; return;
} }
await getUserRecord(target); // make sure the user record exist in the database await getUserRecord(target); // make sure the user record exist in the database
@@ -57,8 +56,7 @@ export default new Item({
`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`, `GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`,
), ),
changeItemCount(user, userObj, ITEMNAME), changeItemCount(user, userObj, ITEMNAME),
createTimeoutRecord(user, target, ITEMNAME), createTimeoutEventItem(user, target, ITEMNAME),
createUsedItemRecord(user, ITEMNAME),
playAlert({ playAlert({
name: "userBlast", name: "userBlast",
user: user.displayName, user: user.displayName,

View File

@@ -1,6 +1,5 @@
import { createTimeoutRecord } from "db/dbTimeouts";
import { createUsedItemRecord } from "db/dbUsedItems";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { createTimeoutEventItem } from "db/ItemEvents";
import { changeItemCount, Item } from "items"; import { changeItemCount, Item } from "items";
import { sendMessage } from "lib/commandUtils"; import { sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
@@ -47,8 +46,7 @@ export default new Item({
`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`, `wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`,
), ),
changeItemCount(user, userObj, ITEMNAME), changeItemCount(user, userObj, ITEMNAME),
createTimeoutRecord(user, target!, ITEMNAME), createTimeoutEventItem(user, target!, ITEMNAME),
createUsedItemRecord(user, ITEMNAME),
playAlert({ playAlert({
name: "grenadeExplosion", name: "grenadeExplosion",
user: user.displayName, user: user.displayName,

View File

@@ -1,6 +1,5 @@
import { createTimeoutRecord } from "db/dbTimeouts";
import { createUsedItemRecord } from "db/dbUsedItems";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { createTimeoutEventItem } from "db/ItemEvents";
import { changeItemCount, Item } from "items"; import { changeItemCount, Item } from "items";
import { sendMessage } from "lib/commandUtils"; import { sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
@@ -18,7 +17,7 @@ export default new Item({
plural: "s", plural: "s",
description: "Times targeted or random vulnerable user out for 30 minutes", description: "Times targeted or random vulnerable user out for 30 minutes",
aliases: ["execute", "silverbullet"], aliases: ["execute", "silverbullet"],
specialaliases: ["blastin"], specialaliases: ["blastin", "fuck"],
price: 666, price: 666,
execution: async (msg, user, specialargs) => { execution: async (msg, user, specialargs) => {
const messagequery = parseCommandArgs( const messagequery = parseCommandArgs(
@@ -67,7 +66,7 @@ export default new Item({
} }
if (!target) { if (!target) {
await user.clearLock(); await user.clearLock();
await sendMessage(`${messagequery[0]} doesn't exist`); await sendMessage(`${messagequery[0]} doesn't exist`, msg.messageId);
return; return;
} }
@@ -78,21 +77,24 @@ export default new Item({
`You got blasted by ${user.displayName}!`, `You got blasted by ${user.displayName}!`,
60 * 30, 60 * 30,
); );
if (result.status) if (result.status) {
await Promise.all([ await Promise.all([
sendMessage( sendMessage(
`KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`, `KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`,
), ),
changeItemCount(user, userObj, ITEMNAME),
createTimeoutRecord(user, target, ITEMNAME),
createUsedItemRecord(user, ITEMNAME),
playAlert({ playAlert({
name: "userExecution", name: "userExecution",
user: user.displayName, user: user.displayName,
target: target.displayName, target: target.displayName,
}), }),
]); ]);
else { if (user.id !== streamerId || process.env.NODE_ENV === "development")
// streamer doesn't consume bullets and doesn't count for timeouts
await Promise.all([
changeItemCount(user, userObj, ITEMNAME),
createTimeoutEventItem(user, target, ITEMNAME),
]);
} else {
switch (result.reason) { switch (result.reason) {
case "banned": case "banned":
await sendMessage( await sendMessage(

View File

@@ -1,6 +1,5 @@
import { createTimeoutRecord } from "db/dbTimeouts";
import { createUsedItemRecord } from "db/dbUsedItems";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { createTimeoutEventItem } from "db/ItemEvents";
import { changeItemCount, Item } from "items"; import { changeItemCount, Item } from "items";
import { sendMessage } from "lib/commandUtils"; import { sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
@@ -49,13 +48,12 @@ export default new Item({
sendMessage( sendMessage(
`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`, `wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`,
), ),
createTimeoutRecord(user, target!, ITEMNAME),
]); ]);
}), }),
); );
await Promise.all([ await Promise.all([
createUsedItemRecord(user, ITEMNAME), createTimeoutEventItem(user, targets, ITEMNAME),
playAlert({ playAlert({
name: "tntExplosion", name: "tntExplosion",
user: user.displayName, user: user.displayName,

View File

@@ -4,9 +4,9 @@ import { sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
import { timeout } from "lib/timeout"; import { timeout } from "lib/timeout";
import type User from "user"; import type User from "user";
import { playTTS } from "web/alerts/serverFunctions"; import { playMSTTS } from "web/alerts/serverFunctions";
const ANIVNAMES: anivBots[] = ["a_n_e_e_v", "a_n_i_v"]; export const ANIVNAMES: anivBots[] = ["a_n_e_e_v", "a_n_i_v"];
type anivMessageStore = { type anivMessageStore = {
[key: string]: string; [key: string]: string;
@@ -44,7 +44,7 @@ export default async function handleMessage(
user: User, user: User,
) { ) {
if (ANIVNAMES.map((a) => a.toLowerCase()).includes(user.username)) { if (ANIVNAMES.map((a) => a.toLowerCase()).includes(user.username)) {
await playTTS({ text: msg.messageText }); await playMSTTS({ text: msg.messageText });
const data: anivMessageStore = await redis const data: anivMessageStore = await redis
.get("anivmessages") .get("anivmessages")
.then((a) => (a === null ? {} : JSON.parse(a))); .then((a) => (a === null ? {} : JSON.parse(a)));

View File

@@ -1,18 +1,39 @@
import kleur from "kleur";
const logger = { const logger = {
err: (arg: string) => err: (arg: string) =>
console.error( console.error(
kleur.red().bold().italic("[ERROR] ") + kleur.red().bold(arg), Bun.wrapAnsi(
`\x1b[1;91m[ERROR]: \x1b[0;97m${arg}\x1b[0m`,
process.stdout.columns,
),
), ),
warn: (arg: string) => warn: (arg: string) =>
console.warn( console.warn(
kleur.yellow().bold().italic("[WARN] ") + kleur.yellow().bold(arg), Bun.wrapAnsi(
`\x1b[1;93m[WARN]: \x1b[0;97m${arg}\x1b[0m`,
process.stdout.columns,
),
), ),
info: (arg: string) => info: (arg: string) =>
console.info(kleur.white().bold().italic("[INFO] ") + kleur.white(arg)), console.info(
ok: (arg: string) => console.info(kleur.green().bold(arg)), Bun.wrapAnsi(
enverr: (arg: string) => logger.err(`Please provide a ${arg} in the .env`), `\x1b[37;1m[INFO]: \x1b[0;97m${arg}\x1b[0m`,
process.stdout.columns,
),
),
ok: (arg: string) =>
console.info(
Bun.wrapAnsi(
`\x1b[1;92m[OK]: \x1b[0;97m${arg}\x1b[0m`,
process.stdout.columns,
),
),
enverr: (arg: string) =>
logger.err(
Bun.wrapAnsi(
`Please provide a \x1b[4;3;93m${arg}\x1b[0;97m in the .env`,
process.stdout.columns,
),
),
}; };
export default logger; export default logger;

View File

@@ -0,0 +1,16 @@
import PointRedeem from "pointRedeems";
import { sendMessage } from "lib/commandUtils";
import { playDecTalk } from "web/alerts/serverFunctions";
export default new PointRedeem({
name: "dectalk",
cost: 25000,
title: "Dectalk TTS",
color: "#FF0000",
input: true,
prompt: "I HECKIN LOVE DECTALK TTS!!!",
async execution(msg) {
await playDecTalk(msg.input);
await sendMessage("LETSGO SUFFERING LETSGO");
},
});

View File

@@ -88,7 +88,9 @@ for (const [_, redeem] of Array.from(namedRedeems)) {
backgroundColor: redeem.color, backgroundColor: redeem.color,
userInputRequired: redeem.input, userInputRequired: redeem.input,
}); });
logger.ok(`Created custom point redeem ${redeem.title}`); logger.ok(
`Created custom point redeem \x1b[3;4;1;95m${redeem.title}\x1b[0;97m`,
);
idMap.set(redeem.name, creation.id); idMap.set(redeem.name, creation.id);
activeRedeems.set(creation.id, redeem); activeRedeems.set(creation.id, redeem);
} }
@@ -97,7 +99,7 @@ for (const [_, redeem] of Array.from(namedRedeems)) {
Array.from(currentRedeems).map(async ([title, redeem]) => { Array.from(currentRedeems).map(async ([title, redeem]) => {
if (process.env.NODE_ENV !== "production") return; if (process.env.NODE_ENV !== "production") return;
await api.channelPoints.deleteCustomReward(streamerId, redeem); await api.channelPoints.deleteCustomReward(streamerId, redeem);
logger.ok(`Deleted custom point redeem ${title}`); logger.ok(`Deleted custom point redeem \x1b[3;4;1;95m${title}\x1b[0;97m`);
}); });
logger.ok("Successfully synced all custom point redeems"); logger.ok("Successfully synced all custom point redeems");
@@ -108,7 +110,7 @@ export async function enableRedeem(redeem: PointRedeem, id: string) {
isEnabled: true, isEnabled: true,
}); });
activeRedeems.set(id, redeem); activeRedeems.set(id, redeem);
logger.ok(`Enabled the ${redeem.name} point redeem`); logger.ok(`Enabled the \x1b[3;4;1;95m${redeem.name}\x1b[0;97m point redeem`);
} }
export async function disableRedeem(redeem: PointRedeem, id: string) { export async function disableRedeem(redeem: PointRedeem, id: string) {
@@ -117,7 +119,7 @@ export async function disableRedeem(redeem: PointRedeem, id: string) {
isEnabled: false, isEnabled: false,
}); });
activeRedeems.delete(id); activeRedeems.delete(id);
logger.ok(`Disabled the ${redeem.name} point redeem`); logger.ok(`Disabled the \x1b[3;4;1;95m${redeem.name}\x1b[0;97m point redeem`);
} }
export { activeRedeems, idMap }; export { activeRedeems, idMap };

View File

@@ -0,0 +1,26 @@
import PointRedeem from "pointRedeems";
import { sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis";
export default new PointRedeem({
name: "setwelcomemsg",
cost: 15000,
title: "Set welcome message",
input: true,
color: "#0099FF",
prompt:
"Set your welcome message (echoed once per stream). Character limit is 200",
async execution(msg) {
if (msg.input.length > 200) {
await sendMessage(`Your desired welcome message is too long`);
if (process.env.NODE_ENV === "production")
await msg.updateStatus("CANCELED");
}
await Promise.all([
sendMessage(
`${msg.userDisplayName} successfully set their new welcome message`,
),
redis.set(`user:${msg.userId}:welcomemessagetext`, "1"),
]);
},
});

View File

@@ -6,4 +6,9 @@ export type MSTTS = {
speed: string; speed: string;
}; };
export type TTS = MSTTS; export type DecTalk = {
type: "dectalk";
text: string;
};
export type TTS = MSTTS | DecTalk;

View File

@@ -12,14 +12,14 @@ export async function playAlert(alert: alert) {
}); });
} }
type TTSOptions = { type MSTTSOptions = {
text: string; text: string;
voice?: string; voice?: string;
pitch?: number; pitch?: number;
speed?: number; speed?: number;
}; };
export async function playTTS(options: TTSOptions) { export async function playMSTTS(options: MSTTSOptions) {
await sendAlertEvent({ await sendAlertEvent({
function: "playTTS", function: "playTTS",
tts: { tts: {
@@ -32,6 +32,16 @@ export async function playTTS(options: TTSOptions) {
}); });
} }
export async function playDecTalk(input: string) {
await sendAlertEvent({
function: "playTTS",
tts: {
type: "dectalk",
text: input,
},
});
}
export async function stopTTS() { export async function stopTTS() {
await sendAlertEvent({ await sendAlertEvent({
function: "cancelTTS", function: "cancelTTS",

View File

@@ -30,6 +30,9 @@ class TTSManager {
`https://tetyys.com/SAPI4/SAPI4?text=${tts.text}&voice=${tts.voice}&pitch=${tts.pitch}&speed=${tts.speed}`, `https://tetyys.com/SAPI4/SAPI4?text=${tts.text}&voice=${tts.voice}&pitch=${tts.pitch}&speed=${tts.speed}`,
); );
break; break;
case "dectalk":
await this.playAudio(`http://tts.cyzon.us/tts?text=${tts.text}`);
break;
} }
const newTTS = this.ttsQueue.shift(); const newTTS = this.ttsQueue.shift();