Compare commits

..

16 Commits

Author SHA1 Message Date
b88a93a6cf add events to database, remove kleur dependency/slightly nicer logging, update twurple 2026-01-29 21:06:01 +01:00
f3c6f6a6b3 fix that lootboxes are disabled with use command as well 2026-01-22 00:37:10 +01:00
aa757a563d add economy command (so darkxoa for idea), streamer silverbullet doesn't get stored, minor blaster and silverbullet fixes 2025-12-27 00:56:35 +01:00
5a17e405fa add timeout cleaning at startup 2025-12-24 13:06:55 +01:00
e24e00701c not all redeems get fulfilled instantly 2025-12-22 17:39:14 +01:00
92fe7bb75d fix capitalization in readme 2025-12-21 21:12:46 +01:00
cb08cc0786 finish migrating to gitlab 2025-12-21 21:01:02 +01:00
45e09482be getloot now says if the lootbox was a scambox 2025-12-11 00:51:28 +01:00
64dd8e6dd5 disallow aniv bots from giving items 2025-12-08 18:41:19 +01:00
2bc1d2b953 add color to dectalk redeem 2025-12-08 18:32:17 +01:00
07c619f54d add tts documentation to readme 2025-12-08 18:30:51 +01:00
6a71806881 add dectalk tts for 25k channel points (let the suffering begin) 2025-12-08 18:14:25 +01:00
eb5cca7897 add personalized welcome messages (untested SMILERS) 2025-12-07 23:41:59 +01:00
3aca8a9210 !addinvuln now clears wipes the inventory and wallet of the target 2025-12-06 23:34:13 +01:00
fd0461a30f add @biomejs/biome to devdependencies to fix check script 2025-12-06 04:53:02 +01:00
afbf08f21a fix tsc configs, add realsilverbullets for 6666 bits 2025-12-06 04:51:34 +01:00
58 changed files with 846 additions and 257 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
@@ -217,14 +238,17 @@ NAME|AMOUNT|USAGE|FUNCTION
`superloot`|150|`cheer150`|Get superloot. Details and drop rates can be found [(here)](#lootbox). `superloot`|150|`cheer150`|Get superloot. Details and drop rates can be found [(here)](#lootbox).
`execute`|666|`cheer666 [target]`|Times specified or random vulnerable user out for 30 minutes. On failure gives cheerer a silver bullet `execute`|666|`cheer666 [target]`|Times specified or random vulnerable user out for 30 minutes. On failure gives cheerer a silver bullet
`tnt`|1000|`cheer1000`|Gives 5-10 random vulnerable chatters 60 second timeouts. On failure gives cheerer a TNT `tnt`|1000|`cheer1000`|Gives 5-10 random vulnerable chatters 60 second timeouts. On failure gives cheerer a TNT
`realsilverbullet`|6666|`cheer6666 [target]`|Times specified or random vulnerable chatter out for 24 hours. On failure gives the user nothing. Get scammed.
## Point Redeems ## Point Redeems
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.7/schema.json", "$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
"clientKind": "git", "clientKind": "git",
@@ -19,7 +19,8 @@
"suspicious": { "suspicious": {
"noNonNullAssertedOptionalChain": "off", "noNonNullAssertedOptionalChain": "off",
"noExplicitAny": "off", "noExplicitAny": "off",
"noControlCharactersInRegex": "off" "noControlCharactersInRegex": "off",
"noTsIgnore": "off"
}, },
"style": { "style": {
"noNonNullAssertion": "off" "noNonNullAssertion": "off"

130
bun.lock
View File

@@ -6,39 +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": {
"@twurple/eventsub-ngrok": "^7.4.0", "@biomejs/biome": "^2.3.13",
"@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": {
"@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=="], "@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=="],
"@d-fischer/cross-fetch": ["@d-fischer/cross-fetch@5.0.5", "", { "dependencies": { "node-fetch": "^2.6.12" } }, "sha512-symjDUPInTrkfIsZc2n2mo9hiAJLcTJsZkNICjZajEWnWpJ3s3zn50/FY8xpNUAf5w3eFuQii2wxztTGpvG1Xg=="], "@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.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-AGr8OoemT/ejynbIu56qeil2+F2WLkIjn2d8jGK1JkchxnMUhYOfnqc9sVzcRxpG9Ycvw4weQ5sprRvtb7Yhcw=="],
"@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.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-TUdDCSY+Eo/EHjhJz7P2GnWwfqet+lFxBZzGHldrvULr59AgahamLs/N85SC4+bdF86EhqDuuw9rYLvLFWWlXA=="],
"@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.13", "", { "os": "linux", "cpu": "x64" }, "sha512-0bdwFVSbbM//Sds6OjtnmQGp4eUjOTt6kHvR/1P0ieR9GcTUAlPNvPC3DiavTqq302W34Ae2T6u5VVNGuQtGlQ=="],
"@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.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/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=="],
@@ -47,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=="],
@@ -149,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=="],
@@ -173,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=="],
@@ -183,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=="],
@@ -209,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=="],
@@ -229,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=="],
@@ -255,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=="],
@@ -269,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=="],
@@ -287,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,12 +2,14 @@
"name": "qweribot", "name": "qweribot",
"module": "src/index.ts", "module": "src/index.ts",
"devDependencies": { "devDependencies": {
"@twurple/eventsub-ngrok": "^7.4.0", "@biomejs/biome": "^2.3.13",
"@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",
"start": "NODE_ENV=production bun src/index.ts", "start": "NODE_ENV=production bun src/index.ts",
"start-dev": "NODE_ENV=development bun src/index.ts", "start-dev": "NODE_ENV=development bun src/index.ts",
"start-discord": "NODE_ENV=production bun src/discord/index.ts", "start-discord": "NODE_ENV=production bun src/discord/index.ts",
@@ -24,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

@@ -1,8 +1,16 @@
import type { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base"; import type { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
import type User from "user"; import type User from "user";
export type cheers =
| "execute"
| "grenade"
| "tnt"
| "timeout"
| "superloot"
| "realsilverbullet";
type cheerOptions = { type cheerOptions = {
name: string; name: cheers;
amount: number; amount: number;
isItem: boolean; isItem: boolean;
execute: ( execute: (
@@ -12,7 +20,7 @@ type cheerOptions = {
}; };
export class Cheer { export class Cheer {
public readonly name: string; public readonly name: cheers;
public readonly amount: number; public readonly amount: number;
public readonly execute: ( public readonly execute: (
msg: EventSubChannelChatMessageEvent, msg: EventSubChannelChatMessageEvent,
@@ -20,7 +28,7 @@ export class Cheer {
) => Promise<void>; ) => Promise<void>;
public readonly isItem: boolean; public readonly isItem: boolean;
constructor(options: cheerOptions) { constructor(options: cheerOptions) {
this.name = options.name.toLowerCase(); this.name = options.name;
this.amount = options.amount; this.amount = options.amount;
this.execute = options.execute; this.execute = options.execute;
this.isItem = options.isItem; this.isItem = options.isItem;
@@ -55,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);
@@ -72,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

@@ -0,0 +1,86 @@
import { Cheer } from "cheers";
import { createTimeoutEventCheer } from "db/CheerEvents";
import { getUserRecord } from "db/dbUser";
import { sendMessage } from "lib/commandUtils";
import { parseCheerArgs } from "lib/parseCommandArgs";
import { redis } from "lib/redis";
import { timeout } from "lib/timeout";
import User from "user";
import { playAlert } from "web/alerts/serverFunctions";
export default new Cheer({
name: "realsilverbullet",
amount: 6666,
isItem: false,
async execute(msg, user) {
const args = parseCheerArgs(msg.messageText);
let target: User | null;
if (!args[0]) {
const vulnsids = await redis.keys("user:*:vulnerable");
const baseusers = vulnsids.map((a) => User.initUserId(a.slice(5, -11)));
const users: User[] = [];
for (const user of baseusers) {
const a = await user;
if (!a) continue;
users.push(a);
}
if (users.length === 0) {
await sendMessage("No vulnerable chatters, -6666 KEKPOINT");
return;
}
target = users[Math.floor(Math.random() * users.length)]!;
await playAlert({
name: "blastinRoulette",
user: user.displayName,
targets: users.map((a) => a.displayName),
finaltarget: target.displayName,
});
await new Promise((res, _) => setTimeout(res, 4000));
} else {
target = await User.initUsername(args[0].toLowerCase());
}
if (!target) {
await sendMessage("dumbass wasted 6666 bits KEKPOINT", msg.messageId);
return;
}
await getUserRecord(target);
const result = await timeout(
target,
`You got fucking DELETED by ${user.displayName}!`,
60 * 60 * 24,
);
if (result.status)
await Promise.all([
sendMessage(
`KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`,
),
createTimeoutEventCheer(user, target, "realsilverbullet"),
playAlert({
name: "userExecution",
user: user.displayName,
target: target.displayName,
}),
]);
else {
switch (result.reason) {
case "banned":
await sendMessage(
`${target.displayName} is already timed out/banned`,
msg.messageId,
);
break;
case "illegal":
await Promise.all([
sendMessage(`${user.displayName} Nou Nou Nou`),
timeout(user, "nah", 60 * 60 * 24),
]);
break;
case "unknown":
await sendMessage("Something went wrong... oops", msg.messageId);
break;
}
}
},
});

View File

@@ -1,5 +1,5 @@
import { Cheer } from "cheers"; import { Cheer } from "cheers";
import { createGetLootRecord } from "db/dbGetLoot"; import { createSuperLootEvent } from "db/CheerEvents";
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";
@@ -11,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;
@@ -80,7 +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),
createGetLootRecord(user, gainedqbucks, itemDiff, "superloot"), createSuperLootEvent(user, gainedqbucks, itemDiff),
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,18 +1,8 @@
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,
): 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,21 +1,8 @@
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,
): 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

@@ -1,4 +1,5 @@
import type { AccessToken } from "@twurple/auth"; import type { AccessToken } from "@twurple/auth";
import type { cheers as cheertypes } from "cheers";
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
import { import {
boolean, boolean,
@@ -32,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", {
@@ -42,8 +44,10 @@ export const timeouts = pgTable("timeouts", {
target: integer() target: integer()
.notNull() .notNull()
.references(() => users.id), .references(() => users.id),
item: varchar().$type<items>().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 }) => ({
@@ -57,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", {
@@ -68,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>().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", {
@@ -145,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"),
`${msg.broadcasterDisplayName.toUpperCase()} IS LIVE! START DIGGING!`, sendMessage(
); `${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"),
`${msg.broadcasterDisplayName.toUpperCase()} IS OFFLINE! NO MORE FREE LOOT!`, sendMessage(
); `${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,7 +1,9 @@
import { Command, type commandOptions } from "lib/commandUtils"; import { Command, type commandOptions } from "lib/commandUtils";
import type User from "user"; import type User from "user";
interface itemOptions extends commandOptions { export type items = "blaster" | "silverbullet" | "grenade" | "tnt";
interface itemOptions extends Omit<commandOptions, "usertype"> {
name: items; name: items;
prettyName: string; prettyName: string;
plural: string; plural: string;
@@ -10,7 +12,7 @@ interface itemOptions extends commandOptions {
} }
export class Item extends Command { export class Item extends Command {
public readonly name: items; public readonly name: items = "blaster";
public readonly prettyName: string; public readonly prettyName: string;
public readonly plural: string; public readonly plural: string;
public readonly description: string; public readonly description: string;
@@ -18,9 +20,11 @@ export class Item extends Command {
/** Creates an item object */ /** Creates an item object */
constructor(options: itemOptions) { constructor(options: itemOptions) {
options.disableable = true; // Items can always be disabled super({
options.usertype = "chatter"; // Everyone can use items ...options,
super(options); usertype: "chatter", // Everyone can use items
disableable: true, // Items can always be disabled
});
this.name = options.name; this.name = options.name;
this.prettyName = options.prettyName; this.prettyName = options.prettyName;
this.plural = options.plural; this.plural = options.plural;
@@ -59,7 +63,6 @@ for (const file of files) {
export default itemAliasMap; export default itemAliasMap;
export { emptyInventory, itemarray, specialAliasItems, itemObjectArray }; export { emptyInventory, itemarray, specialAliasItems, itemObjectArray };
export type items = "blaster" | "silverbullet" | "grenade" | "tnt";
export type inventory = { export type inventory = {
[key in items]?: number; [key in items]?: number;
}; };

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

@@ -59,8 +59,11 @@ export async function getItemStats(target: User, thismonth: boolean) {
} }
for (const cheer of cheers) { for (const cheer of cheers) {
if (!returnObj[cheer.event]) returnObj[cheer.event] = 0; if (cheer.event in returnObj) {
returnObj[cheer.event]! += 1; if (!returnObj[cheer.event as keyof inventory])
returnObj[cheer.event as keyof inventory] = 0;
returnObj[cheer.event as keyof inventory]! += 1;
}
} }
return returnObj; return returnObj;

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();

View File

@@ -1,6 +1,7 @@
import type { alertEventData } from "web/alerts/types"; import type { alertEventData } from "web/alerts/types";
import type { serverInstruction } from "web/serverTypes"; import type { serverInstruction } from "web/serverTypes";
import AlertManager from "./AlertManager"; import AlertManager from "./AlertManager";
// @ts-ignore
import "@fontsource/jersey-15"; import "@fontsource/jersey-15";
import TTSManager from "./TTSManager"; import TTSManager from "./TTSManager";

View File

@@ -1,4 +1,6 @@
// @ts-ignore
import "./style.css"; import "./style.css";
// @ts-ignore
import "@fontsource/jersey-15"; import "@fontsource/jersey-15";
import type { twitchEventData } from "web/chatWidget/websockettypes"; import type { twitchEventData } from "web/chatWidget/websockettypes";

View File

@@ -1,6 +1,7 @@
{ {
"files": [], "files": [],
"compilerOptions": { "compilerOptions": {
"noEmit": true,
"baseUrl": "./src", "baseUrl": "./src",
"paths": { "paths": {
"lib/*": ["./lib/*"], "lib/*": ["./lib/*"],

View File

@@ -3,7 +3,10 @@
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ESNext",
"module": "ESNext", "module": "ESNext",
"lib": ["DOM", "ES2020"] "moduleResolution": "bundler",
"lib": ["DOM", "ES2022"],
"skipLibCheck": true
}, },
"include": ["src/web/chatWidget/www/**/*", "src/web/alerts/www/**/*"] "include": ["src/web/chatWidget/www/**/*", "src/web/alerts/www/**/*"],
"exclude": ["node_modules"]
} }