mirror of
https://github.com/qwerinope/qweribot.git
synced 2025-12-18 21:11:39 +01:00
add stats command to readme, implement optional stacking timeouts, fully rework timeout management
This commit is contained in:
@@ -67,6 +67,8 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
|
||||
`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:
|
||||
|
||||
### Qweribucks commands
|
||||
|
||||
@@ -93,6 +95,7 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
|
||||
`getcommands [enabled/disabled]`|Get a list of all, enabled or disabled commands|anyone|`getcommands` `getc`|:x:
|
||||
`getcheers [enabled/disabled]`|Get a list of all, enabled or disabled cheers|anyone|`getcheers` `getcheer`|:x:
|
||||
`gettimeout {target}`|Get the remaining timeout duration of targeted user|anyone|`gettimeout` `gett`|:white_check_mark:
|
||||
`stacking [on/off]`|Check/set if timeouts are stacking. Only admins can set the stacking state|anyone/admins|`stacking`|:x:
|
||||
`vulnchatters`|Get amount of chatters vulnerable to explosives|anyone|`vulnchatters` `vulnc`|:white_check_mark:
|
||||
`disablecommand {command/item}`|Disable a specific command/item|admins|`disablecommand`|:x:
|
||||
`enablecommand {command/item}`|Re-enable a specific command/item|admins|`enablecommand`|:x:
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Command, sendMessage } from "commands";
|
||||
import { streamerApi, streamerId } from "main";
|
||||
import { buildTimeString } from "lib/dateManager";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
import { timeoutDuration } from "lib/timeout";
|
||||
|
||||
export default new Command('gettimeout', ['gett', 'gettimeout'], 'chatter', async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; };
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||
const data = await streamerApi.moderation.getBannedUsers(streamerId, { userId: target.id }).then(a => a.data);
|
||||
if (!data[0]) { await sendMessage(`Chatter ${target.displayName} isn't timed out`, msg.messageId); return; };
|
||||
if (data[0].expiryDate) { await sendMessage(`${target.displayName} is still timed out for ${buildTimeString(data[0].expiryDate.getTime(), Date.now())}`, msg.messageId); return; };
|
||||
const data = await timeoutDuration(target);
|
||||
if (data === false) { await sendMessage(`Chatter ${target.displayName} isn't timed out`, msg.messageId); return; };
|
||||
if (data) { await sendMessage(`${target.displayName} is still timed out for ${buildTimeString(data * 1000, Date.now())}`, msg.messageId); return; };
|
||||
await sendMessage(`${target.displayName} is permanently banned`, msg.messageId);
|
||||
});
|
||||
|
||||
22
src/commands/stacking.ts
Normal file
22
src/commands/stacking.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { redis } from "bun";
|
||||
import { Command, sendMessage } from "commands";
|
||||
import { isAdmin } from "lib/admins";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
|
||||
export default new Command('stacking', ['stacking'], 'chatter', async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0] || !await isAdmin(msg.chatterId)) { await sendMessage(`Timeout stacking is currently ${await redis.exists('timeoutStacking') ? "on" : "off"}`, msg.messageId); return; };
|
||||
// Only admins can reach this part of code
|
||||
switch (args[0]) {
|
||||
case 'enable':
|
||||
case 'on':
|
||||
await redis.set('timeoutStacking', '1');
|
||||
await sendMessage('Timeout stacking is now on')
|
||||
break;
|
||||
case 'disable':
|
||||
case 'off':
|
||||
await redis.del('timeoutStacking');
|
||||
await sendMessage('Timeout stacking is now off')
|
||||
break;
|
||||
};
|
||||
}, false);
|
||||
@@ -1,6 +1,9 @@
|
||||
import { eventSub, streamerId } from "main";
|
||||
import { deleteBannedUserMessagesFromChatWidget } from "web/chatWidget/message";
|
||||
import { redis } from "bun";
|
||||
|
||||
eventSub.onChannelBan(streamerId, async msg => {
|
||||
deleteBannedUserMessagesFromChatWidget(msg);
|
||||
await redis.set(`user:${msg.userId}:timeout`, '1');
|
||||
if (msg.endDate) await redis.expire(`user:${msg.userId}:timeout`, Math.floor((msg.endDate.getTime() - Date.now()) / 1000));
|
||||
});
|
||||
|
||||
6
src/events/unban.ts
Normal file
6
src/events/unban.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { redis } from "bun";
|
||||
import { eventSub, streamerId } from "main";
|
||||
|
||||
eventSub.onChannelUnban(streamerId, async msg => {
|
||||
await redis.del(`user:${msg.userId}:timeout`);
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import { EventSubWsListener } from "@twurple/eventsub-ws";
|
||||
import { addAdmin } from "lib/admins";
|
||||
import logger from "lib/logger";
|
||||
import { addInvuln } from "lib/invuln";
|
||||
import { redis } from "bun";
|
||||
|
||||
const CHATTERINTENTS = ["user:read:chat", "user:write:chat", "user:bot"];
|
||||
const STREAMERINTENTS = ["user:read:chat", "moderation:read", "channel:manage:moderators", "moderator:manage:banned_users", "bits:read", "channel:moderate"];
|
||||
@@ -31,6 +32,13 @@ export const commandPrefix = process.env.COMMAND_PREFIX ?? "!";
|
||||
export const streamerUsers = [chatterId, streamerId];
|
||||
streamerUsers.forEach(async id => await Promise.all([addAdmin(id), addInvuln(id)]));
|
||||
|
||||
const banned = await streamerApi.moderation.getBannedUsers(streamerId).then(a => a.data);
|
||||
banned.forEach(async ban => {
|
||||
await redis.set(`user:${ban.userId}:timeout`, '1');
|
||||
if (ban.expiryDate) redis.expire(`user:${ban.userId}:timeout`, Math.floor((ban.expiryDate.getTime() - Date.now()) / 1000));
|
||||
logger.info(`Set the timeout/ban of ${ban.userDisplayName} in the Redis/Valkey database.`);
|
||||
});
|
||||
|
||||
await import("./events");
|
||||
|
||||
await import("./web");
|
||||
|
||||
@@ -2,6 +2,7 @@ import { streamerApi, streamerId } from "main";
|
||||
import logger from "lib/logger";
|
||||
import User from "user";
|
||||
import { isInvuln } from "lib/invuln";
|
||||
import { redis } from "bun";
|
||||
|
||||
type SuccessfulTimeout = { status: true; };
|
||||
type UnSuccessfulTimeout = { status: false; reason: 'banned' | 'unknown' | 'illegal'; };
|
||||
@@ -14,9 +15,13 @@ type TimeoutResult = SuccessfulTimeout | UnSuccessfulTimeout;
|
||||
export const timeout = async (user: User, reason: string, duration?: number): Promise<TimeoutResult> => {
|
||||
if (await isInvuln(user.id)) return { status: false, reason: 'illegal' }; // Don't timeout invulnerable chatters
|
||||
|
||||
// Check if user already has a timeout
|
||||
const banStatus = await streamerApi.moderation.getBannedUsers(streamerId, { userId: user.id }).then(a => a.data);
|
||||
if (banStatus[0]) return { status: false, reason: 'banned' };
|
||||
// Check if user already has a timeout and handle stacking
|
||||
const banStatus = await timeoutDuration(user);
|
||||
if (banStatus) {
|
||||
if (await redis.exists('timeoutStacking')) {
|
||||
if (duration) duration += Math.floor((banStatus * 1000 - Date.now()) / 1000); // the target is timed out and stacking is on
|
||||
} else return { status: false, reason: 'banned' }; // the target is timed out, but stacking is off
|
||||
} else if (banStatus === null) return { status: false, reason: 'banned' }; // target is perma banned
|
||||
|
||||
if (await streamerApi.moderation.checkUserMod(streamerId, user.id!)) {
|
||||
if (!duration) duration = 60; // make sure that mods don't get perma-banned
|
||||
@@ -28,18 +33,21 @@ export const timeout = async (user: User, reason: string, duration?: number): Pr
|
||||
await streamerApi.moderation.banUser(streamerId, { user: user.id, reason, duration });
|
||||
} catch (err) {
|
||||
logger.err(err as string);
|
||||
return { status: false, reason: 'unknown' }
|
||||
return { status: false, reason: 'unknown' };
|
||||
};
|
||||
|
||||
await redis.set(`user:${user.id}:timeout`, '1');
|
||||
if (duration) await redis.expire(`user:${user.id}:timeout`, duration);
|
||||
|
||||
return { status: true };
|
||||
};
|
||||
|
||||
/** Give the target mod status back after timeout */
|
||||
function remodMod(target: User, duration: number) {
|
||||
setTimeout(async () => {
|
||||
const bandata = await streamerApi.moderation.getBannedUsers(streamerId, { userId: target.id }).then(a => a.data);
|
||||
if (bandata[0]) { // If the target is still timed out, try again when new timeout expires
|
||||
const timeoutleft = Date.parse(bandata[0].expiryDate?.toString()!) - Date.now(); // date when timeout expires - current date
|
||||
const bandata = await timeoutDuration(target);
|
||||
if (bandata) { // If the target is still timed out, try again when new timeout expires
|
||||
const timeoutleft = bandata * 1000 - Date.now(); // date when timeout expires - current date
|
||||
remodMod(target, timeoutleft); // Call the current function with new time (recursion)
|
||||
} else {
|
||||
try {
|
||||
@@ -48,3 +56,11 @@ function remodMod(target: User, duration: number) {
|
||||
};
|
||||
}, duration + 3000); // callback gets called after duration of timeout + 3 seconds
|
||||
};
|
||||
|
||||
/** This returns number if there is a duration of time for the timeout, false if not banned and null if perma banned */
|
||||
export async function timeoutDuration(user: User): Promise<number | null | false> {
|
||||
const data = await redis.expiretime(`user:${user.id}:timeout`);
|
||||
if (data === -1) return null; // Perma banned
|
||||
else if (data === -2) return false; // Not banned
|
||||
return data;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user