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.
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
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:
`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:
`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
@@ -174,8 +182,8 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
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 30 minutes|`silverbullet` `execute` `{blastin}`|666
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
## 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 { anivTimeouts } from "db/schema";
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) {
await db.insert(anivTimeouts).values({
message,
anivBot,
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 { 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 { relations } from "drizzle-orm";
@@ -95,8 +95,9 @@ export const anivTimeouts = pgTable('anivTimeouts', {
user: integer().notNull().references(() => users.id),
message: varchar().notNull(),
anivBot: varchar().$type<anivBots>().notNull(),
duration: integer().notNull(),
created: timestamp().defaultNow().notNull()
duration: integer(),
created: timestamp().defaultNow().notNull(),
timeout: boolean().default(true)
});
export const anivTimeoutsRelations = relations(anivTimeouts, ({ one }) => ({

View File

@@ -1,5 +1,5 @@
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 commands, { Command, sendMessage, specialAliasCommands } from "commands";
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}: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`);
await redis.set(`user:${user?.id}:haschatted`, "1");
await redis.set(`user:${user?.id}:welcomemessageid`, message.id);
@@ -69,8 +69,7 @@ async function handleChatMessage(msg: EventSubChannelChatMessageEvent, user: Use
try {
await selection.execute(msg, user, { activation });
}
catch (err) {
} catch (err) {
logger.err(err as string);
await sendMessage('ERROR: Something went wrong', msg.messageId);
await user.clearLock();

View File

@@ -42,10 +42,19 @@ export default async function handleMessage(msg: EventSubChannelChatMessageEvent
await redis.set('anivmessages', JSON.stringify(data));
} else {
const data = await isAnivMessage(msg.messageText);
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, data.anivbot, user, 30)
]);
if (data.isAnivMessage) {
if (Math.random() > 0.5) { // 1/2 chance to dodge aniv timeout
await createAnivTimeoutRecord(msg.messageText, data.anivbot, user, 0)
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 sliceLength = 0;
if (specialAlias) {
nice = input.toLowerCase().slice(specialAlias.length).trim();
nice = input.toLowerCase().slice(specialAlias.length).replace(/[^\x00-\x7F]/g, '').trim();
} 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;
}
return nice.split(' ').slice(sliceLength).map(a => a.replaceAll(/!/gi, ''));
@@ -17,7 +17,7 @@ export function parseCheerArgs(input: string) {
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
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
return nice.split(' ').filter(a => !/cheer[0-9]+/i.test(a));