diff --git a/bot/commands/index.ts b/bot/commands/index.ts index 179e066..2a0b1dd 100644 --- a/bot/commands/index.ts +++ b/bot/commands/index.ts @@ -17,21 +17,26 @@ export class Command { import { readdir } from 'node:fs/promises'; const commands = new Map; -const intents: string[] = []; +const commandintents: string[] = []; const files = await readdir(import.meta.dir); for (const file of files) { if (!file.endsWith('.ts')) continue; if (file === import.meta.file) continue; const command: Command = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default); - intents.push(...command.requiredIntents); + commandintents.push(...command.requiredIntents); for (const alias of command.aliases) { commands.set(alias, command); // Since it's not a primitive type the map is filled with references to the command, not the actual object }; }; +import items from "../items"; +for (const [name, item] of Array.from(items)) { + commands.set(name, item); // As Item is basically just Command but with more parameters, this should work fine +}; + export default commands; -export { intents }; +export { commandintents }; import { singleUserMode, chatterApi, chatterId, streamerId } from ".."; diff --git a/bot/commands/seiso.ts b/bot/commands/seiso.ts new file mode 100644 index 0000000..885278e --- /dev/null +++ b/bot/commands/seiso.ts @@ -0,0 +1,15 @@ +import { Command, sendMessage } from "."; +import { timeout } from "../lib/timeout"; + +export default new Command('seiso', ['seiso'], ['moderator:manage:banned_users'], async (msg, user) => { + const rand = Math.floor(Math.random() * 101); + if (rand > 75) await sendMessage(`${rand}% seiso YAAAA`, msg.messageId); + else if (rand > 51) await sendMessage(`${rand}% seiso POGGERS`, msg.messageId); + else if (rand === 50) await sendMessage(`${rand}% seiso ok`, msg.messageId); + else if (rand > 30) await sendMessage(`${rand}% seiso SWEAT`, msg.messageId); + else if (rand > 10) await sendMessage(`${rand}% seiso catErm`, msg.messageId); + else { + await sendMessage(`${rand}% seiso RIPBOZO`); + timeout(user, 'TOO YABAI!', 60); + }; +}); diff --git a/bot/index.ts b/bot/index.ts index 3fd9207..0e5e81e 100644 --- a/bot/index.ts +++ b/bot/index.ts @@ -1,7 +1,8 @@ import { createAuthProvider } from "./auth"; import { ApiClient } from "@twurple/api"; import { EventSubHttpListener, ReverseProxyAdapter } from "@twurple/eventsub-http"; -import { intents } from "./commands"; +import { commandintents } from "./commands"; +import { itemintents } from "./items"; const CHATTERBASEINTENTS = ["user:read:chat", "user:write:chat", "user:bot"]; const STREAMERBASEINTENTS = ["user:read:chat", "moderation:read", "channel:manage:moderators"]; @@ -17,8 +18,8 @@ if (hostName === "") { console.error('Please set a EVENTSUB_HOSTNAME in the .env const port = Number(process.env.EVENTSUB_PORT) ?? 0; if (port === 0) { console.error('Please set a EVENTSUB_PORT in the .env'); process.exit(1); }; -const chatterIntents = singleUserMode ? CHATTERBASEINTENTS.concat(STREAMERBASEINTENTS) : CHATTERBASEINTENTS; -const streamerIntents = STREAMERBASEINTENTS.concat(intents); +const streamerIntents = STREAMERBASEINTENTS.concat(commandintents, itemintents); +const chatterIntents = singleUserMode ? CHATTERBASEINTENTS.concat(streamerIntents) : CHATTERBASEINTENTS; export const chatterAuthProvider = await createAuthProvider(chatterId, chatterIntents); export const streamerAuthProvider = singleUserMode ? undefined : await createAuthProvider(streamerId, streamerIntents, true); @@ -38,6 +39,6 @@ export const eventSub = new EventSubHttpListener({ export const commandPrefix = process.env.COMMAND_PREFIX ?? "!"; -export const unbannableUsers = [chatterId, streamerId] +export const unbannableUsers = [chatterId, streamerId]; await import("./events"); diff --git a/bot/items/grenade.ts b/bot/items/grenade.ts new file mode 100644 index 0000000..1899f0f --- /dev/null +++ b/bot/items/grenade.ts @@ -0,0 +1,22 @@ +import { redis } from "bun"; +import { sendMessage } from "../commands"; +import { timeout } from "../lib/timeout"; +import { Item } from "."; +import { User } from "../user"; + +export default new Item('grenade', 'Grenade', 's', + 'Give a random chatter a 60s timeout', + ['grenade'], + ['moderator:manage:banned_users'], + async (msg, user) => { + const targets = await redis.keys('vulnchatters:*'); + if (targets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; }; + const selection = targets[Math.floor(Math.random() * targets.length)]!; + const target = await User.initUserId(selection.split(':')[1]!); + await Promise.all([ + timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60), + redis.del(selection), + sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`) + ]); + } +); diff --git a/bot/items/index.ts b/bot/items/index.ts new file mode 100644 index 0000000..12e30f3 --- /dev/null +++ b/bot/items/index.ts @@ -0,0 +1,47 @@ +import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base"; +import { User } from "../user"; + +export class Item { + public readonly name: string; + public readonly prettyName: string; + public readonly plural: string; + public readonly description: string; + public readonly aliases: string[]; + public readonly requiredIntents: string[]; + public readonly execute: (message: EventSubChannelChatMessageEvent, sender: User) => Promise; + /** Creates an item object + * @param name - internal name of item + * @param prettyName - name of item for presenting to chat + * @param plural - plural appendage; example: lootbox(es) + * @param description - description of what item does + * @param aliases - alternative ways to activate item + * @param requiredIntents - required twitch API scopes to use item + * @param execution - code that gets executed when item gets used */ + constructor(name: string, prettyName: string, plural: string, description: string, aliases: string[], requiredIntents: string[], execution: (message: EventSubChannelChatMessageEvent, sender: User) => Promise) { + this.name = name; + this.prettyName = prettyName; + this.plural = plural; + this.description = description; + this.aliases = aliases; + this.requiredIntents = requiredIntents; + this.execute = execution; + }; +}; + +import { readdir } from 'node:fs/promises'; +const items = new Map; +const itemintents: string[] = []; + +const files = await readdir(import.meta.dir); +for (const file of files) { + if (!file.endsWith('.ts')) continue; + if (file === import.meta.file) continue; + const item: Item = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default); + itemintents.push(...item.requiredIntents); + 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 + }; +}; + +export default items; +export { itemintents };