mirror of
https://github.com/qwerinope/qweribot.git
synced 2025-12-18 21:11:39 +01:00
Move Database from pocketbase to postgres
Move Database from pocketbase to postgres
This commit is contained in:
@@ -17,8 +17,11 @@ STREAMER_ID= # Twitch ID of the streaming user
|
||||
CHATTER_ID= # Twitch ID of the chatting user
|
||||
CHATTER_IS_STREAMER= # If the bot that activates on commands is on the same account as the streamer, set this to true. Make sure the STREAMER_ID and CHATTER_ID match in that case.
|
||||
|
||||
# Pocketbase config
|
||||
# POCKETBASE_URL= # Pocketbase URL. Defaults to http://localhost:8090
|
||||
# Postgres config
|
||||
POSTGRES_HOST= # Hostname + port of the postgres database
|
||||
POSTGRES_USER= # Username for logging in on the postgres database
|
||||
POSTGRES_PASSWORD= # Password for logging in on the postgres database
|
||||
POSTGRES_DB=twitchbot # Database name. Recommended value: twitchbot
|
||||
|
||||
# Redis/Valkey config
|
||||
# REDIS_URL= # Redis URL. Defaults to redis://localhost:6379
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -24,6 +24,9 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# Drizzle config files
|
||||
*.config.ts
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
|
||||
33
README.md
33
README.md
@@ -64,6 +64,8 @@ When using/giving an item or qbucks the itemlock will be set at the start of the
|
||||
Admins can toggle the itemlock on chatters with the [`itemlock`](#administrative-commands) command. This will stop a chatter from giving, receiving and using items and qweribucks.
|
||||
It will NOT stop them from using items by cheering, but if that cheer item usage fails, they will not be given an equivalent item as compensation.
|
||||
|
||||
The only ways to get items is through the `getloot` command or by buying them with qbucks.
|
||||
|
||||
Items can be used with the alias as a command (example: `blast qwerinope`) or with the [`use` command](#qweribucksitem-commands).
|
||||
|
||||
When an Item is used it is removed from the inventory of the chatter.
|
||||
@@ -78,10 +80,10 @@ ITEM|RATE
|
||||
`grenade`|`1/5`
|
||||
`blaster`|`1/5`
|
||||
`tnt`|`1/20`
|
||||
`silver bullet`|`1/250`
|
||||
`silver bullet`|`1/1000`
|
||||
|
||||
Each of these rates get pulled 5 times, then the result is added to your inventory.
|
||||
It's theoretically possible to get 5 of each item.
|
||||
Each of these rates get pulled 3 times, then the result is added to your inventory.
|
||||
It's theoretically possible to get 3 of each item.
|
||||
|
||||
### Chatterbot/streamerbot
|
||||
|
||||
@@ -104,24 +106,25 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
|
||||
`seiso`|Get a seiso rating|anyone|`seiso`|:white_check_mark:
|
||||
`backshot`|'Backshot' a random previous chatter|anyone|`backshot`|:white_check_mark:
|
||||
`roulette`|Play russian roulette for a 5 minute timeout|anyone|`roulette`|:white_check_mark:
|
||||
`timeout {target}`|Times targeted user out for 60 seconds (costs 100 qweribucks)|anyone|`timeout`|:white_check_mark:
|
||||
`stats [target]`|Get timeout and some item stats for yourself or specified user this month|anyone|`stats` `monthlystats`|:white_check_mark:
|
||||
`alltime [target]`|Get timeout and some item stats for yourself or specified user of all time|anyone|`alltime` `alltimestats`|:white_check_mark:
|
||||
`monthlyleaderboard`|Get the K/D leaderboard for this month [(info)](#leaderboards)|anyone|`monthlyleaderboard` `kdleaderboard` `leaderboard`|:white_check_mark:
|
||||
`alltimeleaderboard`|Get the K/D leaderboard of all time [(info)](#leaderboards)|anyone|`alltimeleaderboard` `alltimekdleaderboard`|:white_check_mark:
|
||||
`qbucksleaderboard`|Get the current qbucks leaderboard [(info)](#leaderboards)|anyone|`qbucksleaderboard` `moneyleaderboard` `baltop`|:white_check_mark:
|
||||
|
||||
### Qweribucks/Item commands
|
||||
|
||||
COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
|
||||
-|-|-|-|-
|
||||
`getloot`|Get a random assortment of items and qbucks every 10 minutes. [(drop rates)](#lootbox)|anyone|`getloot` `loot` `dig`|:white_check_mark:
|
||||
`getbalance [target]`|Get balance of target or self|anyone|`getbalance` `balance` `qbucks` `qweribucks` `wallet` `getwallet`|:white_check_mark:
|
||||
`donate {target} {amount}`|Give the targeted user some or all of your qweribucks|anyone|`donate`|:white_check_mark:
|
||||
`iteminfo {item}`|Get item function and aliases|anyone|`iteminfo` `itemhelp` `info`|:white_check_mark:
|
||||
`inventory [target]`|Get inventory contents of target or self|anyone|`inventory` `inv` `pocket`|:white_check_mark:
|
||||
`getprices`|Get the current price of items in the shop|anyone|`getprices` `prices` `shop`|:white_check_mark:
|
||||
`buyitem {item} [amount]`|Buy one or more items for some qbucks. Prices are [here](#items)|anyone|`buyitem` `buy` `purchase`|:white_check_mark:
|
||||
`getbalance [target]`|Get balance of target or self|anyone|`getbalance` `balance` `qbucks` `qweribucks` `wallet` `getwallet`|:white_check_mark:
|
||||
`give {target} {item} {amount}`|Give targeted user amount of items|anyone|`give`|:white_check_mark:
|
||||
`donate {target} {amount}`|Give the targeted user some or all of your qweribucks|anyone|`donate`|:white_check_mark:
|
||||
`use {item} ...`|Use item. More info at [The items section](#items)|anyone|`use`|:x:
|
||||
`monthlyleaderboard`|Get the K/D leaderboard for this month [(info)](#leaderboards)|anyone|`monthlyleaderboard` `kdleaderboard` `leaderboard`|:white_check_mark:
|
||||
`alltimeleaderboard`|Get the K/D leaderboard of all time [(info)](#leaderboards)|anyone|`alltimeleaderboard` `alltimekdleaderboard`|:white_check_mark:
|
||||
`qbucksleaderboard`|Get the current qbucks leaderboard [(info)](#leaderboards)|anyone|`qbucksleaderboard` `moneyleaderboard` `baltop`|:white_check_mark:
|
||||
`admindonate {target} {amount}`|Gives the targeted user amount of qweribucks|admins|`admindonate`|:white_check_mark:
|
||||
`admingive {target} {item} {amount}`|Give targeted user amount of new items|admins|`admingive`|:white_check_mark:
|
||||
|
||||
@@ -149,12 +152,12 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
|
||||
|
||||
## Items
|
||||
|
||||
NAME|COMMAND|FUNCTION|ALIASES
|
||||
-|-|-|-
|
||||
Blaster|`blaster {target}`|Times targeted user out for 60 seconds|`blaster` `blast`
|
||||
Silver Bullet|`silverbullet {target}`|Times targeted user out for 24 hours|`silverbullet` `execute` `{blastin}`
|
||||
Grenade|`grenade`|Times a random vulnerable chatter out for 60 seconds|`grenade`
|
||||
TNT|`tnt`|Give 5-10 random chatters 60 second timeouts|`tnt`
|
||||
NAME|COMMAND|FUNCTION|ALIASES|COST
|
||||
-|-|-|-|-
|
||||
Blaster|`blaster {target}`|Times targeted user out for 60 seconds|`blaster` `blast`|100
|
||||
Silver Bullet|`silverbullet {target}`|Times targeted user out for 24 hours|`silverbullet` `execute` `{blastin}`|6666
|
||||
Grenade|`grenade`|Times a random vulnerable chatter out for 60 seconds|`grenade`|99
|
||||
TNT|`tnt`|Give 5-10 random chatters 60 second timeouts|`tnt`|1000
|
||||
|
||||
## Cheers
|
||||
|
||||
|
||||
158
bun.lock
158
bun.lock
@@ -7,11 +7,13 @@
|
||||
"@fontsource/jersey-15": "^5.2.6",
|
||||
"@twurple/auth": "^7.3.0",
|
||||
"@twurple/eventsub-ws": "^7.3.0",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
"kleur": "^4.1.5",
|
||||
"pocketbase": "^0.26.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"pg": "^8.16.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.8.3",
|
||||
@@ -39,6 +41,64 @@
|
||||
|
||||
"@d-fischer/typed-event-emitter": ["@d-fischer/typed-event-emitter@3.3.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-OvSEOa8icfdWDqcRtjSEZtgJTFOFNgTjje7zaL0+nAtu2/kZtRCSK5wUMrI/aXtCH8o0Qz2vA8UqkhWUTARFQQ=="],
|
||||
|
||||
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
|
||||
|
||||
"@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
|
||||
|
||||
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="],
|
||||
|
||||
"@fontsource/jersey-15": ["@fontsource/jersey-15@5.2.6", "", {}, "sha512-3zkkEnu91esusWLqAK/AN1uc6jNtWT8idfO0UfYLqNlbMBKkbbiIVXtq6UbQsyegxnmRMppVV1J2t1zrJ36VgA=="],
|
||||
|
||||
"@twurple/api": ["@twurple/api@7.3.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.3.0", "@twurple/common": "7.3.0", "retry": "^0.13.1", "tslib": "^2.0.3" }, "peerDependencies": { "@twurple/auth": "7.3.0" } }, "sha512-QtaVgYi50E3AB/Nxivjou/u6w1cuQ6g4R8lzQawYDaQNtlP2Ue8vvYuSp2PfxSpe8vNiKhgV8hZAs+j4V29sxQ=="],
|
||||
@@ -59,18 +119,64 @@
|
||||
|
||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"drizzle-kit": ["drizzle-kit@0.31.4", "", { "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-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA=="],
|
||||
|
||||
"drizzle-orm": ["drizzle-orm@0.44.5", "", { "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-jBe37K7d8ZSKptdKfakQFdeljtu3P2Cbo7tJoJSVZADzIKOBo9IAJPOmMsH2bZl90bZgh8FQlD8BjxXA/zuBkQ=="],
|
||||
|
||||
"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-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="],
|
||||
|
||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||
|
||||
"klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"pocketbase": ["pocketbase@0.26.1", "", {}, "sha512-fjcPDpxyqTZCwqGUTPUV7vssIsNMqHxk9GxbhxYHPEf18RqX2d9cpSqbbHk7aas30jqkgptuKfG7aY/Mytjj3g=="],
|
||||
"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.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="],
|
||||
|
||||
"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-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="],
|
||||
|
||||
"pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="],
|
||||
|
||||
"postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
|
||||
|
||||
"postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="],
|
||||
|
||||
"postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
|
||||
|
||||
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
|
||||
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
"retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||
|
||||
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
@@ -84,5 +190,53 @@
|
||||
"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=="],
|
||||
|
||||
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
||||
|
||||
"@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/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||
}
|
||||
}
|
||||
|
||||
17
compose.yml
17
compose.yml
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
valkey:
|
||||
image: valkey/valkey:alpine
|
||||
container_name: valkey
|
||||
container_name: qweribot-valkey
|
||||
ports:
|
||||
- 6379:6379
|
||||
restart: no
|
||||
@@ -9,12 +9,13 @@ services:
|
||||
- ./db/redis:/data
|
||||
environment:
|
||||
- VALKEY_EXTRA_FLAGS=--save 60 1
|
||||
pocketbase:
|
||||
container_name: qweribot-pocketbase
|
||||
build:
|
||||
context: ./pocketbase
|
||||
postgres:
|
||||
container_name: qweribot-postgres
|
||||
image: postgres:latest
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 8090:8090
|
||||
restart: no
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- ./db/pocketbase:/pb/pb_data
|
||||
- ./db/postgresql:/var/lib/postgresql/data
|
||||
|
||||
16
package.json
16
package.json
@@ -2,7 +2,17 @@
|
||||
"name": "qweribot",
|
||||
"module": "src/index.ts",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
"@types/bun": "latest",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"pg": "^8.16.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=production bun src/index.ts",
|
||||
"start-dev": "NODE_ENV=development bun src/index.ts",
|
||||
"migrate": "drizzle-kit push --config=drizzle-prod.config.ts",
|
||||
"migrate-dev": "drizzle-kit push --config=drizzle-dev.config.ts",
|
||||
"studio": "drizzle-kit studio --config=drizzle-prod.config.ts",
|
||||
"studio-dev": "drizzle-kit studio --config=drizzle-dev.config.ts"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
@@ -13,7 +23,7 @@
|
||||
"@fontsource/jersey-15": "^5.2.6",
|
||||
"@twurple/auth": "^7.3.0",
|
||||
"@twurple/eventsub-ws": "^7.3.0",
|
||||
"kleur": "^4.1.5",
|
||||
"pocketbase": "^0.26.1"
|
||||
"drizzle-orm": "^0.44.5",
|
||||
"kleur": "^4.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
FROM alpine:latest
|
||||
|
||||
ARG PB_VERSION=0.28.4
|
||||
ARG PB_ZIPNAME=pocketbase_${PB_VERSION}_linux_amd64.zip
|
||||
|
||||
RUN apk add --no-cache \
|
||||
unzip \
|
||||
ca-certificates
|
||||
|
||||
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/${PB_ZIPNAME} /tmp/${PB_ZIPNAME}
|
||||
ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/checksums.txt /tmp/checksums.txt
|
||||
WORKDIR /tmp
|
||||
RUN grep ${PB_ZIPNAME} checksums.txt | sha256sum -c
|
||||
RUN unzip /tmp/${PB_ZIPNAME} -d /pb/
|
||||
|
||||
COPY ./pb_migrations /pb/pb_migrations
|
||||
COPY ./pb_hooks /pb/pb_hooks
|
||||
|
||||
EXPOSE 8090
|
||||
CMD ["/pb/pocketbase", "serve", "--http=0.0.0.0:8090"]
|
||||
@@ -45,5 +45,5 @@ export default new Cheer('execute', 6666, async (msg, user) => {
|
||||
break;
|
||||
};
|
||||
};
|
||||
});
|
||||
}, true);
|
||||
|
||||
|
||||
@@ -30,4 +30,4 @@ export default new Cheer('grenade', 99, async (msg, user) => {
|
||||
target: target?.displayName!
|
||||
})
|
||||
]);
|
||||
});
|
||||
}, true);
|
||||
|
||||
@@ -5,10 +5,12 @@ export class Cheer {
|
||||
public readonly name: string;
|
||||
public readonly amount: number;
|
||||
public readonly execute: (msg: EventSubChannelChatMessageEvent, sender: User) => Promise<void>;
|
||||
constructor(name: string, amount: number, execution: (msg: EventSubChannelChatMessageEvent, sender: User) => Promise<void>) {
|
||||
public readonly isItem: boolean;
|
||||
constructor(name: string, amount: number, execution: (msg: EventSubChannelChatMessageEvent, sender: User) => Promise<void>, isItem = false) {
|
||||
this.name = name.toLowerCase();
|
||||
this.amount = amount;
|
||||
this.execute = execution;
|
||||
this.isItem = isItem;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -29,14 +31,12 @@ export default cheers;
|
||||
export { namedcheers };
|
||||
|
||||
import { sendMessage } from 'commands';
|
||||
import logger from 'lib/logger';
|
||||
import { getUserRecord } from 'db/dbUser';
|
||||
import { changeItemCount } from 'items';
|
||||
import { changeItemCount, type items } from 'items';
|
||||
|
||||
export async function handleNoTarget(msg: EventSubChannelChatMessageEvent, user: User, itemname: string, silent = true) {
|
||||
export async function handleNoTarget(msg: EventSubChannelChatMessageEvent, user: User, itemname: items, silent = true) {
|
||||
if (await user.itemLock()) {
|
||||
await sendMessage(`Cannot give ${user.displayName} a ${itemname}`, msg.messageId);
|
||||
logger.err(`Failed to give ${user.displayName} a ${itemname} for their cheer`);
|
||||
await sendMessage(`Cannot give ${user.displayName} a ${itemname} (itemlock)`, msg.messageId);
|
||||
return;
|
||||
};
|
||||
await user.setLock();
|
||||
|
||||
@@ -46,4 +46,4 @@ export default new Cheer('timeout', 100, async (msg, user) => {
|
||||
break;
|
||||
};
|
||||
};
|
||||
});
|
||||
}, true);
|
||||
|
||||
@@ -23,16 +23,18 @@ export default new Cheer('tnt', 1000, async (msg, user) => {
|
||||
timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60),
|
||||
redis.del(`user:${targetid}:vulnerable`),
|
||||
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`),
|
||||
createTimeoutRecord(user, target!, ITEMNAME),
|
||||
createCheerEventRecord(user, ITEMNAME),
|
||||
createTimeoutRecord(user, target!, ITEMNAME)
|
||||
]);
|
||||
}));
|
||||
await playAlert({
|
||||
name: 'tntExplosion',
|
||||
user: user.displayName,
|
||||
targets
|
||||
})
|
||||
|
||||
await Promise.all([
|
||||
createCheerEventRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: 'tntExplosion',
|
||||
user: user.displayName,
|
||||
targets
|
||||
})
|
||||
]);
|
||||
|
||||
await sendMessage(`RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? '' : 's'} with their TNT RIPBOZO`);
|
||||
});
|
||||
|
||||
}, true);
|
||||
|
||||
@@ -16,8 +16,8 @@ export default new Command({
|
||||
const userRecord = await getUserRecord(target);
|
||||
if (!args[1]) { await sendMessage('Please specify the amount qweribucks you want to give', msg.messageId); return; };
|
||||
const amount = parseInt(args[1]);
|
||||
if (isNaN(amount)) { await sendMessage(`${args[1]} is not a valid amount`); return; };
|
||||
if (await target.itemLock()) { await sendMessage('Cannot give qweribucks: item lock is set', msg.messageId); return; };
|
||||
if (isNaN(amount)) { await sendMessage(`'${args[1]}' is not a valid amount`); return; };
|
||||
if (await target.itemLock()) { await sendMessage('Cannot give qweribucks (itemlock)', msg.messageId); return; };
|
||||
await target.setLock();
|
||||
const data = await changeBalance(target, userRecord, amount);
|
||||
if (!data) {
|
||||
|
||||
@@ -19,8 +19,8 @@ export default new Command({
|
||||
if (!item) { await sendMessage(`Item ${args[1]} doesn't exist`, msg.messageId); return; };
|
||||
if (!args[2]) { await sendMessage('Please specify the amount of the item you want to give', msg.messageId); return; };
|
||||
const amount = parseInt(args[2]);
|
||||
if (isNaN(amount)) { await sendMessage(`${args[2]} is not a valid amount`); return; };
|
||||
if (await target.itemLock()) { await sendMessage('Cannot give item: item lock is set', msg.messageId); return; };
|
||||
if (isNaN(amount)) { await sendMessage(`'${args[2]}' is not a valid amount`); return; };
|
||||
if (await target.itemLock()) { await sendMessage('Cannot give item (itemlock)', msg.messageId); return; };
|
||||
await target.setLock();
|
||||
const data = await changeItemCount(target, userRecord, item.name, amount);
|
||||
if (data) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Command, sendMessage } from "commands";
|
||||
import { getAllUserRecords } from "db/dbUser";
|
||||
import { getTimeoutStats } from "lib/getStats";
|
||||
import { getKDLeaderboard } from "db/dbUser";
|
||||
import User from "user";
|
||||
|
||||
type KD = { user: User; kd: number; };
|
||||
@@ -10,27 +9,19 @@ export default new Command({
|
||||
aliases: ['alltimeleaderboard', 'alltimekdleaderboard'],
|
||||
usertype: 'chatter',
|
||||
execution: async msg => {
|
||||
const users = await getAllUserRecords();
|
||||
if (!users) return;
|
||||
|
||||
const userKDs: KD[] = [];
|
||||
await Promise.all(users.map(async userRecord => {
|
||||
const user = await User.initUserId(userRecord.id);
|
||||
if (!user) return;
|
||||
const data = await getTimeoutStats(user, false);
|
||||
if (!data) return;
|
||||
if (data.hit.blaster < 5) return;
|
||||
|
||||
let kd = data.shot.blaster / data.hit.blaster;
|
||||
if (isNaN(kd)) kd = 0;
|
||||
userKDs.push({ user, kd });
|
||||
}));
|
||||
|
||||
if (userKDs.length === 0) {
|
||||
const rawKD = await getKDLeaderboard();
|
||||
if (rawKD.length === 0) {
|
||||
await sendMessage(`No users on leaderboard yet!`, msg.messageId);
|
||||
return;
|
||||
};
|
||||
|
||||
const userKDs: KD[] = [];
|
||||
await Promise.all(rawKD.map(async userRecord => {
|
||||
const user = await User.initUserId(userRecord.userId.toString());
|
||||
if (!user) return;
|
||||
userKDs.push({ user, kd: userRecord.KD })
|
||||
}));
|
||||
|
||||
userKDs.sort((a, b) => b.kd - a.kd);
|
||||
|
||||
const txt: string[] = [];
|
||||
|
||||
37
src/commands/buyitem.ts
Normal file
37
src/commands/buyitem.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Command, sendMessage } from "commands";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import items from "items";
|
||||
import { getUserRecord, updateUserRecord } from "db/dbUser";
|
||||
|
||||
export default new Command({
|
||||
name: 'buyitem',
|
||||
aliases: ['buyitem', 'buy', 'purchase'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage(`Specify the item you'd like to buy`, msg.messageId); return; };
|
||||
const selecteditem = items.get(args[0].toLowerCase());
|
||||
if (!selecteditem) { await sendMessage(`'${args[0]}' is not a valid item`, msg.messageId); return; };
|
||||
const amount = args[1] ? parseInt(args[1]) : 1;
|
||||
if (isNaN(amount) || amount < 1) { await sendMessage(`'${args[1]}' is not a valid amount to buy`, msg.messageId); return; };
|
||||
const totalcost = amount * selecteditem.price;
|
||||
|
||||
if (await user.itemLock()) { await sendMessage('Cannot buy item (itemlock)', msg.messageId); return; };
|
||||
await user.setLock();
|
||||
|
||||
const userRecord = await getUserRecord(user);
|
||||
if (userRecord.balance < totalcost) { await sendMessage(`You don't have enough qbucks to buy ${amount} ${selecteditem.prettyName}${amount === 1 ? '' : selecteditem.plural}! You have ${userRecord.balance}, need ${totalcost}`); await user.clearLock(); return; };
|
||||
|
||||
if (userRecord.inventory[selecteditem.name]) userRecord.inventory[selecteditem.name]! += amount
|
||||
else userRecord.inventory[selecteditem.name] = amount;
|
||||
|
||||
userRecord.balance -= totalcost;
|
||||
|
||||
await Promise.all([
|
||||
updateUserRecord(user, userRecord),
|
||||
sendMessage(`${user.displayName} bought ${amount} ${selecteditem.prettyName}${amount === 1 ? '' : selecteditem.plural} for ${totalcost} qbucks. They now have ${userRecord.inventory[selecteditem.name]} ${selecteditem.prettyName}${userRecord.inventory[selecteditem.name] === 1 ? '' : selecteditem.plural} and ${userRecord.balance} qbucks`, msg.messageId)
|
||||
]);
|
||||
|
||||
await user.clearLock();
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Command, sendMessage } from "commands";
|
||||
import type { userRecord } from "db/connection";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { changeBalance } from "lib/changeBalance";
|
||||
@@ -19,12 +18,12 @@ export default new Command({
|
||||
const targetRecord = await getUserRecord(target);
|
||||
if (!args[1]) { await sendMessage('Please specify the amount of the item you want to give', msg.messageId); return; };
|
||||
const amount = parseInt(args[1]);
|
||||
if (isNaN(amount) || amount < 1) { await sendMessage(`${args[1]} is not a valid amount`); return; };
|
||||
if (isNaN(amount) || amount < 1) { await sendMessage(`'${args[1]}' is not a valid amount`); return; };
|
||||
|
||||
const userRecord = await getUserRecord(user);
|
||||
if (userRecord.balance < amount) { await sendMessage(`You can't give qweribucks you don't have!`, msg.messageId); return; };
|
||||
|
||||
if (await user.itemLock() || await target.itemLock()) { await sendMessage('Cannot give qweribucks', msg.messageId); return; };
|
||||
if (await user.itemLock() || await target.itemLock()) { await sendMessage('Cannot give qweribucks (itemlock)', msg.messageId); return; };
|
||||
|
||||
await Promise.all([
|
||||
user.setLock(),
|
||||
@@ -37,7 +36,7 @@ export default new Command({
|
||||
]);
|
||||
|
||||
if (!data.includes(false)) {
|
||||
const { balance: newamount } = data[0] as userRecord;
|
||||
const { balance: newamount } = data[0];
|
||||
await sendMessage(`${user.displayName} gave ${amount} qweribuck${amount === 1 ? '' : 's'} to ${target.displayName}. They now have ${newamount} qweribuck${newamount === 1 ? '' : 's'}`, msg.messageId);
|
||||
} else {
|
||||
// TODO: Rewrite this section
|
||||
|
||||
@@ -8,7 +8,7 @@ export default new Command({
|
||||
execution: async (_msg, user) => {
|
||||
await Promise.all([
|
||||
timeout(user, "NO MODME", 60),
|
||||
sendMessage(`NO MODME COMMAND!!! UltraMad`)
|
||||
sendMessage(`NO MODME COMMAND!!! UltraMad UltraMad UltraMad`)
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { redis } from "bun";
|
||||
import { Command, sendMessage } from "commands";
|
||||
import { getUserRecord, updateUserRecord } from "db/dbUser";
|
||||
import items from "items";
|
||||
import itemMap, { type inventory, type items } from "items";
|
||||
import { buildTimeString } from "lib/dateManager";
|
||||
import { timeout } from "lib/timeout";
|
||||
import { isInvuln, removeInvuln } from "lib/invuln";
|
||||
@@ -19,7 +19,7 @@ export default new Command({
|
||||
if (await isInvuln(msg.chatterId) && !streamerUsers.includes(msg.chatterId)) { await sendMessage(`You're no longer an invuln because used a lootbox.`, msg.messageId); await removeInvuln(msg.chatterId); };
|
||||
if (await user.itemLock()) { await sendMessage(`Cannot get loot (itemlock)`, msg.messageId); return; };
|
||||
const userData = await getUserRecord(user);
|
||||
const lastlootbox = Date.parse(userData.lastlootbox);
|
||||
const lastlootbox = userData.lastlootbox.getTime();
|
||||
const now = Date.now();
|
||||
if ((lastlootbox + COOLDOWN) > now) {
|
||||
if (await user.greedy()) {
|
||||
@@ -40,26 +40,25 @@ export default new Command({
|
||||
await user.clearGreed();
|
||||
await user.setLock();
|
||||
|
||||
userData.lastlootbox = new Date(now).toISOString();
|
||||
userData.lastlootbox = new Date(now);
|
||||
|
||||
const gainedqbucks = Math.floor(Math.random() * 100) + 50; // range from 50 to 150
|
||||
userData.balance += gainedqbucks;
|
||||
|
||||
const itemDiff = {
|
||||
const itemDiff: inventory = {
|
||||
grenade: 0,
|
||||
blaster: 0,
|
||||
tnt: 0,
|
||||
silverbullet: 0
|
||||
};
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (Math.floor(Math.random() * 5) === 0) itemDiff.grenade += 1;
|
||||
if (Math.floor(Math.random() * 5) === 0) itemDiff.blaster += 1;
|
||||
if (Math.floor(Math.random() * 25) === 0) itemDiff.tnt += 1;
|
||||
if (Math.floor(Math.random() * 250) === 0) itemDiff.silverbullet += 1;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (Math.floor(Math.random() * 5) === 0) itemDiff.grenade! += 1;
|
||||
if (Math.floor(Math.random() * 5) === 0) itemDiff.blaster! += 1;
|
||||
if (Math.floor(Math.random() * 25) === 0) itemDiff.tnt! += 1;
|
||||
if (Math.floor(Math.random() * 1000) === 0) itemDiff.silverbullet! += 1;
|
||||
};
|
||||
|
||||
for (const [item, amount] of Object.entries(itemDiff)) {
|
||||
for (const [item, amount] of Object.entries(itemDiff) as [items, number][]) {
|
||||
if (userData.inventory[item]) userData.inventory[item] += amount;
|
||||
else userData.inventory[item] = amount;
|
||||
};
|
||||
@@ -68,7 +67,7 @@ export default new Command({
|
||||
|
||||
for (const [item, amount] of Object.entries(itemDiff)) {
|
||||
if (amount === 0) continue;
|
||||
const selection = items.get(item);
|
||||
const selection = itemMap.get(item);
|
||||
if (!selection) continue;
|
||||
itemstrings.push(`${amount} ${selection.prettyName + (amount === 1 ? '' : selection.plural)}`);
|
||||
};
|
||||
|
||||
12
src/commands/getprices.ts
Normal file
12
src/commands/getprices.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Command, sendMessage } from "commands";
|
||||
import { itemObjectArray } from "items";
|
||||
|
||||
export default new Command({
|
||||
name: 'getprices',
|
||||
aliases: ['getprices', 'prices', 'shop'],
|
||||
usertype: 'chatter',
|
||||
execution: async msg => {
|
||||
const txt = itemObjectArray.toSorted((a, b) => a.price - b.price).map(item => `${item.prettyName}: ${item.price}`);
|
||||
await sendMessage(`Prices: ${txt.join(' | ')}`, msg.messageId);
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Command, sendMessage } from "commands";
|
||||
import type { userRecord } from "db/connection";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import items, { changeItemCount } from "items";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
@@ -22,11 +21,11 @@ export default new Command({
|
||||
if (!item) { await sendMessage(`Item ${args[1]} doesn't exist`, msg.messageId); return; };
|
||||
if (!args[2]) { await sendMessage('Please specify the amount of the item you want to give', msg.messageId); return; };
|
||||
const amount = parseInt(args[2]);
|
||||
if (isNaN(amount) || amount < 1) { await sendMessage(`${args[2]} is not a valid amount`); return; };
|
||||
if (isNaN(amount) || amount < 1) { await sendMessage(`'${args[2]}' is not a valid amount`); return; };
|
||||
const userRecord = await getUserRecord(user);
|
||||
if (userRecord.inventory[item.name]! < amount) { await sendMessage(`You can't give items you don't have!`, msg.messageId); return; };
|
||||
|
||||
if (await user.itemLock() || await target.itemLock()) { await sendMessage('Cannot give item', msg.messageId); return; };
|
||||
if (await user.itemLock() || await target.itemLock()) { await sendMessage('Cannot give item (itemlock)', msg.messageId); return; };
|
||||
|
||||
await Promise.all([
|
||||
user.setLock(),
|
||||
@@ -38,14 +37,14 @@ export default new Command({
|
||||
await changeItemCount(user, userRecord, item.name, -amount)
|
||||
]);
|
||||
|
||||
if (!data.includes(false)) {
|
||||
const tempdata = data[0] as userRecord;
|
||||
if (data[0] !== false && data[1] !== false) {
|
||||
const tempdata = data[0];
|
||||
const newamount = tempdata.inventory[item.name]!;
|
||||
await sendMessage(`${user.displayName} gave ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)} to ${target.displayName}. They now have ${newamount} ${item.prettyName + (newamount === 1 ? '' : item.plural)}`, msg.messageId);
|
||||
} else {
|
||||
// TODO: Rewrite this section
|
||||
await sendMessage(`Failed to give ${target.displayName} ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)}`, msg.messageId);
|
||||
logger.warn(`WARNING: Item donation failed: target success: ${data[0] !== false}, donator success: ${data[1] !== false}`);
|
||||
logger.warn(`WARNING: Item donation failed: target success: ${data[0] !== false ? "yes" : "no"}, donator success: ${data[1] !== false ? "yes" : "no"}`);
|
||||
};
|
||||
await user.clearLock();
|
||||
await target.clearLock();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Command, sendMessage } from "commands";
|
||||
import { getAllUserRecords } from "db/dbUser";
|
||||
import { getTimeoutStats } from "lib/getStats";
|
||||
import { getKDLeaderboard } from "db/dbUser";
|
||||
import User from "user";
|
||||
|
||||
type KD = { user: User; kd: number; };
|
||||
@@ -10,27 +9,21 @@ export default new Command({
|
||||
aliases: ['monthlyleaderboard', 'kdleaderboard', 'leaderboard'],
|
||||
usertype: 'chatter',
|
||||
execution: async msg => {
|
||||
const users = await getAllUserRecords();
|
||||
if (!users) return;
|
||||
const monthdata = new Date().toISOString().slice(0, 7);
|
||||
|
||||
const userKDs: KD[] = [];
|
||||
await Promise.all(users.map(async userRecord => {
|
||||
const user = await User.initUserId(userRecord.id);
|
||||
if (!user) return;
|
||||
const data = await getTimeoutStats(user, true);
|
||||
if (!data) return;
|
||||
if (data.hit.blaster < 5) return;
|
||||
|
||||
let kd = data.shot.blaster / data.hit.blaster;
|
||||
if (isNaN(kd)) kd = 0;
|
||||
userKDs.push({ user, kd });
|
||||
}));
|
||||
|
||||
if (userKDs.length === 0) {
|
||||
const rawKD = await getKDLeaderboard(monthdata);
|
||||
if (rawKD.length === 0) {
|
||||
await sendMessage(`No users on leaderboard yet!`, msg.messageId);
|
||||
return;
|
||||
};
|
||||
|
||||
const userKDs: KD[] = [];
|
||||
await Promise.all(rawKD.map(async userRecord => {
|
||||
const user = await User.initUserId(userRecord.userId.toString());
|
||||
if (!user) return;
|
||||
userKDs.push({ user, kd: userRecord.KD })
|
||||
}));
|
||||
|
||||
userKDs.sort((a, b) => b.kd - a.kd);
|
||||
|
||||
const txt: string[] = [];
|
||||
|
||||
@@ -14,7 +14,7 @@ export default new Command({
|
||||
const txt: string[] = [];
|
||||
for (const userRecord of data) {
|
||||
if (userRecord.balance === 0) continue;
|
||||
const user = await User.initUserId(userRecord.id);
|
||||
const user = await User.initUserId(userRecord.id.toString());
|
||||
if (!user) continue;
|
||||
txt.push(`${index}. ${user.displayName}: ${userRecord.balance}`);
|
||||
index++;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Command, sendMessage } from "commands";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
import { timeout } from "lib/timeout";
|
||||
import { changeBalance } from "lib/changeBalance";
|
||||
import { createTimeoutRecord } from "db/dbTimeouts";
|
||||
|
||||
export default new Command({
|
||||
name: 'timeout',
|
||||
aliases: ['timeout'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.balance < 100) { await sendMessage(`You don't have enough qweribucks (need 100, have ${userObj.balance})`, msg.messageId); return; };
|
||||
const messagequery = parseCommandArgs(msg.messageText);
|
||||
if (!messagequery[0]) { await sendMessage('Please specify a target'); return; };
|
||||
const target = await User.initUsername(messagequery[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`${messagequery[0]} doesn't exist`); return; };
|
||||
await getUserRecord(target); // make sure the user record exist in the database
|
||||
|
||||
const result = await timeout(target, `You got BLASTED by ${user.displayName}`, 60);
|
||||
if (result.status) {
|
||||
await Promise.all([
|
||||
sendMessage(`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`),
|
||||
changeBalance(user, userObj, -100),
|
||||
createTimeoutRecord(user, target, 'blaster')
|
||||
]);
|
||||
} 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)
|
||||
]);
|
||||
break;
|
||||
case "unknown":
|
||||
await sendMessage('Something went wrong...', msg.messageId);
|
||||
break;
|
||||
};
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -1,12 +1,13 @@
|
||||
import pocketbase from "db/connection";
|
||||
import { RedisClient } from "bun";
|
||||
import db from "db/connection";
|
||||
import { users } from "db/schema";
|
||||
import logger from "lib/logger";
|
||||
|
||||
export async function connectionCheck() {
|
||||
let pbstatus = false;
|
||||
let pgstatus = false;
|
||||
try {
|
||||
await pocketbase.health.check().then(a => a.code === 200);
|
||||
pbstatus = true;
|
||||
await db.select().from(users); // The query doesn't matter, only that it fails. This also fails if the migration hasn't taken place
|
||||
pgstatus = true;
|
||||
} catch { };
|
||||
const tempclient = new RedisClient(undefined, {
|
||||
connectionTimeout: 100,
|
||||
@@ -18,7 +19,7 @@ export async function connectionCheck() {
|
||||
redisstatus = true;
|
||||
} catch { };
|
||||
logger.info(`Currently using the "${process.env.NODE_ENV ?? "production"}" database`);
|
||||
pbstatus ? logger.ok(`Pocketbase status: good`) : logger.err(`Pocketbase status: bad`);
|
||||
pgstatus ? logger.ok(`Postgresql status: good`) : logger.err(`Postgresql status: bad`);
|
||||
redisstatus ? logger.ok(`Redis/Valkey status: good`) : logger.err(`Redis/Valkey status: bad`);
|
||||
if (!pbstatus || !redisstatus) process.exit(1);
|
||||
if (!pgstatus || !redisstatus) process.exit(1);
|
||||
};
|
||||
|
||||
@@ -1,75 +1,15 @@
|
||||
import type { AccessToken } from "@twurple/auth";
|
||||
import PocketBase, { RecordService } from "pocketbase";
|
||||
import type { inventory } from "items";
|
||||
import * as schema from "db/schema";
|
||||
import logger from "lib/logger";
|
||||
|
||||
const pocketbaseurl = process.env.POCKETBASE_URL ?? "localhost:8090";
|
||||
if (pocketbaseurl === "") { logger.enverr("POCKETBASE_URL"); process.exit(1); };
|
||||
const host = process.env.POSTGRES_HOST ?? "";
|
||||
if (!host) { logger.enverr("POSTGRES_HOST"); process.exit(1); };
|
||||
const user = process.env.POSTGRES_USER ?? "";
|
||||
if (!user) { logger.enverr("POSTGRES_USER"); process.exit(1); };
|
||||
const password = process.env.POSTGRES_PASSWORD ?? "";
|
||||
if (!password) { logger.enverr("POSTGRES_USER"); process.exit(1); };
|
||||
const database = process.env.POSTGRES_DB ?? "";
|
||||
if (!database) { logger.enverr("POSTGRES_DB"); process.exit(1); };
|
||||
const url = `postgresql://${user}:${password}@${host}/${database}`;
|
||||
|
||||
export type authRecord = {
|
||||
id: string;
|
||||
accesstoken: AccessToken;
|
||||
};
|
||||
|
||||
export type userRecord = {
|
||||
id: string;
|
||||
username: string; // Don't use this, Use User.username or User.displayName. This is just to make the pocketbase data easier to read.
|
||||
balance: number;
|
||||
inventory: inventory;
|
||||
lastlootbox: string;
|
||||
};
|
||||
|
||||
export type usedItemRecord = {
|
||||
id?: string;
|
||||
user: string;
|
||||
item: string;
|
||||
created: string;
|
||||
};
|
||||
|
||||
export type timeoutRecord = {
|
||||
id?: string;
|
||||
user: string;
|
||||
target: string;
|
||||
item: string;
|
||||
created: string;
|
||||
};
|
||||
|
||||
export type cheerEventRecord = {
|
||||
id?: string;
|
||||
user: string;
|
||||
cheer: string;
|
||||
created: string;
|
||||
};
|
||||
|
||||
export type cheerRecord = {
|
||||
id?: string;
|
||||
user: string;
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export type anivTimeoutRecord = {
|
||||
id?: string;
|
||||
message: string;
|
||||
user: string;
|
||||
duration: number;
|
||||
};
|
||||
|
||||
export type getLootRecord = {
|
||||
id?: string;
|
||||
user: string;
|
||||
qbucks: number;
|
||||
items: inventory;
|
||||
};
|
||||
|
||||
interface TypedPocketBase extends PocketBase {
|
||||
collection(idOrName: 'auth'): RecordService<authRecord>;
|
||||
collection(idOrName: 'users'): RecordService<userRecord>;
|
||||
collection(idOrName: 'usedItems'): RecordService<usedItemRecord>;
|
||||
collection(idOrName: 'timeouts'): RecordService<timeoutRecord>;
|
||||
collection(idOrName: 'cheerEvents'): RecordService<cheerEventRecord>;
|
||||
collection(idOrName: 'cheers'): RecordService<cheerRecord>;
|
||||
collection(idOrName: 'anivTimeouts'): RecordService<anivTimeoutRecord>;
|
||||
collection(idOrName: 'getLoots'): RecordService<getLootRecord>;
|
||||
};
|
||||
|
||||
export default new PocketBase(pocketbaseurl).autoCancellation(false) as TypedPocketBase;
|
||||
import { drizzle } from 'drizzle-orm/bun-sql';
|
||||
export default drizzle(url, { schema });
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import pocketbase from "db/connection";
|
||||
import db from "db/connection";
|
||||
import User from "user";
|
||||
import logger from "lib/logger";
|
||||
import { anivTimeouts } from "db/schema";
|
||||
import { type anivBots } from "lib/handleAnivMessage";
|
||||
|
||||
const pb = pocketbase.collection('anivTimeouts');
|
||||
|
||||
export async function createAnivTimeoutRecord(message: string, user: User, duration: number) {
|
||||
try {
|
||||
await pb.create({ message, user: user.id, duration });
|
||||
} catch (e) {
|
||||
logger.err(`Failed to create anivTimeoutRecord: user: ${user.displayName} message: "${message}" duration: ${duration}`);
|
||||
logger.err(e as string);
|
||||
};
|
||||
export async function createAnivTimeoutRecord(message: string, anivBot: anivBots, user: User, duration: number) {
|
||||
await db.insert(anivTimeouts).values({
|
||||
message,
|
||||
anivBot,
|
||||
user: parseInt(user.id),
|
||||
duration
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,38 +1,28 @@
|
||||
import type { AccessToken } from "@twurple/auth";
|
||||
import pocketbase, { type authRecord } from "db/connection";
|
||||
const pb = pocketbase.collection('auth');
|
||||
import db from "db/connection";
|
||||
import { auth } from "db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export async function createAuthRecord(token: AccessToken, userId: string) {
|
||||
try {
|
||||
const data: authRecord = {
|
||||
accesstoken: token,
|
||||
id: userId
|
||||
};
|
||||
await pb.create(data);
|
||||
} catch (err) { };
|
||||
await db.insert(auth).values({
|
||||
id: parseInt(userId),
|
||||
accesstoken: token
|
||||
});
|
||||
};
|
||||
|
||||
export async function getAuthRecord(userId: string, requiredIntents: string[]) {
|
||||
try {
|
||||
const data = await pb.getOne(userId);
|
||||
if (!requiredIntents.every(intent => data.accesstoken.scope.includes(intent))) return undefined;
|
||||
return { accesstoken: data.accesstoken };
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
};
|
||||
const data = await db.query.auth.findFirst({
|
||||
where: eq(auth.id, parseInt(userId))
|
||||
});
|
||||
if (!data) return undefined;
|
||||
if (!requiredIntents.every(intent => data.accesstoken.scope.includes(intent))) return undefined;
|
||||
return { accesstoken: data.accesstoken };
|
||||
};
|
||||
|
||||
export async function updateAuthRecord(userId: string, newtoken: AccessToken) {
|
||||
try {
|
||||
const newrecord = {
|
||||
accesstoken: newtoken,
|
||||
};
|
||||
await pb.update(userId, newrecord);
|
||||
} catch (err) { };
|
||||
await db.update(auth).set({ accesstoken: newtoken }).where(eq(auth.id, parseInt(userId)));
|
||||
};
|
||||
|
||||
export async function deleteAuthRecord(userId: string): Promise<void> {
|
||||
try {
|
||||
await pb.delete(userId);
|
||||
} catch (err) { };
|
||||
await db.delete(auth).where(eq(auth.id, parseInt(userId)));
|
||||
};
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
import pocketbase from "db/connection";
|
||||
import db from "db/connection";
|
||||
import { cheerEvents } from "db/schema";
|
||||
import { and, between, eq, SQL } from "drizzle-orm";
|
||||
import type { items } from "items";
|
||||
import User from "user";
|
||||
import logger from "lib/logger";
|
||||
const pb = pocketbase.collection('cheerEvents');
|
||||
|
||||
export async function createCheerEventRecord(user: User, cheer: string): Promise<void> {
|
||||
try {
|
||||
await pb.create({ user: user.id, cheer });
|
||||
} catch (e) {
|
||||
logger.err(`Failed to create cheerEvent record in database: user: ${user.id}, cheer: ${cheer}`);
|
||||
logger.err(e as string);
|
||||
};
|
||||
export async function createCheerEventRecord(user: User, cheer: items): Promise<void> {
|
||||
await db.insert(cheerEvents).values({ user: parseInt(user.id), event: cheer });
|
||||
};
|
||||
|
||||
export async function getCheerEvents(user: User, monthData?: string) {
|
||||
try {
|
||||
const monthquery = monthData ? ` && created~"${monthData}"` : '';
|
||||
const data = await pb.getFullList({
|
||||
filter: `user="${user.id}"${monthquery}`
|
||||
});
|
||||
return data;
|
||||
} catch (e) {
|
||||
logger.err(`Failed to get cheerEvents for user: ${user.id}, month: ${monthData}`);
|
||||
logger.err(e as string);
|
||||
let condition: SQL<unknown> | undefined = eq(cheerEvents.user, parseInt(user.id));
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(condition, between(cheerEvents.created, new Date(begin), new Date(end)));
|
||||
};
|
||||
const data = await db.select().from(cheerEvents).where(condition);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
import pocketbase from "db/connection";
|
||||
import db from "db/connection";
|
||||
import { cheers } from "db/schema";
|
||||
import User from "user";
|
||||
import logger from "lib/logger";
|
||||
const pb = pocketbase.collection('cheers');
|
||||
import { and, between, eq, SQL } from "drizzle-orm";
|
||||
|
||||
export async function createCheerRecord(user: User, amount: number): Promise<void> {
|
||||
try {
|
||||
await pb.create({ user: user.id, amount })
|
||||
} catch (e) {
|
||||
logger.err(`Failed to create cheer record in database: user: ${user.id}, amount: ${amount}`);
|
||||
logger.err(e as string);
|
||||
};
|
||||
await db.insert(cheers).values({ user: parseInt(user.id), amount });
|
||||
};
|
||||
|
||||
export async function getCheers(user: User, monthData?: string) {
|
||||
try {
|
||||
const monthquery = monthData ? ` && created~"${monthData}"` : '';
|
||||
const data = await pb.getFullList({
|
||||
filter: `user="${user.id}"${monthquery}`
|
||||
});
|
||||
return data;
|
||||
} catch (e) {
|
||||
logger.err(`Failed to get cheers for user: ${user.id}, month: ${monthData}`);
|
||||
logger.err(e as string);
|
||||
let condition: SQL<unknown> | undefined = eq(cheers.user, parseInt(user.id));
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(condition, between(cheers.created, new Date(begin), new Date(end)));
|
||||
};
|
||||
const data = await db.select().from(cheers).where(condition);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import pocketbase from "db/connection";
|
||||
import db from "db/connection";
|
||||
import { getLoots } from "db/schema";
|
||||
import type { inventory } from "items";
|
||||
import logger from "lib/logger";
|
||||
import type User from "user";
|
||||
|
||||
const pb = pocketbase.collection('getLoots');
|
||||
|
||||
export async function createGetLootRecord(user: User, qbucks: number, inventory: inventory) {
|
||||
try {
|
||||
await pb.create({
|
||||
user: user.id,
|
||||
qbucks,
|
||||
items: inventory
|
||||
});
|
||||
} catch (e) {
|
||||
logger.err(`Failed to create getLoot record for ${user.displayName}: ${inventory}`);
|
||||
logger.err(e as string);
|
||||
};
|
||||
await db.insert(getLoots).values({
|
||||
user: parseInt(user.id),
|
||||
qbucks: qbucks,
|
||||
items: inventory
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,39 +1,35 @@
|
||||
import pocketbase from "db/connection";
|
||||
import db from "db/connection";
|
||||
import { timeouts } from "db/schema";
|
||||
import User from "user";
|
||||
import logger from "lib/logger";
|
||||
const pb = pocketbase.collection('timeouts');
|
||||
import type { items } from "items";
|
||||
import { and, between, eq, type SQL } from "drizzle-orm";
|
||||
|
||||
export async function createTimeoutRecord(user: User, target: User, item: string): Promise<void> {
|
||||
try {
|
||||
await pb.create({ user: user.id, target: target.id, item });
|
||||
} catch (err) {
|
||||
logger.err(`Failed to create timeout record in database: user: ${user.id}, target: ${target.id}, item: ${item}`);
|
||||
logger.err(err as string);
|
||||
};
|
||||
export async function createTimeoutRecord(user: User, target: User, item: items): Promise<void> {
|
||||
await db.insert(timeouts).values({
|
||||
user: parseInt(user.id),
|
||||
target: parseInt(target.id),
|
||||
item
|
||||
});
|
||||
};
|
||||
|
||||
export async function getTimeoutsAsUser(user: User, monthData?: string) {
|
||||
try {
|
||||
const monthquery = monthData ? ` && created~"${monthData}"` : '';
|
||||
const data = await pb.getFullList({
|
||||
filter: `user="${user.id}"${monthquery}`
|
||||
});
|
||||
return data;
|
||||
} catch (e) {
|
||||
logger.err(`Failed to get timeouts as user: ${user.id}, month: ${monthData}`);
|
||||
logger.err(e as string);
|
||||
let condition: SQL<unknown> | undefined = eq(timeouts.user, parseInt(user.id));
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(condition, between(timeouts.created, new Date(begin), new Date(end)));
|
||||
};
|
||||
const data = await db.select().from(timeouts).where(condition);
|
||||
return data;
|
||||
};
|
||||
|
||||
export async function getTimeoutsAsTarget(user: User, monthData?: string) {
|
||||
try {
|
||||
const monthquery = monthData ? ` && created~"${monthData}"` : '';
|
||||
const data = await pb.getFullList({
|
||||
filter: `target="${user.id}"${monthquery}`
|
||||
});
|
||||
return data;
|
||||
} catch (e) {
|
||||
logger.err(`Failed to get timeouts as target: ${user.id}, month: ${monthData}`);
|
||||
logger.err(e as string);
|
||||
let condition: SQL<unknown> | undefined = eq(timeouts.target, parseInt(user.id));
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(condition, between(timeouts.created, new Date(begin), new Date(end)));
|
||||
};
|
||||
const data = await db.select().from(timeouts).where(condition);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
import pocketbase from "db/connection";
|
||||
import db from "db/connection";
|
||||
import { usedItems } from "db/schema";
|
||||
import User from "user";
|
||||
import logger from "lib/logger";
|
||||
const pb = pocketbase.collection('usedItems');
|
||||
import type { items } from "items";
|
||||
import { and, between, eq, type SQL } from "drizzle-orm";
|
||||
|
||||
export async function createUsedItemRecord(user: User, item: string): Promise<void> {
|
||||
try {
|
||||
await pb.create({ user: user.id, item });
|
||||
} catch (err) {
|
||||
logger.err(`Failed to create usedItem record in database: user: ${user.id}, item: ${item}`);
|
||||
logger.err(err as string);
|
||||
};
|
||||
export async function createUsedItemRecord(user: User, item: items): Promise<void> {
|
||||
await db.insert(usedItems).values({ user: parseInt(user.id), item });
|
||||
};
|
||||
|
||||
export async function getItemsUsed(user: User, monthData?: string) {
|
||||
try {
|
||||
const monthquery = monthData ? ` && created~"${monthData}"` : '';
|
||||
const data = await pb.getFullList({
|
||||
filter: `user="${user.id}"${monthquery}`
|
||||
});
|
||||
return data;
|
||||
} catch (e) {
|
||||
logger.err(`Failed to get items used for user: ${user.id}, month: ${monthData}`);
|
||||
logger.err(e as string);
|
||||
let condition: SQL<unknown> | undefined = eq(usedItems.user, parseInt(user.id));
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(condition, between(usedItems.created, new Date(begin), new Date(end)));
|
||||
};
|
||||
const data = await db.select().from(usedItems).where(condition);
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
124
src/db/dbUser.ts
124
src/db/dbUser.ts
@@ -1,58 +1,92 @@
|
||||
import pocketbase, { type userRecord } from "db/connection";
|
||||
import { emptyInventory, itemarray } from "items";
|
||||
import db from "db/connection";
|
||||
import { timeouts, users } from "db/schema";
|
||||
import { itemarray, type inventory } from "items";
|
||||
import type User from "user";
|
||||
import logger from "lib/logger";
|
||||
|
||||
const pb = pocketbase.collection('users');
|
||||
import { count, desc, eq, inArray, sql, ne, between, and, SQL } from "drizzle-orm";
|
||||
|
||||
/** Use this function to both ensure existance and to retreive data */
|
||||
export async function getUserRecord(user: User): Promise<userRecord> {
|
||||
try {
|
||||
const data = await pb.getOne(user.id);
|
||||
export async function getUserRecord(user: User) {
|
||||
const data = await db.query.users.findFirst({ where: eq(users.id, parseInt(user.id)) });
|
||||
if (!data) return createUserRecord(user);
|
||||
|
||||
if (Object.keys(data.inventory).sort().toString() !== itemarray.sort().toString()) { // If the items in the user inventory are missing an item.
|
||||
itemarray.forEach(key => {
|
||||
if (!(key in data.inventory)) data.inventory[key] = 0;
|
||||
});
|
||||
};
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
// This gets triggered if the user doesn't exist in the database
|
||||
return await createUserRecord(user);
|
||||
if (Object.keys(data.inventory).sort().toString() !== itemarray.sort().toString()) { // If the items in the user inventory are missing an item.
|
||||
itemarray.forEach(key => {
|
||||
if (!(key in data.inventory)) data.inventory[key] = 0;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export async function getAllUserRecords(): Promise<userRecord[]> {
|
||||
return await pb.getFullList();
|
||||
};
|
||||
|
||||
async function createUserRecord(user: User): Promise<userRecord> {
|
||||
const data = await pb.create({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
balance: 0,
|
||||
inventory: emptyInventory,
|
||||
lastlootbox: new Date(0).toISOString()
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export async function updateUserRecord(user: User, newData: userRecord): Promise<boolean> {
|
||||
try {
|
||||
await pb.update(user.id, newData);
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.err(err as string);
|
||||
return false;
|
||||
};
|
||||
export async function getAllUserRecords() {
|
||||
return await db.select().from(users);
|
||||
};
|
||||
|
||||
async function createUserRecord(user: User) {
|
||||
return await db.insert(users).values({
|
||||
id: parseInt(user.id),
|
||||
username: user.username
|
||||
}).returning().then(a => {
|
||||
if (!a[0]) throw Error('Something went horribly wrong');
|
||||
return a[0]
|
||||
});
|
||||
};
|
||||
|
||||
export type balanceUpdate = { balance: number; };
|
||||
export type inventoryUpdate = { inventory: inventory; };
|
||||
type updateUser = balanceUpdate | inventoryUpdate;
|
||||
|
||||
export async function updateUserRecord(user: User, newData: updateUser) {
|
||||
await db.update(users).set(newData).where(eq(users.id, parseInt(user.id)));
|
||||
return true;
|
||||
};
|
||||
|
||||
export async function getBalanceLeaderboard() {
|
||||
try {
|
||||
return await pb.getList(1, 10, { sort: '-balance,id' }).then(a => a.items);
|
||||
} catch (err) {
|
||||
logger.err(err as string);
|
||||
};
|
||||
return await db.select().from(users).orderBy(desc(users.balance)).limit(10);
|
||||
};
|
||||
|
||||
export async function getKDLeaderboard(monthData?: string) {
|
||||
let condition: SQL<unknown> | undefined = ne(timeouts.item, 'silverbullet');
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(condition, between(timeouts.created, new Date(begin), new Date(end)));
|
||||
};
|
||||
|
||||
const usersGotShot = await db.select({
|
||||
userId: users.id,
|
||||
amount: count(timeouts.target),
|
||||
})
|
||||
.from(users)
|
||||
.innerJoin(timeouts, eq(users.id, timeouts.target))
|
||||
.groupBy(users.id)
|
||||
.having(sql`count(${timeouts.id}) > 5`)
|
||||
.where(condition);
|
||||
|
||||
const usersThatShot = await db.select({
|
||||
userId: users.id,
|
||||
amount: count(timeouts.user)
|
||||
})
|
||||
.from(users)
|
||||
.innerJoin(timeouts, eq(users.id, timeouts.user))
|
||||
.groupBy(users.id)
|
||||
.where(
|
||||
and(
|
||||
condition,
|
||||
inArray(users.id, usersGotShot.map(a => a.userId))
|
||||
)
|
||||
);
|
||||
|
||||
const lookup = new Map(usersThatShot.map(a => [a.userId, a.amount]));
|
||||
const result = usersGotShot.map(user => ({
|
||||
userId: user.userId,
|
||||
KD: lookup.get(user.userId)! / user.amount
|
||||
}));
|
||||
|
||||
result.map(user => {
|
||||
if (isNaN(user.KD)) user.KD = 0;
|
||||
return user
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
122
src/db/schema.ts
Normal file
122
src/db/schema.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import type { AccessToken } from "@twurple/auth";
|
||||
import type { inventory, items } from "items";
|
||||
import { integer, jsonb, pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
|
||||
import type { anivBots } from "lib/handleAnivMessage";
|
||||
import { relations } from "drizzle-orm";
|
||||
|
||||
export const auth = pgTable('auth', {
|
||||
id: integer().primaryKey(),
|
||||
accesstoken: jsonb().$type<AccessToken>().notNull()
|
||||
});
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: integer().primaryKey().notNull(),
|
||||
username: varchar().notNull(),
|
||||
balance: integer().default(0).notNull(),
|
||||
inventory: jsonb().$type<inventory>().default({}).notNull(),
|
||||
lastlootbox: timestamp().default(new Date(0)).notNull()
|
||||
});
|
||||
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
timeouts_target: many(timeouts),
|
||||
timeouts_shooter: many(timeouts),
|
||||
usedItems: many(usedItems),
|
||||
cheerEvents: many(cheerEvents),
|
||||
cheers: many(cheers),
|
||||
anivTimeouts: many(anivTimeouts),
|
||||
getLoots: many(getLoots)
|
||||
}));
|
||||
|
||||
export const timeouts = pgTable('timeouts', {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer().notNull().references(() => users.id),
|
||||
target: integer().notNull().references(() => users.id),
|
||||
item: varchar().$type<items>().notNull(),
|
||||
created: timestamp().defaultNow().notNull()
|
||||
});
|
||||
|
||||
export const timeoutsRelations = relations(timeouts, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [timeouts.user],
|
||||
references: [users.id],
|
||||
relationName: 'shooter'
|
||||
}),
|
||||
target: one(users, {
|
||||
fields: [timeouts.target],
|
||||
references: [users.id],
|
||||
relationName: 'target'
|
||||
})
|
||||
}))
|
||||
|
||||
export const usedItems = pgTable('usedItems', {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer().notNull().references(() => users.id),
|
||||
item: varchar().$type<items>().notNull(),
|
||||
created: timestamp().defaultNow().notNull()
|
||||
});
|
||||
|
||||
export const usedItemsRelations = relations(usedItems, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [usedItems.user],
|
||||
references: [users.id]
|
||||
})
|
||||
}));
|
||||
|
||||
export const cheerEvents = pgTable('cheerEvents', {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer().notNull().references(() => users.id),
|
||||
event: varchar().$type<items>().notNull(),
|
||||
created: timestamp().defaultNow().notNull()
|
||||
});
|
||||
|
||||
export const cheerEventsRelations = relations(cheerEvents, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [cheerEvents.user],
|
||||
references: [users.id]
|
||||
})
|
||||
}));
|
||||
|
||||
export const cheers = pgTable('cheers', {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer().notNull().references(() => users.id),
|
||||
amount: integer().notNull(),
|
||||
created: timestamp().defaultNow().notNull()
|
||||
});
|
||||
|
||||
export const cheersRelations = relations(cheers, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [cheers.user],
|
||||
references: [users.id]
|
||||
})
|
||||
}));
|
||||
|
||||
export const anivTimeouts = pgTable('anivTimeouts', {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer().notNull().references(() => users.id),
|
||||
message: varchar().notNull(),
|
||||
anivBot: varchar().$type<anivBots>().notNull(),
|
||||
duration: integer().notNull(),
|
||||
created: timestamp().defaultNow().notNull()
|
||||
});
|
||||
|
||||
export const anivTimeoutsRelations = relations(anivTimeouts, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [anivTimeouts.user],
|
||||
references: [users.id]
|
||||
})
|
||||
}));
|
||||
|
||||
export const getLoots = pgTable('getLoots', {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer().notNull().references(() => users.id),
|
||||
qbucks: integer().notNull(),
|
||||
items: jsonb().$type<inventory>().notNull(),
|
||||
created: timestamp().defaultNow().notNull()
|
||||
});
|
||||
|
||||
export const getLootsRelations = relations(getLoots, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [getLoots.user],
|
||||
references: [users.id]
|
||||
})
|
||||
}));
|
||||
@@ -28,7 +28,7 @@ async function parseChatMessage(msg: EventSubChannelChatMessageEvent) {
|
||||
// and both are usable to target the same user (id is the same)
|
||||
// The only problem would be if a user changed their name and someone else took their name right after
|
||||
|
||||
if (msg.chatterId === chatterId) return;
|
||||
if (msg.chatterId === chatterId && chatterId !== streamerId) return;
|
||||
|
||||
if (!await redis.exists(`user:${user?.id}:haschatted`) && !msg.sourceMessageId) {
|
||||
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`);
|
||||
@@ -68,9 +68,7 @@ async function handleChatMessage(msg: EventSubChannelChatMessageEvent, user: Use
|
||||
};
|
||||
|
||||
try {
|
||||
await selection.execute(msg, user, {
|
||||
activation
|
||||
});
|
||||
await selection.execute(msg, user, { activation });
|
||||
}
|
||||
catch (err) {
|
||||
logger.err(err as string);
|
||||
@@ -105,9 +103,11 @@ export async function handleCheer(msg: EventSubChannelChatMessageEvent, bits: nu
|
||||
if (!selection) return;
|
||||
|
||||
if (await redis.sismember('disabledcheers', selection.name)) { await sendMessage(`The ${selection.name} cheer is disabled! Sorry!`, msg.messageId); return; };
|
||||
if (selection.isItem && await isInvuln(user.id) && !streamerUsers.includes(user.id)) { await sendMessage(`${user.displayName} Is no longer an invuln`); await removeInvuln(user.id); };
|
||||
try {
|
||||
selection.execute(msg, user);
|
||||
await selection.execute(msg, user);
|
||||
} catch (err) {
|
||||
await sendMessage(`[ERROR]: Something went wrong with cheer execution`);
|
||||
logger.err(err as string);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -16,17 +16,20 @@ export default new Item({
|
||||
plural: 's',
|
||||
description: 'Times a specific person out for 60 seconds',
|
||||
aliases: ['blaster', 'blast'],
|
||||
price: 100,
|
||||
execution: async (msg, user) => {
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any blasters!`, msg.messageId); return; };
|
||||
const messagequery = parseCommandArgs(msg.messageText);
|
||||
if (!messagequery[0]) { await sendMessage('Please specify a target'); return; };
|
||||
const target = await User.initUsername(messagequery[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`${messagequery[0]} doesn't exist`); return; };
|
||||
await getUserRecord(target); // make sure the user record exist in the database
|
||||
|
||||
if (await user.itemLock()) { await sendMessage('Cannot use an item right now', msg.messageId); return; };
|
||||
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; };
|
||||
await user.setLock();
|
||||
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any blasters!`, msg.messageId); await user.clearLock(); return; };
|
||||
|
||||
const result = await timeout(target, `You got blasted by ${user.displayName}!`, 60);
|
||||
if (result.status) await Promise.all([
|
||||
sendMessage(`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`),
|
||||
|
||||
@@ -16,9 +16,8 @@ export default new Item({
|
||||
plural: 's',
|
||||
description: 'Give a random chatter a 60s timeout',
|
||||
aliases: ['grenade'],
|
||||
price: 99,
|
||||
execution: async (msg, user) => {
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any grenades!`, msg.messageId); return; };
|
||||
const targets = await redis.keys(`user:*:vulnerable`);
|
||||
if (targets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; };
|
||||
const selection = targets[Math.floor(Math.random() * targets.length)]!;
|
||||
@@ -26,8 +25,12 @@ export default new Item({
|
||||
|
||||
await getUserRecord(target!); // make sure the user record exist in the database
|
||||
|
||||
if (await user.itemLock()) { await sendMessage('Cannot use an item right now', msg.messageId); return; };
|
||||
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; };
|
||||
await user.setLock();
|
||||
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any grenades!`, msg.messageId); await user.clearLock(); return; };
|
||||
|
||||
await Promise.all([
|
||||
timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60),
|
||||
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`),
|
||||
|
||||
@@ -3,29 +3,31 @@ import User from "user";
|
||||
import { type userType, type specialExecuteArgs } from "commands";
|
||||
|
||||
type itemOptions = {
|
||||
name: string;
|
||||
name: items;
|
||||
aliases: string[];
|
||||
prettyName: string;
|
||||
plural: string;
|
||||
description: string;
|
||||
execution: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>;
|
||||
specialaliases?: string[];
|
||||
price: number;
|
||||
};
|
||||
|
||||
export class Item {
|
||||
public readonly name: string;
|
||||
public readonly name: items;
|
||||
public readonly prettyName: string;
|
||||
public readonly plural: string;
|
||||
public readonly description: string;
|
||||
public readonly aliases: string[];
|
||||
public readonly specialaliases: string[];
|
||||
public readonly usertype: userType;
|
||||
public readonly price: number;
|
||||
public readonly execute: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>;
|
||||
public readonly disableable: boolean;
|
||||
|
||||
/** Creates an item object */
|
||||
constructor(options: itemOptions) {
|
||||
this.name = options.name.toLowerCase();
|
||||
this.name = options.name;
|
||||
this.prettyName = options.prettyName;
|
||||
this.plural = options.plural;
|
||||
this.description = options.description;
|
||||
@@ -34,16 +36,17 @@ export class Item {
|
||||
this.execute = options.execution;
|
||||
this.disableable = true;
|
||||
this.specialaliases = options.specialaliases ?? [];
|
||||
this.price = options.price;
|
||||
};
|
||||
};
|
||||
|
||||
import { readdir } from 'node:fs/promises';
|
||||
import type { userRecord } from "db/connection";
|
||||
import { updateUserRecord } from "db/dbUser";
|
||||
const items = new Map<string, Item>;
|
||||
import { updateUserRecord, type inventoryUpdate } from "db/dbUser";
|
||||
const itemAliasMap = new Map<string, Item>;
|
||||
const itemObjectArray: Item[] = []
|
||||
const specialAliasItems = new Map<string, Item>;
|
||||
const emptyInventory: inventory = {};
|
||||
const itemarray: string[] = [];
|
||||
const itemarray: items[] = [];
|
||||
|
||||
const files = await readdir(import.meta.dir);
|
||||
for (const file of files) {
|
||||
@@ -52,21 +55,24 @@ for (const file of files) {
|
||||
const item: Item = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default);
|
||||
emptyInventory[item.name] = 0;
|
||||
itemarray.push(item.name);
|
||||
itemObjectArray.push(item);
|
||||
for (const alias of item.aliases) {
|
||||
items.set(alias, item); // Since it's not a primitive type the map is filled with references to the item, not the actual object
|
||||
itemAliasMap.set(alias, item); // Since it's not a primitive type the map is filled with references to the item, not the actual object
|
||||
};
|
||||
for (const alias of item.specialaliases) {
|
||||
specialAliasItems.set(alias, item);
|
||||
};
|
||||
};
|
||||
|
||||
export default items;
|
||||
export { emptyInventory, itemarray, specialAliasItems };
|
||||
export default itemAliasMap;
|
||||
export { emptyInventory, itemarray, specialAliasItems, itemObjectArray };
|
||||
|
||||
export type items = "blaster" | "silverbullet" | "grenade" | "tnt";
|
||||
export type inventory = {
|
||||
[key: string]: number;
|
||||
[key in items]?: number;
|
||||
};
|
||||
|
||||
export async function changeItemCount(user: User, userRecord: userRecord, itemname: string, amount = -1): Promise<false | userRecord> {
|
||||
export async function changeItemCount(user: User, userRecord: inventoryUpdate, itemname: items, amount = -1): Promise<false | inventoryUpdate> {
|
||||
userRecord.inventory[itemname] = userRecord.inventory[itemname]! += amount;
|
||||
if (userRecord.inventory[itemname] < 0) return false;
|
||||
await updateUserRecord(user, userRecord);
|
||||
|
||||
@@ -17,17 +17,20 @@ export default new Item({
|
||||
description: 'Times a specific person out for 24 hours',
|
||||
aliases: ['execute', 'silverbullet'],
|
||||
specialaliases: ['blastin'],
|
||||
price: 6666,
|
||||
execution: async (msg, user, specialargs) => {
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any silver bullets!`, msg.messageId); return; };
|
||||
const messagequery = parseCommandArgs(msg.messageText);
|
||||
const messagequery = parseCommandArgs(msg.messageText, specialargs?.activation);
|
||||
if (!messagequery[0]) { await sendMessage('Please specify a target'); return; };
|
||||
const target = await User.initUsername(messagequery[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`${messagequery[0]} doesn't exist`); return; };
|
||||
await getUserRecord(target); // make sure the user record exist in the database
|
||||
|
||||
if (await user.itemLock()) { await sendMessage('Cannot use an item right now', msg.messageId); return; };
|
||||
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; };
|
||||
await user.setLock();
|
||||
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any silver bullets!`, msg.messageId); await user.clearLock(); return; };
|
||||
|
||||
const result = await timeout(target, `You got blasted by ${user.displayName}!`, 60 * 60 * 24);
|
||||
if (result.status) await Promise.all([
|
||||
sendMessage(`${target.displayName} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`),
|
||||
|
||||
@@ -16,16 +16,18 @@ export default new Item({
|
||||
plural: 's',
|
||||
description: 'Give 5-10 random chatters 60 second timeouts',
|
||||
aliases: ['tnt'],
|
||||
price: 1000,
|
||||
execution: async (msg, user) => {
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any TNTs!`, msg.messageId); return; };
|
||||
const vulntargets = await redis.keys('user:*:vulnerable').then(a => a.map(b => b.slice(5, -11)));
|
||||
if (vulntargets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; };
|
||||
const targets = getTNTTargets(vulntargets);
|
||||
|
||||
if (await user.itemLock()) { await sendMessage('Cannot use an item right now', msg.messageId); return; };
|
||||
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; };
|
||||
await user.setLock();
|
||||
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any TNTs!`, msg.messageId); await user.clearLock(); return; };
|
||||
|
||||
await Promise.all(targets.map(async targetid => {
|
||||
const target = await User.initUserId(targetid);
|
||||
await getUserRecord(target!); // make sure the user record exist in the database
|
||||
@@ -45,6 +47,7 @@ export default new Item({
|
||||
}),
|
||||
changeItemCount(user, userObj, ITEMNAME)
|
||||
]);
|
||||
|
||||
await user.clearLock();
|
||||
await sendMessage(`RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? '' : 's'} with their TNT RIPBOZO`);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,12 @@ export async function getItemStats(target: User, thismonth: boolean) {
|
||||
]);
|
||||
if (!items || !cheers) return;
|
||||
|
||||
const returnObj: inventory = {};
|
||||
const returnObj: inventory = {
|
||||
blaster: 0,
|
||||
silverbullet: 0,
|
||||
grenade: 0,
|
||||
tnt: 0,
|
||||
};
|
||||
|
||||
for (const item of items) {
|
||||
if (!returnObj[item.item]) returnObj[item.item] = 0;
|
||||
@@ -48,8 +53,8 @@ export async function getItemStats(target: User, thismonth: boolean) {
|
||||
};
|
||||
|
||||
for (const cheer of cheers) {
|
||||
if (!returnObj[cheer.cheer]) returnObj[cheer.cheer] = 0;
|
||||
returnObj[cheer.cheer]! += 1
|
||||
if (!returnObj[cheer.event]) returnObj[cheer.event] = 0;
|
||||
returnObj[cheer.event]! += 1
|
||||
};
|
||||
|
||||
return returnObj;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { timeout } from "lib/timeout";
|
||||
import { sendMessage } from "commands";
|
||||
import { createAnivTimeoutRecord } from "db/dbAnivTimeouts";
|
||||
|
||||
const ANIVNAMES = ['a_n_e_e_v', 'a_n_i_v'];
|
||||
const ANIVNAMES: anivBots[] = ['a_n_e_e_v', 'a_n_i_v'];
|
||||
|
||||
type anivMessageStore = {
|
||||
[key: string]: string;
|
||||
@@ -14,13 +14,15 @@ type anivMessageStore = {
|
||||
type IsAnivMessage = {
|
||||
isAnivMessage: true;
|
||||
message: string;
|
||||
anivbot: string;
|
||||
anivbot: anivBots;
|
||||
};
|
||||
|
||||
type isNotAnivMessage = {
|
||||
isAnivMessage: false;
|
||||
};
|
||||
|
||||
export type anivBots = 'a_n_i_v' | 'a_n_e_e_v';
|
||||
|
||||
type anivMessageResult = IsAnivMessage | isNotAnivMessage;
|
||||
|
||||
async function isAnivMessage(message: string): Promise<anivMessageResult> {
|
||||
@@ -34,7 +36,7 @@ async function isAnivMessage(message: string): Promise<anivMessageResult> {
|
||||
};
|
||||
|
||||
export default async function handleMessage(msg: EventSubChannelChatMessageEvent, user: User) {
|
||||
if (ANIVNAMES.includes(user.displayName)) {
|
||||
if (ANIVNAMES.map(a => a.toLowerCase()).includes(user.username)) {
|
||||
const data: anivMessageStore = await redis.get('anivmessages').then(a => a === null ? {} : JSON.parse(a));
|
||||
data[user.displayName] = msg.messageText;
|
||||
await redis.set('anivmessages', JSON.stringify(data));
|
||||
@@ -43,7 +45,7 @@ export default async function handleMessage(msg: EventSubChannelChatMessageEvent
|
||||
if (data.isAnivMessage) await Promise.all([
|
||||
timeout(user, 'copied an aniv message', 30),
|
||||
sendMessage(`${user.displayName} got timed out for copying an ${data.anivbot} message`),
|
||||
createAnivTimeoutRecord(msg.messageText, user, 30)
|
||||
createAnivTimeoutRecord(msg.messageText, data.anivbot, user, 30)
|
||||
]);
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user