add better aniv timeouts, add aniv dodges, fix blank user target bug

This commit is contained in:
2025-10-09 17:30:48 +02:00
parent bdc7b4a171
commit e46cec80ed
7 changed files with 72 additions and 17 deletions

View File

@@ -48,6 +48,13 @@ If you've been timed out, you can ghost whisper a message to the chatterbot and
You can only send one message every 5 minutes. You can only send one message every 5 minutes.
Try to bargain for your release with the chatter that shot you, or just call them names. Try to bargain for your release with the chatter that shot you, or just call them names.
### Aniv timeouts
When chatter `a_n_i_v` (a_normal_imyt_viewer) or `a_n_e_e_v` sends a message it's stored in the database.
If then someone copies them, they might get timed out.
These timeouts and dodges are stored. You can get your stats with the `anivtimeouts` command.
The current dodge rate is `1/2`, and the timeout duration range is between `30` and `60` seconds.
### Leaderboards ### Leaderboards
There are 3 types of leaderboards: monthlyKD, alltimeKD and qbucks. There are 3 types of leaderboards: monthlyKD, alltimeKD and qbucks.
@@ -126,6 +133,7 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
`monthlyleaderboard`|Get the K/D leaderboard for this month [(info)](#leaderboards)|anyone|`monthlyleaderboard` `kdleaderboard` `leaderboard`|: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: `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: `qbucksleaderboard`|Get the current qbucks leaderboard [(info)](#leaderboards)|anyone|`qbucksleaderboard` `moneyleaderboard` `baltop`|:white_check_mark:
`anivtimeouts`|Get the amount of timeouts, dodges and dodge percentage from aniv timeouts [(info)](#aniv-timeouts)|anyone|`anivtimeouts` `anivtimeout`|:white_check_mark:
### Qweribucks/Item commands ### Qweribucks/Item commands
@@ -174,8 +182,8 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
NAME|COMMAND|FUNCTION|ALIASES|COST NAME|COMMAND|FUNCTION|ALIASES|COST
-|-|-|-|- -|-|-|-|-
Blaster|`blaster {target}`|Times targeted user out for 60 seconds|`blaster` `blast`|100 Blaster|`blaster {target}`|Times targeted user out for 60 seconds|`blaster` `blast`|100
Silver Bullet|`silverbullet {target}`|Times targeted user out for 30 minutes|`silverbullet` `execute` `{blastin}`|666
Grenade|`grenade`|Times a random vulnerable chatter out for 60 seconds|`grenade`|99 Grenade|`grenade`|Times a random vulnerable chatter out for 60 seconds|`grenade`|99
Silver Bullet|`silverbullet {target}`|Times targeted user out for 30 minutes|`silverbullet` `execute` `{blastin}`|666
TNT|`tnt`|Give 5-10 random chatters 60 second timeouts|`tnt`|1000 TNT|`tnt`|Give 5-10 random chatters 60 second timeouts|`tnt`|1000
## Cheers ## Cheers

View File

@@ -0,0 +1,19 @@
import { Command, sendMessage } from "commands";
import { getAnivTimeouts } from "db/dbAnivTimeouts";
import parseCommandArgs from "lib/parseCommandArgs";
import User from "user";
export default new Command({
name: 'anivtimeouts',
aliases: ['anivtimeouts', 'anivtimeout'],
usertype: 'chatter',
execution: async (msg, user) => {
const args = parseCommandArgs(msg.messageText);
const target = args[0] ? await User.initUsername(args[0].toLowerCase()) : user;
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist!`, msg.messageId); return; };
const { dodge, dead } = await getAnivTimeouts(target);
const percentage = ((dodge / (dead + dodge)) * 100);
const message = `Aniv timeouts of ${target.displayName}: Dodge: ${dodge}, Timeout: ${dead}. Dodge percentage: ${isNaN(percentage) ? "0" : percentage.toFixed(1)}%`;
await sendMessage(message, msg.messageId);
}
});

View File

@@ -2,12 +2,31 @@ import db from "db/connection";
import User from "user"; import User from "user";
import { anivTimeouts } from "db/schema"; import { anivTimeouts } from "db/schema";
import { type anivBots } from "lib/handleAnivMessage"; import { type anivBots } from "lib/handleAnivMessage";
import { count, eq, and } from "drizzle-orm";
/** To create a dodge record, set the duration to 0 */
export async function createAnivTimeoutRecord(message: string, anivBot: anivBots, user: User, duration: number) { export async function createAnivTimeoutRecord(message: string, anivBot: anivBots, user: User, duration: number) {
await db.insert(anivTimeouts).values({ await db.insert(anivTimeouts).values({
message, message,
anivBot, anivBot,
user: parseInt(user.id), user: parseInt(user.id),
duration duration,
timeout: duration !== 0
}); });
}; };
export async function getAnivTimeouts(user: User) {
let [dodge, dead] = await Promise.all([
db.select({
dodge: count()
}).from(anivTimeouts).where(and(eq(anivTimeouts.user, parseInt(user.id)), eq(anivTimeouts.timeout, false))).then(a => a[0]?.dodge),
db.select({
dead: count()
}).from(anivTimeouts).where(and(eq(anivTimeouts.user, parseInt(user.id)), eq(anivTimeouts.timeout, true))).then(a => a[0]?.dead)
]);
if (!dodge) dodge = 0;
if (!dead) dead = 0;
return { dodge, dead };
};

View File

@@ -1,6 +1,6 @@
import type { AccessToken } from "@twurple/auth"; import type { AccessToken } from "@twurple/auth";
import type { inventory, items } from "items"; import type { inventory, items } from "items";
import { integer, jsonb, pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core"; import { boolean, integer, jsonb, pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
import type { anivBots } from "lib/handleAnivMessage"; import type { anivBots } from "lib/handleAnivMessage";
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
@@ -95,8 +95,9 @@ export const anivTimeouts = pgTable('anivTimeouts', {
user: integer().notNull().references(() => users.id), user: integer().notNull().references(() => users.id),
message: varchar().notNull(), message: varchar().notNull(),
anivBot: varchar().$type<anivBots>().notNull(), anivBot: varchar().$type<anivBots>().notNull(),
duration: integer().notNull(), duration: integer(),
created: timestamp().defaultNow().notNull() created: timestamp().defaultNow().notNull(),
timeout: boolean().default(true)
}); });
export const anivTimeoutsRelations = relations(anivTimeouts, ({ one }) => ({ export const anivTimeoutsRelations = relations(anivTimeouts, ({ one }) => ({

View File

@@ -1,5 +1,5 @@
import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base" import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base"
import { streamerId, eventSub, commandPrefix, streamerUsers, chatterId } from "main"; import { streamerId, eventSub, commandPrefix, streamerUsers } from "main";
import User from "user"; import User from "user";
import commands, { Command, sendMessage, specialAliasCommands } from "commands"; import commands, { Command, sendMessage, specialAliasCommands } from "commands";
import { redis } from "bun"; import { redis } from "bun";
@@ -30,7 +30,7 @@ async function parseChatMessage(msg: EventSubChannelChatMessageEvent) {
if (await redis.exists(`user:${user?.id}:bot`)) return; // Ignore all bot commands if (await redis.exists(`user:${user?.id}:bot`)) return; // Ignore all bot commands
if (!await redis.exists(`user:${user?.id}:haschatted`) && !msg.sourceMessageId) { if (!await redis.exists(`user:${user?.id}:haschatted`) && !msg.sourceMessageId) { // The msg.sourceMessageId checks if the message is from shared chat. shared chat should be ignored
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`); 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`);
await redis.set(`user:${user?.id}:haschatted`, "1"); await redis.set(`user:${user?.id}:haschatted`, "1");
await redis.set(`user:${user?.id}:welcomemessageid`, message.id); await redis.set(`user:${user?.id}:welcomemessageid`, message.id);
@@ -69,8 +69,7 @@ async function handleChatMessage(msg: EventSubChannelChatMessageEvent, user: Use
try { try {
await selection.execute(msg, user, { activation }); await selection.execute(msg, user, { activation });
} } catch (err) {
catch (err) {
logger.err(err as string); logger.err(err as string);
await sendMessage('ERROR: Something went wrong', msg.messageId); await sendMessage('ERROR: Something went wrong', msg.messageId);
await user.clearLock(); await user.clearLock();

View File

@@ -42,10 +42,19 @@ export default async function handleMessage(msg: EventSubChannelChatMessageEvent
await redis.set('anivmessages', JSON.stringify(data)); await redis.set('anivmessages', JSON.stringify(data));
} else { } else {
const data = await isAnivMessage(msg.messageText); const data = await isAnivMessage(msg.messageText);
if (data.isAnivMessage) await Promise.all([ if (data.isAnivMessage) {
timeout(user, 'copied an aniv message', 30), if (Math.random() > 0.5) { // 1/2 chance to dodge aniv timeout
sendMessage(`${user.displayName} got timed out for copying an ${data.anivbot} message`), await createAnivTimeoutRecord(msg.messageText, data.anivbot, user, 0)
createAnivTimeoutRecord(msg.messageText, data.anivbot, user, 30) return;
]); };
const duration = Math.floor(Math.random() * 30) + 30 // minimum timeout of 30 sec, maximum of 60 sec
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, data.anivbot, user, duration)
]);
};
}; };
}; };

View File

@@ -5,9 +5,9 @@ export default function parseCommandArgs(input: string, specialAlias?: string) {
let nice = ''; let nice = '';
let sliceLength = 0; let sliceLength = 0;
if (specialAlias) { if (specialAlias) {
nice = input.toLowerCase().slice(specialAlias.length).trim(); nice = input.toLowerCase().slice(specialAlias.length).replace(/[^\x00-\x7F]/g, '').trim();
} else { } else {
nice = input.toLowerCase().slice(commandPrefix.length).trim(); nice = input.toLowerCase().slice(commandPrefix.length).replace(/[^\x00-\x7F]/g, '').trim();
sliceLength = nice.startsWith('use') ? 2 : 1; sliceLength = nice.startsWith('use') ? 2 : 1;
} }
return nice.split(' ').slice(sliceLength).map(a => a.replaceAll(/!/gi, '')); return nice.split(' ').slice(sliceLength).map(a => a.replaceAll(/!/gi, ''));
@@ -17,7 +17,7 @@ export function parseCheerArgs(input: string) {
const nice = input.toLowerCase().trim(); const nice = input.toLowerCase().trim();
// This is for the test command. Remove the command prefix, the command, the whitespace after and the amount of fake bits // This is for the test command. Remove the command prefix, the command, the whitespace after and the amount of fake bits
if (nice.startsWith(commandPrefix + 'testcheer')) return nice.slice(commandPrefix.length + 'testcheer'.length + 1).split(' ').slice(1); if (nice.startsWith(commandPrefix + 'testcheer')) return nice.slice(commandPrefix.length + 'testcheer'.length + 1).replaceAll(/\W/g, '').split(' ').slice(1);
// This is for actual cheers. Remove all 'cheerx' parts of the message // This is for actual cheers. Remove all 'cheerx' parts of the message
return nice.split(' ').filter(a => !/cheer[0-9]+/i.test(a)); return nice.split(' ').filter(a => !/cheer[0-9]+/i.test(a));