handle user bans on widget, add tnt cheer, bugfixes

This commit is contained in:
2025-07-19 17:48:42 +01:00
parent a26903183b
commit e76d22ab77
11 changed files with 96 additions and 52 deletions

View File

@@ -1,8 +1,8 @@
import { EventSubChannelChatMessageEvent, EventSubChannelChatMessageDeleteEvent } from "@twurple/eventsub-base";
import { EventSubChannelChatMessageEvent, EventSubChannelChatMessageDeleteEvent, EventSubChannelBanEvent } from "@twurple/eventsub-base";
import { sendTwitchEvent } from ".";
export async function addMessageToChatWidget(msg: EventSubChannelChatMessageEvent) {
sendTwitchEvent({
await sendTwitchEvent({
function: 'createMessage',
messageParts: msg.messageParts,
displayName: msg.chatterDisplayName,
@@ -14,8 +14,15 @@ export async function addMessageToChatWidget(msg: EventSubChannelChatMessageEven
};
export async function deleteMessageFromChatWidget(msg: EventSubChannelChatMessageDeleteEvent) {
sendTwitchEvent({
await sendTwitchEvent({
function: 'deleteMessage',
messageId: msg.messageId
})
};
export async function deleteBannedUserMessagesFromChatWidget(msg: EventSubChannelBanEvent) {
sendTwitchEvent({
function: 'userBan',
chatterId: msg.userId
});
}

View File

@@ -12,14 +12,20 @@ export type deleteMessageEvent = {
function: 'deleteMessage';
messageId: string;
};
export type userBanEvent = {
function: 'userBan';
chatterId: string;
};
export type serverNotificationEvent = {
function: 'serverNotification';
message: string;
};
export type twitchEventData = createMessageEvent |
deleteMessageEvent |
serverNotificationEvent;
export type twitchEventData =
createMessageEvent
| deleteMessageEvent
| userBanEvent
| serverNotificationEvent;
// The types below are taken straight from @twurple/eventsub-base
// I would import this from the package, but that's impossible

View File

@@ -18,10 +18,14 @@ socket.onmessage = event => {
case 'createMessage':
const newMessageElement = parseMessage(data);
newMessageElement.id = data.messageId;
newMessageElement.classList.add(data.chatterId);
document.querySelector("#message-container")?.appendChild(newMessageElement);
break;
case 'deleteMessage':
document.querySelector(`#${CSS.escape(data.messageId)}`)?.remove();
document.querySelectorAll(`#${CSS.escape(data.messageId)}`).forEach(msg => msg.remove());
break;
case 'userBan':
document.querySelectorAll(`.${CSS.escape(data.chatterId)}`).forEach(msg => msg.remove());
break;
case 'serverNotification':
console.log(data.message);

View File

@@ -1,28 +1,27 @@
import { Cheer } from ".";
import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base"
import { changeItemCount } from "../items";
import { Cheer, handleNoTarget } from ".";
import { sendMessage } from "../commands";
import { getUserRecord } from "../db/dbUser";
import { User } from "../user";
import { timeout } from "../lib/timeout";
import { createTimeoutRecord } from "../db/dbTimeouts";
import logger from "../lib/logger";
import { parseCheerArgs } from "../lib/parseCommandArgs";
const ITEMNAME = 'silverbullet';
export default new Cheer('execute', 6666, async (msg, user) => {
const args = parseCheerArgs(msg.messageText);
if (!args[0]) { await handleNoExecuteTarget(msg, user, false); return; };
if (!args[0]) { await handleNoTarget(msg, user, ITEMNAME, false); return; };
const target = await User.initUsername(args[0].toLowerCase());
if (!target) { await handleNoExecuteTarget(msg, user, false); return; };
if (!target) { await handleNoTarget(msg, user, ITEMNAME, false); return; };
await getUserRecord(target);
const result = await timeout(target, `You got executed by ${user.displayName}!`, 60 * 60 * 24);
if (result.status) await Promise.all([
sendMessage(`${target.displayName} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`),
createTimeoutRecord(user, target, 'silverbullet'),
createTimeoutRecord(user, target, ITEMNAME),
]);
else {
await handleNoExecuteTarget(msg, user);
await handleNoTarget(msg, user, ITEMNAME);
switch (result.reason) {
case "banned":
await sendMessage(`${target.displayName} is already timed out/banned`, msg.messageId);
@@ -40,15 +39,3 @@ export default new Cheer('execute', 6666, async (msg, user) => {
};
});
async function handleNoExecuteTarget(msg: EventSubChannelChatMessageEvent, user: User, silent = true) {
if (await user.itemLock()) {
await sendMessage(`Cannot give ${user.displayName} a silver bullet`, msg.messageId);
logger.err(`Failed to give ${user.displayName} a silver bullet for their cheer`);
return;
};
await user.setLock();
const userRecord = await getUserRecord(user);
if (!silent) await sendMessage('No (valid) target specified. You got a silver bullet!', msg.messageId);
await changeItemCount(user, userRecord, 'silverbullet', 1);
await user.clearLock();
};

View File

@@ -27,3 +27,21 @@ for (const file of files) {
export default cheers;
export { namedcheers };
import { sendMessage } from '../commands';
import logger from '../lib/logger';
import { getUserRecord } from '../db/dbUser';
import { changeItemCount } from '../items';
export async function handleNoTarget(msg: EventSubChannelChatMessageEvent, user: User, itemname: string, 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`);
return;
};
await user.setLock();
const userRecord = await getUserRecord(user);
if (!silent) await sendMessage(`No (valid) target specified. You got a ${itemname}!`, msg.messageId);
await changeItemCount(user, userRecord, itemname, 1);
await user.clearLock();
}

View File

@@ -1,28 +1,27 @@
import { Cheer } from ".";
import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base"
import { changeItemCount } from "../items";
import { Cheer, handleNoTarget } from ".";
import { sendMessage } from "../commands";
import { getUserRecord } from "../db/dbUser";
import { User } from "../user";
import { timeout } from "../lib/timeout";
import { createTimeoutRecord } from "../db/dbTimeouts";
import logger from "../lib/logger";
import { parseCheerArgs } from "../lib/parseCommandArgs";
const ITEMNAME = 'blaster';
export default new Cheer('timeout', 100, async (msg, user) => {
const args = parseCheerArgs(msg.messageText);
if (!args[0]) { await handleNoBlasterTarget(msg, user, false); return; };
if (!args[0]) { await handleNoTarget(msg, user, ITEMNAME, false); return; };
const target = await User.initUsername(args[0].toLowerCase());
if (!target) { await handleNoBlasterTarget(msg, user, false); return; };
if (!target) { await handleNoTarget(msg, user, ITEMNAME, false); return; };
await getUserRecord(target);
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`),
createTimeoutRecord(user, target, 'blaster'),
createTimeoutRecord(user, target, ITEMNAME)
]);
else {
await handleNoBlasterTarget(msg, user);
await handleNoTarget(msg, user, ITEMNAME);
switch (result.reason) {
case "banned":
await sendMessage(`${target.displayName} is already timed out/banned`, msg.messageId);
@@ -39,16 +38,3 @@ export default new Cheer('timeout', 100, async (msg, user) => {
};
};
});
async function handleNoBlasterTarget(msg: EventSubChannelChatMessageEvent, user: User, silent = true) {
if (await user.itemLock()) {
await sendMessage(`Cannot give ${user.displayName} a blaster`, msg.messageId);
logger.err(`Failed to give ${user.displayName} a blaster for their cheer`);
return;
};
await user.setLock();
const userRecord = await getUserRecord(user);
if (!silent) await sendMessage('No (valid) target specified. You got a blaster!', msg.messageId);
await changeItemCount(user, userRecord, 'blaster', 1);
await user.clearLock();
};

30
src/cheers/tnt.ts Normal file
View File

@@ -0,0 +1,30 @@
import { Cheer, handleNoTarget } from ".";
import { sendMessage } from "../commands";
import { getUserRecord } from "../db/dbUser";
import { User } from "../user";
import { timeout } from "../lib/timeout";
import { createTimeoutRecord } from "../db/dbTimeouts";
import { getTNTTargets } from "../items/tnt";
import { redis } from "bun";
const ITEMNAME = 'tnt';
export default new Cheer('tnt', 1000, async (msg, user) => {
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); handleNoTarget(msg, user, ITEMNAME); return; };
const targets = getTNTTargets(vulntargets);
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
await Promise.all([
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),
]);
}));
await sendMessage(`RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? '' : 's'} with their TNT RIPBOZO`);
});

6
src/events/banned.ts Normal file
View File

@@ -0,0 +1,6 @@
import { eventSub, streamerId } from "..";
import { deleteBannedUserMessagesFromChatWidget } from "../chatwidget/message";
eventSub.onChannelBan(streamerId, async msg => {
deleteBannedUserMessagesFromChatWidget(msg);
});

View File

@@ -62,7 +62,7 @@ export async function handleCheer(msg: EventSubChannelChatMessageEvent, bits: nu
const selection = cheers.get(bits);
if (!selection) return;
if (await redis.sismember('disabledcheers', selection.name)) { await sendMessage(`The ${selection.name} cheer is disabled`); return; };
if (await redis.sismember('disabledcheers', selection.name)) { await sendMessage(`The ${selection.name} cheer is disabled! Sorry!`, msg.messageId); return; };
try {
selection.execute(msg, user);
} catch (err) {

View File

@@ -6,7 +6,7 @@ import logger from "./lib/logger";
import { addInvuln } from "./lib/invuln";
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"];
const STREAMERINTENTS = ["user:read:chat", "moderation:read", "channel:manage:moderators", "moderator:manage:banned_users", "bits:read", "channel:moderate"];
export const singleUserMode = process.env.CHATTER_IS_STREAMER === 'true';
export const chatterId = process.env.CHATTER_ID ?? "";

View File

@@ -27,7 +27,7 @@ export default new Item(ITEMNAME, 'TNT', 's',
await getUserRecord(target!); // make sure the user record exist in the database
await Promise.all([
timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60),
redis.del(targetid),
redis.del(`user:${targetid}:vulnerable`),
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`),
createTimeoutRecord(user, target!, ITEMNAME),
]);
@@ -42,7 +42,7 @@ export default new Item(ITEMNAME, 'TNT', 's',
}
);
function getTNTTargets<T>(arr: T[]): T[] {
export function getTNTTargets<T>(arr: T[]): T[] {
if (arr.length <= 5) {
return arr;
};