proper formatting and linting YAY, change cheer constructor to take object

This commit is contained in:
2025-11-24 17:05:18 +01:00
parent 253775a66e
commit af946e59b8
123 changed files with 4890 additions and 3383 deletions

43
biome.json Normal file
View File

@@ -0,0 +1,43 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.7/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noNonNullAssertedOptionalChain": "off",
"noExplicitAny": "off",
"noControlCharactersInRegex": "off"
},
"style": {
"noNonNullAssertion": "off"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"semicolons": "always"
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

View File

@@ -1,34 +1,34 @@
{ {
"name": "qweribot", "name": "qweribot",
"module": "src/index.ts", "module": "src/index.ts",
"devDependencies": { "devDependencies": {
"@twurple/eventsub-ngrok": "^7.4.0", "@twurple/eventsub-ngrok": "^7.4.0",
"@types/bun": "latest", "@types/bun": "latest",
"drizzle-kit": "^0.31.5", "drizzle-kit": "^0.31.5",
"pg": "^8.16.3" "pg": "^8.16.3"
}, },
"scripts": { "scripts": {
"start": "NODE_ENV=production bun src/index.ts", "start": "NODE_ENV=production bun src/index.ts",
"start-dev": "NODE_ENV=development bun src/index.ts", "start-dev": "NODE_ENV=development bun src/index.ts",
"start-discord": "NODE_ENV=production bun src/discord/index.ts", "start-discord": "NODE_ENV=production bun src/discord/index.ts",
"start-dev-discord": "NODE_ENV=development bun src/discord/index.ts", "start-dev-discord": "NODE_ENV=development bun src/discord/index.ts",
"migrate": "drizzle-kit push --config=drizzle-prod.config.ts", "migrate": "drizzle-kit push --config=drizzle-prod.config.ts",
"migrate-dev": "drizzle-kit push --config=drizzle-dev.config.ts", "migrate-dev": "drizzle-kit push --config=drizzle-dev.config.ts",
"studio": "drizzle-kit studio --config=drizzle-prod.config.ts", "studio": "drizzle-kit studio --config=drizzle-prod.config.ts",
"studio-dev": "drizzle-kit studio --config=drizzle-dev.config.ts" "studio-dev": "drizzle-kit studio --config=drizzle-dev.config.ts"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.8.3" "typescript": "^5.8.3"
}, },
"private": true, "private": true,
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@fontsource/jersey-15": "^5.2.8", "@fontsource/jersey-15": "^5.2.8",
"@twurple/api": "7.4.0", "@twurple/api": "7.4.0",
"@twurple/auth": "^7.4.0", "@twurple/auth": "^7.4.0",
"@twurple/eventsub-http": "^7.4.0", "@twurple/eventsub-http": "^7.4.0",
"discord.js": "^14.24.0", "discord.js": "^14.24.0",
"drizzle-orm": "^0.44.6", "drizzle-orm": "^0.44.6",
"kleur": "^4.1.5" "kleur": "^4.1.5"
} }
} }

View File

@@ -1,103 +1,160 @@
import { RefreshingAuthProvider, exchangeCode, type AccessToken } from "@twurple/auth"; import {
import { createAuthRecord, deleteAuthRecord, getAuthRecord, updateAuthRecord } from "db/dbAuth"; type AccessToken,
exchangeCode,
import logger from "lib/logger"; RefreshingAuthProvider,
} from "@twurple/auth";
import {
createAuthRecord,
deleteAuthRecord,
getAuthRecord,
updateAuthRecord,
} from "db/dbAuth";
import kleur from "kleur"; import kleur from "kleur";
import logger from "lib/logger";
async function initAuth(userId: string, clientId: string, clientSecret: string, requestedIntents: string[], streamer: boolean): Promise<AccessToken> { async function initAuth(
const port = process.env.REDIRECT_PORT ?? 3456 userId: string,
const redirectURL = process.env.REDIRECT_URL ?? `http://localhost:${port}`; clientId: string,
// Set the default url and port to http://localhost:3456 clientSecret: string,
requestedIntents: string[],
streamer: boolean,
): Promise<AccessToken> {
const port = process.env.REDIRECT_PORT ?? 3456;
const redirectURL = process.env.REDIRECT_URL ?? `http://localhost:${port}`;
// Set the default url and port to http://localhost:3456
const state = Bun.randomUUIDv7().replace(/-/g, "").slice(0, 32).toUpperCase(); const state = Bun.randomUUIDv7().replace(/-/g, "").slice(0, 32).toUpperCase();
// Generate random state variable to prevent cross-site-scripting attacks // Generate random state variable to prevent cross-site-scripting attacks
const instruction = `Visit this URL as ${kleur.red().underline().italic(streamer ? 'the streamer' : 'the chatter')} to authenticate the bot.` const instruction = `Visit this URL as ${kleur
logger.info(instruction); .red()
logger.info(`https://id.twitch.tv/oauth2/authorize?client_id=${clientId}&redirect_uri=${redirectURL}&response_type=code&scope=${requestedIntents.join('+')}&state=${state}`); .underline()
.italic(
streamer ? "the streamer" : "the chatter",
)} to authenticate the bot.`;
logger.info(instruction);
logger.info(
`https://id.twitch.tv/oauth2/authorize?client_id=${clientId}&redirect_uri=${redirectURL}&response_type=code&scope=${requestedIntents.join("+")}&state=${state}`,
);
const createCodePromise = () => { const createCodePromise = () => {
let resolver: (code: string) => void; let resolver: (code: string) => void;
const promise = new Promise<string>((resolve) => { resolver = resolve; }); const promise = new Promise<string>((resolve) => {
return { promise, resolver: resolver! }; resolver = resolve;
} });
return { promise, resolver: resolver! };
};
const { promise: codepromise, resolver } = createCodePromise(); const { promise: codepromise, resolver } = createCodePromise();
const server = Bun.serve({ const server = Bun.serve({
port, port,
fetch(request) { fetch(request) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
if (searchParams.has('code') && searchParams.has('state') && searchParams.get('state') === state) { if (
// Check if the required fields exist on the params and validate the state searchParams.has("code") &&
resolver(searchParams.get('code')!); searchParams.has("state") &&
return new Response("Successfully authenticated!"); searchParams.get("state") === state
} else { ) {
return new Response(`Authentication attempt unsuccessful, please make sure the redirect url in the twitch developer console is set to ${redirectURL} and that the bot is listening to that url & port.`, { status: 400 }); // Check if the required fields exist on the params and validate the state
} resolver(searchParams.get("code")!);
} return new Response("Successfully authenticated!");
}); } else {
return new Response(
`Authentication attempt unsuccessful, please make sure the redirect url in the twitch developer console is set to ${redirectURL} and that the bot is listening to that url & port.`,
{ status: 400 },
);
}
},
});
await deleteAuthRecord(userId); await deleteAuthRecord(userId);
const code = await codepromise; const code = await codepromise;
server.stop(false); server.stop(false);
logger.info(`Authentication code received.`); logger.info(`Authentication code received.`);
const tokenData = await exchangeCode(clientId, clientSecret, code, redirectURL); const tokenData = await exchangeCode(
logger.info(`Successfully authenticated code.`); clientId,
await createAuthRecord(tokenData, userId); clientSecret,
logger.ok(`Successfully saved auth data in the database.`); code,
return tokenData; redirectURL,
}; );
logger.info(`Successfully authenticated code.`);
await createAuthRecord(tokenData, userId);
logger.ok(`Successfully saved auth data in the database.`);
return tokenData;
}
export type authProviderInstructions = { export type authProviderInstructions = {
userId: string; userId: string;
intents: string[]; intents: string[];
streamer: boolean; streamer: boolean;
}; };
export async function createAuthProvider(data: authProviderInstructions[]): Promise<RefreshingAuthProvider> { export async function createAuthProvider(
const clientId = process.env.CLIENT_ID; data: authProviderInstructions[],
if (!clientId) { logger.enverr("CLIENT_ID"); process.exit(1); }; ): Promise<RefreshingAuthProvider> {
const clientId = process.env.CLIENT_ID;
if (!clientId) {
logger.enverr("CLIENT_ID");
process.exit(1);
}
const clientSecret = process.env.CLIENT_SECRET; const clientSecret = process.env.CLIENT_SECRET;
if (!clientSecret) { logger.enverr("CLIENT_SECRET"); process.exit(1); }; if (!clientSecret) {
logger.enverr("CLIENT_SECRET");
process.exit(1);
}
const authData = new RefreshingAuthProvider({ const authData = new RefreshingAuthProvider({
clientId, clientId,
clientSecret clientSecret,
}); });
authData.onRefresh(async (user, token) => { authData.onRefresh(async (user, token) => {
logger.ok(`Successfully refreshed auth for user ${user}`); logger.ok(`Successfully refreshed auth for user ${user}`);
await updateAuthRecord(user, token); await updateAuthRecord(user, token);
}); });
authData.onRefreshFailure((user, err) => { authData.onRefreshFailure((user, err) => {
logger.err(`Failed to refresh auth for user ${user}: ${err.name} ${err.message}`); logger.err(
}); `Failed to refresh auth for user ${user}: ${err.name} ${err.message}`,
);
});
for (const user of data) { for (const user of data) {
const authRecord = await getAuthRecord(user.userId, user.intents); const authRecord = await getAuthRecord(user.userId, user.intents);
const token = authRecord ? authRecord.accesstoken : await initAuth(user.userId, clientId, clientSecret, user.intents, user.streamer); const token = authRecord
? authRecord.accesstoken
: await initAuth(
user.userId,
clientId,
clientSecret,
user.intents,
user.streamer,
);
try { try {
await authData.addUserForToken(token, user.intents); await authData.addUserForToken(token, user.intents);
} catch (err) { } catch (_err) {
logger.err(`Failed to setup user auth. Please restart the bot and re-authenticate.`); logger.err(
await deleteAuthRecord(user.userId); `Failed to setup user auth. Please restart the bot and re-authenticate.`,
process.exit(1); );
}; await deleteAuthRecord(user.userId);
process.exit(1);
}
try { try {
await authData.refreshAccessTokenForUser(user.userId); await authData.refreshAccessTokenForUser(user.userId);
} catch (err) { } catch (_err) {
logger.err(`Failed to refresh user ${user.userId}. Please restart the bot and re-authenticate it. Make sure the user that auths the bot and the user that's defined in .env are the same.`); logger.err(
await deleteAuthRecord(user.userId); `Failed to refresh user ${user.userId}. Please restart the bot and re-authenticate it. Make sure the user that auths the bot and the user that's defined in .env are the same.`,
process.exit(1); );
}; await deleteAuthRecord(user.userId);
}; process.exit(1);
}
}
return authData; return authData;
}; }

View File

@@ -1,71 +1,92 @@
import { Cheer, handleNoTarget } from "cheers"; import { Cheer, handleNoTarget } from "cheers";
import { sendMessage } from "lib/commandUtils";
import { getUserRecord } from "db/dbUser";
import User from "user";
import { timeout } from "lib/timeout";
import { createTimeoutRecord } from "db/dbTimeouts";
import { parseCheerArgs } from "lib/parseCommandArgs";
import { createCheerEventRecord } from "db/dbCheerEvents"; import { createCheerEventRecord } from "db/dbCheerEvents";
import { playAlert } from "web/alerts/serverFunctions"; import { createTimeoutRecord } from "db/dbTimeouts";
import { getUserRecord } from "db/dbUser";
import { sendMessage } from "lib/commandUtils";
import { parseCheerArgs } from "lib/parseCommandArgs";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
import { timeout } from "lib/timeout";
import User from "user";
import { playAlert } from "web/alerts/serverFunctions";
const ITEMNAME = 'silverbullet'; const ITEMNAME = "silverbullet";
export default new Cheer('execute', 666, async (msg, user) => { export default new Cheer({
const args = parseCheerArgs(msg.messageText); name: "execute",
amount: 666,
isItem: true,
async execute(msg, user) {
const args = parseCheerArgs(msg.messageText);
let target: User | null; let target: User | null;
if (!args[0]) { if (!args[0]) {
const vulnsids = await redis.keys('user:*:vulnerable'); const vulnsids = await redis.keys("user:*:vulnerable");
const baseusers = vulnsids.map(a => User.initUserId(a.slice(5, -11))); const baseusers = vulnsids.map((a) => User.initUserId(a.slice(5, -11)));
const users: User[] = []; const users: User[] = [];
for (const user of baseusers) { for (const user of baseusers) {
const a = await user; const a = await user;
if (!a) continue; if (!a) continue;
users.push(a); users.push(a);
}; }
if (users.length === 0) { await sendMessage('No vulnerable chatters'); await handleNoTarget(msg, user, ITEMNAME, true); return; }; if (users.length === 0) {
target = users[Math.floor(Math.random() * users.length)]!; await sendMessage("No vulnerable chatters");
await playAlert({ await handleNoTarget(msg, user, ITEMNAME, true);
name: 'blastinRoulette', return;
user: user.displayName, }
targets: users.map(a => a.displayName), target = users[Math.floor(Math.random() * users.length)]!;
finaltarget: target.displayName await playAlert({
}); name: "blastinRoulette",
await new Promise((res, _) => setTimeout(res, 4000)); user: user.displayName,
} else { targets: users.map((a) => a.displayName),
target = await User.initUsername(args[0].toLowerCase()); finaltarget: target.displayName,
}; });
if (!target) { await handleNoTarget(msg, user, ITEMNAME, false); return; }; await new Promise((res, _) => setTimeout(res, 4000));
await getUserRecord(target); } else {
target = await User.initUsername(args[0].toLowerCase());
const result = await timeout(target, `You got executed by ${user.displayName}!`, 60 * 30); }
if (result.status) await Promise.all([ if (!target) {
sendMessage(`KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`), await handleNoTarget(msg, user, ITEMNAME, false);
createTimeoutRecord(user, target, ITEMNAME), return;
createCheerEventRecord(user, ITEMNAME), }
playAlert({ await getUserRecord(target);
name: 'userExecution',
user: user.displayName,
target: target.displayName
})
]);
else {
await handleNoTarget(msg, user, ITEMNAME);
switch (result.reason) {
case "banned":
await sendMessage(`${target.displayName} is already timed out/banned`, msg.messageId);
break;
case "illegal":
await Promise.all([
sendMessage(`${user.displayName} Nou Nou Nou`),
timeout(user, 'nah', 60)
]);
break;
case "unknown":
await sendMessage('Something went wrong...', msg.messageId);
break;
};
};
}, true);
const result = await timeout(
target,
`You got executed by ${user.displayName}!`,
60 * 30,
);
if (result.status)
await Promise.all([
sendMessage(
`KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`,
),
createTimeoutRecord(user, target, ITEMNAME),
createCheerEventRecord(user, ITEMNAME),
playAlert({
name: "userExecution",
user: user.displayName,
target: target.displayName,
}),
]);
else {
await handleNoTarget(msg, user, ITEMNAME);
switch (result.reason) {
case "banned":
await sendMessage(
`${target.displayName} is already timed out/banned`,
msg.messageId,
);
break;
case "illegal":
await Promise.all([
sendMessage(`${user.displayName} Nou Nou Nou`),
timeout(user, "nah", 60),
]);
break;
case "unknown":
await sendMessage("Something went wrong...", msg.messageId);
break;
}
}
},
});

View File

@@ -1,33 +1,44 @@
import { redis } from "lib/redis"; import { Cheer, handleNoTarget } from "cheers";
import { createCheerEventRecord } from "db/dbCheerEvents";
import { createTimeoutRecord } from "db/dbTimeouts";
import { getUserRecord } from "db/dbUser";
import { sendMessage } from "lib/commandUtils"; import { sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis";
import { timeout } from "lib/timeout"; import { timeout } from "lib/timeout";
import User from "user"; import User from "user";
import { getUserRecord } from "db/dbUser";
import { createTimeoutRecord } from "db/dbTimeouts";
import { createCheerEventRecord } from "db/dbCheerEvents";
import { Cheer, handleNoTarget } from "cheers";
import { playAlert } from "web/alerts/serverFunctions"; import { playAlert } from "web/alerts/serverFunctions";
const ITEMNAME = 'grenade'; const ITEMNAME = "grenade";
export default new Cheer('grenade', 99, async (msg, user) => { export default new Cheer({
const targets = await redis.keys(`user:*:vulnerable`); name: "grenade",
if (targets.length === 0) { await sendMessage('No vulnerable chatters to blow up!', msg.messageId); await handleNoTarget(msg, user, ITEMNAME); return; }; amount: 99,
const selection = targets[Math.floor(Math.random() * targets.length)]!; isItem: true,
const target = await User.initUserId(selection.slice(5, -11)); async execute(msg, user) {
const targets = await redis.keys(`user:*:vulnerable`);
if (targets.length === 0) {
await sendMessage("No vulnerable chatters to blow up!", msg.messageId);
await handleNoTarget(msg, user, ITEMNAME);
return;
}
const selection = targets[Math.floor(Math.random() * targets.length)]!;
const target = await User.initUserId(selection.slice(5, -11));
await getUserRecord(target!); // make sure the user record exist in the database await getUserRecord(target!); // make sure the user record exist in the database
await Promise.all([ await Promise.all([
timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60), timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60),
redis.del(selection), redis.del(selection),
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`), sendMessage(
createTimeoutRecord(user, target!, ITEMNAME), `wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`,
createCheerEventRecord(user, ITEMNAME), ),
playAlert({ createTimeoutRecord(user, target!, ITEMNAME),
name: 'grenadeExplosion', createCheerEventRecord(user, ITEMNAME),
user: user.displayName, playAlert({
target: target?.displayName! name: "grenadeExplosion",
}) user: user.displayName,
]); target: target?.displayName!,
}, true); }),
]);
},
});

View File

@@ -1,47 +1,75 @@
import User from 'user'; import type { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base"; import type User from "user";
export class Cheer { type cheerOptions = {
public readonly name: string; name: string;
public readonly amount: number; amount: number;
public readonly execute: (msg: EventSubChannelChatMessageEvent, sender: User) => Promise<void>; isItem: boolean;
public readonly isItem: boolean; execute: (
constructor(name: string, amount: number, execution: (msg: EventSubChannelChatMessageEvent, sender: User) => Promise<void>, isItem = false) { msg: EventSubChannelChatMessageEvent,
this.name = name.toLowerCase(); sender: User,
this.amount = amount; ) => Promise<void>;
this.execute = execution;
this.isItem = isItem;
};
}; };
import { readdir } from 'node:fs/promises'; export class Cheer {
const cheers = new Map<number, Cheer>; public readonly name: string;
const namedcheers = new Map<string, Cheer>; public readonly amount: number;
public readonly execute: (
msg: EventSubChannelChatMessageEvent,
sender: User,
) => Promise<void>;
public readonly isItem: boolean;
constructor(options: cheerOptions) {
this.name = options.name.toLowerCase();
this.amount = options.amount;
this.execute = options.execute;
this.isItem = options.isItem;
}
}
import { readdir } from "node:fs/promises";
const cheers = new Map<number, Cheer>();
const namedcheers = new Map<string, Cheer>();
const files = await readdir(import.meta.dir); const files = await readdir(import.meta.dir);
for (const file of files) { for (const file of files) {
if (!file.endsWith('.ts')) continue; if (!file.endsWith(".ts")) continue;
if (file === import.meta.file) continue; if (file === import.meta.file) continue;
const cheer: Cheer = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default); const cheer: Cheer = await import(
cheers.set(cheer.amount, cheer); `${import.meta.dir}/${file.slice(0, -3)}`
namedcheers.set(cheer.name, cheer); ).then((a) => a.default);
}; cheers.set(cheer.amount, cheer);
namedcheers.set(cheer.name, cheer);
}
export default cheers; export default cheers;
export { namedcheers }; export { namedcheers };
import { sendMessage } from 'lib/commandUtils'; import { getUserRecord } from "db/dbUser";
import { getUserRecord } from 'db/dbUser'; import { changeItemCount, type items } from "items";
import { changeItemCount, type items } from 'items'; import { sendMessage } from "lib/commandUtils";
export async function handleNoTarget(msg: EventSubChannelChatMessageEvent, user: User, itemname: items, silent = true) { export async function handleNoTarget(
if (await user.itemLock()) { msg: EventSubChannelChatMessageEvent,
await sendMessage(`Cannot give ${user.displayName} a ${itemname} (itemlock)`, msg.messageId); user: User,
return; itemname: items,
}; silent = true,
await user.setLock(); ) {
const userRecord = await getUserRecord(user); if (await user.itemLock()) {
if (!silent) await sendMessage(`No (valid) target specified. You got a ${itemname}!`, msg.messageId); await sendMessage(
await changeItemCount(user, userRecord, itemname, 1); `Cannot give ${user.displayName} a ${itemname} (itemlock)`,
await user.clearLock(); msg.messageId,
);
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,68 +1,87 @@
import { Cheer } from "cheers"; import { Cheer } from "cheers";
import { sendMessage } from "lib/commandUtils"; import { createGetLootRecord } from "db/dbGetLoot";
import { getUserRecord, updateUserRecord } from "db/dbUser"; import { getUserRecord, updateUserRecord } from "db/dbUser";
import itemMap, { type inventory, type items } from "items"; import itemMap, { type inventory, type items } from "items";
import { createGetLootRecord } from "db/dbGetLoot"; import { sendMessage } from "lib/commandUtils";
import { timeout } from "lib/timeout";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
import { timeout } from "lib/timeout";
export default new Cheer('superloot', 150, async (msg, user) => { export default new Cheer({
if (!await redis.exists('streamIsLive')) { await sendMessage(`No loot while stream is offline`, msg.messageId); return; }; name: "superloot",
if (await user.itemLock()) { await sendMessage(`Cannot get loot (itemlock)`, msg.messageId); return; }; amount: 150,
await user.setLock(); isItem: true,
const userData = await getUserRecord(user); async execute(msg, user) {
if (!(await redis.exists("streamIsLive"))) {
await sendMessage(`No loot while stream is offline`, msg.messageId);
return;
}
if (await user.itemLock()) {
await sendMessage(`Cannot get loot (itemlock)`, msg.messageId);
return;
}
await user.setLock();
const userData = await getUserRecord(user);
await sendMessage("HOLD"); await sendMessage("HOLD");
await new Promise(res => setTimeout(res, 1000 * 5)); await new Promise((res) => setTimeout(res, 1000 * 5));
if (Math.random() > 0.5) { if (Math.random() > 0.5) {
await Promise.all([ await Promise.all([
sendMessage(`SUPERLOOT FAILED!!! KEKPOINT KEKPOINT KEKPOINT ${msg.chatterDisplayName.toUpperCase()} SEE YOU IN 5 MINUTES!!!`), sendMessage(
timeout(user, `RIP BOZO! NO SUPERLOOT FOR YOU`, 60 * 5), `SUPERLOOT FAILED!!! KEKPOINT KEKPOINT KEKPOINT ${msg.chatterDisplayName.toUpperCase()} SEE YOU IN 5 MINUTES!!!`,
user.clearLock() ),
]); timeout(user, `RIP BOZO! NO SUPERLOOT FOR YOU`, 60 * 5),
return; user.clearLock(),
}; ]);
return;
}
const gainedqbucks = Math.floor(Math.random() * 250) + 150; // range from 150 to 400 const gainedqbucks = Math.floor(Math.random() * 250) + 150; // range from 150 to 400
userData.balance += gainedqbucks; userData.balance += gainedqbucks;
const itemDiff: inventory = { const itemDiff: inventory = {
grenade: 0, grenade: 0,
blaster: 0, blaster: 0,
tnt: 0, tnt: 0,
silverbullet: 0 silverbullet: 0,
}; };
for (let i = 0; i < 15; i++) { for (let i = 0; i < 15; i++) {
if (Math.floor(Math.random() * 5) === 0) itemDiff.grenade! += 1; // 1 in 5 if (Math.floor(Math.random() * 5) === 0) itemDiff.grenade! += 1; // 1 in 5
if (Math.floor(Math.random() * 5) === 0) itemDiff.blaster! += 1; // 1 in 5 if (Math.floor(Math.random() * 5) === 0) itemDiff.blaster! += 1; // 1 in 5
if (Math.floor(Math.random() * 50) === 0) itemDiff.tnt! += 1; // 1 in 50 if (Math.floor(Math.random() * 50) === 0) itemDiff.tnt! += 1; // 1 in 50
if (Math.floor(Math.random() * 50) === 0) itemDiff.silverbullet! += 1; // 1 in 50 if (Math.floor(Math.random() * 50) === 0) itemDiff.silverbullet! += 1; // 1 in 50
}; }
for (const [item, amount] of Object.entries(itemDiff) as [items, number][]) { for (const [item, amount] of Object.entries(itemDiff) as [
if (userData.inventory[item]) userData.inventory[item] += amount; items,
else userData.inventory[item] = amount; number,
}; ][]) {
if (userData.inventory[item]) userData.inventory[item] += amount;
else userData.inventory[item] = amount;
}
const itemstrings: string[] = [`${gainedqbucks} qbucks`]; const itemstrings: string[] = [`${gainedqbucks} qbucks`];
for (const [item, amount] of Object.entries(itemDiff)) { for (const [item, amount] of Object.entries(itemDiff)) {
if (amount === 0) continue; if (amount === 0) continue;
const selection = itemMap.get(item); const selection = itemMap.get(item);
if (!selection) continue; if (!selection) continue;
itemstrings.push(`${amount} ${selection.prettyName + (amount === 1 ? '' : selection.plural)}`); itemstrings.push(
}; `${amount} ${selection.prettyName + (amount === 1 ? "" : selection.plural)}`,
);
}
const last = itemstrings.pop(); const last = itemstrings.pop();
const itemstring = itemstrings.length === 0 ? last : itemstrings.join(', ') + " and " + last; const itemstring =
const message = `You got ${itemstring}`; itemstrings.length === 0 ? last : `${itemstrings.join(", ")} and ${last}`;
const message = `You got ${itemstring}`;
await Promise.all([ await Promise.all([
updateUserRecord(user, userData), updateUserRecord(user, userData),
sendMessage(message, msg.messageId), sendMessage(message, msg.messageId),
createGetLootRecord(user, gainedqbucks, itemDiff, 'superloot'), createGetLootRecord(user, gainedqbucks, itemDiff, "superloot"),
user.clearLock() user.clearLock(),
]); ]);
}, true); },
});

View File

@@ -1,49 +1,69 @@
import { Cheer, handleNoTarget } from "cheers"; import { Cheer, handleNoTarget } from "cheers";
import { sendMessage } from "lib/commandUtils";
import { getUserRecord } from "db/dbUser";
import User from "user";
import { timeout } from "lib/timeout";
import { createTimeoutRecord } from "db/dbTimeouts";
import { createCheerEventRecord } from "db/dbCheerEvents"; import { createCheerEventRecord } from "db/dbCheerEvents";
import { createTimeoutRecord } from "db/dbTimeouts";
import { getUserRecord } from "db/dbUser";
import { sendMessage } from "lib/commandUtils";
import { parseCheerArgs } from "lib/parseCommandArgs"; import { parseCheerArgs } from "lib/parseCommandArgs";
import { timeout } from "lib/timeout";
import User from "user";
import { playAlert } from "web/alerts/serverFunctions"; import { playAlert } from "web/alerts/serverFunctions";
const ITEMNAME = 'blaster'; const ITEMNAME = "blaster";
export default new Cheer('timeout', 100, async (msg, user) => { export default new Cheer({
const args = parseCheerArgs(msg.messageText); name: "timeout",
if (!args[0]) { await handleNoTarget(msg, user, ITEMNAME, false); return; }; amount: 100,
const target = await User.initUsername(args[0].toLowerCase()); isItem: true,
if (!target) { await handleNoTarget(msg, user, ITEMNAME, false); return; }; async execute(msg, user) {
await getUserRecord(target); const args = parseCheerArgs(msg.messageText);
if (!args[0]) {
await handleNoTarget(msg, user, ITEMNAME, false);
return;
}
const target = await User.initUsername(args[0].toLowerCase());
if (!target) {
await handleNoTarget(msg, user, ITEMNAME, false);
return;
}
await getUserRecord(target);
const result = await timeout(target, `You got blasted by ${user.displayName}!`, 60); const result = await timeout(
if (result.status) await Promise.all([ target,
sendMessage(`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`), `You got blasted by ${user.displayName}!`,
createTimeoutRecord(user, target, ITEMNAME), 60,
createCheerEventRecord(user, ITEMNAME), );
playAlert({ if (result.status)
name: 'userBlast', await Promise.all([
user: user.displayName, sendMessage(
target: target.displayName `GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`,
}) ),
createTimeoutRecord(user, target, ITEMNAME),
]); createCheerEventRecord(user, ITEMNAME),
else { playAlert({
await handleNoTarget(msg, user, ITEMNAME); name: "userBlast",
switch (result.reason) { user: user.displayName,
case "banned": target: target.displayName,
await sendMessage(`${target.displayName} is already timed out/banned`, msg.messageId); }),
break; ]);
case "illegal": else {
await Promise.all([ await handleNoTarget(msg, user, ITEMNAME);
sendMessage(`${user.displayName} Nou Nou Nou`), switch (result.reason) {
timeout(user, 'nah', 60) case "banned":
]); await sendMessage(
break; `${target.displayName} is already timed out/banned`,
case "unknown": msg.messageId,
await sendMessage('Something went wrong...', msg.messageId); );
break; break;
}; case "illegal":
}; await Promise.all([
}, true); sendMessage(`${user.displayName} Nou Nou Nou`),
timeout(user, "nah", 60),
]);
break;
case "unknown":
await sendMessage("Something went wrong...", msg.messageId);
break;
}
}
},
});

View File

@@ -1,40 +1,57 @@
import { Cheer, handleNoTarget } from "cheers"; import { Cheer, handleNoTarget } from "cheers";
import { sendMessage } from "lib/commandUtils";
import { getUserRecord } from "db/dbUser";
import User from "user";
import { timeout } from "lib/timeout";
import { createTimeoutRecord } from "db/dbTimeouts";
import { createCheerEventRecord } from "db/dbCheerEvents"; import { createCheerEventRecord } from "db/dbCheerEvents";
import { createTimeoutRecord } from "db/dbTimeouts";
import { getUserRecord } from "db/dbUser";
import { getTNTTargets } from "items/tnt"; import { getTNTTargets } from "items/tnt";
import { sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
import { timeout } from "lib/timeout";
import User from "user";
import { playAlert } from "web/alerts/serverFunctions"; import { playAlert } from "web/alerts/serverFunctions";
const ITEMNAME = 'tnt'; const ITEMNAME = "tnt";
export default new Cheer('tnt', 1000, async (msg, user) => { export default new Cheer({
const vulntargets = await redis.keys('user:*:vulnerable').then(a => a.map(b => b.slice(5, -11))); name: "tnt",
if (vulntargets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); await handleNoTarget(msg, user, ITEMNAME); return; }; amount: 1000,
const targets = getTNTTargets(vulntargets); isItem: true,
async execute(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);
await handleNoTarget(msg, user, ITEMNAME);
return;
}
const targets = getTNTTargets(vulntargets);
await Promise.all(targets.map(async targetid => { await Promise.all(
const target = await User.initUserId(targetid); targets.map(async (targetid) => {
await getUserRecord(target!); // make sure the user record exist in the database const target = await User.initUserId(targetid);
await Promise.all([ await getUserRecord(target!); // make sure the user record exist in the database
timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60), await Promise.all([
redis.del(`user:${targetid}:vulnerable`), timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60),
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`), redis.del(`user:${targetid}:vulnerable`),
createTimeoutRecord(user, target!, ITEMNAME) sendMessage(
]); `wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`,
})); ),
createTimeoutRecord(user, target!, ITEMNAME),
]);
}),
);
await Promise.all([ await Promise.all([
createCheerEventRecord(user, ITEMNAME), createCheerEventRecord(user, ITEMNAME),
playAlert({ playAlert({
name: 'tntExplosion', name: "tntExplosion",
user: user.displayName, user: user.displayName,
targets targets,
}) }),
]); ]);
await sendMessage(`RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? '' : 's'} with their TNT RIPBOZO`); await sendMessage(
}, true); `RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? "" : "s"} with their TNT RIPBOZO`,
);
},
});

View File

@@ -1,20 +1,31 @@
import { Command, sendMessage } from "lib/commandUtils";
import { addAdmin } from "lib/admins"; import { addAdmin } from "lib/admins";
import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'addadmin', name: "addadmin",
aliases: ['addadmin'], aliases: ["addadmin"],
usertype: 'streamer', usertype: "streamer",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; if (!args[0]) {
const target = await User.initUsername(args[0].toLowerCase()); await sendMessage("Please specify a target", msg.messageId);
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; return;
const data = await addAdmin(target.id); }
if (data === "OK") await sendMessage(`${target.displayName} is now an admin`, msg.messageId); const target = await User.initUsername(args[0].toLowerCase());
else await sendMessage(`${target.displayName} is already an admin`, msg.messageId); if (!target) {
} await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
return;
}
const data = await addAdmin(target.id);
if (data === "OK")
await sendMessage(`${target.displayName} is now an admin`, msg.messageId);
else
await sendMessage(
`${target.displayName} is already an admin`,
msg.messageId,
);
},
}); });

View File

@@ -1,23 +1,40 @@
import { redis } from "lib/redis";
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { redis } from "lib/redis";
import { streamerUsers } from "main"; import { streamerUsers } from "main";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'addbot', name: "addbot",
aliases: ['addbot'], aliases: ["addbot"],
usertype: 'streamer', usertype: "streamer",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; if (!args[0]) {
const target = await User.initUsername(args[0].toLowerCase()); await sendMessage("Please specify a target", msg.messageId);
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; return;
if (streamerUsers.includes(target.id)) { await sendMessage(`Cannot change bot status of qweribot managed user`, msg.messageId); return; }; }
const data = await redis.set(`user:${target.id}:bot`, '1'); const target = await User.initUsername(args[0].toLowerCase());
await target.clearVulnerable(); if (!target) {
if (data === "OK") await sendMessage(`${target.displayName} is now a bot`, msg.messageId); await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
else await sendMessage(`${target.displayName} is already a bot`, msg.messageId); return;
} }
if (streamerUsers.includes(target.id)) {
await sendMessage(
`Cannot change bot status of qweribot managed user`,
msg.messageId,
);
return;
}
const data = await redis.set(`user:${target.id}:bot`, "1");
await target.clearVulnerable();
if (data === "OK")
await sendMessage(`${target.displayName} is now a bot`, msg.messageId);
else
await sendMessage(
`${target.displayName} is already a bot`,
msg.messageId,
);
},
}); });

View File

@@ -1,20 +1,34 @@
import { addInvuln } from "lib/invuln";
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import { addInvuln } from "lib/invuln";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'addinvuln', name: "addinvuln",
aliases: ['addinvuln'], aliases: ["addinvuln"],
usertype: 'moderator', usertype: "moderator",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; if (!args[0]) {
const target = await User.initUsername(args[0].toLowerCase()); await sendMessage("Please specify a target", msg.messageId);
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; return;
const data = await addInvuln(target.id); }
if (data === "OK") await sendMessage(`${target.displayName} is now an invuln`, msg.messageId); const target = await User.initUsername(args[0].toLowerCase());
else await sendMessage(`${target.displayName} is already an invuln`, msg.messageId); if (!target) {
} await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
return;
}
const data = await addInvuln(target.id);
if (data === "OK")
await sendMessage(
`${target.displayName} is now an invuln`,
msg.messageId,
);
else
await sendMessage(
`${target.displayName} is already an invuln`,
msg.messageId,
);
},
}); });

View File

@@ -1,30 +1,54 @@
import { Command, sendMessage } from "lib/commandUtils";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { changeBalance } from "lib/changeBalance"; import { changeBalance } from "lib/changeBalance";
import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'admindonate', name: "admindonate",
aliases: ['admindonate'], aliases: ["admindonate"],
usertype: 'admin', usertype: "admin",
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a user', msg.messageId); return; }; if (!args[0]) {
const target = await User.initUsername(args[0].toLowerCase()); await sendMessage("Please specify a user", msg.messageId);
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; return;
const userRecord = await getUserRecord(target); }
if (!args[1]) { await sendMessage('Please specify the amount qweribucks you want to give', msg.messageId); return; }; const target = await User.initUsername(args[0].toLowerCase());
const amount = parseInt(args[1]); if (!target) {
if (isNaN(amount)) { await sendMessage(`'${args[1]}' is not a valid amount`); return; }; await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
if (await target.itemLock()) { await sendMessage('Cannot give qweribucks (itemlock)', msg.messageId); return; }; return;
await target.setLock(); }
const data = await changeBalance(target, userRecord, amount); const userRecord = await getUserRecord(target);
if (!data) { if (!args[1]) {
await sendMessage(`Failed to give ${target.displayName} ${amount} qweribuck${amount === 1 ? '' : 's'}`, msg.messageId); await sendMessage(
} else { "Please specify the amount qweribucks you want to give",
await sendMessage(`${target.displayName} now has ${data.balance} qweribuck${data.balance === 1 ? '' : 's'}`, msg.messageId); msg.messageId,
}; );
await target.clearLock(); return;
} }
const amount = parseInt(args[1], 10);
if (Number.isNaN(amount)) {
await sendMessage(`'${args[1]}' is not a valid amount`);
return;
}
if (await target.itemLock()) {
await sendMessage("Cannot give qweribucks (itemlock)", msg.messageId);
return;
}
await target.setLock();
const data = await changeBalance(target, userRecord, amount);
if (!data) {
await sendMessage(
`Failed to give ${target.displayName} ${amount} qweribuck${amount === 1 ? "" : "s"}`,
msg.messageId,
);
} else {
await sendMessage(
`${target.displayName} now has ${data.balance} qweribuck${data.balance === 1 ? "" : "s"}`,
msg.messageId,
);
}
await target.clearLock();
},
}); });

View File

@@ -1,34 +1,64 @@
import { Command, sendMessage } from "lib/commandUtils";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import items, { changeItemCount } from "items"; import items, { changeItemCount } from "items";
import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'admingive', name: "admingive",
aliases: ['admingive'], aliases: ["admingive"],
usertype: 'admin', usertype: "admin",
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a user', msg.messageId); return; }; if (!args[0]) {
const target = await User.initUsername(args[0].toLowerCase()); await sendMessage("Please specify a user", msg.messageId);
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; return;
const userRecord = await getUserRecord(target); }
if (!args[1]) { await sendMessage('Please specify an item to give', msg.messageId); return; }; const target = await User.initUsername(args[0].toLowerCase());
const item = items.get(args[1].toLowerCase()); if (!target) {
if (!item) { await sendMessage(`Item ${args[1]} doesn't exist`, msg.messageId); return; }; await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
if (!args[2]) { await sendMessage('Please specify the amount of the item you want to give', msg.messageId); return; }; return;
const amount = parseInt(args[2]); }
if (isNaN(amount)) { await sendMessage(`'${args[2]}' is not a valid amount`); return; }; const userRecord = await getUserRecord(target);
if (await target.itemLock()) { await sendMessage('Cannot give item (itemlock)', msg.messageId); return; }; if (!args[1]) {
await target.setLock(); await sendMessage("Please specify an item to give", msg.messageId);
const data = await changeItemCount(target, userRecord, item.name, amount); return;
if (data) { }
const newamount = data.inventory[item.name]!; const item = items.get(args[1].toLowerCase());
await sendMessage(`${target.displayName} now has ${newamount} ${item.prettyName + (newamount === 1 ? '' : item.plural)}`, msg.messageId); if (!item) {
} else { await sendMessage(`Item ${args[1]} doesn't exist`, msg.messageId);
await sendMessage(`Failed to give ${target.displayName} ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)}`, msg.messageId); return;
}; }
await target.clearLock(); if (!args[2]) {
} await sendMessage(
"Please specify the amount of the item you want to give",
msg.messageId,
);
return;
}
const amount = parseInt(args[2], 10);
if (Number.isNaN(amount)) {
await sendMessage(`'${args[2]}' is not a valid amount`);
return;
}
if (await target.itemLock()) {
await sendMessage("Cannot give item (itemlock)", msg.messageId);
return;
}
await target.setLock();
const data = await changeItemCount(target, userRecord, item.name, amount);
if (data) {
const newamount = data.inventory[item.name]!;
await sendMessage(
`${target.displayName} now has ${newamount} ${item.prettyName + (newamount === 1 ? "" : item.plural)}`,
msg.messageId,
);
} else {
await sendMessage(
`Failed to give ${target.displayName} ${amount} ${item.prettyName + (amount === 1 ? "" : item.plural)}`,
msg.messageId,
);
}
await target.clearLock();
},
}); });

View File

@@ -1,34 +1,38 @@
import { Command, sendMessage } from "lib/commandUtils";
import { getKDLeaderboard } from "db/dbUser"; import { getKDLeaderboard } from "db/dbUser";
import { Command, sendMessage } from "lib/commandUtils";
import User from "user"; import User from "user";
type KD = { user: User; kd: number; }; type KD = { user: User; kd: number };
export default new Command({ export default new Command({
name: 'alltimekdleaderboard', name: "alltimekdleaderboard",
aliases: ['alltimeleaderboard', 'alltimekdleaderboard'], aliases: ["alltimeleaderboard", "alltimekdleaderboard"],
usertype: 'chatter', usertype: "chatter",
execution: async msg => { execution: async (msg) => {
const rawKD = await getKDLeaderboard(); const rawKD = await getKDLeaderboard();
if (rawKD.length === 0) { if (rawKD.length === 0) {
await sendMessage(`No users on leaderboard yet!`, msg.messageId); await sendMessage(`No users on leaderboard yet!`, msg.messageId);
return; return;
}; }
const userKDs: KD[] = []; const userKDs: KD[] = [];
await Promise.all(rawKD.map(async userRecord => { await Promise.all(
const user = await User.initUserId(userRecord.userId.toString()); rawKD.map(async (userRecord) => {
if (!user) return; const user = await User.initUserId(userRecord.userId.toString());
userKDs.push({ user, kd: userRecord.KD }) if (!user) return;
})); userKDs.push({ user, kd: userRecord.KD });
}),
);
userKDs.sort((a, b) => b.kd - a.kd); userKDs.sort((a, b) => b.kd - a.kd);
const txt: string[] = []; const txt: string[] = [];
for (let i = 0; i < (userKDs.length < 10 ? userKDs.length : 10); i++) { for (let i = 0; i < (userKDs.length < 10 ? userKDs.length : 10); i++) {
txt.push(`${i + 1}. ${userKDs[i]?.user.displayName}: ${userKDs[i]?.kd.toFixed(2)}`); txt.push(
}; `${i + 1}. ${userKDs[i]?.user.displayName}: ${userKDs[i]?.kd.toFixed(2)}`,
);
}
await sendMessage(`Alltime leaderboard: ${txt.join(' | ')}`, msg.messageId); await sendMessage(`Alltime leaderboard: ${txt.join(" | ")}`, msg.messageId);
} },
}); });

View File

@@ -1,33 +1,45 @@
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import { getTimeoutStats, getItemStats } from "lib/getStats"; import { getItemStats, getTimeoutStats } from "lib/getStats";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'alltimestats', name: "alltimestats",
aliases: ['alltime', 'alltimestats'], aliases: ["alltime", "alltimestats"],
usertype: 'chatter', usertype: "chatter",
execution: async (msg, user) => { execution: async (msg, user) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
let target: User | null = user; let target: User | null = user;
if (args[0]) { if (args[0]) {
target = await User.initUsername(args[0]); target = await User.initUsername(args[0]);
if (!target) { await sendMessage(`User ${args[0]} doesn't exist!`, msg.messageId); return; }; if (!target) {
}; await sendMessage(`User ${args[0]} doesn't exist!`, msg.messageId);
return;
}
}
const [timeout, item] = await Promise.all([getTimeoutStats(target, false), getItemStats(target, false)]); const [timeout, item] = await Promise.all([
if (!timeout || !item) { await sendMessage(`ERROR: Something went wrong!`, msg.messageId); return; }; getTimeoutStats(target, false),
getItemStats(target, false),
]);
if (!timeout || !item) {
await sendMessage(`ERROR: Something went wrong!`, msg.messageId);
return;
}
const KD = timeout.shot.blaster / timeout.hit.blaster; const KD = timeout.shot.blaster / timeout.hit.blaster;
await sendMessage(` await sendMessage(
`
Alltime: stats of ${target.displayName}: Alltime: stats of ${target.displayName}:
Users blasted: ${timeout.shot.blaster}, Users blasted: ${timeout.shot.blaster},
Blasted by others: ${timeout.hit.blaster} (${isNaN(KD) ? 0 : KD.toFixed(2)} K/D). Blasted by others: ${timeout.hit.blaster} (${Number.isNaN(KD) ? 0 : KD.toFixed(2)} K/D).
Grenades lobbed: ${item.grenade}, Grenades lobbed: ${item.grenade},
TNT exploded: ${item.tnt}. TNT exploded: ${item.tnt}.
Silver bullets fired: ${timeout.shot.silverbullet}, Silver bullets fired: ${timeout.shot.silverbullet},
Silver bullets taken: ${timeout.hit.silverbullet}. Silver bullets taken: ${timeout.hit.silverbullet}.
`, msg.messageId); `,
} msg.messageId,
);
},
}); });

View File

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

View File

@@ -1,15 +1,18 @@
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import User from "user";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
import User from "user";
export default new Command({ export default new Command({
name: 'backshot', name: "backshot",
aliases: ['backshot'], aliases: ["backshot"],
usertype: 'chatter', usertype: "chatter",
execution: async (msg, user) => { execution: async (msg, user) => {
const targets = await redis.keys(`user:*:haschatted`); const targets = await redis.keys(`user:*:haschatted`);
const selection = targets[Math.floor(Math.random() * targets.length)]!; const selection = targets[Math.floor(Math.random() * targets.length)]!;
const target = await User.initUserId(selection.slice(5, -11)); const target = await User.initUserId(selection.slice(5, -11));
await sendMessage(`${user.displayName} backshotted ${target?.displayName}`, msg.messageId); await sendMessage(
} `${user.displayName} backshotted ${target?.displayName}`,
msg.messageId,
);
},
}); });

View File

@@ -1,37 +1,62 @@
import { getUserRecord, updateUserRecord } from "db/dbUser";
import items from "items";
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import items from "items";
import { getUserRecord, updateUserRecord } from "db/dbUser";
export default new Command({ export default new Command({
name: 'buyitem', name: "buyitem",
aliases: ['buyitem', 'buy', 'purchase'], aliases: ["buyitem", "buy", "purchase"],
usertype: 'chatter', usertype: "chatter",
execution: async (msg, user) => { execution: async (msg, user) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage(`Specify the item you'd like to buy`, msg.messageId); return; }; if (!args[0]) {
const selecteditem = items.get(args[0].toLowerCase()); await sendMessage(`Specify the item you'd like to buy`, msg.messageId);
if (!selecteditem) { await sendMessage(`'${args[0]}' is not a valid item`, msg.messageId); return; }; return;
const amount = args[1] ? parseInt(args[1]) : 1; }
if (isNaN(amount) || amount < 1) { await sendMessage(`'${args[1]}' is not a valid amount to buy`, msg.messageId); return; }; const selecteditem = items.get(args[0].toLowerCase());
const totalcost = amount * selecteditem.price; if (!selecteditem) {
await sendMessage(`'${args[0]}' is not a valid item`, msg.messageId);
return;
}
const amount = args[1] ? parseInt(args[1], 10) : 1;
if (Number.isNaN(amount) || amount < 1) {
await sendMessage(
`'${args[1]}' is not a valid amount to buy`,
msg.messageId,
);
return;
}
const totalcost = amount * selecteditem.price;
if (await user.itemLock()) { await sendMessage('Cannot buy item (itemlock)', msg.messageId); return; }; if (await user.itemLock()) {
await user.setLock(); await sendMessage("Cannot buy item (itemlock)", msg.messageId);
return;
}
await user.setLock();
const userRecord = await getUserRecord(user); const userRecord = await getUserRecord(user);
if (userRecord.balance < totalcost) { await sendMessage(`You don't have enough qbucks to buy ${amount} ${selecteditem.prettyName}${amount === 1 ? '' : selecteditem.plural}! You have ${userRecord.balance}, need ${totalcost}`); await user.clearLock(); return; }; if (userRecord.balance < totalcost) {
await sendMessage(
`You don't have enough qbucks to buy ${amount} ${selecteditem.prettyName}${amount === 1 ? "" : selecteditem.plural}! You have ${userRecord.balance}, need ${totalcost}`,
);
await user.clearLock();
return;
}
if (userRecord.inventory[selecteditem.name]) userRecord.inventory[selecteditem.name]! += amount if (userRecord.inventory[selecteditem.name])
else userRecord.inventory[selecteditem.name] = amount; userRecord.inventory[selecteditem.name]! += amount;
else userRecord.inventory[selecteditem.name] = amount;
userRecord.balance -= totalcost; userRecord.balance -= totalcost;
await Promise.all([ await Promise.all([
updateUserRecord(user, userRecord), updateUserRecord(user, userRecord),
sendMessage(`${user.displayName} bought ${amount} ${selecteditem.prettyName}${amount === 1 ? '' : selecteditem.plural} for ${totalcost} qbucks. They now have ${userRecord.inventory[selecteditem.name]} ${selecteditem.prettyName}${userRecord.inventory[selecteditem.name] === 1 ? '' : selecteditem.plural} and ${userRecord.balance} qbucks`, msg.messageId) sendMessage(
]); `${user.displayName} bought ${amount} ${selecteditem.prettyName}${amount === 1 ? "" : selecteditem.plural} for ${totalcost} qbucks. They now have ${userRecord.inventory[selecteditem.name]} ${selecteditem.prettyName}${userRecord.inventory[selecteditem.name] === 1 ? "" : selecteditem.plural} and ${userRecord.balance} qbucks`,
msg.messageId,
),
]);
await user.clearLock(); await user.clearLock();
} },
}); });

View File

@@ -1,20 +1,35 @@
import { redis } from "lib/redis"; import { namedcheers } from "cheers";
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { namedcheers } from "cheers"; import { redis } from "lib/redis";
export default new Command({ export default new Command({
name: 'disablecheer', name: "disablecheer",
aliases: ['disablecheer'], aliases: ["disablecheer"],
usertype: 'moderator', usertype: "moderator",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a cheer to disable', msg.messageId); return; }; if (!args[0]) {
const selection = namedcheers.get(args[0].toLowerCase()); await sendMessage("Please specify a cheer to disable", msg.messageId);
if (!selection) { await sendMessage(`There is no ${args[0]} cheer`, msg.messageId); return; }; return;
const result = await redis.sadd('disabledcheers', selection.name); }
if (result === 0) { await sendMessage(`The ${selection.name} cheer is already disabled`, msg.messageId); return; }; const selection = namedcheers.get(args[0].toLowerCase());
await sendMessage(`Successfully disabled the ${selection.name} cheer`, msg.messageId); if (!selection) {
} await sendMessage(`There is no ${args[0]} cheer`, msg.messageId);
return;
}
const result = await redis.sadd("disabledcheers", selection.name);
if (result === 0) {
await sendMessage(
`The ${selection.name} cheer is already disabled`,
msg.messageId,
);
return;
}
await sendMessage(
`Successfully disabled the ${selection.name} cheer`,
msg.messageId,
);
},
}); });

View File

@@ -1,21 +1,42 @@
import { redis } from "lib/redis";
import commands from "commands"; import commands from "commands";
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { redis } from "lib/redis";
export default new Command({ export default new Command({
name: 'disablecommand', name: "disablecommand",
aliases: ['disablecommand'], aliases: ["disablecommand"],
usertype: 'moderator', usertype: "moderator",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a command to disable', msg.messageId); return; }; if (!args[0]) {
const selection = commands.get(args[0].toLowerCase()); await sendMessage("Please specify a command to disable", msg.messageId);
if (!selection) { await sendMessage(`There is no ${args[0]} command`, msg.messageId); return; }; return;
if (!selection.disableable) { await sendMessage(`Cannot disable ${selection.name} as the command is not disableable`, msg.messageId); return; }; }
const result = await redis.sadd('disabledcommands', selection.name); const selection = commands.get(args[0].toLowerCase());
if (result === 0) { await sendMessage(`The ${selection.name} command is already disabled`, msg.messageId); return; }; if (!selection) {
await sendMessage(`Successfully disabled the ${selection.name} command`, msg.messageId); await sendMessage(`There is no ${args[0]} command`, msg.messageId);
} return;
}
if (!selection.disableable) {
await sendMessage(
`Cannot disable ${selection.name} as the command is not disableable`,
msg.messageId,
);
return;
}
const result = await redis.sadd("disabledcommands", selection.name);
if (result === 0) {
await sendMessage(
`The ${selection.name} command is already disabled`,
msg.messageId,
);
return;
}
await sendMessage(
`Successfully disabled the ${selection.name} command`,
msg.messageId,
);
},
}); });

View File

@@ -1,30 +1,55 @@
import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs";
import { disableRedeem, idMap, namedRedeems, sfxRedeems } from "pointRedeems"; import { disableRedeem, idMap, namedRedeems, sfxRedeems } from "pointRedeems";
import { Command, sendMessage } from "lib/commandUtils";
import logger from "lib/logger"; import logger from "lib/logger";
import parseCommandArgs from "lib/parseCommandArgs";
export default new Command({ export default new Command({
name: 'disableRedeem', name: "disableRedeem",
aliases: ['disableredeem'], aliases: ["disableredeem"],
usertype: 'moderator', usertype: "moderator",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage("Please specify a point redemption to disable", msg.messageId); return; }; if (!args[0]) {
if (args[0] === 'sfx' || args[0] === 'sound') { await sendMessage(
sfxRedeems.forEach(async redeem => { "Please specify a point redemption to disable",
const id = idMap.get(redeem.name); msg.messageId,
if (!id) { await sendMessage(`Failed to find the ID for redeem ${redeem.name}`, msg.messageId); logger.err(`Failed to find the ID for ${redeem.name} while enabling`); return; }; );
await disableRedeem(redeem, id); return;
}); }
await sendMessage(`Disabled all sound (sfx) channel point redemptions`, msg.messageId); if (args[0] === "sfx" || args[0] === "sound") {
return; sfxRedeems.forEach(async (redeem) => {
}; const id = idMap.get(redeem.name);
if (!id) {
await sendMessage(
`Failed to find the ID for redeem ${redeem.name}`,
msg.messageId,
);
logger.err(`Failed to find the ID for ${redeem.name} while enabling`);
return;
}
await disableRedeem(redeem, id);
});
await sendMessage(
`Disabled all sound (sfx) channel point redemptions`,
msg.messageId,
);
return;
}
const selection = namedRedeems.get(args[0]); const selection = namedRedeems.get(args[0]);
if (!selection) { await sendMessage(`Redeem ${args[0]} doesn't exist. The internal names for redeems are here: https://github.com/qwerinope/qweribot#point-redeems`, msg.messageId); return; }; if (!selection) {
const id = idMap.get(selection.name); await sendMessage(
await disableRedeem(selection, id!); `Redeem ${args[0]} doesn't exist. The internal names for redeems are here: https://github.com/qwerinope/qweribot#point-redeems`,
await sendMessage(`The ${selection.name} point redeem is now disabled`, msg.messageId); msg.messageId,
} );
return;
}
const id = idMap.get(selection.name);
await disableRedeem(selection, id!);
await sendMessage(
`The ${selection.name} point redeem is now disabled`,
msg.messageId,
);
},
}); });

View File

@@ -1,52 +1,81 @@
import { Command, sendMessage } from "lib/commandUtils";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import parseCommandArgs from "lib/parseCommandArgs";
import { changeBalance } from "lib/changeBalance"; import { changeBalance } from "lib/changeBalance";
import User from "user"; import { Command, sendMessage } from "lib/commandUtils";
import logger from "lib/logger"; import logger from "lib/logger";
import parseCommandArgs from "lib/parseCommandArgs";
import User from "user";
export default new Command({ export default new Command({
name: 'donate', name: "donate",
aliases: ['donate'], aliases: ["donate"],
usertype: 'chatter', usertype: "chatter",
execution: async (msg, user) => { execution: async (msg, user) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a user', msg.messageId); return; }; if (!args[0]) {
const target = await User.initUsername(args[0].toLowerCase()); await sendMessage("Please specify a user", msg.messageId);
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; return;
if (target.username === user.username) { await sendMessage("You can't give yourself qweribucks", msg.messageId); return; }; }
const targetRecord = await getUserRecord(target); const target = await User.initUsername(args[0].toLowerCase());
if (!args[1]) { await sendMessage('Please specify the amount of the item you want to give', msg.messageId); return; }; if (!target) {
const amount = parseInt(args[1]); await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
if (isNaN(amount) || amount < 1) { await sendMessage(`'${args[1]}' is not a valid amount`); return; }; return;
}
if (target.username === user.username) {
await sendMessage("You can't give yourself qweribucks", msg.messageId);
return;
}
const targetRecord = await getUserRecord(target);
if (!args[1]) {
await sendMessage(
"Please specify the amount of the item you want to give",
msg.messageId,
);
return;
}
const amount = parseInt(args[1], 10);
if (Number.isNaN(amount) || amount < 1) {
await sendMessage(`'${args[1]}' is not a valid amount`);
return;
}
const userRecord = await getUserRecord(user); const userRecord = await getUserRecord(user);
if (userRecord.balance < amount) { await sendMessage(`You can't give qweribucks you don't have!`, msg.messageId); return; }; if (userRecord.balance < amount) {
await sendMessage(
`You can't give qweribucks you don't have!`,
msg.messageId,
);
return;
}
if (await user.itemLock() || await target.itemLock()) { await sendMessage('Cannot give qweribucks (itemlock)', msg.messageId); return; }; if ((await user.itemLock()) || (await target.itemLock())) {
await sendMessage("Cannot give qweribucks (itemlock)", msg.messageId);
return;
}
await Promise.all([ await Promise.all([user.setLock(), target.setLock()]);
user.setLock(),
target.setLock()
]);
const data = await Promise.all([ const data = await Promise.all([
await changeBalance(target, targetRecord, amount), await changeBalance(target, targetRecord, amount),
await changeBalance(user, userRecord, -amount) await changeBalance(user, userRecord, -amount),
]); ]);
if (data[0] !== false && data[1] !== false) { if (data[0] !== false && data[1] !== false) {
const { balance: newamount } = data[0]; const { balance: newamount } = data[0];
await sendMessage(`${user.displayName} gave ${amount} qweribuck${amount === 1 ? '' : 's'} to ${target.displayName}. They now have ${newamount} qweribuck${newamount === 1 ? '' : 's'}`, msg.messageId); await sendMessage(
} else { `${user.displayName} gave ${amount} qweribuck${amount === 1 ? "" : "s"} to ${target.displayName}. They now have ${newamount} qweribuck${newamount === 1 ? "" : "s"}`,
// TODO: Rewrite this section msg.messageId,
await sendMessage(`Failed to give ${target.displayName} ${amount} qbuck${(amount === 1 ? '' : 's')}`, msg.messageId); );
logger.err(`WARNING: Qweribucks donation failed: target success: ${data[0] !== false}, donator success: ${data[1] !== false}`); } else {
}; // TODO: Rewrite this section
await sendMessage(
`Failed to give ${target.displayName} ${amount} qbuck${amount === 1 ? "" : "s"}`,
msg.messageId,
);
logger.err(
`WARNING: Qweribucks donation failed: target success: ${data[0] !== false}, donator success: ${data[1] !== false}`,
);
}
await Promise.all([ await Promise.all([user.clearLock(), target.clearLock()]);
user.clearLock(), },
target.clearLock()
]);
}
}); });

View File

@@ -1,20 +1,35 @@
import { redis } from "lib/redis"; import { namedcheers } from "cheers";
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { namedcheers } from "cheers"; import { redis } from "lib/redis";
export default new Command({ export default new Command({
name: 'enablecheer', name: "enablecheer",
aliases: ['enablecheer'], aliases: ["enablecheer"],
usertype: 'moderator', usertype: "moderator",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a cheer to enable', msg.messageId); return; }; if (!args[0]) {
const selection = namedcheers.get(args[0].toLowerCase()); await sendMessage("Please specify a cheer to enable", msg.messageId);
if (!selection) { await sendMessage(`There is no ${args[0]} cheer`, msg.messageId); return; }; return;
const result = await redis.srem('disabledcheers', selection.name); }
if (result === 0) { await sendMessage(`The ${selection.name} cheer isn't disabled`, msg.messageId); return; }; const selection = namedcheers.get(args[0].toLowerCase());
await sendMessage(`Successfully enabled the ${selection.name} cheer`, msg.messageId); if (!selection) {
} await sendMessage(`There is no ${args[0]} cheer`, msg.messageId);
return;
}
const result = await redis.srem("disabledcheers", selection.name);
if (result === 0) {
await sendMessage(
`The ${selection.name} cheer isn't disabled`,
msg.messageId,
);
return;
}
await sendMessage(
`Successfully enabled the ${selection.name} cheer`,
msg.messageId,
);
},
}); });

View File

@@ -1,20 +1,35 @@
import { redis } from "lib/redis";
import { Command, sendMessage } from "lib/commandUtils";
import commands from "commands"; import commands from "commands";
import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { redis } from "lib/redis";
export default new Command({ export default new Command({
name: 'enablecommand', name: "enablecommand",
aliases: ['enablecommand'], aliases: ["enablecommand"],
usertype: 'moderator', usertype: "moderator",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a command to enable', msg.messageId); return; }; if (!args[0]) {
const selection = commands.get(args[0].toLowerCase()); await sendMessage("Please specify a command to enable", msg.messageId);
if (!selection) { await sendMessage(`There is no ${args[0]} command`, msg.messageId); return; }; return;
const result = await redis.srem('disabledcommands', selection.name); }
if (result === 0) { await sendMessage(`The ${selection.name} command isn't disabled`, msg.messageId); return; }; const selection = commands.get(args[0].toLowerCase());
await sendMessage(`Successfully enabled the ${selection.name} command`, msg.messageId); if (!selection) {
} await sendMessage(`There is no ${args[0]} command`, msg.messageId);
return;
}
const result = await redis.srem("disabledcommands", selection.name);
if (result === 0) {
await sendMessage(
`The ${selection.name} command isn't disabled`,
msg.messageId,
);
return;
}
await sendMessage(
`Successfully enabled the ${selection.name} command`,
msg.messageId,
);
},
}); });

View File

@@ -1,29 +1,54 @@
import { enableRedeem, idMap, namedRedeems, sfxRedeems } from "pointRedeems";
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import logger from "lib/logger"; import logger from "lib/logger";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { enableRedeem, idMap, namedRedeems, sfxRedeems } from "pointRedeems";
export default new Command({ export default new Command({
name: 'enableRedeem', name: "enableRedeem",
aliases: ['enableredeem'], aliases: ["enableredeem"],
usertype: 'moderator', usertype: "moderator",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage("Please specify a point redemption to enable", msg.messageId); return; }; if (!args[0]) {
if (args[0] === 'sfx' || args[0] === 'sound') { await sendMessage(
sfxRedeems.forEach(async redeem => { "Please specify a point redemption to enable",
const id = idMap.get(redeem.name); msg.messageId,
if (!id) { await sendMessage(`Failed to find the ID for redeem ${redeem.name}`, msg.messageId); logger.err(`Failed to find the ID for ${redeem.name} while enabling`); return; }; );
await enableRedeem(redeem, id); return;
}); }
await sendMessage(`Enabled all sound (sfx) channel point redemptions`, msg.messageId); if (args[0] === "sfx" || args[0] === "sound") {
return; sfxRedeems.forEach(async (redeem) => {
}; const id = idMap.get(redeem.name);
const selection = namedRedeems.get(args[0]); if (!id) {
if (!selection) { await sendMessage(`Redeem ${args[0]} doesn't exist. The internal names for redeems are here: https://github.com/qwerinope/qweribot#point-redeems`, msg.messageId); return; }; await sendMessage(
const id = idMap.get(selection.name); `Failed to find the ID for redeem ${redeem.name}`,
await enableRedeem(selection, id!); msg.messageId,
await sendMessage(`The ${selection.name} point redeem is now enabled`, msg.messageId); );
} logger.err(`Failed to find the ID for ${redeem.name} while enabling`);
return;
}
await enableRedeem(redeem, id);
});
await sendMessage(
`Enabled all sound (sfx) channel point redemptions`,
msg.messageId,
);
return;
}
const selection = namedRedeems.get(args[0]);
if (!selection) {
await sendMessage(
`Redeem ${args[0]} doesn't exist. The internal names for redeems are here: https://github.com/qwerinope/qweribot#point-redeems`,
msg.messageId,
);
return;
}
const id = idMap.get(selection.name);
await enableRedeem(selection, id!);
await sendMessage(
`The ${selection.name} point redeem is now enabled`,
msg.messageId,
);
},
}); });

View File

@@ -2,13 +2,13 @@ import { Command, sendMessage } from "lib/commandUtils";
import { timeout } from "lib/timeout"; import { timeout } from "lib/timeout";
export default new Command({ export default new Command({
name: 'fakemodme', name: "fakemodme",
aliases: ['modme', 'mod'], aliases: ["modme", "mod"],
usertype: 'chatter', usertype: "chatter",
execution: async (_msg, user) => { execution: async (_msg, user) => {
await Promise.all([ await Promise.all([
timeout(user, "NO MODME", 60), timeout(user, "NO MODME", 60),
sendMessage(`NO MODME COMMAND!!! UltraMad UltraMad UltraMad`) sendMessage(`NO MODME COMMAND!!! UltraMad UltraMad UltraMad`),
]); ]);
} },
}); });

View File

@@ -1,19 +1,22 @@
import { Command, sendMessage } from "lib/commandUtils";
import { getAdmins } from "lib/admins"; import { getAdmins } from "lib/admins";
import { Command, sendMessage } from "lib/commandUtils";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'getadmins', name: "getadmins",
aliases: ['getadmins'], aliases: ["getadmins"],
usertype: 'chatter', usertype: "chatter",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const admins = await getAdmins() const admins = await getAdmins();
const adminnames: string[] = []; const adminnames: string[] = [];
for (const id of admins) { for (const id of admins) {
const admin = await User.initUserId(id); const admin = await User.initUserId(id);
adminnames.push(admin?.displayName!); adminnames.push(admin?.displayName!);
}; }
await sendMessage(`Current admins: ${adminnames.join(', ')}`, msg.messageId); await sendMessage(
} `Current admins: ${adminnames.join(", ")}`,
msg.messageId,
);
},
}); });

View File

@@ -1,17 +1,32 @@
import { Command, sendMessage } from "lib/commandUtils";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'getbalance', name: "getbalance",
aliases: ['getbalance', 'balance', 'qbucks', 'qweribucks', 'wallet', 'getwallet'], aliases: [
usertype: 'chatter', "getbalance",
execution: async (msg, user) => { "balance",
const args = parseCommandArgs(msg.messageText); "qbucks",
const target = args[0] ? await User.initUsername(args[0].toLowerCase()) : user; "qweribucks",
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist!`, msg.messageId); return; }; "wallet",
const data = await getUserRecord(target); "getwallet",
await sendMessage(`${target.displayName} has ${data.balance} qbuck${data.balance === 1 ? '' : 's'}`, msg.messageId); ],
} 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 data = await getUserRecord(target);
await sendMessage(
`${target.displayName} has ${data.balance} qbuck${data.balance === 1 ? "" : "s"}`,
msg.messageId,
);
},
}); });

View File

@@ -1,39 +1,63 @@
import { redis } from "lib/redis"; import { namedcheers } from "cheers";
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { namedcheers } from "cheers"; import { redis } from "lib/redis";
export default new Command({ export default new Command({
name: 'getcheers', name: "getcheers",
aliases: ['getcheers', 'getcheer'], aliases: ["getcheers", "getcheer"],
usertype: 'chatter', usertype: "chatter",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage(`A full list of cheers can be found here: https://github.com/qwerinope/qweribot#cheers`, msg.messageId); return; }; if (!args[0]) {
const disabledcheers = await redis.smembers('disabledcheers'); await sendMessage(
const cheerstrings: string[] = []; `A full list of cheers can be found here: https://github.com/qwerinope/qweribot#cheers`,
msg.messageId,
);
return;
}
const disabledcheers = await redis.smembers("disabledcheers");
const cheerstrings: string[] = [];
if (args[0].toLowerCase() === "enabled") { if (args[0].toLowerCase() === "enabled") {
for (const [name, cheer] of Array.from(namedcheers.entries())) { for (const [name, cheer] of Array.from(namedcheers.entries())) {
if (disabledcheers.includes(name)) continue; if (disabledcheers.includes(name)) continue;
cheerstrings.push(`${cheer.amount}: ${name}`); cheerstrings.push(`${cheer.amount}: ${name}`);
}; }
const last = cheerstrings.pop(); const last = cheerstrings.pop();
if (!last) { await sendMessage("No enabled cheers", msg.messageId); return; }; if (!last) {
await sendMessage(cheerstrings.length === 0 ? last : cheerstrings.join(', ') + " and " + last, msg.messageId); await sendMessage("No enabled cheers", msg.messageId);
return;
}
await sendMessage(
cheerstrings.length === 0
? last
: `${cheerstrings.join(", ")} and ${last}`,
msg.messageId,
);
} else if (args[0].toLowerCase() === "disabled") {
for (const [name, cheer] of Array.from(namedcheers.entries())) {
if (!disabledcheers.includes(name)) continue;
cheerstrings.push(`${cheer.amount}: ${name}`);
}
} else if (args[0].toLowerCase() === "disabled") { const last = cheerstrings.pop();
for (const [name, cheer] of Array.from(namedcheers.entries())) { if (!last) {
if (!disabledcheers.includes(name)) continue; await sendMessage("No disabled cheers", msg.messageId);
cheerstrings.push(`${cheer.amount}: ${name}`); return;
}; }
await sendMessage(
const last = cheerstrings.pop(); cheerstrings.length === 0
if (!last) { await sendMessage("No disabled cheers", msg.messageId); return; }; ? last
await sendMessage(cheerstrings.length === 0 ? last : cheerstrings.join(', ') + " and " + last, msg.messageId); : `${cheerstrings.join(", ")} and ${last}`,
msg.messageId,
} else await sendMessage('Please specify if you want the enabled or disabled cheers', msg.messageId); );
} } else
await sendMessage(
"Please specify if you want the enabled or disabled cheers",
msg.messageId,
);
},
}); });

View File

@@ -1,30 +1,51 @@
import { redis } from "lib/redis";
import { Command, sendMessage } from "lib/commandUtils";
import { basecommands } from "commands"; import { basecommands } from "commands";
import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { redis } from "lib/redis";
export default new Command({ export default new Command({
name: 'getcommands', name: "getcommands",
aliases: ['getcommands', 'getc', 'commands'], aliases: ["getcommands", "getc", "commands"],
usertype: 'chatter', usertype: "chatter",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage(`A full list of commands can be found here: https://github.com/qwerinope/qweribot#commands-1`, msg.messageId); return; }; if (!args[0]) {
const disabledcommands = await redis.smembers('disabledcommands'); await sendMessage(
if (args[0].toLowerCase() === 'enabled') { `A full list of commands can be found here: https://github.com/qwerinope/qweribot#commands-1`,
const commandnames: string[] = []; msg.messageId,
for (const [name, command] of Array.from(basecommands.entries())) { );
if (command.usertype !== 'chatter') continue; // Admin only commands should be somewhat hidden return;
if (disabledcommands.includes(name)) continue; }
commandnames.push(name); const disabledcommands = await redis.smembers("disabledcommands");
}; if (args[0].toLowerCase() === "enabled") {
if (commandnames.length === 0) await sendMessage('No commands besides non-disableable commands are enabled', msg.messageId); const commandnames: string[] = [];
else await sendMessage(`Currently enabled commands: ${commandnames.join(', ')}`, msg.messageId); for (const [name, command] of Array.from(basecommands.entries())) {
} else if (args[0].toLowerCase() === 'disabled') { if (command.usertype !== "chatter") continue; // Admin only commands should be somewhat hidden
if (disabledcommands.length === 0) await sendMessage('No commands are disabled', msg.messageId); if (disabledcommands.includes(name)) continue;
else await sendMessage(`Currently disabled commands: ${disabledcommands.join(', ')}`); commandnames.push(name);
} }
else await sendMessage('Please specify if you want the enabled or disabled commands', msg.messageId); if (commandnames.length === 0)
} await sendMessage(
"No commands besides non-disableable commands are enabled",
msg.messageId,
);
else
await sendMessage(
`Currently enabled commands: ${commandnames.join(", ")}`,
msg.messageId,
);
} else if (args[0].toLowerCase() === "disabled") {
if (disabledcommands.length === 0)
await sendMessage("No commands are disabled", msg.messageId);
else
await sendMessage(
`Currently disabled commands: ${disabledcommands.join(", ")}`,
);
} else
await sendMessage(
"Please specify if you want the enabled or disabled commands",
msg.messageId,
);
},
}); });

View File

@@ -1,31 +1,42 @@
import { Command, sendMessage } from "lib/commandUtils";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import items from "items";
import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import User from "user"; import User from "user";
import items from "items";
export default new Command({ export default new Command({
name: 'inventory', name: "inventory",
aliases: ['inv', 'inventory', 'pocket'], aliases: ["inv", "inventory", "pocket"],
usertype: 'chatter', usertype: "chatter",
execution: async (msg, user) => { execution: async (msg, user) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
let target: User = user; let target: User = user;
if (args[0]) { if (args[0]) {
const obj = await User.initUsername(args[0].toLowerCase()); const obj = await User.initUsername(args[0].toLowerCase());
if (!obj) { await sendMessage(`User ${args[0]} doesn't exist`, msg.messageId); return; }; if (!obj) {
target = obj; await sendMessage(`User ${args[0]} doesn't exist`, msg.messageId);
}; return;
}
target = obj;
}
const data = await getUserRecord(target); const data = await getUserRecord(target);
const messagedata: string[] = []; const messagedata: string[] = [];
for (const [key, amount] of Object.entries(data.inventory)) { for (const [key, amount] of Object.entries(data.inventory)) {
if (amount === 0) continue; if (amount === 0) continue;
const itemselection = items.get(key); const itemselection = items.get(key);
messagedata.push(`${itemselection?.prettyName}${amount === 1 ? '' : itemselection?.plural}: ${amount}`); messagedata.push(
}; `${itemselection?.prettyName}${amount === 1 ? "" : itemselection?.plural}: ${amount}`,
);
}
if (messagedata.length === 0) { await sendMessage(`${target.displayName} has no items`, msg.messageId); return; }; if (messagedata.length === 0) {
await sendMessage(`Inventory of ${target.displayName}: ${messagedata.join(', ')}`, msg.messageId); await sendMessage(`${target.displayName} has no items`, msg.messageId);
} return;
}
await sendMessage(
`Inventory of ${target.displayName}: ${messagedata.join(", ")}`,
msg.messageId,
);
},
}); });

View File

@@ -3,17 +3,20 @@ import { getInvulns } from "lib/invuln";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'getinvulns', name: "getinvulns",
aliases: ['getinvulns'], aliases: ["getinvulns"],
usertype: 'chatter', usertype: "chatter",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const invulns = await getInvulns() const invulns = await getInvulns();
const invulnnames: string[] = []; const invulnnames: string[] = [];
for (const id of invulns) { for (const id of invulns) {
const invuln = await User.initUserId(id); const invuln = await User.initUserId(id);
invulnnames.push(invuln?.displayName!); invulnnames.push(invuln?.displayName!);
}; }
await sendMessage(`Current invulnerable chatters: ${invulnnames.join(', ')}`, msg.messageId); await sendMessage(
} `Current invulnerable chatters: ${invulnnames.join(", ")}`,
msg.messageId,
);
},
}); });

View File

@@ -1,103 +1,139 @@
import { redis } from "lib/redis"; import { createGetLootRecord } from "db/dbGetLoot";
import { Command, sendMessage } from "lib/commandUtils";
import { getUserRecord, updateUserRecord } from "db/dbUser"; import { getUserRecord, updateUserRecord } from "db/dbUser";
import itemMap, { type inventory, type items } from "items"; import itemMap, { type inventory, type items } from "items";
import { Command, sendMessage } from "lib/commandUtils";
import { buildTimeString } from "lib/dateManager"; import { buildTimeString } from "lib/dateManager";
import { timeout } from "lib/timeout";
import { isInvuln, removeInvuln } from "lib/invuln"; import { isInvuln, removeInvuln } from "lib/invuln";
import { redis } from "lib/redis";
import { timeout } from "lib/timeout";
import { streamerUsers } from "main"; import { streamerUsers } from "main";
import { createGetLootRecord } from "db/dbGetLoot";
import { playAlert } from "web/alerts/serverFunctions"; import { playAlert } from "web/alerts/serverFunctions";
const COOLDOWN = 10 * 60; // 10 mins (s) const COOLDOWN = 10 * 60; // 10 mins (s)
export default new Command({ export default new Command({
name: 'getloot', name: "getloot",
aliases: ['getloot', 'dig', 'loot', 'mine'], aliases: ["getloot", "dig", "loot", "mine"],
usertype: 'chatter', usertype: "chatter",
execution: async (msg, user) => { execution: async (msg, user) => {
if (!await redis.exists('streamIsLive')) { await sendMessage(`No loot while stream is offline`, msg.messageId); return; }; if (!(await redis.exists("streamIsLive"))) {
if (await isInvuln(msg.chatterId) && !streamerUsers.includes(msg.chatterId)) { await sendMessage(`You're no longer an invuln because used a lootbox.`, msg.messageId); await removeInvuln(msg.chatterId); }; await sendMessage(`No loot while stream is offline`, msg.messageId);
if (await user.itemLock()) { await sendMessage(`Cannot get loot (itemlock)`, msg.messageId); return; }; return;
await user.setLock(); }
const userData = await getUserRecord(user); if (
const timeData = await redis.expiretime(`user:${user.id}:lootboxcooldown`) * 1000; (await isInvuln(msg.chatterId)) &&
if ((timeData) > Date.now()) { !streamerUsers.includes(msg.chatterId)
await user.clearLock(); ) {
if (await user.greedy()) { await sendMessage(
await Promise.all([ `You're no longer an invuln because used a lootbox.`,
sendMessage(`${user.displayName} STOP BEING GREEDY!!! UltraMad UltraMad UltraMad`), msg.messageId,
timeout(user, `Wait ${buildTimeString(timeData, Date.now())} for another lootbox`, 60) );
]); await removeInvuln(msg.chatterId);
return; }
} else { if (await user.itemLock()) {
await Promise.all([ await sendMessage(`Cannot get loot (itemlock)`, msg.messageId);
user.setGreed(), return;
sendMessage(`Wait ${buildTimeString(timeData, Date.now())} for another lootbox.`, msg.messageId) }
]); await user.setLock();
return; const userData = await getUserRecord(user);
}; const timeData =
}; (await redis.expiretime(`user:${user.id}:lootboxcooldown`)) * 1000;
if (timeData > Date.now()) {
await user.clearLock();
if (await user.greedy()) {
await Promise.all([
sendMessage(
`${user.displayName} STOP BEING GREEDY!!! UltraMad UltraMad UltraMad`,
),
timeout(
user,
`Wait ${buildTimeString(timeData, Date.now())} for another lootbox`,
60,
),
]);
return;
} else {
await Promise.all([
user.setGreed(),
sendMessage(
`Wait ${buildTimeString(timeData, Date.now())} for another lootbox.`,
msg.messageId,
),
]);
return;
}
}
await user.clearGreed(); await user.clearGreed();
await redis.set(`user:${user.id}:lootboxcooldown`, '1'); await redis.set(`user:${user.id}:lootboxcooldown`, "1");
await redis.expire(`user:${user.id}:lootboxcooldown`, COOLDOWN); await redis.expire(`user:${user.id}:lootboxcooldown`, COOLDOWN);
if (!await redis.exists(`user:${user.id}:subbed`) && Math.random() < 0.1) { if (
await Promise.all([ !(await redis.exists(`user:${user.id}:subbed`)) &&
user.clearLock(), Math.random() < 0.1
updateUserRecord(user, userData), ) {
timeout(user, "THE LOOTBOX WAS TRAPPED!!!", 60), await Promise.all([
sendMessage(`wybuh wybuh ${user.displayName.toUpperCase()} FOUND A TRAPPED LOOTBOX!!! wybuh wybuh`), user.clearLock(),
playAlert({ updateUserRecord(user, userData),
name: 'grenadeExplosion', timeout(user, "THE LOOTBOX WAS TRAPPED!!!", 60),
user: 'trapped lootbox', sendMessage(
target: user.displayName `wybuh wybuh ${user.displayName.toUpperCase()} FOUND A TRAPPED LOOTBOX!!! wybuh wybuh`,
}) ),
]); playAlert({
return; name: "grenadeExplosion",
}; user: "trapped lootbox",
const gainedqbucks = Math.floor(Math.random() * 100) + 50; // range from 50 to 150 target: user.displayName,
userData.balance += gainedqbucks; }),
]);
return;
}
const gainedqbucks = Math.floor(Math.random() * 100) + 50; // range from 50 to 150
userData.balance += gainedqbucks;
const itemDiff: inventory = { const itemDiff: inventory = {
grenade: 0, grenade: 0,
blaster: 0, blaster: 0,
tnt: 0, tnt: 0,
silverbullet: 0 silverbullet: 0,
}; };
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
if (Math.floor(Math.random() * 5) === 0) itemDiff.grenade! += 1; // 1 in 5 if (Math.floor(Math.random() * 5) === 0) itemDiff.grenade! += 1; // 1 in 5
if (Math.floor(Math.random() * 5) === 0) itemDiff.blaster! += 1; // 1 in 5 if (Math.floor(Math.random() * 5) === 0) itemDiff.blaster! += 1; // 1 in 5
if (Math.floor(Math.random() * 50) === 0) itemDiff.tnt! += 1; // 1 in 50 if (Math.floor(Math.random() * 50) === 0) itemDiff.tnt! += 1; // 1 in 50
if (Math.floor(Math.random() * 50) === 0) itemDiff.silverbullet! += 1; // 1 in 50 if (Math.floor(Math.random() * 50) === 0) itemDiff.silverbullet! += 1; // 1 in 50
}; }
for (const [item, amount] of Object.entries(itemDiff) as [items, number][]) { for (const [item, amount] of Object.entries(itemDiff) as [
if (userData.inventory[item]) userData.inventory[item] += amount; items,
else userData.inventory[item] = amount; number,
}; ][]) {
if (userData.inventory[item]) userData.inventory[item] += amount;
else userData.inventory[item] = amount;
}
const itemstrings: string[] = [`${gainedqbucks} qbucks`]; const itemstrings: string[] = [`${gainedqbucks} qbucks`];
for (const [item, amount] of Object.entries(itemDiff)) { for (const [item, amount] of Object.entries(itemDiff)) {
if (amount === 0) continue; if (amount === 0) continue;
const selection = itemMap.get(item); const selection = itemMap.get(item);
if (!selection) continue; if (!selection) continue;
itemstrings.push(`${amount} ${selection.prettyName + (amount === 1 ? '' : selection.plural)}`); itemstrings.push(
}; `${amount} ${selection.prettyName + (amount === 1 ? "" : selection.plural)}`,
);
}
const last = itemstrings.pop(); const last = itemstrings.pop();
const itemstring = itemstrings.length === 0 ? last : itemstrings.join(', ') + " and " + last; const itemstring =
const message = `You got ${itemstring}`; itemstrings.length === 0 ? last : `${itemstrings.join(", ")} and ${last}`;
const message = `You got ${itemstring}`;
await Promise.all([ await Promise.all([
updateUserRecord(user, userData), updateUserRecord(user, userData),
sendMessage(message, msg.messageId), sendMessage(message, msg.messageId),
createGetLootRecord(user, gainedqbucks, itemDiff, 'getloot'), createGetLootRecord(user, gainedqbucks, itemDiff, "getloot"),
user.clearLock() user.clearLock(),
]); ]);
} },
}); });

View File

@@ -1,12 +1,14 @@
import { Command, sendMessage } from "lib/commandUtils";
import { itemObjectArray } from "items"; import { itemObjectArray } from "items";
import { Command, sendMessage } from "lib/commandUtils";
export default new Command({ export default new Command({
name: 'getprices', name: "getprices",
aliases: ['getprices', 'prices', 'shop'], aliases: ["getprices", "prices", "shop"],
usertype: 'chatter', usertype: "chatter",
execution: async msg => { execution: async (msg) => {
const txt = itemObjectArray.toSorted((a, b) => a.price - b.price).map(item => `${item.prettyName}: ${item.price}`); const txt = itemObjectArray
await sendMessage(`Prices: ${txt.join(' | ')}`, msg.messageId); .toSorted((a, b) => a.price - b.price)
} .map((item) => `${item.prettyName}: ${item.price}`);
await sendMessage(`Prices: ${txt.join(" | ")}`, msg.messageId);
},
}); });

View File

@@ -1,21 +1,42 @@
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import { buildTimeString } from "lib/dateManager"; import { buildTimeString } from "lib/dateManager";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import User from "user";
import { timeoutDuration } from "lib/timeout"; import { timeoutDuration } from "lib/timeout";
import User from "user";
export default new Command({ export default new Command({
name: 'gettimeout', name: "gettimeout",
aliases: ['gett', 'gettimeout', 'releasetime'], aliases: ["gett", "gettimeout", "releasetime"],
usertype: 'chatter', usertype: "chatter",
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; if (!args[0]) {
const target = await User.initUsername(args[0].toLowerCase()); await sendMessage("Please specify a target", msg.messageId);
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; return;
const data = await timeoutDuration(target); }
if (data === false) { await sendMessage(`Chatter ${target.displayName} isn't timed out`, msg.messageId); return; }; const target = await User.initUsername(args[0].toLowerCase());
if (data) { await sendMessage(`${target.displayName} is still timed out for ${buildTimeString(data * 1000, Date.now())}`, msg.messageId); return; }; if (!target) {
await sendMessage(`${target.displayName} is permanently banned`, msg.messageId); await sendMessage(`Chatter ${args[0]} doesn't exist`, 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,
);
},
}); });

View File

@@ -1,52 +1,87 @@
import { Command, sendMessage } from "lib/commandUtils";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import items, { changeItemCount } from "items"; import items, { changeItemCount } from "items";
import { Command, sendMessage } from "lib/commandUtils";
import logger from "lib/logger";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import User from "user"; import User from "user";
import logger from "lib/logger";
export default new Command({ export default new Command({
name: 'give', name: "give",
aliases: ['give'], aliases: ["give"],
usertype: 'chatter', usertype: "chatter",
execution: async (msg, user) => { execution: async (msg, user) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a user', msg.messageId); return; }; if (!args[0]) {
const target = await User.initUsername(args[0].toLowerCase()); await sendMessage("Please specify a user", msg.messageId);
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; return;
if (target.username === user.username) { await sendMessage("You can't give yourself items", msg.messageId); return; }; }
const targetRecord = await getUserRecord(target); const target = await User.initUsername(args[0].toLowerCase());
if (!args[1]) { await sendMessage('Please specify an item to give', msg.messageId); return; }; if (!target) {
const item = items.get(args[1].toLowerCase()); await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
if (!item) { await sendMessage(`Item ${args[1]} doesn't exist`, msg.messageId); return; }; return;
if (!args[2]) { await sendMessage('Please specify the amount of the item you want to give', msg.messageId); return; }; }
const amount = parseInt(args[2]); if (target.username === user.username) {
if (isNaN(amount) || amount < 1) { await sendMessage(`'${args[2]}' is not a valid amount`); return; }; await sendMessage("You can't give yourself items", msg.messageId);
const userRecord = await getUserRecord(user); return;
if (userRecord.inventory[item.name]! < amount) { await sendMessage(`You can't give items you don't have!`, msg.messageId); return; }; }
const targetRecord = await getUserRecord(target);
if (!args[1]) {
await sendMessage("Please specify an item to give", msg.messageId);
return;
}
const item = items.get(args[1].toLowerCase());
if (!item) {
await sendMessage(`Item ${args[1]} doesn't exist`, msg.messageId);
return;
}
if (!args[2]) {
await sendMessage(
"Please specify the amount of the item you want to give",
msg.messageId,
);
return;
}
const amount = parseInt(args[2], 10);
if (Number.isNaN(amount) || amount < 1) {
await sendMessage(`'${args[2]}' is not a valid amount`);
return;
}
const userRecord = await getUserRecord(user);
if (userRecord.inventory[item.name]! < amount) {
await sendMessage(`You can't give items you don't have!`, msg.messageId);
return;
}
if (await user.itemLock() || await target.itemLock()) { await sendMessage('Cannot give item (itemlock)', msg.messageId); return; }; if ((await user.itemLock()) || (await target.itemLock())) {
await sendMessage("Cannot give item (itemlock)", msg.messageId);
return;
}
await Promise.all([ await Promise.all([user.setLock(), target.setLock()]);
user.setLock(),
target.setLock()
]);
const data = await Promise.all([ const data = await Promise.all([
await changeItemCount(target, targetRecord, item.name, amount), await changeItemCount(target, targetRecord, item.name, amount),
await changeItemCount(user, userRecord, item.name, -amount) await changeItemCount(user, userRecord, item.name, -amount),
]); ]);
if (data[0] !== false && data[1] !== false) { if (data[0] !== false && data[1] !== false) {
const tempdata = data[0]; const tempdata = data[0];
const newamount = tempdata.inventory[item.name]!; const newamount = tempdata.inventory[item.name]!;
await sendMessage(`${user.displayName} gave ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)} to ${target.displayName}. They now have ${newamount} ${item.prettyName + (newamount === 1 ? '' : item.plural)}`, msg.messageId); await sendMessage(
} else { `${user.displayName} gave ${amount} ${item.prettyName + (amount === 1 ? "" : item.plural)} to ${target.displayName}. They now have ${newamount} ${item.prettyName + (newamount === 1 ? "" : item.plural)}`,
// TODO: Rewrite this section msg.messageId,
await sendMessage(`Failed to give ${target.displayName} ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)}`, msg.messageId); );
logger.warn(`WARNING: Item donation failed: target success: ${data[0] !== false ? "yes" : "no"}, donator success: ${data[1] !== false ? "yes" : "no"}`); } else {
}; // TODO: Rewrite this section
await user.clearLock(); await sendMessage(
await target.clearLock(); `Failed to give ${target.displayName} ${amount} ${item.prettyName + (amount === 1 ? "" : item.plural)}`,
} msg.messageId,
);
logger.warn(
`WARNING: Item donation failed: target success: ${data[0] !== false ? "yes" : "no"}, donator success: ${data[1] !== false ? "yes" : "no"}`,
);
}
await user.clearLock();
await target.clearLock();
},
}); });

View File

@@ -1,30 +1,34 @@
import { Command } from 'lib/commandUtils'; import { readdir } from "node:fs/promises";
import { readdir } from 'node:fs/promises'; import type { Command } from "lib/commandUtils";
const commands = new Map<string, Command>; // This map has all command/item aliases mapped to commands/items (many-to-one)
const specialAliasCommands = new Map<string, Command>; // This map has all special command/item aliases mapped to commands/items (just like commands map) const commands = new Map<string, Command>(); // This map has all command/item aliases mapped to commands/items (many-to-one)
const basecommands = new Map<string, Command>; // This map has all command names mapped to commands (one-to-one) (no items) const specialAliasCommands = new Map<string, Command>(); // This map has all special command/item aliases mapped to commands/items (just like commands map)
const basecommands = new Map<string, Command>(); // This map has all command names mapped to commands (one-to-one) (no items)
const files = await readdir(import.meta.dir); const files = await readdir(import.meta.dir);
for (const file of files) { for (const file of files) {
if (!file.endsWith('.ts')) continue; if (!file.endsWith(".ts")) continue;
if (file === import.meta.file) continue; if (file === import.meta.file) continue;
const command: Command = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default); const command: Command = await import(
basecommands.set(command.name, command); `${import.meta.dir}/${file.slice(0, -3)}`
for (const alias of command.aliases) { ).then((a) => a.default);
commands.set(alias, command); // Since it's not a primitive type the map is filled with references to the command, not the actual object basecommands.set(command.name, command);
}; for (const alias of command.aliases) {
for (const alias of command.specialaliases) { commands.set(alias, command); // Since it's not a primitive type the map is filled with references to the command, not the actual object
specialAliasCommands.set(alias, command); }
}; for (const alias of command.specialaliases) {
}; specialAliasCommands.set(alias, command);
}
}
import items, { specialAliasItems } from "items"; import items, { specialAliasItems } from "items";
for (const [name, item] of Array.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 commands.set(name, item); // As Item is basically just Command but with more parameters, this should work fine
}; }
for (const [alias, item] of Array.from(specialAliasItems)) { for (const [alias, item] of Array.from(specialAliasItems)) {
specialAliasCommands.set(alias, item); specialAliasCommands.set(alias, item);
}; }
export default commands; export default commands;
export { specialAliasCommands, basecommands }; export { specialAliasCommands, basecommands };

View File

@@ -1,16 +1,28 @@
import { Command, sendMessage } from "lib/commandUtils";
import items from "items"; import items from "items";
import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
export default new Command({ export default new Command({
name: 'iteminfo', name: "iteminfo",
aliases: ['iteminfo', 'itemhelp', 'info'], aliases: ["iteminfo", "itemhelp", "info"],
usertype: 'chatter', usertype: "chatter",
execution: async msg => { execution: async (msg) => {
const messagequery = parseCommandArgs(msg.messageText).join(' '); const messagequery = parseCommandArgs(msg.messageText).join(" ");
if (!messagequery) { await sendMessage('Please specify an item you would like to get info about', msg.messageId); return; }; if (!messagequery) {
const selection = items.get(messagequery.toLowerCase()); await sendMessage(
if (!selection) { await sendMessage(`'${messagequery}' is not an item`, msg.messageId); return; }; "Please specify an item you would like to get info about",
await sendMessage(`Name: ${selection.prettyName}, Description: ${selection.description}, Aliases: ${selection.aliases.join(', ')}`, msg.messageId); msg.messageId,
} );
return;
}
const selection = items.get(messagequery.toLowerCase());
if (!selection) {
await sendMessage(`'${messagequery}' is not an item`, msg.messageId);
return;
}
await sendMessage(
`Name: ${selection.prettyName}, Description: ${selection.description}, Aliases: ${selection.aliases.join(", ")}`,
msg.messageId,
);
},
}); });

View File

@@ -3,17 +3,29 @@ import parseCommandArgs from "lib/parseCommandArgs";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'itemlock', name: "itemlock",
aliases: ['itemlock'], aliases: ["itemlock"],
usertype: 'moderator', usertype: "moderator",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a chatter to toggle the lock for', msg.messageId); return; }; if (!args[0]) {
const target = await User.initUsername(args[0].toLowerCase()); await sendMessage(
if (!target) { await sendMessage('Targeted user does not exist', msg.messageId); return; }; "Please specify a chatter to toggle the lock for",
const status = await target.itemLock(); msg.messageId,
status ? await target.clearLock() : await target.setLock(); );
await sendMessage(`Successfully ${status ? 'cleared' : 'set'} the item lock on ${target.displayName}`, msg.messageId); return;
} }
const target = await User.initUsername(args[0].toLowerCase());
if (!target) {
await sendMessage("Targeted user does not exist", msg.messageId);
return;
}
const status = await target.itemLock();
status ? await target.clearLock() : await target.setLock();
await sendMessage(
`Successfully ${status ? "cleared" : "set"} the item lock on ${target.displayName}`,
msg.messageId,
);
},
}); });

View File

@@ -1,36 +1,40 @@
import { Command, sendMessage } from "lib/commandUtils";
import { getKDLeaderboard } from "db/dbUser"; import { getKDLeaderboard } from "db/dbUser";
import { Command, sendMessage } from "lib/commandUtils";
import User from "user"; import User from "user";
type KD = { user: User; kd: number; }; type KD = { user: User; kd: number };
export default new Command({ export default new Command({
name: 'monthlykdleaderboard', name: "monthlykdleaderboard",
aliases: ['monthlyleaderboard', 'kdleaderboard', 'leaderboard'], aliases: ["monthlyleaderboard", "kdleaderboard", "leaderboard"],
usertype: 'chatter', usertype: "chatter",
execution: async msg => { execution: async (msg) => {
const monthdata = new Date().toISOString().slice(0, 7); const monthdata = new Date().toISOString().slice(0, 7);
const rawKD = await getKDLeaderboard(monthdata); const rawKD = await getKDLeaderboard(monthdata);
if (rawKD.length === 0) { if (rawKD.length === 0) {
await sendMessage(`No users on leaderboard yet!`, msg.messageId); await sendMessage(`No users on leaderboard yet!`, msg.messageId);
return; return;
}; }
const userKDs: KD[] = []; const userKDs: KD[] = [];
await Promise.all(rawKD.map(async userRecord => { await Promise.all(
const user = await User.initUserId(userRecord.userId.toString()); rawKD.map(async (userRecord) => {
if (!user) return; const user = await User.initUserId(userRecord.userId.toString());
userKDs.push({ user, kd: userRecord.KD }) if (!user) return;
})); userKDs.push({ user, kd: userRecord.KD });
}),
);
userKDs.sort((a, b) => b.kd - a.kd); userKDs.sort((a, b) => b.kd - a.kd);
const txt: string[] = []; const txt: string[] = [];
for (let i = 0; i < (userKDs.length < 10 ? userKDs.length : 10); i++) { for (let i = 0; i < (userKDs.length < 10 ? userKDs.length : 10); i++) {
txt.push(`${i + 1}. ${userKDs[i]?.user.displayName}: ${userKDs[i]?.kd.toFixed(2)}`); txt.push(
}; `${i + 1}. ${userKDs[i]?.user.displayName}: ${userKDs[i]?.kd.toFixed(2)}`,
);
}
await sendMessage(`Monthly leaderboard: ${txt.join(' | ')}`, msg.messageId); await sendMessage(`Monthly leaderboard: ${txt.join(" | ")}`, msg.messageId);
} },
}); });

View File

@@ -1,33 +1,45 @@
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import { getTimeoutStats, getItemStats } from "lib/getStats"; import { getItemStats, getTimeoutStats } from "lib/getStats";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'monthlystats', name: "monthlystats",
aliases: ['stats', 'monthlystats'], aliases: ["stats", "monthlystats"],
usertype: 'chatter', usertype: "chatter",
execution: async (msg, user) => { execution: async (msg, user) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
let target: User | null = user; let target: User | null = user;
if (args[0]) { if (args[0]) {
target = await User.initUsername(args[0]); target = await User.initUsername(args[0]);
if (!target) { await sendMessage(`User ${args[0]} doesn't exist!`, msg.messageId); return; }; if (!target) {
}; await sendMessage(`User ${args[0]} doesn't exist!`, msg.messageId);
return;
}
}
const [timeout, item] = await Promise.all([getTimeoutStats(target, true), getItemStats(target, true)]); const [timeout, item] = await Promise.all([
if (!timeout || !item) { await sendMessage(`ERROR: Something went wrong!`, msg.messageId); return; }; getTimeoutStats(target, true),
getItemStats(target, true),
]);
if (!timeout || !item) {
await sendMessage(`ERROR: Something went wrong!`, msg.messageId);
return;
}
const KD = timeout.shot.blaster / timeout.hit.blaster; const KD = timeout.shot.blaster / timeout.hit.blaster;
await sendMessage(` await sendMessage(
`
This month: stats of ${target.displayName}: This month: stats of ${target.displayName}:
Users blasted: ${timeout.shot.blaster}, Users blasted: ${timeout.shot.blaster},
Blasted by others: ${timeout.hit.blaster} (${isNaN(KD) ? 0 : KD.toFixed(2)} K/D). Blasted by others: ${timeout.hit.blaster} (${Number.isNaN(KD) ? 0 : KD.toFixed(2)} K/D).
Grenades lobbed: ${item.grenade}, Grenades lobbed: ${item.grenade},
TNT exploded: ${item.tnt}. TNT exploded: ${item.tnt}.
Silver bullets fired: ${timeout.shot.silverbullet}, Silver bullets fired: ${timeout.shot.silverbullet},
Silver bullets taken: ${timeout.hit.silverbullet}. Silver bullets taken: ${timeout.hit.silverbullet}.
`, msg.messageId); `,
} msg.messageId,
);
},
}); });

View File

@@ -1,25 +1,25 @@
import { Command, sendMessage } from "lib/commandUtils";
import { getBalanceLeaderboard } from "db/dbUser"; import { getBalanceLeaderboard } from "db/dbUser";
import { Command, sendMessage } from "lib/commandUtils";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'qbucksleaderboard', name: "qbucksleaderboard",
aliases: ['qbucksleaderboard', 'baltop', 'moneyleaderboard'], aliases: ["qbucksleaderboard", "baltop", "moneyleaderboard"],
usertype: 'chatter', usertype: "chatter",
execution: async msg => { execution: async (msg) => {
const data = await getBalanceLeaderboard(); const data = await getBalanceLeaderboard();
if (!data) return; if (!data) return;
let index = 1; let index = 1;
const txt: string[] = []; const txt: string[] = [];
for (const userRecord of data) { for (const userRecord of data) {
if (userRecord.balance === 0) continue; if (userRecord.balance === 0) continue;
const user = await User.initUserId(userRecord.id.toString()); const user = await User.initUserId(userRecord.id.toString());
if (!user) continue; if (!user) continue;
txt.push(`${index}. ${user.displayName}: ${userRecord.balance}`); txt.push(`${index}. ${user.displayName}: ${userRecord.balance}`);
index++; index++;
}; }
await sendMessage(`Balance leaderboard: ${txt.join(' | ')}`, msg.messageId); await sendMessage(`Balance leaderboard: ${txt.join(" | ")}`, msg.messageId);
} },
}); });

View File

@@ -4,29 +4,40 @@ import { streamerId } from "main";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'racetime', name: "racetime",
aliases: ['racetime', 'raceroom'], aliases: ["racetime", "raceroom"],
usertype: 'chatter', usertype: "chatter",
execution: async msg => { execution: async (msg) => {
try { // this might be some of the worst http code ever try {
const streamer = await User.initUserId(streamerId); // this might be some of the worst http code ever
const streamer = await User.initUserId(streamerId);
const races = await fetch(`https://racetime.gg/smr/data`).then(a => a.json() as any); const races = await fetch(`https://racetime.gg/smr/data`).then(
if (races.current_races.length < 1) { await sendMessage(`No Super Metroid Randomizer races active`, msg.messageId); return; }; (a) => a.json() as any,
);
if (races.current_races.length < 1) {
await sendMessage(
`No Super Metroid Randomizer races active`,
msg.messageId,
);
return;
}
for (const race of races.current_races) { for (const race of races.current_races) {
const data = await fetch(`https://racetime.gg${race.data_url}`).then(a => a.json() as any); const data = await fetch(`https://racetime.gg${race.data_url}`).then(
for (const racer of data.entrants) { (a) => a.json() as any,
if (racer.user.twitch_name === streamer?.username) { );
await sendMessage(`https://racetime.gg${data.url}`, msg.messageId); for (const racer of data.entrants) {
return; if (racer.user.twitch_name === streamer?.username) {
}; await sendMessage(`https://racetime.gg${data.url}`, msg.messageId);
}; return;
}; }
await sendMessage('Streamer is not in a racetime race.', msg.messageId); }
} catch (err) { }
await sendMessage("Failed to get racetime status", msg.messageId); await sendMessage("Streamer is not in a racetime race.", msg.messageId);
logger.err(err as string); } catch (err) {
}; await sendMessage("Failed to get racetime status", msg.messageId);
} logger.err(err as string);
}
},
}); });

View File

@@ -4,19 +4,19 @@ import { streamerId } from "main";
import { playAlert } from "web/alerts/serverFunctions"; import { playAlert } from "web/alerts/serverFunctions";
export default new Command({ export default new Command({
name: 'randomchatter', name: "randomchatter",
aliases: ['randomchatter'], aliases: ["randomchatter"],
usertype: 'moderator', usertype: "moderator",
execution: async (msg) => { execution: async (msg) => {
const data = await api.chat.getChatters(streamerId).then(a => a.data); const data = await api.chat.getChatters(streamerId).then((a) => a.data);
const target = data[Math.floor(Math.random() * data.length)]; const target = data[Math.floor(Math.random() * data.length)];
await playAlert({ await playAlert({
name: 'blastinRoulette', name: "blastinRoulette",
user: msg.chatterName, user: msg.chatterName,
targets: data.map(a => a.userDisplayName), targets: data.map((a) => a.userDisplayName),
finaltarget: target?.userDisplayName finaltarget: target?.userDisplayName,
}); });
await new Promise((res, _) => setTimeout(res, 4000)); await new Promise((res, _) => setTimeout(res, 4000));
await sendMessage(`${target?.userDisplayName}`, msg.messageId); await sendMessage(`${target?.userDisplayName}`, msg.messageId);
} },
}); });

View File

@@ -1,22 +1,39 @@
import { Command, sendMessage } from "lib/commandUtils";
import { streamerUsers } from "main";
import { removeAdmin } from "lib/admins"; import { removeAdmin } from "lib/admins";
import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { streamerUsers } from "main";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'removeadmin', name: "removeadmin",
aliases: ['removeadmin'], aliases: ["removeadmin"],
usertype: 'streamer', usertype: "streamer",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; if (!args[0]) {
const target = await User.initUsername(args[0].toLowerCase()); await sendMessage("Please specify a target", msg.messageId);
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; return;
if (streamerUsers.includes(target.id)) { await sendMessage(`Can't remove admin ${target.displayName} as they are managed by the bot program`, msg.messageId); return; }; }
const data = await removeAdmin(target.id); const target = await User.initUsername(args[0].toLowerCase());
if (data === 1) await sendMessage(`${target.displayName} is no longer an admin`, msg.messageId); if (!target) {
else await sendMessage(`${target.displayName} isn't an admin`, msg.messageId); await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
} return;
}
if (streamerUsers.includes(target.id)) {
await sendMessage(
`Can't remove admin ${target.displayName} as they are managed by the bot program`,
msg.messageId,
);
return;
}
const data = await removeAdmin(target.id);
if (data === 1)
await sendMessage(
`${target.displayName} is no longer an admin`,
msg.messageId,
);
else
await sendMessage(`${target.displayName} isn't an admin`, msg.messageId);
},
}); });

View File

@@ -1,22 +1,38 @@
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import { streamerUsers } from "main";
import { redis } from "lib/redis";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { redis } from "lib/redis";
import { streamerUsers } from "main";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'removebot', name: "removebot",
aliases: ['removebot'], aliases: ["removebot"],
usertype: 'streamer', usertype: "streamer",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; if (!args[0]) {
const target = await User.initUsername(args[0].toLowerCase()); await sendMessage("Please specify a target", msg.messageId);
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; return;
if (streamerUsers.includes(target.id)) { await sendMessage(`Cannot change bot status of qweribot managed user`, msg.messageId); return; }; }
const data = await redis.del(`user:${target.id}:bot`); const target = await User.initUsername(args[0].toLowerCase());
if (data === 1) await sendMessage(`${target.displayName} is no longer a bot`, msg.messageId); if (!target) {
else await sendMessage(`${target.displayName} isn't a bot`, msg.messageId); await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
} return;
}
if (streamerUsers.includes(target.id)) {
await sendMessage(
`Cannot change bot status of qweribot managed user`,
msg.messageId,
);
return;
}
const data = await redis.del(`user:${target.id}:bot`);
if (data === 1)
await sendMessage(
`${target.displayName} is no longer a bot`,
msg.messageId,
);
else await sendMessage(`${target.displayName} isn't a bot`, msg.messageId);
},
}); });

View File

@@ -1,22 +1,42 @@
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import { streamerUsers } from "main";
import { removeInvuln } from "lib/invuln"; import { removeInvuln } from "lib/invuln";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { streamerUsers } from "main";
import User from "user"; import User from "user";
export default new Command({ export default new Command({
name: 'removeinvuln', name: "removeinvuln",
aliases: ['removeinvuln'], aliases: ["removeinvuln"],
usertype: 'moderator', usertype: "moderator",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; if (!args[0]) {
const target = await User.initUsername(args[0].toLowerCase()); await sendMessage("Please specify a target", msg.messageId);
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; return;
if (streamerUsers.includes(target.id)) { await sendMessage(`Can't remove invulnerability from ${target.displayName} as they are managed by the bot program`, msg.messageId); return; }; }
const data = await removeInvuln(target.id); const target = await User.initUsername(args[0].toLowerCase());
if (data === 1) await sendMessage(`${target.displayName} is no longer invulnerable`, msg.messageId); if (!target) {
else await sendMessage(`${target.displayName} isn't invulnerable`, msg.messageId); await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
} return;
}
if (streamerUsers.includes(target.id)) {
await sendMessage(
`Can't remove invulnerability from ${target.displayName} as they are managed by the bot program`,
msg.messageId,
);
return;
}
const data = await removeInvuln(target.id);
if (data === 1)
await sendMessage(
`${target.displayName} is no longer invulnerable`,
msg.messageId,
);
else
await sendMessage(
`${target.displayName} isn't invulnerable`,
msg.messageId,
);
},
}); });

View File

@@ -5,21 +5,24 @@ import { timeout } from "lib/timeout";
const barrelCount = 6; const barrelCount = 6;
export default new Command({ export default new Command({
name: 'roulette', name: "roulette",
aliases: ['roulette'], aliases: ["roulette"],
usertype: 'chatter', usertype: "chatter",
execution: async (msg, user) => { execution: async (msg, user) => {
if (!await redis.exists('rouletteCount')) await redis.set('rouletteCount', "0"); if (!(await redis.exists("rouletteCount")))
const currentChamber = Number(await redis.get('rouletteCount')); await redis.set("rouletteCount", "0");
const shot = Math.random() < 1 / (barrelCount - currentChamber); const currentChamber = Number(await redis.get("rouletteCount"));
if (!shot) await Promise.all([ const shot = Math.random() < 1 / (barrelCount - currentChamber);
redis.incr('rouletteCount'), if (!shot)
sendMessage("SWEAT Click SWEAT", msg.messageId) await Promise.all([
]); redis.incr("rouletteCount"),
else await Promise.all([ sendMessage("SWEAT Click SWEAT", msg.messageId),
redis.set('rouletteCount', "0"), ]);
sendMessage("wybuh BANG!! wybuh"), else
timeout(user, "You lost at russian roulette!", 5 * 60) await Promise.all([
]); redis.set("rouletteCount", "0"),
} sendMessage("wybuh BANG!! wybuh"),
timeout(user, "You lost at russian roulette!", 5 * 60),
]);
},
}); });

View File

@@ -2,23 +2,28 @@ import { Command, sendMessage } from "lib/commandUtils";
import { timeout } from "lib/timeout"; import { timeout } from "lib/timeout";
export default new Command({ export default new Command({
name: 'seiso', name: "seiso",
aliases: ['seiso'], aliases: ["seiso"],
usertype: 'chatter', usertype: "chatter",
execution: async (msg, user) => { execution: async (msg, user) => {
const rand = Math.floor(Math.random() * 101); const rand = Math.floor(Math.random() * 101);
if (rand > 75) await sendMessage(`${rand}% seiso YAAAA`, msg.messageId); if (rand > 75) await sendMessage(`${rand}% seiso YAAAA`, msg.messageId);
else if (rand === 67) await Promise.all([ else if (rand === 67)
sendMessage(`KOKPEG 67 KOKPEG`), await Promise.all([
timeout(user, 'SIX SEVEN', 67) sendMessage(`KOKPEG 67 KOKPEG`),
]) timeout(user, "SIX SEVEN", 67),
else if (rand > 50) await sendMessage(`${rand}% seiso POGGERS`, msg.messageId); ]);
else if (rand === 50) await sendMessage(`${rand}% seiso ok`, msg.messageId); else if (rand > 50)
else if (rand > 30) await sendMessage(`${rand}% seiso SWEAT`, msg.messageId); await sendMessage(`${rand}% seiso POGGERS`, msg.messageId);
else if (rand > 10) await sendMessage(`${rand}% seiso catErm`, msg.messageId); else if (rand === 50) await sendMessage(`${rand}% seiso ok`, msg.messageId);
else await Promise.all([ else if (rand > 30)
sendMessage(`${rand}% seiso RIPBOZO`), await sendMessage(`${rand}% seiso SWEAT`, msg.messageId);
timeout(user, 'TOO YABAI!', 60) else if (rand > 10)
]); await sendMessage(`${rand}% seiso catErm`, msg.messageId);
} else
await Promise.all([
sendMessage(`${rand}% seiso RIPBOZO`),
timeout(user, "TOO YABAI!", 60),
]);
},
}); });

View File

@@ -1,28 +1,34 @@
import { redis } from "lib/redis";
import { Command, sendMessage } from "lib/commandUtils";
import { isAdmin } from "lib/admins"; import { isAdmin } from "lib/admins";
import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { redis } from "lib/redis";
export default new Command({ export default new Command({
name: 'stacking', name: "stacking",
aliases: ['stacking'], aliases: ["stacking"],
usertype: 'chatter', usertype: "chatter",
disableable: false, disableable: false,
execution: async msg => { execution: async (msg) => {
const args = parseCommandArgs(msg.messageText); 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; }; if (!args[0] || !(await isAdmin(msg.chatterId))) {
// Only admins can reach this part of code await sendMessage(
switch (args[0]) { `Timeout stacking is currently ${(await redis.exists("timeoutStacking")) ? "on" : "off"}`,
case 'enable': msg.messageId,
case 'on': );
await redis.set('timeoutStacking', '1'); return;
await sendMessage('Timeout stacking is now on') }
break; // Only admins can reach this part of code
case 'disable': switch (args[0]) {
case 'off': case "enable":
await redis.del('timeoutStacking'); case "on":
await sendMessage('Timeout stacking is now off') await redis.set("timeoutStacking", "1");
break; await sendMessage("Timeout stacking is now on");
}; break;
} case "disable":
case "off":
await redis.del("timeoutStacking");
await sendMessage("Timeout stacking is now off");
break;
}
},
}); });

View File

@@ -1,17 +1,26 @@
import { Command, sendMessage } from "lib/commandUtils";
import { handleCheer } from "events/message"; import { handleCheer } from "events/message";
import { Command, sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
export default new Command({ export default new Command({
name: 'testcheer', name: "testcheer",
aliases: ['testcheer'], aliases: ["testcheer"],
usertype: 'streamer', usertype: "streamer",
disableable: false, disableable: false,
execution: async (msg, user) => { execution: async (msg, user) => {
const args = parseCommandArgs(msg.messageText); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify the amount of fake bits you want to send', msg.messageId); return; }; if (!args[0]) {
if (isNaN(parseInt(args[0]))) { await sendMessage(`${args[0]} is not a valid amout of bits`); return; }; await sendMessage(
const bits = Number(args.shift()); // we shift it so the amount of bits isn't part of the handleCheer message, we already know that args[0] can be parsed as a number so this is fine. "Please specify the amount of fake bits you want to send",
await handleCheer(msg, bits, user); msg.messageId,
} );
return;
}
if (Number.isNaN(parseInt(args[0], 10))) {
await sendMessage(`${args[0]} is not a valid amout of bits`);
return;
}
const bits = Number(args.shift()); // we shift it so the amount of bits isn't part of the handleCheer message, we already know that args[0] can be parsed as a number so this is fine.
await handleCheer(msg, bits, user);
},
}); });

View File

@@ -1,25 +1,56 @@
import { redis } from "lib/redis";
import { Command, sendMessage } from "lib/commandUtils";
import items from "items";
import { isInvuln, removeInvuln } from "lib/invuln";
import { streamerUsers } from "main";
import getloot from "commands/getloot"; import getloot from "commands/getloot";
import items from "items";
import { Command, sendMessage } from "lib/commandUtils";
import { isInvuln, removeInvuln } from "lib/invuln";
import { redis } from "lib/redis";
import { streamerUsers } from "main";
export default new Command({ export default new Command({
name: 'use', name: "use",
aliases: ['use'], aliases: ["use"],
usertype: 'chatter', usertype: "chatter",
disableable: false, disableable: false,
specialaliases: ['i'], specialaliases: ["i"],
execution: async (msg, user, specialargs) => { execution: async (msg, user, specialargs) => {
const messagequery = msg.messageText.trim().split(' ').slice(1); // This selects the item, so on "i blast mrockstar20" it would pick ["blast", "mrockstar20"] const messagequery = msg.messageText.trim().split(" ").slice(1); // This selects the item, so on "i blast mrockstar20" it would pick ["blast", "mrockstar20"]
const silent = msg.messageText.toLowerCase().startsWith('i'); const silent = msg.messageText.toLowerCase().startsWith("i");
if (!messagequery[0]) { if (!silent) { await sendMessage('Please specify an item you would like to use', msg.messageId); }; return; }; if (!messagequery[0]) {
const selection = items.get(messagequery[0].toLowerCase()); if (!silent) {
if (messagequery[0].toLowerCase() === "lootbox") { await getloot.execute(msg, user); return; }; await sendMessage(
if (!selection) { if (!silent) { await sendMessage(`'${messagequery[0]}' is not an item`, msg.messageId); }; return; }; "Please specify an item you would like to use",
if (await redis.sismember('disabledcommands', selection.name)) { await sendMessage(`The ${selection.prettyName} item is disabled`, msg.messageId); return; }; msg.messageId,
if (await isInvuln(msg.chatterId) && !streamerUsers.includes(msg.chatterId)) { await sendMessage(`You're no longer an invuln because you used an item.`, msg.messageId); await removeInvuln(msg.chatterId); }; );
await selection.execute(msg, user, specialargs); }
} return;
}
const selection = items.get(messagequery[0].toLowerCase());
if (messagequery[0].toLowerCase() === "lootbox") {
await getloot.execute(msg, user);
return;
}
if (!selection) {
if (!silent) {
await sendMessage(`'${messagequery[0]}' is not an item`, msg.messageId);
}
return;
}
if (await redis.sismember("disabledcommands", selection.name)) {
await sendMessage(
`The ${selection.prettyName} item is disabled`,
msg.messageId,
);
return;
}
if (
(await isInvuln(msg.chatterId)) &&
!streamerUsers.includes(msg.chatterId)
) {
await sendMessage(
`You're no longer an invuln because you used an item.`,
msg.messageId,
);
await removeInvuln(msg.chatterId);
}
await selection.execute(msg, user, specialargs);
},
}); });

View File

@@ -1,13 +1,16 @@
import { redis } from "lib/redis";
import { Command, sendMessage } from "lib/commandUtils"; import { Command, sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis";
export default new Command({ export default new Command({
name: 'vulnchatters', name: "vulnchatters",
aliases: ['vulnchatters', 'vulnc', 'vc'], aliases: ["vulnchatters", "vulnc", "vc"],
usertype: 'chatter', usertype: "chatter",
execution: async msg => { execution: async (msg) => {
const data = await redis.keys('user:*:vulnerable'); const data = await redis.keys("user:*:vulnerable");
const one = data.length === 1; const one = data.length === 1;
await sendMessage(`There ${one ? 'is' : 'are'} ${data.length} vulnerable chatter${one ? '' : 's'}`, msg.messageId); await sendMessage(
} `There ${one ? "is" : "are"} ${data.length} vulnerable chatter${one ? "" : "s"}`,
msg.messageId,
);
},
}); });

View File

@@ -3,22 +3,24 @@ import { timeout } from "lib/timeout";
// Remake of the !yabai command in ttv/kiara_tv // Remake of the !yabai command in ttv/kiara_tv
export default new Command({ export default new Command({
name: 'yabai', name: "yabai",
aliases: ['yabai', 'goon'], aliases: ["yabai", "goon"],
usertype: 'chatter', usertype: "chatter",
execution: async (msg, user) => { execution: async (msg, user) => {
const rand = Math.floor(Math.random() * 101); const rand = Math.floor(Math.random() * 101);
if (rand < 25) sendMessage(`${rand}% yabai! GIGACHAD`, msg.messageId); if (rand < 25) sendMessage(`${rand}% yabai! GIGACHAD`, msg.messageId);
else if (rand < 50) sendMessage(`${rand}% yabai POGGERS`, msg.messageId); else if (rand < 50) sendMessage(`${rand}% yabai POGGERS`, msg.messageId);
else if (rand === 50) sendMessage(`${rand}% yabai ok`, msg.messageId); else if (rand === 50) sendMessage(`${rand}% yabai ok`, msg.messageId);
else if (rand === 67) await Promise.all([ else if (rand === 67)
sendMessage(`KOKPEG 67 KOKPEG`), await Promise.all([
timeout(user, 'SIX SEVEN', 67) sendMessage(`KOKPEG 67 KOKPEG`),
]) timeout(user, "SIX SEVEN", 67),
else if (rand < 90) sendMessage(`${rand}% yabai AINTNOWAY`, msg.messageId); ]);
else await Promise.all([ else if (rand < 90) sendMessage(`${rand}% yabai AINTNOWAY`, msg.messageId);
sendMessage(`${msg.chatterDisplayName} is ${rand}% yabai CAUGHT`), else
timeout(user, "TOO YABAI!", 60) await Promise.all([
]); sendMessage(`${msg.chatterDisplayName} is ${rand}% yabai CAUGHT`),
} timeout(user, "TOO YABAI!", 60),
]);
},
}); });

View File

@@ -4,22 +4,28 @@ import { users } from "db/schema";
import logger from "lib/logger"; import logger from "lib/logger";
export async function connectionCheck() { export async function connectionCheck() {
let pgstatus = false; let pgstatus = false;
try { try {
await db.select().from(users); // The query doesn't matter, only that it fails. This also fails if the migration hasn't taken place await db.select().from(users); // The query doesn't matter, only that it fails. This also fails if the migration hasn't taken place
pgstatus = true; pgstatus = true;
} catch { }; } catch {}
const tempclient = new RedisClient(undefined, { const tempclient = new RedisClient(undefined, {
connectionTimeout: 200, connectionTimeout: 200,
maxRetries: 1, maxRetries: 1,
}); });
let redisstatus = false; let redisstatus = false;
try { try {
await tempclient.connect(); await tempclient.connect();
redisstatus = true; redisstatus = true;
} catch { }; } catch {}
logger.info(`Currently using the "${process.env.NODE_ENV ?? "production"}" database`); logger.info(
pgstatus ? logger.ok(`Postgresql status: good`) : logger.err(`Postgresql status: bad`); `Currently using the "${process.env.NODE_ENV ?? "production"}" database`,
redisstatus ? logger.ok(`Redis/Valkey status: good`) : logger.err(`Redis/Valkey status: bad`); );
if (!pgstatus || !redisstatus) process.exit(1); pgstatus
}; ? logger.ok(`Postgresql status: good`)
: logger.err(`Postgresql status: bad`);
redisstatus
? logger.ok(`Redis/Valkey status: good`)
: logger.err(`Redis/Valkey status: bad`);
if (!pgstatus || !redisstatus) process.exit(1);
}

View File

@@ -6,5 +6,5 @@ export const password = process.env.POSTGRES_PASSWORD ?? "";
export const database = process.env.POSTGRES_DB ?? ""; export const database = process.env.POSTGRES_DB ?? "";
export const url = `postgresql://${user}:${password}@${host}/${database}`; export const url = `postgresql://${user}:${password}@${host}/${database}`;
import { drizzle } from 'drizzle-orm/bun-sql'; import { drizzle } from "drizzle-orm/bun-sql";
export default drizzle(url, { schema }) export default drizzle(url, { schema });

View File

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

View File

@@ -4,25 +4,31 @@ import { auth } from "db/schema";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
export async function createAuthRecord(token: AccessToken, userId: string) { export async function createAuthRecord(token: AccessToken, userId: string) {
await db.insert(auth).values({ await db.insert(auth).values({
id: parseInt(userId), id: parseInt(userId, 10),
accesstoken: token accesstoken: token,
}); });
}; }
export async function getAuthRecord(userId: string, requiredIntents: string[]) { export async function getAuthRecord(userId: string, requiredIntents: string[]) {
const data = await db.query.auth.findFirst({ const data = await db.query.auth.findFirst({
where: eq(auth.id, parseInt(userId)) where: eq(auth.id, parseInt(userId, 10)),
}); });
if (!data) return undefined; if (!data) return undefined;
if (!requiredIntents.every(intent => data.accesstoken.scope.includes(intent))) return undefined; if (
return { accesstoken: data.accesstoken }; !requiredIntents.every((intent) => data.accesstoken.scope.includes(intent))
}; )
return undefined;
return { accesstoken: data.accesstoken };
}
export async function updateAuthRecord(userId: string, newtoken: AccessToken) { export async function updateAuthRecord(userId: string, newtoken: AccessToken) {
await db.update(auth).set({ accesstoken: newtoken }).where(eq(auth.id, parseInt(userId))); await db
}; .update(auth)
.set({ accesstoken: newtoken })
.where(eq(auth.id, parseInt(userId, 10)));
}
export async function deleteAuthRecord(userId: string): Promise<void> { export async function deleteAuthRecord(userId: string): Promise<void> {
await db.delete(auth).where(eq(auth.id, parseInt(userId))); await db.delete(auth).where(eq(auth.id, parseInt(userId, 10)));
}; }

View File

@@ -1,20 +1,31 @@
import db from "db/connection"; import db from "db/connection";
import { cheerEvents } from "db/schema"; import { cheerEvents } from "db/schema";
import { and, between, eq, SQL } from "drizzle-orm"; import { and, between, eq, type SQL } from "drizzle-orm";
import type { items } from "items"; import type { items } from "items";
import User from "user"; import type User from "user";
export async function createCheerEventRecord(user: User, cheer: items): Promise<void> { export async function createCheerEventRecord(
await db.insert(cheerEvents).values({ user: parseInt(user.id), event: cheer }); user: User,
}; cheer: items,
): Promise<void> {
await db
.insert(cheerEvents)
.values({ user: parseInt(user.id, 10), event: cheer });
}
export async function getCheerEvents(user: User, monthData?: string) { export async function getCheerEvents(user: User, monthData?: string) {
let condition: SQL<unknown> | undefined = eq(cheerEvents.user, parseInt(user.id)); let condition: SQL<unknown> | undefined = eq(
if (monthData) { cheerEvents.user,
const begin = Date.parse(monthData); parseInt(user.id, 10),
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1); );
condition = and(condition, between(cheerEvents.created, new Date(begin), new Date(end))); if (monthData) {
}; const begin = Date.parse(monthData);
const data = await db.select().from(cheerEvents).where(condition); const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
return data; condition = and(
}; condition,
between(cheerEvents.created, new Date(begin), new Date(end)),
);
}
const data = await db.select().from(cheerEvents).where(condition);
return data;
}

View File

@@ -1,19 +1,28 @@
import db from "db/connection"; import db from "db/connection";
import { cheers } from "db/schema"; import { cheers } from "db/schema";
import User from "user"; import { and, between, eq, type SQL } from "drizzle-orm";
import { and, between, eq, SQL } from "drizzle-orm"; import type User from "user";
export async function createCheerRecord(user: User, amount: number): Promise<void> { export async function createCheerRecord(
await db.insert(cheers).values({ user: parseInt(user.id), amount }); user: User,
}; amount: number,
): Promise<void> {
await db.insert(cheers).values({ user: parseInt(user.id, 10), amount });
}
export async function getCheers(user: User, monthData?: string) { export async function getCheers(user: User, monthData?: string) {
let condition: SQL<unknown> | undefined = eq(cheers.user, parseInt(user.id)); let condition: SQL<unknown> | undefined = eq(
if (monthData) { cheers.user,
const begin = Date.parse(monthData); parseInt(user.id, 10),
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1); );
condition = and(condition, between(cheers.created, new Date(begin), new Date(end))); if (monthData) {
}; const begin = Date.parse(monthData);
const data = await db.select().from(cheers).where(condition); const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
return data; condition = and(
}; condition,
between(cheers.created, new Date(begin), new Date(end)),
);
}
const data = await db.select().from(cheers).where(condition);
return data;
}

View File

@@ -3,11 +3,16 @@ import { getLoots, type lootTriggers } from "db/schema";
import type { inventory } from "items"; import type { inventory } from "items";
import type User from "user"; import type User from "user";
export async function createGetLootRecord(user: User, qbucks: number, inventory: inventory, trigger: lootTriggers) { export async function createGetLootRecord(
await db.insert(getLoots).values({ user: User,
user: parseInt(user.id), qbucks: number,
qbucks: qbucks, inventory: inventory,
items: inventory, trigger: lootTriggers,
trigger ) {
}); await db.insert(getLoots).values({
}; user: parseInt(user.id, 10),
qbucks: qbucks,
items: inventory,
trigger,
});
}

View File

@@ -1,35 +1,51 @@
import db from "db/connection"; import db from "db/connection";
import { timeouts } from "db/schema"; import { timeouts } from "db/schema";
import User from "user";
import type { items } from "items";
import { and, between, eq, type SQL } from "drizzle-orm"; import { and, between, eq, type SQL } from "drizzle-orm";
import type { items } from "items";
import type User from "user";
export async function createTimeoutRecord(user: User, target: User, item: items): Promise<void> { export async function createTimeoutRecord(
await db.insert(timeouts).values({ user: User,
user: parseInt(user.id), target: User,
target: parseInt(target.id), item: items,
item ): Promise<void> {
}); await db.insert(timeouts).values({
}; user: parseInt(user.id, 10),
target: parseInt(target.id, 10),
item,
});
}
export async function getTimeoutsAsUser(user: User, monthData?: string) { export async function getTimeoutsAsUser(user: User, monthData?: string) {
let condition: SQL<unknown> | undefined = eq(timeouts.user, parseInt(user.id)); let condition: SQL<unknown> | undefined = eq(
if (monthData) { timeouts.user,
const begin = Date.parse(monthData); parseInt(user.id, 10),
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1); );
condition = and(condition, between(timeouts.created, new Date(begin), new Date(end))); if (monthData) {
}; const begin = Date.parse(monthData);
const data = await db.select().from(timeouts).where(condition); const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
return data; condition = and(
}; condition,
between(timeouts.created, new Date(begin), new Date(end)),
);
}
const data = await db.select().from(timeouts).where(condition);
return data;
}
export async function getTimeoutsAsTarget(user: User, monthData?: string) { export async function getTimeoutsAsTarget(user: User, monthData?: string) {
let condition: SQL<unknown> | undefined = eq(timeouts.target, parseInt(user.id)); let condition: SQL<unknown> | undefined = eq(
if (monthData) { timeouts.target,
const begin = Date.parse(monthData); parseInt(user.id, 10),
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1); );
condition = and(condition, between(timeouts.created, new Date(begin), new Date(end))); if (monthData) {
}; const begin = Date.parse(monthData);
const data = await db.select().from(timeouts).where(condition); const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
return data; condition = and(
}; condition,
between(timeouts.created, new Date(begin), new Date(end)),
);
}
const data = await db.select().from(timeouts).where(condition);
return data;
}

View File

@@ -1,20 +1,29 @@
import db from "db/connection"; import db from "db/connection";
import { usedItems } from "db/schema"; import { usedItems } from "db/schema";
import User from "user";
import type { items } from "items";
import { and, between, eq, type SQL } from "drizzle-orm"; import { and, between, eq, type SQL } from "drizzle-orm";
import type { items } from "items";
import type User from "user";
export async function createUsedItemRecord(user: User, item: items): Promise<void> { export async function createUsedItemRecord(
await db.insert(usedItems).values({ user: parseInt(user.id), item }); user: User,
}; item: items,
): Promise<void> {
await db.insert(usedItems).values({ user: parseInt(user.id, 10), item });
}
export async function getItemsUsed(user: User, monthData?: string) { export async function getItemsUsed(user: User, monthData?: string) {
let condition: SQL<unknown> | undefined = eq(usedItems.user, parseInt(user.id)); let condition: SQL<unknown> | undefined = eq(
if (monthData) { usedItems.user,
const begin = Date.parse(monthData); parseInt(user.id, 10),
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1); );
condition = and(condition, between(usedItems.created, new Date(begin), new Date(end))); if (monthData) {
}; const begin = Date.parse(monthData);
const data = await db.select().from(usedItems).where(condition); const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
return data; condition = and(
}; condition,
between(usedItems.created, new Date(begin), new Date(end)),
);
}
const data = await db.select().from(usedItems).where(condition);
return data;
}

View File

@@ -1,90 +1,122 @@
import db from "db/connection"; import db from "db/connection";
import { timeouts, users } from "db/schema"; import { timeouts, users } from "db/schema";
import {
and,
between,
count,
desc,
eq,
type InferSelectModel,
inArray,
ne,
type SQL,
sql,
} from "drizzle-orm";
import { itemarray } from "items"; import { itemarray } from "items";
import type User from "user"; import type User from "user";
import { count, desc, eq, inArray, sql, ne, between, and, SQL, type InferSelectModel } from "drizzle-orm";
/** Use this function to both ensure existance and to retreive data */ /** Use this function to both ensure existance and to retreive data */
export async function getUserRecord(user: User) { export async function getUserRecord(user: User) {
const data = await db.query.users.findFirst({ where: eq(users.id, parseInt(user.id)) }); const data = await db.query.users.findFirst({
if (!data) return createUserRecord(user); where: eq(users.id, parseInt(user.id, 10)),
});
if (!data) return createUserRecord(user);
if (Object.keys(data.inventory).sort().toString() !== itemarray.sort().toString()) { // If the items in the user inventory are missing an item. if (
itemarray.forEach(key => { Object.keys(data.inventory).sort().toString() !==
if (!(key in data.inventory)) data.inventory[key] = 0; itemarray.sort().toString()
}); ) {
}; // If the items in the user inventory are missing an item.
itemarray.forEach((key) => {
if (!(key in data.inventory)) data.inventory[key] = 0;
});
}
return data; return data;
}; }
export async function getAllUserRecords() { export async function getAllUserRecords() {
return await db.select().from(users); return await db.select().from(users);
}; }
async function createUserRecord(user: User) { async function createUserRecord(user: User) {
return await db.insert(users).values({ return await db
id: parseInt(user.id), .insert(users)
username: user.username .values({
}).returning().then(a => { id: parseInt(user.id, 10),
if (!a[0]) throw Error('Something went horribly wrong'); username: user.username,
return a[0] })
}); .returning()
}; .then((a) => {
if (!a[0]) throw Error("Something went horribly wrong");
return a[0];
});
}
export type UserRecord = InferSelectModel<typeof users>; export type UserRecord = InferSelectModel<typeof users>;
export async function updateUserRecord(user: User, newData: UserRecord) { export async function updateUserRecord(user: User, newData: UserRecord) {
await db.update(users).set(newData).where(eq(users.id, parseInt(user.id))); await db
return true; .update(users)
}; .set(newData)
.where(eq(users.id, parseInt(user.id, 10)));
return true;
}
export async function getBalanceLeaderboard() { export async function getBalanceLeaderboard() {
return await db.select().from(users).orderBy(desc(users.balance)).limit(10); return await db.select().from(users).orderBy(desc(users.balance)).limit(10);
}; }
export async function getKDLeaderboard(monthData?: string) { export async function getKDLeaderboard(monthData?: string) {
let condition: SQL<unknown> | undefined = ne(timeouts.item, 'silverbullet'); let condition: SQL<unknown> | undefined = ne(timeouts.item, "silverbullet");
if (monthData) { if (monthData) {
const begin = Date.parse(monthData); const begin = Date.parse(monthData);
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1); const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
condition = and(condition, between(timeouts.created, new Date(begin), new Date(end))); condition = and(
}; condition,
between(timeouts.created, new Date(begin), new Date(end)),
);
}
const usersGotShot = await db.select({ const usersGotShot = await db
userId: users.id, .select({
amount: count(timeouts.target), userId: users.id,
}) amount: count(timeouts.target),
.from(users) })
.innerJoin(timeouts, eq(users.id, timeouts.target)) .from(users)
.groupBy(users.id) .innerJoin(timeouts, eq(users.id, timeouts.target))
.having(sql`count(${timeouts.id}) > 5`) .groupBy(users.id)
.where(condition); .having(sql`count(${timeouts.id}) > 5`)
.where(condition);
const usersThatShot = await db.select({ const usersThatShot = await db
userId: users.id, .select({
amount: count(timeouts.user) userId: users.id,
}) amount: count(timeouts.user),
.from(users) })
.innerJoin(timeouts, eq(users.id, timeouts.user)) .from(users)
.groupBy(users.id) .innerJoin(timeouts, eq(users.id, timeouts.user))
.where( .groupBy(users.id)
and( .where(
condition, and(
inArray(users.id, usersGotShot.map(a => a.userId)) condition,
) inArray(
); users.id,
usersGotShot.map((a) => a.userId),
),
),
);
const lookup = new Map(usersThatShot.map(a => [a.userId, a.amount])); const lookup = new Map(usersThatShot.map((a) => [a.userId, a.amount]));
const result = usersGotShot.map(user => ({ const result = usersGotShot.map((user) => ({
userId: user.userId, userId: user.userId,
KD: lookup.get(user.userId)! / user.amount KD: lookup.get(user.userId)! / user.amount,
})); }));
result.map(user => { result.map((user) => {
if (isNaN(user.KD)) user.KD = 0; if (Number.isNaN(user.KD)) user.KD = 0;
return user return user;
}); });
return result; return result;
}; }

View File

@@ -1,125 +1,147 @@
import type { AccessToken } from "@twurple/auth"; import type { AccessToken } from "@twurple/auth";
import type { inventory, items } from "items";
import { boolean, integer, jsonb, pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
import type { anivBots } from "lib/handleAnivMessage";
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
import {
boolean,
integer,
jsonb,
pgTable,
timestamp,
uuid,
varchar,
} from "drizzle-orm/pg-core";
import type { inventory, items } from "items";
import type { anivBots } from "lib/handleAnivMessage";
export const auth = pgTable('auth', { export const auth = pgTable("auth", {
id: integer().primaryKey(), id: integer().primaryKey(),
accesstoken: jsonb().$type<AccessToken>().notNull() accesstoken: jsonb().$type<AccessToken>().notNull(),
}); });
export const users = pgTable('users', { export const users = pgTable("users", {
id: integer().primaryKey().notNull(), id: integer().primaryKey().notNull(),
username: varchar().notNull(), username: varchar().notNull(),
balance: integer().default(0).notNull(), balance: integer().default(0).notNull(),
inventory: jsonb().$type<inventory>().default({}).notNull() inventory: jsonb().$type<inventory>().default({}).notNull(),
}); });
export const usersRelations = relations(users, ({ many }) => ({ export const usersRelations = relations(users, ({ many }) => ({
timeouts_target: many(timeouts), timeouts_target: many(timeouts),
timeouts_shooter: many(timeouts), timeouts_shooter: many(timeouts),
usedItems: many(usedItems), usedItems: many(usedItems),
cheerEvents: many(cheerEvents), cheerEvents: many(cheerEvents),
cheers: many(cheers), cheers: many(cheers),
anivTimeouts: many(anivTimeouts), anivTimeouts: many(anivTimeouts),
getLoots: many(getLoots) getLoots: many(getLoots),
})); }));
export const timeouts = pgTable('timeouts', { export const timeouts = pgTable("timeouts", {
id: uuid().defaultRandom().primaryKey(), id: uuid().defaultRandom().primaryKey(),
user: integer().notNull().references(() => users.id), user: integer()
target: integer().notNull().references(() => users.id), .notNull()
item: varchar().$type<items>().notNull(), .references(() => users.id),
created: timestamp().defaultNow().notNull() target: integer()
.notNull()
.references(() => users.id),
item: varchar().$type<items>().notNull(),
created: timestamp().defaultNow().notNull(),
}); });
export const timeoutsRelations = relations(timeouts, ({ one }) => ({ export const timeoutsRelations = relations(timeouts, ({ one }) => ({
user: one(users, { user: one(users, {
fields: [timeouts.user], fields: [timeouts.user],
references: [users.id], references: [users.id],
relationName: 'shooter' relationName: "shooter",
}), }),
target: one(users, { target: one(users, {
fields: [timeouts.target], fields: [timeouts.target],
references: [users.id], references: [users.id],
relationName: 'target' relationName: "target",
}) }),
})) }));
export const usedItems = pgTable('usedItems', { export const usedItems = pgTable("usedItems", {
id: uuid().defaultRandom().primaryKey(), id: uuid().defaultRandom().primaryKey(),
user: integer().notNull().references(() => users.id), user: integer()
item: varchar().$type<items>().notNull(), .notNull()
created: timestamp().defaultNow().notNull() .references(() => users.id),
item: varchar().$type<items>().notNull(),
created: timestamp().defaultNow().notNull(),
}); });
export const usedItemsRelations = relations(usedItems, ({ one }) => ({ export const usedItemsRelations = relations(usedItems, ({ one }) => ({
user: one(users, { user: one(users, {
fields: [usedItems.user], fields: [usedItems.user],
references: [users.id] references: [users.id],
}) }),
})); }));
export const cheerEvents = pgTable('cheerEvents', { export const cheerEvents = pgTable("cheerEvents", {
id: uuid().defaultRandom().primaryKey(), id: uuid().defaultRandom().primaryKey(),
user: integer().notNull().references(() => users.id), user: integer()
event: varchar().$type<items>().notNull(), .notNull()
created: timestamp().defaultNow().notNull() .references(() => users.id),
event: varchar().$type<items>().notNull(),
created: timestamp().defaultNow().notNull(),
}); });
export const cheerEventsRelations = relations(cheerEvents, ({ one }) => ({ export const cheerEventsRelations = relations(cheerEvents, ({ one }) => ({
user: one(users, { user: one(users, {
fields: [cheerEvents.user], fields: [cheerEvents.user],
references: [users.id] references: [users.id],
}) }),
})); }));
export const cheers = pgTable('cheers', { export const cheers = pgTable("cheers", {
id: uuid().defaultRandom().primaryKey(), id: uuid().defaultRandom().primaryKey(),
user: integer().notNull().references(() => users.id), user: integer()
amount: integer().notNull(), .notNull()
created: timestamp().defaultNow().notNull() .references(() => users.id),
amount: integer().notNull(),
created: timestamp().defaultNow().notNull(),
}); });
export const cheersRelations = relations(cheers, ({ one }) => ({ export const cheersRelations = relations(cheers, ({ one }) => ({
user: one(users, { user: one(users, {
fields: [cheers.user], fields: [cheers.user],
references: [users.id] references: [users.id],
}) }),
})); }));
export const anivTimeouts = pgTable('anivTimeouts', { export const anivTimeouts = pgTable("anivTimeouts", {
id: uuid().defaultRandom().primaryKey(), id: uuid().defaultRandom().primaryKey(),
user: integer().notNull().references(() => users.id), user: integer()
message: varchar().notNull(), .notNull()
anivBot: varchar().$type<anivBots>().notNull(), .references(() => users.id),
duration: integer(), message: varchar().notNull(),
created: timestamp().defaultNow().notNull(), anivBot: varchar().$type<anivBots>().notNull(),
timeout: boolean().default(true) duration: integer(),
created: timestamp().defaultNow().notNull(),
timeout: boolean().default(true),
}); });
export const anivTimeoutsRelations = relations(anivTimeouts, ({ one }) => ({ export const anivTimeoutsRelations = relations(anivTimeouts, ({ one }) => ({
user: one(users, { user: one(users, {
fields: [anivTimeouts.user], fields: [anivTimeouts.user],
references: [users.id] references: [users.id],
}) }),
})); }));
export type lootTriggers = "getloot" | "superloot"; export type lootTriggers = "getloot" | "superloot";
export const getLoots = pgTable('getLoots', { export const getLoots = pgTable("getLoots", {
id: uuid().defaultRandom().primaryKey(), id: uuid().defaultRandom().primaryKey(),
user: integer().notNull().references(() => users.id), user: integer()
qbucks: integer().notNull(), .notNull()
items: jsonb().$type<inventory>().notNull(), .references(() => users.id),
trigger: varchar().$type<lootTriggers>().notNull(), qbucks: integer().notNull(),
created: timestamp().defaultNow().notNull() items: jsonb().$type<inventory>().notNull(),
trigger: varchar().$type<lootTriggers>().notNull(),
created: timestamp().defaultNow().notNull(),
}); });
export const getLootsRelations = relations(getLoots, ({ one }) => ({ export const getLootsRelations = relations(getLoots, ({ one }) => ({
user: one(users, { user: one(users, {
fields: [getLoots.user], fields: [getLoots.user],
references: [users.id] references: [users.id],
}) }),
})); }));

View File

@@ -1,17 +1,26 @@
import { api, eventSub } from "index";
import { redis } from "lib/redis";
import { streamerId } from "main"; import { streamerId } from "main";
import { deleteBannedUserMessagesFromChatWidget } from "web/chatWidget/message"; import { deleteBannedUserMessagesFromChatWidget } from "web/chatWidget/message";
import { eventSub, api } from "index";
import { redis } from "lib/redis";
eventSub.onChannelBan(streamerId, async msg => { eventSub.onChannelBan(streamerId, async (msg) => {
deleteBannedUserMessagesFromChatWidget(msg); deleteBannedUserMessagesFromChatWidget(msg);
const welcomemessageid = await redis.get(`user:${msg.userId}:welcomemessageid`); const welcomemessageid = await redis.get(
if (welcomemessageid) { await api.moderation.deleteChatMessages(streamerId, welcomemessageid); await redis.del(`user:${msg.userId}:welcomemessageid`); }; `user:${msg.userId}:welcomemessageid`,
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)); if (welcomemessageid) {
await api.moderation.deleteChatMessages(streamerId, welcomemessageid);
await redis.del(`user:${msg.userId}:welcomemessageid`);
}
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),
);
}); });
eventSub.onChannelUnban(streamerId, async msg => { eventSub.onChannelUnban(streamerId, async (msg) => {
await redis.del(`user:${msg.userId}:timeout`); await redis.del(`user:${msg.userId}:timeout`);
await redis.del(`user:${msg.userId}:remod`); await redis.del(`user:${msg.userId}:remod`);
}); });

View File

@@ -1,19 +1,25 @@
import { sendMessage } from "lib/commandUtils"; import { activeRedeems } from "pointRedeems";
import { eventSub } from "index"; import { eventSub } from "index";
import { sendMessage } from "lib/commandUtils";
import logger from "lib/logger"; import logger from "lib/logger";
import { streamerId } from "main"; import { streamerId } from "main";
import { activeRedeems } from "pointRedeems";
import User from "user"; import User from "user";
eventSub.onChannelRedemptionAdd(streamerId, async msg => { eventSub.onChannelRedemptionAdd(streamerId, async (msg) => {
const selection = activeRedeems.get(msg.rewardId); const selection = activeRedeems.get(msg.rewardId);
if (!selection) { logger.warn(`Can't find the ${msg.rewardTitle} redeem`); return; }; if (!selection) {
const user = await User.initUsername(msg.userName); logger.warn(`Can't find the ${msg.rewardTitle} redeem`);
try { return;
await selection.execute(msg, user!); }
if (process.env.NODE_ENV === 'production') await msg.updateStatus('FULFILLED'); // only on prod const user = await User.initUsername(msg.userName);
} catch (err) { try {
await sendMessage(`[ERROR]: Something went wrong with ${user?.displayName}'s redeem!`); await selection.execute(msg, user!);
logger.err(err as string); if (process.env.NODE_ENV === "production")
}; await msg.updateStatus("FULFILLED"); // only on prod
} catch (err) {
await sendMessage(
`[ERROR]: Something went wrong with ${user?.displayName}'s redeem!`,
);
logger.err(err as string);
}
}); });

View File

@@ -2,6 +2,6 @@ import { eventSub } from "index";
import { chatterId, streamerId } from "main"; import { chatterId, streamerId } from "main";
import { deleteMessageFromChatWidget } from "web/chatWidget/message"; import { deleteMessageFromChatWidget } from "web/chatWidget/message";
eventSub.onChannelChatMessageDelete(streamerId, chatterId, async msg => { eventSub.onChannelChatMessageDelete(streamerId, chatterId, async (msg) => {
deleteMessageFromChatWidget(msg); deleteMessageFromChatWidget(msg);
}); });

View File

@@ -2,34 +2,45 @@ import { api, eventSub } from "index";
import kleur from "kleur"; import kleur from "kleur";
import logger from "lib/logger"; import logger from "lib/logger";
eventSub.onRevoke(event => { eventSub.onRevoke((event) => {
logger.ok(`Successfully revoked EventSub subscription: ${kleur.underline(event.id)}`); logger.ok(
`Successfully revoked EventSub subscription: ${kleur.underline(event.id)}`,
);
}); });
eventSub.onSubscriptionCreateSuccess(event => { eventSub.onSubscriptionCreateSuccess((event) => {
logger.ok(`Successfully created EventSub subscription: ${kleur.underline(event.id)}`); logger.ok(
`Successfully created EventSub subscription: ${kleur.underline(event.id)}`,
);
}); });
eventSub.onSubscriptionCreateFailure(event => { eventSub.onSubscriptionCreateFailure((event) => {
logger.err(`Failed to create EventSub subscription: ${kleur.underline(event.id)}`); logger.err(
`Failed to create EventSub subscription: ${kleur.underline(event.id)}`,
);
}); });
eventSub.onSubscriptionDeleteSuccess(event => { eventSub.onSubscriptionDeleteSuccess((event) => {
logger.ok(`Successfully deleted EventSub subscription: ${kleur.underline(event.id)}`); logger.ok(
`Successfully deleted EventSub subscription: ${kleur.underline(event.id)}`,
);
}); });
eventSub.onSubscriptionDeleteFailure(event => { eventSub.onSubscriptionDeleteFailure((event) => {
logger.err(`Failed to delete EventSub subscription: ${kleur.underline(event.id)}`); logger.err(
`Failed to delete EventSub subscription: ${kleur.underline(event.id)}`,
);
}); });
await api.eventSub.deleteAllSubscriptions(); await api.eventSub.deleteAllSubscriptions();
import { readdir } from 'node:fs/promises'; import { readdir } from "node:fs/promises";
const files = await readdir(import.meta.dir); const files = await readdir(import.meta.dir);
for (const file of files) { for (const file of files) {
if (!file.endsWith('.ts')) continue; if (!file.endsWith(".ts")) continue;
if (file === import.meta.file) continue; if (file === import.meta.file) continue;
await import(import.meta.dir + '/' + file.slice(0, -3)); await import(`${import.meta.dir}/${file.slice(0, -3)}`);
}; }
eventSub.start(); eventSub.start();

View File

@@ -1,115 +1,172 @@
import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base" import type { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
import { streamerId, commandPrefix, streamerUsers, chatterId } from "main";
import User from "user";
import commands, { specialAliasCommands } from "commands";
import { Command, sendMessage } from "lib/commandUtils";
import { isAdmin } from "lib/admins";
import cheers from "cheers"; import cheers from "cheers";
import logger from "lib/logger"; import commands, { specialAliasCommands } from "commands";
import { addMessageToChatWidget } from "web/chatWidget/message";
import { isInvuln, removeInvuln, setTemporaryInvuln } from "lib/invuln";
import { getUserRecord } from "db/dbUser";
import { createCheerRecord } from "db/dbCheers"; import { createCheerRecord } from "db/dbCheers";
import handleAnivMessage from "lib/handleAnivMessage"; import { getUserRecord } from "db/dbUser";
import { Item } from "items";
import { eventSub } from "index"; import { eventSub } from "index";
import { Item } from "items";
import { isAdmin } from "lib/admins";
import { type Command, sendMessage } from "lib/commandUtils";
import handleAnivMessage from "lib/handleAnivMessage";
import { isInvuln, removeInvuln, setTemporaryInvuln } from "lib/invuln";
import logger from "lib/logger";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
import { chatterId, commandPrefix, streamerId, streamerUsers } from "main";
import User from "user";
import { addMessageToChatWidget } from "web/chatWidget/message";
eventSub.onChannelChatMessage(streamerId, chatterId, parseChatMessage); eventSub.onChannelChatMessage(streamerId, chatterId, parseChatMessage);
async function parseChatMessage(msg: EventSubChannelChatMessageEvent) { async function parseChatMessage(msg: EventSubChannelChatMessageEvent) {
addMessageToChatWidget(msg); addMessageToChatWidget(msg);
const user = await User.initUsername(msg.chatterName); const user = await User.initUsername(msg.chatterName);
// Get user from cache or place user in cache // Get user from cache or place user in cache
// Given the fact that this is the user that chats, this user object always exists and cannot be null // Given the fact that this is the user that chats, this user object always exists and cannot be null
// //
// One of the flaws with the user object is solved by creating the object with the name. // One of the flaws with the user object is solved by creating the object with the name.
// This way, if a user changes their name, the original name stays in the cache for at least 1 hour (extendable by using that name as target for item) // This way, if a user changes their name, the original name stays in the cache for at least 1 hour (extendable by using that name as target for item)
// and both are usable to target the same user (id is the same) // and both are usable to target the same user (id is the same)
// The only problem would be if a user changed their name and someone else took their name right after // The only problem would be if a user changed their name and someone else took their name right after
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) { // The msg.sourceMessageId checks if the message is from shared chat. shared chat should be ignored if (
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.exists(`user:${user?.id}:haschatted`)) &&
await redis.set(`user:${user?.id}:haschatted`, "1"); !msg.sourceMessageId
await redis.set(`user:${user?.id}:welcomemessageid`, message.id); ) {
await redis.expire(`user:${user?.id}:welcomemessageid`, 600); // The msg.sourceMessageId checks if the message is from shared chat. shared chat should be ignored
if (!await isInvuln(msg.chatterId)) await setTemporaryInvuln(user?.id!); // This would set the invuln expiration lmao 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);
await redis.expire(`user:${user?.id}:welcomemessageid`, 600);
if (!(await isInvuln(msg.chatterId))) await setTemporaryInvuln(user?.id!); // This would set the invuln expiration lmao
}
if (!await isInvuln(user?.id!)) user?.setVulnerable(); // Make the user vulnerable to explosions if not marked as invuln if (!(await isInvuln(user?.id!))) user?.setVulnerable(); // Make the user vulnerable to explosions if not marked as invuln
if (!msg.isCheer && !msg.isRedemption) await handleChatMessage(msg, user!) if (!msg.isCheer && !msg.isRedemption) await handleChatMessage(msg, user!);
else if (msg.isCheer && !msg.isRedemption) await handleCheer(msg, msg.bits, user!); else if (msg.isCheer && !msg.isRedemption)
}; await handleCheer(msg, msg.bits, user!);
}
async function handleChatMessage(msg: EventSubChannelChatMessageEvent, user: User) { async function handleChatMessage(
// Aniv message filter msg: EventSubChannelChatMessageEvent,
handleAnivMessage(msg, user); user: User,
) {
// Aniv message filter
handleAnivMessage(msg, user);
// Parse commands: // Parse commands:
const selected = selectCommand(msg.messageText); const selected = selectCommand(msg.messageText);
if (!selected) return; if (!selected) return;
const { cmd: selection, activation, isitem } = selected; const { cmd: selection, activation, isitem } = selected;
if (await redis.sismember('disabledcommands', selection.name)) return; if (await redis.sismember("disabledcommands", selection.name)) return;
if (isitem && await isInvuln(msg.chatterId) && !streamerUsers.includes(msg.chatterId)) { await sendMessage(`You're no longer an invuln because you used an item.`, msg.messageId); await removeInvuln(msg.chatterId); }; if (
isitem &&
(await isInvuln(msg.chatterId)) &&
!streamerUsers.includes(msg.chatterId)
) {
await sendMessage(
`You're no longer an invuln because you used an item.`,
msg.messageId,
);
await removeInvuln(msg.chatterId);
}
switch (selection.usertype) { switch (selection.usertype) {
case "admin": case "admin":
if (!await isAdmin(user.id)) return; if (!(await isAdmin(user.id))) return;
break; break;
case "streamer": case "streamer":
if (!streamerUsers.includes(msg.chatterId)) return; if (!streamerUsers.includes(msg.chatterId)) return;
break; break;
case "moderator": case "moderator":
if (!(await redis.exists(`user:${user.id}:mod`) || await isAdmin(user.id))) return; if (
break; !(
}; (await redis.exists(`user:${user.id}:mod`)) ||
(await isAdmin(user.id))
)
)
return;
break;
}
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();
}; }
}; }
type selectedCommand = { type selectedCommand = {
cmd: Command; cmd: Command;
activation: string; activation: string;
isitem: boolean; isitem: boolean;
}; };
function selectCommand(message: string): selectedCommand | false { function selectCommand(message: string): selectedCommand | false {
const specialcmdselector = message.trim().toLowerCase().split(' ')[0]!; const specialcmdselector = message.trim().toLowerCase().split(" ")[0]!;
const specialcmd = specialAliasCommands.get(specialcmdselector); const specialcmd = specialAliasCommands.get(specialcmdselector);
if (specialcmd) return { cmd: specialcmd, activation: specialcmdselector, isitem: specialcmd instanceof Item }; if (specialcmd)
if (!message.startsWith(commandPrefix)) return false; return {
const commandSelector = message.slice(commandPrefix.length).trim().toLowerCase().split(' ')[0]!; cmd: specialcmd,
const normalcmd = commands.get(commandSelector); activation: specialcmdselector,
if (normalcmd) return { cmd: normalcmd, activation: commandPrefix + commandSelector, isitem: normalcmd instanceof Item }; isitem: specialcmd instanceof Item,
return false; };
}; if (!message.startsWith(commandPrefix)) return false;
const commandSelector = message
.slice(commandPrefix.length)
.trim()
.toLowerCase()
.split(" ")[0]!;
const normalcmd = commands.get(commandSelector);
if (normalcmd)
return {
cmd: normalcmd,
activation: commandPrefix + commandSelector,
isitem: normalcmd instanceof Item,
};
return false;
}
export async function handleCheer(msg: EventSubChannelChatMessageEvent, bits: number, user: User) { export async function handleCheer(
if (msg.isCheer) { msg: EventSubChannelChatMessageEvent,
await getUserRecord(user); // ensure they exist in the database bits: number,
await createCheerRecord(user, bits); user: User,
}; // If this is not triggered it's because of the testcheer command. these fake bits should not be added to the database ) {
if (msg.isCheer) {
await getUserRecord(user); // ensure they exist in the database
await createCheerRecord(user, bits);
} // If this is not triggered it's because of the testcheer command. these fake bits should not be added to the database
const selection = cheers.get(bits); const selection = cheers.get(bits);
if (!selection) return; if (!selection) return;
if (await redis.sismember('disabledcheers', selection.name)) { await sendMessage(`The ${selection.name} cheer is disabled! Sorry!`, msg.messageId); return; }; if (await redis.sismember("disabledcheers", selection.name)) {
if (selection.isItem && await isInvuln(user.id) && !streamerUsers.includes(user.id)) { await sendMessage(`${user.displayName} Is no longer an invuln`); await removeInvuln(user.id); }; await sendMessage(
try { `The ${selection.name} cheer is disabled! Sorry!`,
await selection.execute(msg, user); msg.messageId,
} catch (err) { );
await sendMessage(`[ERROR]: Something went wrong with cheer execution`); return;
logger.err(err as string); }
}; if (
}; selection.isItem &&
(await isInvuln(user.id)) &&
!streamerUsers.includes(user.id)
) {
await sendMessage(`${user.displayName} Is no longer an invuln`);
await removeInvuln(user.id);
}
try {
await selection.execute(msg, user);
} catch (err) {
await sendMessage(`[ERROR]: Something went wrong with cheer execution`);
logger.err(err as string);
}
}

View File

@@ -2,10 +2,10 @@ import { eventSub } from "index";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
import { streamerId } from "main"; import { streamerId } from "main";
eventSub.onChannelModeratorAdd(streamerId, async mod => { eventSub.onChannelModeratorAdd(streamerId, async (mod) => {
await redis.set(`user:${mod.userId}:mod`, '1'); await redis.set(`user:${mod.userId}:mod`, "1");
}); });
eventSub.onChannelModeratorRemove(streamerId, async mod => { eventSub.onChannelModeratorRemove(streamerId, async (mod) => {
await redis.del(`user:${mod.userId}:mod`); await redis.del(`user:${mod.userId}:mod`);
}); });

View File

@@ -1,23 +1,37 @@
import { sendMessage } from "lib/commandUtils";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { eventSub, api } from "index"; import { api, eventSub } from "index";
import { changeItemCount } from "items"; import { changeItemCount } from "items";
import { sendMessage } from "lib/commandUtils";
import logger from "lib/logger"; import logger from "lib/logger";
import { redis } from "lib/redis";
import { streamerId } from "main"; import { streamerId } from "main";
import User from "user"; import User from "user";
import { redis } from "lib/redis";
eventSub.onChannelRaidTo(streamerId, async msg => { eventSub.onChannelRaidTo(streamerId, async (msg) => {
if (await redis.exists(`user:${msg.raidingBroadcasterId}:recentraid`)) { await sendMessage(`Another raid from ${msg.raidedBroadcasterDisplayName}??? SMH`); return; }; if (await redis.exists(`user:${msg.raidingBroadcasterId}:recentraid`)) {
await redis.set(`user:${msg.raidingBroadcasterId}:recentraid`, '1'); await sendMessage(
await redis.expire(`user:${msg.raidingBroadcasterId}:recentraid`, 60 * 30); // raid cooldown is 30 minutes `Another raid from ${msg.raidedBroadcasterDisplayName}??? SMH`,
await sendMessage(`Ty for raiding ${msg.raidingBroadcasterDisplayName}. You get 3 pieces of TNT. Enjoy!`); );
try { return;
await api.chat.shoutoutUser(streamerId, msg.raidingBroadcasterId); }
} catch (e) { await redis.set(`user:${msg.raidingBroadcasterId}:recentraid`, "1");
logger.warn(`Failed to give automatic shoutout to ${msg.raidingBroadcasterDisplayName}`); await redis.expire(`user:${msg.raidingBroadcasterId}:recentraid`, 60 * 30); // raid cooldown is 30 minutes
}; await sendMessage(
const raider = await User.initUsername(msg.raidingBroadcasterName); `Ty for raiding ${msg.raidingBroadcasterDisplayName}. You get 3 pieces of TNT. Enjoy!`,
const result = await changeItemCount(raider!, await getUserRecord(raider!), 'tnt', 3); );
if (!result) await sendMessage("oopsies, no tnt for you!"); try {
await api.chat.shoutoutUser(streamerId, msg.raidingBroadcasterId);
} catch (_e) {
logger.warn(
`Failed to give automatic shoutout to ${msg.raidingBroadcasterDisplayName}`,
);
}
const raider = await User.initUsername(msg.raidingBroadcasterName);
const result = await changeItemCount(
raider!,
await getUserRecord(raider!),
"tnt",
3,
);
if (!result) await sendMessage("oopsies, no tnt for you!");
}); });

View File

@@ -1,16 +1,20 @@
import { sendMessage } from "lib/commandUtils";
import { eventSub } from "index"; import { eventSub } from "index";
import { sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis";
import { streamerId } from "main"; import { streamerId } from "main";
import { sendDiscordMessage } from "web/discordConnection"; import { sendDiscordMessage } from "web/discordConnection";
import { redis } from "lib/redis";
eventSub.onStreamOnline(streamerId, async msg => { eventSub.onStreamOnline(streamerId, async (msg) => {
await redis.set('streamIsLive', '1'); await redis.set("streamIsLive", "1");
await sendMessage(`${msg.broadcasterDisplayName.toUpperCase()} IS LIVE! START DIGGING!`); await sendMessage(
await sendDiscordMessage({ message: 'live' }); `${msg.broadcasterDisplayName.toUpperCase()} IS LIVE! START DIGGING!`,
);
await sendDiscordMessage({ message: "live" });
}); });
eventSub.onStreamOffline(streamerId, async msg => { eventSub.onStreamOffline(streamerId, async (msg) => {
await redis.del('streamIsLive'); await redis.del("streamIsLive");
await sendMessage(`${msg.broadcasterDisplayName.toUpperCase()} IS OFFLINE! NO MORE FREE LOOT!`); await sendMessage(
`${msg.broadcasterDisplayName.toUpperCase()} IS OFFLINE! NO MORE FREE LOOT!`,
);
}); });

View File

@@ -1,123 +1,153 @@
import { sendMessage } from "lib/commandUtils";
import { getUserRecord, updateUserRecord } from "db/dbUser"; import { getUserRecord, updateUserRecord } from "db/dbUser";
import { eventSub } from "index"; import { eventSub } from "index";
import { changeBalance } from "lib/changeBalance"; import { changeBalance } from "lib/changeBalance";
import { sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis";
import { streamerId } from "main"; import { streamerId } from "main";
import User from "user"; import User from "user";
import { redis } from "lib/redis";
eventSub.onChannelSubscription(streamerId, async msg => { eventSub.onChannelSubscription(streamerId, async (msg) => {
await redis.set(`user:${msg.userId}:subbed`, msg.tier.slice(0, 1)); await redis.set(`user:${msg.userId}:subbed`, msg.tier.slice(0, 1));
if (msg.isGift) return; if (msg.isGift) return;
const user = await User.initUsername(msg.userName); const user = await User.initUsername(msg.userName);
const userRecord = await getUserRecord(user!); const userRecord = await getUserRecord(user!);
switch (msg.tier) { switch (msg.tier) {
case "1000": case "1000":
await Promise.all([ await Promise.all([
sendMessage(`YO THANKS FOR THE SUB ${msg.userDisplayName}! YOU GET 500 QBUCKS`), sendMessage(
changeBalance(user!, userRecord, 500) `YO THANKS FOR THE SUB ${msg.userDisplayName}! YOU GET 500 QBUCKS`,
]); ),
break; changeBalance(user!, userRecord, 500),
case "2000": ]);
userRecord.balance += 1500; break;
if (userRecord.inventory.silverbullet) userRecord.inventory.silverbullet += 1 case "2000":
else userRecord.inventory.silverbullet = 1 userRecord.balance += 1500;
await Promise.all([ if (userRecord.inventory.silverbullet)
sendMessage(`YO THANKS FOR THE TIER 2 SUB ${msg.userDisplayName}! YOU GET 1500 QBUCKS AND A SILVER BULLET`), userRecord.inventory.silverbullet += 1;
updateUserRecord(user!, userRecord) else userRecord.inventory.silverbullet = 1;
]); await Promise.all([
break; sendMessage(
case "3000": `YO THANKS FOR THE TIER 2 SUB ${msg.userDisplayName}! YOU GET 1500 QBUCKS AND A SILVER BULLET`,
userRecord.balance += 3000; ),
if (userRecord.inventory.silverbullet) userRecord.inventory.silverbullet += 2 updateUserRecord(user!, userRecord),
else userRecord.inventory.silverbullet = 2; ]);
await Promise.all([ break;
sendMessage(`YO THANKS FOR THE TIER 3 SUB ${msg.userDisplayName}! YOU GET 3000 QBUCKS AND 2 SILVER BULLETS`), case "3000":
updateUserRecord(user!, userRecord) userRecord.balance += 3000;
]); if (userRecord.inventory.silverbullet)
break; userRecord.inventory.silverbullet += 2;
}; else userRecord.inventory.silverbullet = 2;
await Promise.all([
sendMessage(
`YO THANKS FOR THE TIER 3 SUB ${msg.userDisplayName}! YOU GET 3000 QBUCKS AND 2 SILVER BULLETS`,
),
updateUserRecord(user!, userRecord),
]);
break;
}
}); });
eventSub.onChannelSubscriptionGift(streamerId, async msg => { eventSub.onChannelSubscriptionGift(streamerId, async (msg) => {
if (msg.isAnonymous) { if (msg.isAnonymous) {
switch (msg.tier) { switch (msg.tier) {
case "1000": case "1000":
await sendMessage(`YO THANKS ANON FOR THE SCAM SUB${msg.amount === 1 ? '' : 'S'}`); await sendMessage(
break; `YO THANKS ANON FOR THE SCAM SUB${msg.amount === 1 ? "" : "S"}`,
case "2000": );
await sendMessage(`YO THANKS ANON FOR THE ${msg.amount} TIER 2 SCAM SUB${msg.amount === 1 ? '' : 'S'}`); break;
break; case "2000":
case "3000": await sendMessage(
await sendMessage(`YO THANKS ANON FOR THE ${msg.amount} TIER 3 SCAM SUB${msg.amount === 1 ? '' : 'S'}`); `YO THANKS ANON FOR THE ${msg.amount} TIER 2 SCAM SUB${msg.amount === 1 ? "" : "S"}`,
break; );
}; break;
return; case "3000":
}; await sendMessage(
`YO THANKS ANON FOR THE ${msg.amount} TIER 3 SCAM SUB${msg.amount === 1 ? "" : "S"}`,
);
break;
}
return;
}
const user = await User.initUsername(msg.gifterName); const user = await User.initUsername(msg.gifterName);
const amount = msg.amount; const amount = msg.amount;
const userRecord = await getUserRecord(user!); const userRecord = await getUserRecord(user!);
switch (msg.tier) { switch (msg.tier) {
case "1000": case "1000":
await Promise.all([ await Promise.all([
sendMessage(`YO THANKS FOR THE SCAM GIFTS ${msg.gifterDisplayName}! YOU GET ${amount * 500} QBUCKS`), sendMessage(
changeBalance(user!, userRecord, amount * 500) `YO THANKS FOR THE SCAM GIFTS ${msg.gifterDisplayName}! YOU GET ${amount * 500} QBUCKS`,
]); ),
break; changeBalance(user!, userRecord, amount * 500),
case "2000": ]);
userRecord.balance += 1500 * amount; break;
if (userRecord.inventory.silverbullet) userRecord.inventory.silverbullet += amount case "2000":
else userRecord.inventory.silverbullet = amount; userRecord.balance += 1500 * amount;
await Promise.all([ if (userRecord.inventory.silverbullet)
sendMessage(`YO THANKS FOR THE SCAM TIER 2 GIFTS ${msg.gifterDisplayName}! YOU GET ${amount * 1500} QBUCKS AND ${amount} SILVER BULLET${amount === 1 ? '' : 'S'}`), userRecord.inventory.silverbullet += amount;
updateUserRecord(user!, userRecord) else userRecord.inventory.silverbullet = amount;
]); await Promise.all([
break; sendMessage(
case "3000": `YO THANKS FOR THE SCAM TIER 2 GIFTS ${msg.gifterDisplayName}! YOU GET ${amount * 1500} QBUCKS AND ${amount} SILVER BULLET${amount === 1 ? "" : "S"}`,
userRecord.balance += 3000 * amount; ),
if (userRecord.inventory.silverbullet) userRecord.inventory.silverbullet += amount * 2 updateUserRecord(user!, userRecord),
else userRecord.inventory.silverbullet = amount * 2; ]);
await Promise.all([ break;
sendMessage(`YO THANKS FOR THE SCAM TIER 3 GIFTS ${msg.gifterDisplayName}! YOU GET ${amount * 3000} QBUCKS AND ${amount * 2} SILVER BULLETS`), case "3000":
updateUserRecord(user!, userRecord) userRecord.balance += 3000 * amount;
]); if (userRecord.inventory.silverbullet)
break; userRecord.inventory.silverbullet += amount * 2;
} else userRecord.inventory.silverbullet = amount * 2;
await Promise.all([
sendMessage(
`YO THANKS FOR THE SCAM TIER 3 GIFTS ${msg.gifterDisplayName}! YOU GET ${amount * 3000} QBUCKS AND ${amount * 2} SILVER BULLETS`,
),
updateUserRecord(user!, userRecord),
]);
break;
}
}); });
eventSub.onChannelSubscriptionEnd(streamerId, async msg => { eventSub.onChannelSubscriptionEnd(streamerId, async (msg) => {
await redis.del(`user:${msg.userId}:subbed`); await redis.del(`user:${msg.userId}:subbed`);
}); });
eventSub.onChannelSubscriptionMessage(streamerId, async msg => { eventSub.onChannelSubscriptionMessage(streamerId, async (msg) => {
await redis.set(`user:${msg.userId}:subbed`, msg.tier.slice(0, 1)); await redis.set(`user:${msg.userId}:subbed`, msg.tier.slice(0, 1));
const user = await User.initUsername(msg.userName); const user = await User.initUsername(msg.userName);
const userRecord = await getUserRecord(user!); const userRecord = await getUserRecord(user!);
switch (msg.tier) { switch (msg.tier) {
case "1000": case "1000":
await Promise.all([ await Promise.all([
sendMessage(`YO THANKS FOR THE RESUB ${msg.userDisplayName}! YOU GET 500 QBUCKS`), sendMessage(
changeBalance(user!, userRecord, 500) `YO THANKS FOR THE RESUB ${msg.userDisplayName}! YOU GET 500 QBUCKS`,
]); ),
break; changeBalance(user!, userRecord, 500),
case "2000": ]);
userRecord.balance += 1500; break;
if (userRecord.inventory.silverbullet) userRecord.inventory.silverbullet += 1 case "2000":
else userRecord.inventory.silverbullet = 1 userRecord.balance += 1500;
await Promise.all([ if (userRecord.inventory.silverbullet)
sendMessage(`YO THANKS FOR THE TIER 2 RESUB ${msg.userDisplayName}! YOU GET 1500 QBUCKS AND A SILVER BULLET`), userRecord.inventory.silverbullet += 1;
updateUserRecord(user!, userRecord) else userRecord.inventory.silverbullet = 1;
]); await Promise.all([
break; sendMessage(
case "3000": `YO THANKS FOR THE TIER 2 RESUB ${msg.userDisplayName}! YOU GET 1500 QBUCKS AND A SILVER BULLET`,
userRecord.balance += 3000; ),
if (userRecord.inventory.silverbullet) userRecord.inventory.silverbullet += 2 updateUserRecord(user!, userRecord),
else userRecord.inventory.silverbullet = 2; ]);
await Promise.all([ break;
sendMessage(`YO THANKS FOR THE TIER 3 RESUB ${msg.userDisplayName}! YOU GET 3000 QBUCKS AND 2 SILVER BULLETS`), case "3000":
updateUserRecord(user!, userRecord) userRecord.balance += 3000;
]); if (userRecord.inventory.silverbullet)
break; userRecord.inventory.silverbullet += 2;
}; else userRecord.inventory.silverbullet = 2;
await Promise.all([
sendMessage(
`YO THANKS FOR THE TIER 3 RESUB ${msg.userDisplayName}! YOU GET 3000 QBUCKS AND 2 SILVER BULLETS`,
),
updateUserRecord(user!, userRecord),
]);
break;
}
}); });

View File

@@ -1,36 +1,80 @@
import { sendMessage } from "lib/commandUtils";
import { api, eventSub } from "index"; import { api, eventSub } from "index";
import { sendMessage } from "lib/commandUtils";
import { buildTimeString } from "lib/dateManager"; import { buildTimeString } from "lib/dateManager";
import { chatterId, commandPrefix } from "main";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
import { chatterId, commandPrefix } from "main";
const WHISPERCOOLDOWN = 60 * 5; // 5 minutes const WHISPERCOOLDOWN = 60 * 5; // 5 minutes
eventSub.onUserWhisperMessage(chatterId, async msg => { eventSub.onUserWhisperMessage(chatterId, async (msg) => {
if (!msg.messageText.startsWith(commandPrefix)) { await whisper(msg.senderUserId, `Whisper commands start with '${commandPrefix}'. All whisper commands can be found here: https://github.com/qwerinope/qweribot#whisper-commands-1`); return; }; if (!msg.messageText.startsWith(commandPrefix)) {
const cmd = msg.messageText.slice(commandPrefix.length).trim().toLowerCase().split(' ')[0]!; await whisper(
msg.senderUserId,
`Whisper commands start with '${commandPrefix}'. All whisper commands can be found here: https://github.com/qwerinope/qweribot#whisper-commands-1`,
);
return;
}
const cmd = msg.messageText
.slice(commandPrefix.length)
.trim()
.toLowerCase()
.split(" ")[0]!;
switch (cmd) { switch (cmd) {
case 'help': case "help":
case 'h': case "h":
await whisper(msg.senderUserId, `All whisper commands can be found here: https://github.com/qwerinope/qweribot#whisper-commands-1`); await whisper(
break; msg.senderUserId,
case 'ghostwhisper': `All whisper commands can be found here: https://github.com/qwerinope/qweribot#whisper-commands-1`,
case 'ghost': );
case 'g': break;
if (await redis.ttl(`user:${msg.senderUserId}:timeout`) < 0) { await whisper(msg.senderUserId, 'Cannot send ghost whisper while not timed out'); return; }; case "ghostwhisper":
const cooldown = await redis.expiretime(`user:${msg.senderUserId}:whispercooldown`); case "ghost":
if (cooldown < 0) { case "g": {
if (msg.messageText.length > 200) { await whisper(msg.senderUserId, `Message too long. Please send a shorter one.`); return; }; if ((await redis.ttl(`user:${msg.senderUserId}:timeout`)) < 0) {
await redis.set(`user:${msg.senderUserId}:whispercooldown`, '1'); await whisper(
await redis.expire(`user:${msg.senderUserId}:whispercooldown`, WHISPERCOOLDOWN); msg.senderUserId,
await sendMessage(`The ghost of ${msg.senderUserDisplayName} whispered: ${msg.messageText.split(' ').slice(1).join(' ').replaceAll(/cheer[0-9]+/gi, '')}`); "Cannot send ghost whisper while not timed out",
await whisper(msg.senderUserId, `Message sent. You can send another ghost whisper in ${Math.floor(WHISPERCOOLDOWN / 60)} minutes.`); );
} else { return;
await whisper(msg.senderUserId, `Wait another ${buildTimeString(cooldown * 1000, Date.now())} before sending another ghost whisper.`); }
}; const cooldown = await redis.expiretime(
break; `user:${msg.senderUserId}:whispercooldown`,
}; );
if (cooldown < 0) {
if (msg.messageText.length > 200) {
await whisper(
msg.senderUserId,
`Message too long. Please send a shorter one.`,
);
return;
}
await redis.set(`user:${msg.senderUserId}:whispercooldown`, "1");
await redis.expire(
`user:${msg.senderUserId}:whispercooldown`,
WHISPERCOOLDOWN,
);
await sendMessage(
`The ghost of ${msg.senderUserDisplayName} whispered: ${msg.messageText
.split(" ")
.slice(1)
.join(" ")
.replaceAll(/cheer[0-9]+/gi, "")}`,
);
await whisper(
msg.senderUserId,
`Message sent. You can send another ghost whisper in ${Math.floor(WHISPERCOOLDOWN / 60)} minutes.`,
);
} else {
await whisper(
msg.senderUserId,
`Wait another ${buildTimeString(cooldown * 1000, Date.now())} before sending another ghost whisper.`,
);
}
break;
}
}
}); });
const whisper = async (target: string, message: string) => await api.whispers.sendWhisper(chatterId, target, message); const whisper = async (target: string, message: string) =>
await api.whispers.sendWhisper(chatterId, target, message);

View File

@@ -1,128 +1,212 @@
import { chatterId, streamerId, singleUserMode, streamerUsers } from "main";
import { ApiClient } from "@twurple/api";
import { connectionCheck } from "connectionCheck"; import { connectionCheck } from "connectionCheck";
import { ApiClient } from "@twurple/api";
import {
type ConnectionAdapter,
EventSubHttpListener,
ReverseProxyAdapter,
} from "@twurple/eventsub-http";
import { NgrokAdapter } from "@twurple/eventsub-ngrok";
import { type authProviderInstructions, createAuthProvider } from "auth";
import { database, host, password, user } from "db/connection";
import logger from "lib/logger"; import logger from "lib/logger";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
import { createAuthProvider, type authProviderInstructions } from "auth"; import { chatterId, singleUserMode, streamerId, streamerUsers } from "main";
import { user, password, database, host } from "db/connection";
import { ConnectionAdapter, EventSubHttpListener, ReverseProxyAdapter } from "@twurple/eventsub-http";
import { NgrokAdapter } from "@twurple/eventsub-ngrok";
if (chatterId === "") { logger.enverr('CHATTER_ID'); process.exit(1); }; if (chatterId === "") {
if (streamerId === "") { logger.enverr('STREAMER_ID'); process.exit(1); }; logger.enverr("CHATTER_ID");
if (!user) { logger.enverr("POSTGRES_USER"); process.exit(1); }; process.exit(1);
if (!password) { logger.enverr("POSTGRES_USER"); process.exit(1); }; }
if (!database) { logger.enverr("POSTGRES_DB"); process.exit(1); }; if (streamerId === "") {
if (!host) { logger.enverr("POSTGRES_HOST"); process.exit(1); }; logger.enverr("STREAMER_ID");
process.exit(1);
}
if (!user) {
logger.enverr("POSTGRES_USER");
process.exit(1);
}
if (!password) {
logger.enverr("POSTGRES_USER");
process.exit(1);
}
if (!database) {
logger.enverr("POSTGRES_DB");
process.exit(1);
}
if (!host) {
logger.enverr("POSTGRES_HOST");
process.exit(1);
}
const eventSubHostName = process.env.EVENTSUB_HOSTNAME ?? (() => { const eventSubHostName =
logger.enverr('EVENTSUB_HOSTNAME'); process.env.EVENTSUB_HOSTNAME ??
process.exit(1); (() => {
})(); logger.enverr("EVENTSUB_HOSTNAME");
process.exit(1);
})();
const eventSubPort = process.env.EVENTSUB_PORT ?? (() => { const eventSubPort =
logger.enverr('EVENTSUB_PORT'); process.env.EVENTSUB_PORT ??
process.exit(1); (() => {
})(); logger.enverr("EVENTSUB_PORT");
process.exit(1);
})();
const eventSubSecret = process.env.EVENTSUB_SECRET ?? (() => { const eventSubSecret =
logger.enverr('EVENTSUB_SECRET'); process.env.EVENTSUB_SECRET ??
process.exit(1); (() => {
})(); logger.enverr("EVENTSUB_SECRET");
process.exit(1);
})();
const eventSubPath = process.env.EVENTSUB_PATH; const eventSubPath = process.env.EVENTSUB_PATH;
await connectionCheck(); await connectionCheck();
const CHATTERINTENTS = ["user:read:chat", "user:write:chat", "user:bot", "user:manage:whispers"]; const CHATTERINTENTS = [
const STREAMERINTENTS = ["channel:bot", "user:read:chat", "moderation:read", "moderator:read:chatters", "channel:manage:moderators", "moderator:manage:chat_messages", "moderator:manage:banned_users", "bits:read", "channel:moderate", "moderator:manage:shoutouts", "channel:read:subscriptions", "channel:manage:redemptions"]; "user:read:chat",
"user:write:chat",
const users: authProviderInstructions[] = [ "user:bot",
{ "user:manage:whispers",
userId: streamerId, ];
intents: singleUserMode ? CHATTERINTENTS.concat(STREAMERINTENTS) : STREAMERINTENTS, const STREAMERINTENTS = [
streamer: true "channel:bot",
} "user:read:chat",
"moderation:read",
"moderator:read:chatters",
"channel:manage:moderators",
"moderator:manage:chat_messages",
"moderator:manage:banned_users",
"bits:read",
"channel:moderate",
"moderator:manage:shoutouts",
"channel:read:subscriptions",
"channel:manage:redemptions",
]; ];
if (!singleUserMode) users.push({ const users: authProviderInstructions[] = [
userId: chatterId, {
intents: CHATTERINTENTS, userId: streamerId,
streamer: false intents: singleUserMode
}); ? CHATTERINTENTS.concat(STREAMERINTENTS)
: STREAMERINTENTS,
streamer: true,
},
];
const adapter: ConnectionAdapter = process.env.NODE_ENV === 'development' ? new NgrokAdapter({ if (!singleUserMode)
ngrokConfig: { users.push({
authtoken: process.env.EVENTSUB_NGROK_TOKEN ?? (() => { userId: chatterId,
logger.enverr('EVENTSUB_NGROK_TOKEN'); intents: CHATTERINTENTS,
process.exit(1); streamer: false,
})() });
}
}) : new ReverseProxyAdapter({ const adapter: ConnectionAdapter =
pathPrefix: eventSubPath, hostName: eventSubHostName, port: parseInt(eventSubPort) process.env.NODE_ENV === "development"
}); ? new NgrokAdapter({
ngrokConfig: {
authtoken:
process.env.EVENTSUB_NGROK_TOKEN ??
(() => {
logger.enverr("EVENTSUB_NGROK_TOKEN");
process.exit(1);
})(),
},
})
: new ReverseProxyAdapter({
pathPrefix: eventSubPath,
hostName: eventSubHostName,
port: parseInt(eventSubPort, 10),
});
const authProvider = await createAuthProvider(users); const authProvider = await createAuthProvider(users);
export const api = new ApiClient({ authProvider }); export const api = new ApiClient({ authProvider });
export const eventSub = new EventSubHttpListener({ apiClient: api, secret: eventSubSecret, adapter }); export const eventSub = new EventSubHttpListener({
apiClient: api,
secret: eventSubSecret,
adapter,
});
if (!singleUserMode) await redis.set(`user:${chatterId}:bot`, '1'); if (!singleUserMode) await redis.set(`user:${chatterId}:bot`, "1");
import { addAdmin } from "lib/admins"; import { addAdmin } from "lib/admins";
import { addInvuln } from "lib/invuln"; import { addInvuln } from "lib/invuln";
import User from "user";
import { remodMod, timeoutDuration } from "lib/timeout"; import { remodMod, timeoutDuration } from "lib/timeout";
import User from "user";
streamerUsers.forEach(async id => await Promise.all([addAdmin(id), addInvuln(id), redis.set(`user:${id}:mod`, '1')])); streamerUsers.forEach(
async (id) =>
await Promise.all([
addAdmin(id),
addInvuln(id),
redis.set(`user:${id}:mod`, "1"),
]),
);
const banned = await api.moderation.getBannedUsers(streamerId).then(a => a.data); const banned = await api.moderation
.getBannedUsers(streamerId)
.then((a) => a.data);
for (const ban of banned) { for (const ban of banned) {
await redis.set(`user:${ban.userId}:timeout`, '1'); await redis.set(`user:${ban.userId}:timeout`, "1");
const banlength = ban.expiryDate; const banlength = ban.expiryDate;
if (banlength) { if (banlength) {
redis.expire(`user:${ban.userId}:timeout`, Math.floor((ban.expiryDate.getTime() - Date.now()) / 1000) + 1); redis.expire(
logger.info(`Set the timeout of ${ban.userDisplayName} in the Redis/Valkey database.`); `user:${ban.userId}:timeout`,
}; Math.floor((ban.expiryDate.getTime() - Date.now()) / 1000) + 1,
}; );
logger.info(
`Set the timeout of ${ban.userDisplayName} in the Redis/Valkey database.`,
);
}
}
const mods = await api.moderation.getModerators(streamerId).then(a => a.data); const mods = await api.moderation.getModerators(streamerId).then((a) => a.data);
for (const mod of mods) { for (const mod of mods) {
await redis.set(`user:${mod.userId}:mod`, '1'); await redis.set(`user:${mod.userId}:mod`, "1");
logger.info(`Set the mod status of ${mod.userDisplayName} in the Redis/Valkey database.`); logger.info(
}; `Set the mod status of ${mod.userDisplayName} in the Redis/Valkey database.`,
);
}
const bannedmods = await redis.keys('user:*:remod').then(a => Array.from(a).map(b => b.slice(5, -6))); const bannedmods = await redis
.keys("user:*:remod")
.then((a) => Array.from(a).map((b) => b.slice(5, -6)));
for (const remod of bannedmods) { for (const remod of bannedmods) {
const target = await User.initUserId(remod); const target = await User.initUserId(remod);
const durationdata = await timeoutDuration(target!); const durationdata = await timeoutDuration(target!);
let duration = 0; let duration = 0;
if (durationdata) duration = Math.floor((durationdata * 1000 - Date.now()) / 1000); if (durationdata)
remodMod(target!, duration); duration = Math.floor((durationdata * 1000 - Date.now()) / 1000);
logger.info(`Set the remod timer for ${target?.displayName} to ${duration} seconds.`); remodMod(target!, duration);
}; logger.info(
`Set the remod timer for ${target?.displayName} to ${duration} seconds.`,
);
}
const subs = await api.subscriptions.getSubscriptions(streamerId).then(a => a.data); const subs = await api.subscriptions
const redisSubs = await redis.keys('user:*:subbed').then(a => a.map(b => b.slice(5, -7))); .getSubscriptions(streamerId)
.then((a) => a.data);
const redisSubs = await redis
.keys("user:*:subbed")
.then((a) => a.map((b) => b.slice(5, -7)));
for (const sub of subs) { for (const sub of subs) {
if (redisSubs.includes(sub.userId)) { if (redisSubs.includes(sub.userId)) {
const index = redisSubs.indexOf(sub.userId); const index = redisSubs.indexOf(sub.userId);
redisSubs.splice(index, 1); redisSubs.splice(index, 1);
continue; continue;
}; }
await redis.set(`user:${sub.userId}:subbed`, sub.tier.slice(0, 1)); await redis.set(`user:${sub.userId}:subbed`, sub.tier.slice(0, 1));
}; }
redisSubs.map(async a => await redis.del(`user:${a}:subbed`)); redisSubs.map(async (a) => await redis.del(`user:${a}:subbed`));
const streamdata = await api.streams.getStreamByUserId(streamerId); const streamdata = await api.streams.getStreamByUserId(streamerId);
if (streamdata) await redis.set('streamIsLive', '1') if (streamdata) await redis.set("streamIsLive", "1");
else await redis.del('streamIsLive'); else await redis.del("streamIsLive");
await import("./events"); await import("./events");
await import("./pointRedeems"); await import("./pointRedeems");
await import("./web"); await import("./web");

View File

@@ -1,63 +1,89 @@
import { changeItemCount, Item } from "items";
import { sendMessage } from "lib/commandUtils";
import { createTimeoutRecord } from "db/dbTimeouts"; import { createTimeoutRecord } from "db/dbTimeouts";
import { createUsedItemRecord } from "db/dbUsedItems"; import { createUsedItemRecord } from "db/dbUsedItems";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { changeItemCount, Item } from "items";
import { sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { timeout } from "lib/timeout"; import { timeout } from "lib/timeout";
import User from "user"; import User from "user";
import { playAlert } from "web/alerts/serverFunctions"; import { playAlert } from "web/alerts/serverFunctions";
const ITEMNAME = 'blaster'; const ITEMNAME = "blaster";
export default new Item({ export default new Item({
name: ITEMNAME, name: ITEMNAME,
prettyName: 'Blaster', prettyName: "Blaster",
plural: 's', plural: "s",
description: 'Times a specific person out for 60 seconds', description: "Times a specific person out for 60 seconds",
aliases: ['blaster', 'blast'], aliases: ["blaster", "blast"],
price: 100, price: 100,
execution: async (msg, user, specialargs) => { execution: async (msg, user, specialargs) => {
const messagequery = parseCommandArgs(msg.messageText, specialargs?.activation); const messagequery = parseCommandArgs(
if (!messagequery[0]) { await sendMessage('Please specify a target'); return; }; msg.messageText,
const target = await User.initUsername(messagequery[0].toLowerCase()); specialargs?.activation,
if (!target) { await sendMessage(`${messagequery[0]} doesn't exist`); return; }; );
await getUserRecord(target); // make sure the user record exist in the database if (!messagequery[0]) {
await sendMessage("Please specify a target");
return;
}
const target = await User.initUsername(messagequery[0].toLowerCase());
if (!target) {
await sendMessage(`${messagequery[0]} doesn't exist`);
return;
}
await getUserRecord(target); // make sure the user record exist in the database
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; }; if (await user.itemLock()) {
await user.setLock(); await sendMessage("Cannot use an item (itemlock)", msg.messageId);
return;
}
await user.setLock();
const userObj = await getUserRecord(user); const userObj = await getUserRecord(user);
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any blasters!`, msg.messageId); await user.clearLock(); return; }; if (userObj.inventory[ITEMNAME]! < 1) {
await sendMessage(`You don't have any blasters!`, msg.messageId);
await user.clearLock();
return;
}
const result = await timeout(target, `You got blasted by ${user.displayName}!`, 60); const result = await timeout(
if (result.status) await Promise.all([ target,
sendMessage(`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`), `You got blasted by ${user.displayName}!`,
changeItemCount(user, userObj, ITEMNAME), 60,
createTimeoutRecord(user, target, ITEMNAME), );
createUsedItemRecord(user, ITEMNAME), if (result.status)
playAlert({ await Promise.all([
name: 'userBlast', sendMessage(
user: user.displayName, `GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`,
target: target.displayName ),
}) changeItemCount(user, userObj, ITEMNAME),
]); createTimeoutRecord(user, target, ITEMNAME),
else { createUsedItemRecord(user, ITEMNAME),
switch (result.reason) { playAlert({
case "banned": name: "userBlast",
await sendMessage(`${target.displayName} is already timed out/banned`, msg.messageId); user: user.displayName,
break; target: target.displayName,
case "illegal": }),
await Promise.all([ ]);
sendMessage(`${user.displayName} Nou Nou Nou`), else {
timeout(user, 'nah', 60) switch (result.reason) {
]); case "banned":
break; await sendMessage(
case "unknown": `${target.displayName} is already timed out/banned`,
await sendMessage('Something went wrong...', msg.messageId); msg.messageId,
break; );
}; break;
}; case "illegal":
await user.clearLock(); await Promise.all([
} sendMessage(`${user.displayName} Nou Nou Nou`),
timeout(user, "nah", 60),
]);
break;
case "unknown":
await sendMessage("Something went wrong...", msg.messageId);
break;
}
}
await user.clearLock();
},
}); });

View File

@@ -1,48 +1,60 @@
import { redis } from "lib/redis";
import { sendMessage } from "lib/commandUtils";
import { timeout } from "lib/timeout";
import { changeItemCount, Item } from "items";
import User from "user";
import { getUserRecord } from "db/dbUser";
import { createTimeoutRecord } from "db/dbTimeouts"; import { createTimeoutRecord } from "db/dbTimeouts";
import { createUsedItemRecord } from "db/dbUsedItems"; import { createUsedItemRecord } from "db/dbUsedItems";
import { getUserRecord } from "db/dbUser";
import { changeItemCount, Item } from "items";
import { sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis";
import { timeout } from "lib/timeout";
import User from "user";
import { playAlert } from "web/alerts/serverFunctions"; import { playAlert } from "web/alerts/serverFunctions";
const ITEMNAME = 'grenade'; const ITEMNAME = "grenade";
export default new Item({ export default new Item({
name: ITEMNAME, name: ITEMNAME,
prettyName: 'Grenade', prettyName: "Grenade",
plural: 's', plural: "s",
description: 'Give a random chatter a 60s timeout', description: "Give a random chatter a 60s timeout",
aliases: ['grenade'], aliases: ["grenade"],
price: 99, price: 99,
execution: async (msg, user) => { execution: async (msg, user) => {
const targets = await redis.keys(`user:*:vulnerable`); const targets = await redis.keys(`user:*:vulnerable`);
if (targets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; }; if (targets.length === 0) {
const selection = targets[Math.floor(Math.random() * targets.length)]!; await sendMessage("No vulnerable chatters to blow up", msg.messageId);
const target = await User.initUserId(selection.slice(5, -11)); return;
}
const selection = targets[Math.floor(Math.random() * targets.length)]!;
const target = await User.initUserId(selection.slice(5, -11));
await getUserRecord(target!); // make sure the user record exist in the database await getUserRecord(target!); // make sure the user record exist in the database
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; }; if (await user.itemLock()) {
await user.setLock(); await sendMessage("Cannot use an item (itemlock)", msg.messageId);
return;
}
await user.setLock();
const userObj = await getUserRecord(user); const userObj = await getUserRecord(user);
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any grenades!`, msg.messageId); await user.clearLock(); return; }; if (userObj.inventory[ITEMNAME]! < 1) {
await sendMessage(`You don't have any grenades!`, msg.messageId);
await user.clearLock();
return;
}
await Promise.all([ await Promise.all([
timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60), timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60),
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`), sendMessage(
changeItemCount(user, userObj, ITEMNAME), `wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`,
createTimeoutRecord(user, target!, ITEMNAME), ),
createUsedItemRecord(user, ITEMNAME), changeItemCount(user, userObj, ITEMNAME),
playAlert({ createTimeoutRecord(user, target!, ITEMNAME),
name: 'grenadeExplosion', createUsedItemRecord(user, ITEMNAME),
user: user.displayName, playAlert({
target: target?.displayName! name: "grenadeExplosion",
}) user: user.displayName,
]); target: target?.displayName!,
await user.clearLock(); }),
} ]);
await user.clearLock();
},
}); });

View File

@@ -1,80 +1,96 @@
import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base"; import type { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
import User from "user"; import type { specialExecuteArgs, userType } from "lib/commandUtils";
import { type userType, type specialExecuteArgs } from "lib/commandUtils"; import type User from "user";
type itemOptions = { type itemOptions = {
name: items; name: items;
aliases: string[]; aliases: string[];
prettyName: string; prettyName: string;
plural: string; plural: string;
description: string; description: string;
execution: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>; execution: (
specialaliases?: string[]; message: EventSubChannelChatMessageEvent,
price: number; sender: User,
args?: specialExecuteArgs,
) => Promise<void>;
specialaliases?: string[];
price: number;
}; };
export class Item { export class Item {
public readonly name: items; public readonly name: items;
public readonly prettyName: string; public readonly prettyName: string;
public readonly plural: string; public readonly plural: string;
public readonly description: string; public readonly description: string;
public readonly aliases: string[]; public readonly aliases: string[];
public readonly specialaliases: string[]; public readonly specialaliases: string[];
public readonly usertype: userType; public readonly usertype: userType;
public readonly price: number; public readonly price: number;
public readonly execute: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>; public readonly execute: (
public readonly disableable: boolean; message: EventSubChannelChatMessageEvent,
sender: User,
args?: specialExecuteArgs,
) => Promise<void>;
public readonly disableable: boolean;
/** Creates an item object */ /** Creates an item object */
constructor(options: itemOptions) { constructor(options: itemOptions) {
this.name = options.name; this.name = options.name;
this.prettyName = options.prettyName; this.prettyName = options.prettyName;
this.plural = options.plural; this.plural = options.plural;
this.description = options.description; this.description = options.description;
this.aliases = options.aliases; this.aliases = options.aliases;
this.usertype = 'chatter'; // Items are usable by everyone this.usertype = "chatter"; // Items are usable by everyone
this.execute = options.execution; this.execute = options.execution;
this.disableable = true; this.disableable = true;
this.specialaliases = options.specialaliases ?? []; this.specialaliases = options.specialaliases ?? [];
this.price = options.price; this.price = options.price;
}; }
}; }
import { readdir } from 'node:fs/promises'; import { readdir } from "node:fs/promises";
import { updateUserRecord, type UserRecord } from "db/dbUser"; import { type UserRecord, updateUserRecord } from "db/dbUser";
const itemAliasMap = new Map<string, Item>;
const itemObjectArray: Item[] = [] const itemAliasMap = new Map<string, Item>();
const specialAliasItems = new Map<string, Item>; const itemObjectArray: Item[] = [];
const specialAliasItems = new Map<string, Item>();
const emptyInventory: inventory = {}; const emptyInventory: inventory = {};
const itemarray: items[] = []; const itemarray: items[] = [];
const files = await readdir(import.meta.dir); const files = await readdir(import.meta.dir);
for (const file of files) { for (const file of files) {
if (!file.endsWith('.ts')) continue; if (!file.endsWith(".ts")) continue;
if (file === import.meta.file) continue; if (file === import.meta.file) continue;
const item: Item = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default); const item: Item = await import(
emptyInventory[item.name] = 0; `${import.meta.dir}/${file.slice(0, -3)}`
itemarray.push(item.name); ).then((a) => a.default);
itemObjectArray.push(item); emptyInventory[item.name] = 0;
for (const alias of item.aliases) { itemarray.push(item.name);
itemAliasMap.set(alias, item); // Since it's not a primitive type the map is filled with references to the item, not the actual object itemObjectArray.push(item);
}; for (const alias of item.aliases) {
for (const alias of item.specialaliases) { itemAliasMap.set(alias, item); // Since it's not a primitive type the map is filled with references to the item, not the actual object
specialAliasItems.set(alias, item); }
}; for (const alias of item.specialaliases) {
}; specialAliasItems.set(alias, item);
}
}
export default itemAliasMap; export default itemAliasMap;
export { emptyInventory, itemarray, specialAliasItems, itemObjectArray }; export { emptyInventory, itemarray, specialAliasItems, itemObjectArray };
export type items = "blaster" | "silverbullet" | "grenade" | "tnt"; export type items = "blaster" | "silverbullet" | "grenade" | "tnt";
export type inventory = { export type inventory = {
[key in items]?: number; [key in items]?: number;
}; };
export async function changeItemCount(user: User, userRecord: UserRecord, itemname: items, amount = -1): Promise<false | UserRecord> { export async function changeItemCount(
userRecord.inventory[itemname] = userRecord.inventory[itemname]! += amount; user: User,
if (userRecord.inventory[itemname] < 0) return false; userRecord: UserRecord,
await updateUserRecord(user, userRecord); itemname: items,
return userRecord; amount = -1,
}; ): Promise<false | UserRecord> {
userRecord.inventory[itemname] = userRecord.inventory[itemname]! += amount;
if (userRecord.inventory[itemname] < 0) return false;
await updateUserRecord(user, userRecord);
return userRecord;
}

View File

@@ -1,88 +1,116 @@
import { changeItemCount, Item } from "items";
import { sendMessage } from "lib/commandUtils";
import { createTimeoutRecord } from "db/dbTimeouts"; import { createTimeoutRecord } from "db/dbTimeouts";
import { createUsedItemRecord } from "db/dbUsedItems"; import { createUsedItemRecord } from "db/dbUsedItems";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { changeItemCount, Item } from "items";
import { sendMessage } from "lib/commandUtils";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import { timeout } from "lib/timeout";
import { playAlert } from "web/alerts/serverFunctions";
import User from "user";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
import { timeout } from "lib/timeout";
import { streamerId } from "main"; import { streamerId } from "main";
import User from "user";
import { playAlert } from "web/alerts/serverFunctions";
const ITEMNAME = 'silverbullet'; const ITEMNAME = "silverbullet";
export default new Item({ export default new Item({
name: ITEMNAME, name: ITEMNAME,
prettyName: 'Silver bullet', prettyName: "Silver bullet",
plural: 's', plural: "s",
description: 'Times targeted or random vulnerable user out for 30 minutes', description: "Times targeted or random vulnerable user out for 30 minutes",
aliases: ['execute', 'silverbullet'], aliases: ["execute", "silverbullet"],
specialaliases: ['blastin'], specialaliases: ["blastin"],
price: 666, price: 666,
execution: async (msg, user, specialargs) => { execution: async (msg, user, specialargs) => {
const messagequery = parseCommandArgs(msg.messageText, specialargs?.activation); const messagequery = parseCommandArgs(
msg.messageText,
specialargs?.activation,
);
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; }; if (await user.itemLock()) {
await user.setLock(); await sendMessage("Cannot use an item (itemlock)", msg.messageId);
return;
}
await user.setLock();
const userObj = await getUserRecord(user); const userObj = await getUserRecord(user);
if (userObj.inventory[ITEMNAME]! < 1 && user.id !== streamerId) { await sendMessage(`You don't have any silver bullets!`, msg.messageId); await user.clearLock(); return; }; if (userObj.inventory[ITEMNAME]! < 1 && user.id !== streamerId) {
await sendMessage(`You don't have any silver bullets!`, msg.messageId);
await user.clearLock();
return;
}
let target: User | null; let target: User | null;
if (!messagequery[0]) { if (!messagequery[0]) {
const vulnsids = await redis.keys('user:*:vulnerable'); const vulnsids = await redis.keys("user:*:vulnerable");
const baseusers = vulnsids.map(a => User.initUserId(a.slice(5, -11))); const baseusers = vulnsids.map((a) => User.initUserId(a.slice(5, -11)));
const users: User[] = []; const users: User[] = [];
for (const user of baseusers) { for (const user of baseusers) {
const a = await user; const a = await user;
if (!a) continue; if (!a) continue;
users.push(a); users.push(a);
}; }
if (users.length === 0) { await user.clearLock(); await sendMessage('No vulnerable chatters', msg.messageId); return; }; if (users.length === 0) {
target = users[Math.floor(Math.random() * users.length)]!; await user.clearLock();
await playAlert({ await sendMessage("No vulnerable chatters", msg.messageId);
name: 'blastinRoulette', return;
user: user.displayName, }
targets: users.map(a => a.displayName), target = users[Math.floor(Math.random() * users.length)]!;
finaltarget: target.displayName await playAlert({
}); name: "blastinRoulette",
await new Promise((res, _) => setTimeout(res, 4000)); user: user.displayName,
} else { targets: users.map((a) => a.displayName),
target = await User.initUsername(messagequery[0].toLowerCase()); finaltarget: target.displayName,
}; });
if (!target) { await user.clearLock(); await sendMessage(`${messagequery[0]} doesn't exist`); return; }; await new Promise((res, _) => setTimeout(res, 4000));
} else {
target = await User.initUsername(messagequery[0].toLowerCase());
}
if (!target) {
await user.clearLock();
await sendMessage(`${messagequery[0]} doesn't exist`);
return;
}
await getUserRecord(target); // make sure the user record exist in the database await getUserRecord(target); // make sure the user record exist in the database
const result = await timeout(target, `You got blasted by ${user.displayName}!`, 60 * 30); const result = await timeout(
if (result.status) await Promise.all([ target,
sendMessage(`KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`), `You got blasted by ${user.displayName}!`,
changeItemCount(user, userObj, ITEMNAME), 60 * 30,
createTimeoutRecord(user, target, ITEMNAME), );
createUsedItemRecord(user, ITEMNAME), if (result.status)
playAlert({ await Promise.all([
name: 'userExecution', sendMessage(
user: user.displayName, `KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`,
target: target.displayName ),
}) changeItemCount(user, userObj, ITEMNAME),
]); createTimeoutRecord(user, target, ITEMNAME),
else { createUsedItemRecord(user, ITEMNAME),
switch (result.reason) { playAlert({
case "banned": name: "userExecution",
await sendMessage(`${target.displayName} is already timed out/banned`, msg.messageId); user: user.displayName,
break; target: target.displayName,
case "illegal": }),
await Promise.all([ ]);
sendMessage(`${user.displayName} Nou Nou Nou`), else {
timeout(user, 'nah', 60) switch (result.reason) {
]); case "banned":
break; await sendMessage(
case "unknown": `${target.displayName} is already timed out/banned`,
await sendMessage('Something went wrong...', msg.messageId); msg.messageId,
break; );
}; break;
}; case "illegal":
await user.clearLock(); await Promise.all([
} sendMessage(`${user.displayName} Nou Nou Nou`),
timeout(user, "nah", 60),
]);
break;
case "unknown":
await sendMessage("Something went wrong...", msg.messageId);
break;
}
}
await user.clearLock();
},
}); });

View File

@@ -1,64 +1,82 @@
import { redis } from "lib/redis";
import { sendMessage } from "lib/commandUtils";
import { timeout } from "lib/timeout";
import { changeItemCount, Item } from "items";
import User from "user";
import { getUserRecord } from "db/dbUser";
import { createTimeoutRecord } from "db/dbTimeouts"; import { createTimeoutRecord } from "db/dbTimeouts";
import { createUsedItemRecord } from "db/dbUsedItems"; import { createUsedItemRecord } from "db/dbUsedItems";
import { getUserRecord } from "db/dbUser";
import { changeItemCount, Item } from "items";
import { sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis";
import { timeout } from "lib/timeout";
import User from "user";
import { playAlert } from "web/alerts/serverFunctions"; import { playAlert } from "web/alerts/serverFunctions";
const ITEMNAME = 'tnt'; const ITEMNAME = "tnt";
export default new Item({ export default new Item({
name: ITEMNAME, name: ITEMNAME,
prettyName: 'TNT', prettyName: "TNT",
plural: 's', plural: "s",
description: 'Give 5-10 random chatters 60 second timeouts', description: "Give 5-10 random chatters 60 second timeouts",
aliases: ['tnt'], aliases: ["tnt"],
price: 1000, price: 1000,
execution: async (msg, user) => { execution: async (msg, user) => {
const vulntargets = await redis.keys('user:*:vulnerable').then(a => a.map(b => b.slice(5, -11))); const vulntargets = await redis
if (vulntargets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; }; .keys("user:*:vulnerable")
const targets = getTNTTargets(vulntargets); .then((a) => a.map((b) => b.slice(5, -11)));
if (vulntargets.length === 0) {
await sendMessage("No vulnerable chatters to blow up", msg.messageId);
return;
}
const targets = getTNTTargets(vulntargets);
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; }; if (await user.itemLock()) {
await user.setLock(); await sendMessage("Cannot use an item (itemlock)", msg.messageId);
return;
}
await user.setLock();
const userObj = await getUserRecord(user); const userObj = await getUserRecord(user);
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any TNTs!`, msg.messageId); await user.clearLock(); return; }; if (userObj.inventory[ITEMNAME]! < 1) {
await sendMessage(`You don't have any TNTs!`, msg.messageId);
await user.clearLock();
return;
}
await Promise.all(targets.map(async targetid => { await Promise.all(
const target = await User.initUserId(targetid); targets.map(async (targetid) => {
await getUserRecord(target!); // make sure the user record exist in the database const target = await User.initUserId(targetid);
await Promise.all([ await getUserRecord(target!); // make sure the user record exist in the database
timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60), await Promise.all([
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`), timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60),
createTimeoutRecord(user, target!, ITEMNAME), sendMessage(
]); `wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`,
})); ),
createTimeoutRecord(user, target!, ITEMNAME),
]);
}),
);
await Promise.all([ await Promise.all([
createUsedItemRecord(user, ITEMNAME), createUsedItemRecord(user, ITEMNAME),
playAlert({ playAlert({
name: 'tntExplosion', name: "tntExplosion",
user: user.displayName, user: user.displayName,
targets targets,
}), }),
changeItemCount(user, userObj, ITEMNAME) changeItemCount(user, userObj, ITEMNAME),
]); ]);
await user.clearLock(); await user.clearLock();
await sendMessage(`RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? '' : 's'} with their TNT RIPBOZO`); await sendMessage(
} `RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? "" : "s"} with their TNT RIPBOZO`,
);
},
}); });
export function getTNTTargets<T>(arr: T[]): T[] { export function getTNTTargets<T>(arr: T[]): T[] {
if (arr.length <= 5) { if (arr.length <= 5) {
return arr; return arr;
}; }
const count = Math.floor(Math.random() * 6) + 5; // Random number between 5 and 10 const count = Math.floor(Math.random() * 6) + 5; // Random number between 5 and 10
const shuffled = [...arr].sort(() => 0.5 - Math.random()); // Shuffle array const shuffled = [...arr].sort(() => 0.5 - Math.random()); // Shuffle array
return shuffled.slice(0, Math.min(count, arr.length)); // Return up to `count` entries return shuffled.slice(0, Math.min(count, arr.length)); // Return up to `count` entries
}; }

View File

@@ -1,15 +1,15 @@
import { redis } from "lib/redis"; import { redis } from "lib/redis";
export async function getAdmins() { export async function getAdmins() {
const data = await redis.keys('user:*:admin'); const data = await redis.keys("user:*:admin");
return data.map(a => a.slice(5, -6)); return data.map((a) => a.slice(5, -6));
}; }
export async function isAdmin(userid: string) { export async function isAdmin(userid: string) {
return await redis.exists(`user:${userid}:admin`); return await redis.exists(`user:${userid}:admin`);
}; }
export async function addAdmin(userid: string) { export async function addAdmin(userid: string) {
return await redis.set(`user:${userid}:admin`, '1'); return await redis.set(`user:${userid}:admin`, "1");
}; }
export async function removeAdmin(userid: string) { export async function removeAdmin(userid: string) {
return await redis.del(`user:${userid}:admin`); return await redis.del(`user:${userid}:admin`);
}; }

View File

@@ -1,9 +1,13 @@
import { updateUserRecord, type UserRecord } from "db/dbUser"; import { type UserRecord, updateUserRecord } from "db/dbUser";
import User from "user"; import type User from "user";
export async function changeBalance(user: User, userRecord: UserRecord, amount: number): Promise<false | UserRecord> { export async function changeBalance(
userRecord.balance = userRecord.balance += amount; user: User,
if (userRecord.balance < 0) return false; userRecord: UserRecord,
await updateUserRecord(user, userRecord); amount: number,
return userRecord; ): Promise<false | UserRecord> {
}; userRecord.balance = userRecord.balance += amount;
if (userRecord.balance < 0) return false;
await updateUserRecord(user, userRecord);
return userRecord;
}

View File

@@ -1,46 +1,59 @@
import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base"; import type { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
import User from "user";
import { chatterId, streamerId } from "main";
import { api } from "index"; import { api } from "index";
import { chatterId, streamerId } from "main";
import type User from "user";
export type userType = 'chatter' | 'admin' | 'streamer' | 'moderator'; export type userType = "chatter" | "admin" | "streamer" | "moderator";
export type specialExecuteArgs = { export type specialExecuteArgs = {
activation?: string; activation?: string;
}; };
export type commandOptions = { export type commandOptions = {
name: string; name: string;
aliases: string[]; aliases: string[];
usertype: userType; usertype: userType;
execution: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>; execution: (
disableable?: boolean; message: EventSubChannelChatMessageEvent,
specialaliases?: string[]; sender: User,
args?: specialExecuteArgs,
) => Promise<void>;
disableable?: boolean;
specialaliases?: string[];
}; };
/** The Command class represents a command */ /** The Command class represents a command */
export class Command { export class Command {
public readonly name: string; public readonly name: string;
public readonly aliases: string[]; public readonly aliases: string[];
public readonly usertype: userType; public readonly usertype: userType;
public readonly disableable: boolean; public readonly disableable: boolean;
public readonly specialaliases: string[]; public readonly specialaliases: string[];
public readonly execute: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>; public readonly execute: (
constructor(options: commandOptions) { message: EventSubChannelChatMessageEvent,
this.name = options.name.toLowerCase(); sender: User,
this.aliases = options.aliases; args?: specialExecuteArgs,
this.usertype = options.usertype; ) => Promise<void>;
this.execute = options.execution; constructor(options: commandOptions) {
this.disableable = options.disableable ?? true; this.name = options.name.toLowerCase();
this.specialaliases = options.specialaliases ?? []; this.aliases = options.aliases;
}; this.usertype = options.usertype;
}; this.execute = options.execution;
this.disableable = options.disableable ?? true;
this.specialaliases = options.specialaliases ?? [];
}
}
/** Helper function to send a message to the stream */ /** Helper function to send a message to the stream */
export const sendMessage = async (message: string, replyParentMessageId?: string) => { export const sendMessage = async (
try { message: string,
return await api.chat.sendChatMessageAsApp(chatterId, streamerId, message, { replyParentMessageId }); replyParentMessageId?: string,
} catch (e) { ) => {
return await api.chat.sendChatMessageAsApp(chatterId, streamerId, message); try {
}; return await api.chat.sendChatMessageAsApp(chatterId, streamerId, message, {
replyParentMessageId,
});
} catch (_e) {
return await api.chat.sendChatMessageAsApp(chatterId, streamerId, message);
}
}; };

View File

@@ -1,17 +1,19 @@
export function buildTimeString(time1: number, time2: number): string { export function buildTimeString(time1: number, time2: number): string {
const diff = Math.abs(time1 - time2); const diff = Math.abs(time1 - time2);
const timeobj = { const timeobj = {
day: Math.floor(diff / (1000 * 60 * 60 * 24)), day: Math.floor(diff / (1000 * 60 * 60 * 24)),
hour: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)), hour: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
minute: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)), minute: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
second: Math.floor((diff % (1000 * 60)) / 1000) second: Math.floor((diff % (1000 * 60)) / 1000),
}; };
const stringarray: string[] = []; const stringarray: string[] = [];
for (const [unit, value] of Object.entries(timeobj)) { for (const [unit, value] of Object.entries(timeobj)) {
if (value === 0) continue; if (value === 0) continue;
if (unit === 'second' && timeobj.day > 0) continue; if (unit === "second" && timeobj.day > 0) continue;
stringarray.push(`${value} ${unit}${value === 1 ? '' : 's'}`); stringarray.push(`${value} ${unit}${value === 1 ? "" : "s"}`);
}; }
const last = stringarray.pop(); const last = stringarray.pop();
return stringarray.length === 0 ? last! : stringarray.join(', ') + " and " + last; return stringarray.length === 0
}; ? last!
: `${stringarray.join(", ")} and ${last}`;
}

View File

@@ -5,57 +5,63 @@ import type { inventory } from "items";
import type User from "user"; import type User from "user";
export async function getTimeoutStats(target: User, thismonth: boolean) { export async function getTimeoutStats(target: User, thismonth: boolean) {
const monthdata = thismonth ? new Date().toISOString().slice(0, 7) : undefined; const monthdata = thismonth
? new Date().toISOString().slice(0, 7)
: undefined;
const [shot, hit] = await Promise.all([ const [shot, hit] = await Promise.all([
getTimeoutsAsUser(target, monthdata), getTimeoutsAsUser(target, monthdata),
getTimeoutsAsTarget(target, monthdata) getTimeoutsAsTarget(target, monthdata),
]); ]);
if (!shot || !hit) return; if (!shot || !hit) return;
const blasterhit = hit.filter(item => item.item !== 'silverbullet').length; const blasterhit = hit.filter((item) => item.item !== "silverbullet").length;
const silverbullethit = hit.length - blasterhit; const silverbullethit = hit.length - blasterhit;
const blastershot = shot.filter(item => item.item !== 'silverbullet').length; const blastershot = shot.filter(
const silverbulletshot = shot.length - blastershot; (item) => item.item !== "silverbullet",
).length;
const silverbulletshot = shot.length - blastershot;
return { return {
hit: { hit: {
blaster: blasterhit, blaster: blasterhit,
silverbullet: silverbullethit silverbullet: silverbullethit,
}, },
shot: { shot: {
blaster: blastershot, blaster: blastershot,
silverbullet: silverbulletshot silverbullet: silverbulletshot,
} },
}; };
}; }
export async function getItemStats(target: User, thismonth: boolean) { export async function getItemStats(target: User, thismonth: boolean) {
const monthdata = thismonth ? new Date().toISOString().slice(0, 7) : undefined; const monthdata = thismonth
? new Date().toISOString().slice(0, 7)
: undefined;
const [items, cheers] = await Promise.all([ const [items, cheers] = await Promise.all([
getItemsUsed(target, monthdata), getItemsUsed(target, monthdata),
getCheerEvents(target, monthdata) getCheerEvents(target, monthdata),
]); ]);
if (!items || !cheers) return; if (!items || !cheers) return;
const returnObj: inventory = { const returnObj: inventory = {
blaster: 0, blaster: 0,
silverbullet: 0, silverbullet: 0,
grenade: 0, grenade: 0,
tnt: 0, tnt: 0,
}; };
for (const item of items) { for (const item of items) {
if (!returnObj[item.item]) returnObj[item.item] = 0; if (!returnObj[item.item]) returnObj[item.item] = 0;
returnObj[item.item]! += 1; returnObj[item.item]! += 1;
}; }
for (const cheer of cheers) { for (const cheer of cheers) {
if (!returnObj[cheer.event]) returnObj[cheer.event] = 0; if (!returnObj[cheer.event]) returnObj[cheer.event] = 0;
returnObj[cheer.event]! += 1 returnObj[cheer.event]! += 1;
}; }
return returnObj; return returnObj;
}; }

View File

@@ -1,60 +1,71 @@
import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base" import type { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
import { redis } from "lib/redis";
import type User from "user";
import { timeout } from "lib/timeout";
import { sendMessage } from "lib/commandUtils";
import { createAnivTimeoutRecord } from "db/dbAnivTimeouts"; import { createAnivTimeoutRecord } from "db/dbAnivTimeouts";
import { sendMessage } from "lib/commandUtils";
import { redis } from "lib/redis";
import { timeout } from "lib/timeout";
import type User from "user";
const ANIVNAMES: anivBots[] = ['a_n_e_e_v', 'a_n_i_v']; const ANIVNAMES: anivBots[] = ["a_n_e_e_v", "a_n_i_v"];
type anivMessageStore = { type anivMessageStore = {
[key: string]: string; [key: string]: string;
}; };
type IsAnivMessage = { type IsAnivMessage = {
isAnivMessage: true; isAnivMessage: true;
message: string; message: string;
anivbot: anivBots; anivbot: anivBots;
}; };
type isNotAnivMessage = { type isNotAnivMessage = {
isAnivMessage: false; isAnivMessage: false;
}; };
export type anivBots = 'a_n_i_v' | 'a_n_e_e_v'; export type anivBots = "a_n_i_v" | "a_n_e_e_v";
type anivMessageResult = IsAnivMessage | isNotAnivMessage; type anivMessageResult = IsAnivMessage | isNotAnivMessage;
async function isAnivMessage(message: string): Promise<anivMessageResult> { async function isAnivMessage(message: string): Promise<anivMessageResult> {
const data: anivMessageStore = await redis.get('anivmessages').then(a => a === null ? {} : JSON.parse(a)); const data: anivMessageStore = await redis
for (const clanker of ANIVNAMES) { .get("anivmessages")
const anivmessage = data[clanker]; .then((a) => (a === null ? {} : JSON.parse(a)));
if (!anivmessage) continue; for (const clanker of ANIVNAMES) {
if (anivmessage === message) return { isAnivMessage: true, message, anivbot: clanker }; const anivmessage = data[clanker];
}; if (!anivmessage) continue;
return { isAnivMessage: false }; if (anivmessage === message)
}; return { isAnivMessage: true, message, anivbot: clanker };
}
return { isAnivMessage: false };
}
export default async function handleMessage(msg: EventSubChannelChatMessageEvent, user: User) { export default async function handleMessage(
if (ANIVNAMES.map(a => a.toLowerCase()).includes(user.username)) { msg: EventSubChannelChatMessageEvent,
const data: anivMessageStore = await redis.get('anivmessages').then(a => a === null ? {} : JSON.parse(a)); user: User,
data[user.displayName] = msg.messageText; ) {
await redis.set('anivmessages', JSON.stringify(data)); if (ANIVNAMES.map((a) => a.toLowerCase()).includes(user.username)) {
} else { const data: anivMessageStore = await redis
const data = await isAnivMessage(msg.messageText); .get("anivmessages")
if (data.isAnivMessage) { .then((a) => (a === null ? {} : JSON.parse(a)));
if (Math.random() > 0.5) { // 1/2 chance to dodge aniv timeout data[user.displayName] = msg.messageText;
await createAnivTimeoutRecord(msg.messageText, data.anivbot, user, 0) await redis.set("anivmessages", JSON.stringify(data));
return; } else {
}; const data = await isAnivMessage(msg.messageText);
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 const duration = Math.floor(Math.random() * 30) + 30; // minimum timeout of 30 sec, maximum of 60 sec
await Promise.all([ await Promise.all([
timeout(user, 'copied an aniv message', 30), timeout(user, "copied an aniv message", 30),
sendMessage(`${user.displayName} got timed out for copying an ${data.anivbot} message`), sendMessage(
createAnivTimeoutRecord(msg.messageText, data.anivbot, user, duration) `${user.displayName} got timed out for copying an ${data.anivbot} message`,
]); ),
}; createAnivTimeoutRecord(msg.messageText, data.anivbot, user, duration),
}; ]);
}; }
}
}

View File

@@ -2,22 +2,23 @@ import { redis } from "lib/redis";
import { streamerUsers } from "main"; import { streamerUsers } from "main";
export async function getInvulns() { export async function getInvulns() {
const data = await redis.keys('user:*:invulnerable'); const data = await redis.keys("user:*:invulnerable");
return data.map(a => a.slice(5, -13)); return data.map((a) => a.slice(5, -13));
}; }
export async function isInvuln(userid: string) { export async function isInvuln(userid: string) {
return await redis.exists(`user:${userid}:invulnerable`); return await redis.exists(`user:${userid}:invulnerable`);
}; }
export async function addInvuln(userid: string) { export async function addInvuln(userid: string) {
await redis.del(`user:${userid}:vulnerable`); await redis.del(`user:${userid}:vulnerable`);
if (await redis.ttl(`user:${userid}:invulnerable`) > 0) await redis.del(`user:${userid}:invulnerable`); if ((await redis.ttl(`user:${userid}:invulnerable`)) > 0)
return await redis.set(`user:${userid}:invulnerable`, '1'); await redis.del(`user:${userid}:invulnerable`);
}; return await redis.set(`user:${userid}:invulnerable`, "1");
}
export async function removeInvuln(userid: string) { export async function removeInvuln(userid: string) {
if (streamerUsers.includes(userid)) return; if (streamerUsers.includes(userid)) return;
return await redis.del(`user:${userid}:invulnerable`); return await redis.del(`user:${userid}:invulnerable`);
}; }
export async function setTemporaryInvuln(userid: string, duration = 600) { export async function setTemporaryInvuln(userid: string, duration = 600) {
await redis.set(`user:${userid}:invulnerable`, '1'); await redis.set(`user:${userid}:invulnerable`, "1");
await redis.expire(`user:${userid}:invulnerable`, duration); await redis.expire(`user:${userid}:invulnerable`, duration);
}; }

View File

@@ -1,11 +1,18 @@
import kleur from "kleur"; import kleur from "kleur";
const logger = { const logger = {
err: (arg: string) => console.error(kleur.red().bold().italic('[ERROR] ') + kleur.red().bold(arg)), err: (arg: string) =>
warn: (arg: string) => console.warn(kleur.yellow().bold().italic('[WARN] ') + kleur.yellow().bold(arg)), console.error(
info: (arg: string) => console.info(kleur.white().bold().italic('[INFO] ') + kleur.white(arg)), kleur.red().bold().italic("[ERROR] ") + kleur.red().bold(arg),
ok: (arg: string) => console.info(kleur.green().bold(arg)), ),
enverr: (arg: string) => logger.err(`Please provide a ${arg} in the .env`) warn: (arg: string) =>
console.warn(
kleur.yellow().bold().italic("[WARN] ") + kleur.yellow().bold(arg),
),
info: (arg: string) =>
console.info(kleur.white().bold().italic("[INFO] ") + kleur.white(arg)),
ok: (arg: string) => console.info(kleur.green().bold(arg)),
enverr: (arg: string) => logger.err(`Please provide a ${arg} in the .env`),
}; };
export default logger export default logger;

View File

@@ -2,24 +2,40 @@ import { commandPrefix } from "main";
/** Helper function to extract arguments from commands */ /** Helper function to extract arguments from commands */
export default function parseCommandArgs(input: string, specialAlias?: string) { 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).replace(/[^\x00-\x7F]/g, '').trim(); nice = input
sliceLength = input.toLowerCase().startsWith('i') ? 1 : 0; .toLowerCase()
} else { .slice(specialAlias.length)
nice = input.toLowerCase().slice(commandPrefix.length).replace(/[^\x00-\x7F]/g, '').trim(); .replace(/[^\x00-\x7F]/g, "")
sliceLength = nice.startsWith('use') ? 2 : 1; .trim();
} sliceLength = input.toLowerCase().startsWith("i") ? 1 : 0;
return nice.split(' ').slice(sliceLength).map(a => a.replaceAll(/!/gi, '')); } else {
}; 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, ""));
}
export function parseCheerArgs(input: string) { 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).replace(/[^\x00-\x7F]/g, '').split(' ').slice(1); if (nice.startsWith(`${commandPrefix}testcheer`))
return nice
.slice(commandPrefix.length + "testcheer".length + 1)
.replace(/[^\x00-\x7F]/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));
}; }

View File

@@ -1,3 +1,3 @@
import { RedisClient } from "bun"; import { RedisClient } from "bun";
export const redis = new RedisClient(); export const redis = new RedisClient();

View File

@@ -1,70 +1,90 @@
import { streamerId } from "main";
import logger from "lib/logger";
import User from "user";
import { isInvuln } from "lib/invuln";
import { api } from "index"; import { api } from "index";
import { isInvuln } from "lib/invuln";
import logger from "lib/logger";
import { redis } from "lib/redis"; import { redis } from "lib/redis";
import { streamerId } from "main";
import type User from "user";
type SuccessfulTimeout = { status: true; }; type SuccessfulTimeout = { status: true };
type UnSuccessfulTimeout = { status: false; reason: 'banned' | 'unknown' | 'illegal'; }; type UnSuccessfulTimeout = {
status: false;
reason: "banned" | "unknown" | "illegal";
};
type TimeoutResult = SuccessfulTimeout | UnSuccessfulTimeout; type TimeoutResult = SuccessfulTimeout | UnSuccessfulTimeout;
/** Give a user a timeout/ban /** Give a user a timeout/ban
* @param user - user class of target to timeout/ban * @param user - user class of target to timeout/ban
* @param reason - reason for timeout/ban * @param reason - reason for timeout/ban
* @param duration - duration of timeout. don't specifiy for ban */ * @param duration - duration of timeout. don't specifiy for ban */
export const timeout = async (user: User, reason: string, duration?: number): Promise<TimeoutResult> => { export const timeout = async (
if (await isInvuln(user.id) && duration || await redis.exists(`user:${user.id}:bot`)) return { status: false, reason: 'illegal' }; // Don't timeout invulnerable chatters and bots user: User,
reason: string,
duration?: number,
): Promise<TimeoutResult> => {
if (
((await isInvuln(user.id)) && duration) ||
(await redis.exists(`user:${user.id}:bot`))
)
return { status: false, reason: "illegal" }; // Don't timeout invulnerable chatters and bots
// Check if user already has a timeout and handle stacking // Check if user already has a timeout and handle stacking
const banStatus = await timeoutDuration(user); const banStatus = await timeoutDuration(user);
if (banStatus) { if (banStatus) {
if (await redis.exists('timeoutStacking')) { if (await redis.exists("timeoutStacking")) {
if (duration) duration += Math.floor((banStatus * 1000 - Date.now()) / 1000); // the target is timed out and stacking is on if (duration)
} else return { status: false, reason: 'banned' }; // the target is timed out, but stacking is off duration += Math.floor((banStatus * 1000 - Date.now()) / 1000); // the target is timed out and stacking is on
} else if (banStatus === null) return { status: false, reason: 'banned' }; // target is perma banned } 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 redis.exists(`user:${user.id}:mod`)) { if (await redis.exists(`user:${user.id}:mod`)) {
if (!duration) duration = 60; // make sure that mods don't get perma-banned if (!duration) duration = 60; // make sure that mods don't get perma-banned
await redis.set(`user:${user.id}:remod`, '1'); await redis.set(`user:${user.id}:remod`, "1");
remodMod(user, duration); remodMod(user, duration);
await api.moderation.removeModerator(streamerId, user.id!); await api.moderation.removeModerator(streamerId, user.id!);
}; }
try { try {
await api.moderation.banUser(streamerId, { user: user.id, reason, duration }); await api.moderation.banUser(streamerId, {
} catch (err) { user: user.id,
logger.err(err as string); reason,
return { status: false, reason: 'unknown' }; duration,
}; });
} catch (err) {
logger.err(err as string);
return { status: false, reason: "unknown" };
}
await user.clearVulnerable(); await user.clearVulnerable();
await redis.set(`user:${user.id}:timeout`, '1'); await redis.set(`user:${user.id}:timeout`, "1");
if (duration) await redis.expire(`user:${user.id}:timeout`, duration); if (duration) await redis.expire(`user:${user.id}:timeout`, duration);
return { status: true }; return { status: true };
}; };
/** Give the target mod status back after timeout */ /** Give the target mod status back after timeout */
export function remodMod(target: User, duration: number) { export function remodMod(target: User, duration: number) {
setTimeout(async () => { setTimeout(async () => {
const bandata = await timeoutDuration(target); const bandata = await timeoutDuration(target);
if (bandata) { // If the target is still timed out, try again when new timeout expires if (bandata) {
const timeoutleft = bandata * 1000 - Date.now(); // date when timeout expires - current date // If the target is still timed out, try again when new timeout expires
remodMod(target, timeoutleft); // Call the current function with new time (recursion) const timeoutleft = bandata * 1000 - Date.now(); // date when timeout expires - current date
} else { remodMod(target, timeoutleft); // Call the current function with new time (recursion)
try { } else {
await api.moderation.addModerator(streamerId, target.id); try {
await redis.del(`user:${target.id}:remod`); await api.moderation.addModerator(streamerId, target.id);
} catch (err) { }; // This triggers when the timeout got shortened. try/catch so no runtime error await redis.del(`user:${target.id}:remod`);
}; } catch (_err) {} // This triggers when the timeout got shortened. try/catch so no runtime error
}, duration + 3000); // callback gets called after duration of timeout + 3 seconds }
}; }, 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 */ /** 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> { export async function timeoutDuration(
const data = await redis.expiretime(`user:${user.id}:timeout`); user: User,
if (data === -1) return null; // Perma banned ): Promise<number | null | false> {
else if (data === -2) return false; // Not banned const data = await redis.expiretime(`user:${user.id}:timeout`);
return data; if (data === -1)
}; return null; // Perma banned
else if (data === -2) return false; // Not banned
return data;
}

View File

@@ -1,4 +1,4 @@
export const singleUserMode = process.env.CHATTER_IS_STREAMER === 'true'; export const singleUserMode = process.env.CHATTER_IS_STREAMER === "true";
export const chatterId = process.env.CHATTER_ID ?? ""; export const chatterId = process.env.CHATTER_ID ?? "";
export const streamerId = process.env.STREAMER_ID ?? ""; export const streamerId = process.env.STREAMER_ID ?? "";
export const commandPrefix = process.env.COMMAND_PREFIX ?? "!"; export const commandPrefix = process.env.COMMAND_PREFIX ?? "!";

View File

@@ -1,112 +1,123 @@
import { EventSubChannelRedemptionAddEvent } from "@twurple/eventsub-base"; import type { EventSubChannelRedemptionAddEvent } from "@twurple/eventsub-base";
import User from "user"; import type User from "user";
export type pointRedeemOptions = { export type pointRedeemOptions = {
name: string; name: string;
title: string; title: string;
prompt?: string; prompt?: string;
cost: number; cost: number;
color?: string; color?: string;
sfxredeem?: boolean; sfxredeem?: boolean;
input?: boolean; input?: boolean;
execution: (message: EventSubChannelRedemptionAddEvent, sender: User) => Promise<void>; execution: (
message: EventSubChannelRedemptionAddEvent,
sender: User,
) => Promise<void>;
}; };
/** The Command class represents a command */ /** The Command class represents a command */
export default class PointRedeem { export default class PointRedeem {
public readonly name: string; public readonly name: string;
public readonly title: string; public readonly title: string;
public readonly prompt?: string; public readonly prompt?: string;
public readonly cost: number; public readonly cost: number;
public readonly color?: string; public readonly color?: string;
public readonly sfxredeem?: boolean; public readonly sfxredeem?: boolean;
public readonly input?: boolean; public readonly input?: boolean;
public readonly execute: (message: EventSubChannelRedemptionAddEvent, sender: User) => Promise<void>; public readonly execute: (
constructor(options: pointRedeemOptions) { message: EventSubChannelRedemptionAddEvent,
this.name = options.name.toLowerCase(); sender: User,
this.title = options.title; ) => Promise<void>;
this.prompt = options.prompt; constructor(options: pointRedeemOptions) {
this.cost = options.cost; this.name = options.name.toLowerCase();
this.color = options.color; this.title = options.title;
this.execute = options.execution; this.prompt = options.prompt;
this.sfxredeem = options.sfxredeem; this.cost = options.cost;
this.input = options.input; this.color = options.color;
}; this.execute = options.execution;
}; this.sfxredeem = options.sfxredeem;
this.input = options.input;
}
}
import { readdir } from 'node:fs/promises'; import { readdir } from "node:fs/promises";
/** A map of all (including inactive) redeems mapped to names */ /** A map of all (including inactive) redeems mapped to names */
const namedRedeems = new Map<string, PointRedeem>; const namedRedeems = new Map<string, PointRedeem>();
const sfxRedeems = new Map<string, PointRedeem>; const sfxRedeems = new Map<string, PointRedeem>();
const files = await readdir(import.meta.dir); const files = await readdir(import.meta.dir);
for (const file of files) { for (const file of files) {
if (!file.endsWith('.ts')) continue; if (!file.endsWith(".ts")) continue;
if (file === import.meta.file) continue; if (file === import.meta.file) continue;
const redeem: PointRedeem = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default); const redeem: PointRedeem = await import(
namedRedeems.set(redeem.name, redeem); `${import.meta.dir}/${file.slice(0, -3)}`
if (redeem.sfxredeem) sfxRedeems.set(redeem.name, redeem); ).then((a) => a.default);
}; namedRedeems.set(redeem.name, redeem);
if (redeem.sfxredeem) sfxRedeems.set(redeem.name, redeem);
}
export { namedRedeems, sfxRedeems }; export { namedRedeems, sfxRedeems };
const activeRedeems = new Map<string, PointRedeem>; const activeRedeems = new Map<string, PointRedeem>();
/** Map of redeemname to twitch redeem ID */ /** Map of redeemname to twitch redeem ID */
const idMap = new Map<string, string>; const idMap = new Map<string, string>();
import { streamerId } from "main";
import logger from "lib/logger";
import { api } from "index"; import { api } from "index";
import logger from "lib/logger";
import { streamerId } from "main";
const currentRedeems = new Map<string, string>; const currentRedeems = new Map<string, string>();
await api.channelPoints.getCustomRewards(streamerId).then(a => a.map(b => currentRedeems.set(b.title, b.id))); await api.channelPoints
.getCustomRewards(streamerId)
.then((a) => a.map((b) => currentRedeems.set(b.title, b.id)));
for (const [_, redeem] of Array.from(namedRedeems)) { for (const [_, redeem] of Array.from(namedRedeems)) {
const selection = currentRedeems.get(redeem.title); const selection = currentRedeems.get(redeem.title);
if (selection) { if (selection) {
currentRedeems.delete(redeem.title); currentRedeems.delete(redeem.title);
idMap.set(redeem.name, selection); idMap.set(redeem.name, selection);
activeRedeems.set(selection, redeem); activeRedeems.set(selection, redeem);
} else { } else {
if (process.env.NODE_ENV !== 'production') continue; // If created with dev-app we won't be able to change it with prod app if (process.env.NODE_ENV !== "production") continue; // If created with dev-app we won't be able to change it with prod app
const creation = await api.channelPoints.createCustomReward(streamerId, { const creation = await api.channelPoints.createCustomReward(streamerId, {
title: redeem.title, title: redeem.title,
prompt: redeem.prompt, prompt: redeem.prompt,
cost: redeem.cost, cost: redeem.cost,
backgroundColor: redeem.color, backgroundColor: redeem.color,
userInputRequired: redeem.input userInputRequired: redeem.input,
}); });
logger.ok(`Created custom point redeem ${redeem.title}`); logger.ok(`Created custom point redeem ${redeem.title}`);
idMap.set(redeem.name, creation.id); idMap.set(redeem.name, creation.id);
activeRedeems.set(creation.id, redeem); activeRedeems.set(creation.id, redeem);
}; }
}; }
Array.from(currentRedeems).map(async ([title, redeem]) => { Array.from(currentRedeems).map(async ([title, redeem]) => {
if (process.env.NODE_ENV !== 'production') return; if (process.env.NODE_ENV !== "production") return;
await api.channelPoints.deleteCustomReward(streamerId, redeem); logger.ok(`Deleted custom point redeem ${title}`); await api.channelPoints.deleteCustomReward(streamerId, redeem);
logger.ok(`Deleted custom point redeem ${title}`);
}); });
logger.ok("Successfully synced all custom point redeems"); logger.ok("Successfully synced all custom point redeems");
export async function enableRedeem(redeem: PointRedeem, id: string) { export async function enableRedeem(redeem: PointRedeem, id: string) {
if (process.env.NODE_ENV !== 'production') return; if (process.env.NODE_ENV !== "production") return;
await api.channelPoints.updateCustomReward(streamerId, id, { await api.channelPoints.updateCustomReward(streamerId, id, {
isEnabled: true isEnabled: true,
}); });
activeRedeems.set(id, redeem); activeRedeems.set(id, redeem);
logger.ok(`Enabled the ${redeem.name} point redeem`); logger.ok(`Enabled the ${redeem.name} point redeem`);
}; }
export async function disableRedeem(redeem: PointRedeem, id: string) { export async function disableRedeem(redeem: PointRedeem, id: string) {
if (process.env.NODE_ENV !== 'production') return; if (process.env.NODE_ENV !== "production") return;
await api.channelPoints.updateCustomReward(streamerId, id, { await api.channelPoints.updateCustomReward(streamerId, id, {
isEnabled: false isEnabled: false,
}); });
activeRedeems.delete(id); activeRedeems.delete(id);
logger.ok(`Disabled the ${redeem.name} point redeem`); logger.ok(`Disabled the ${redeem.name} point redeem`);
}; }
export { activeRedeems, idMap }; export { activeRedeems, idMap };

View File

@@ -1,16 +1,18 @@
import { sendMessage } from "lib/commandUtils"; import PointRedeem from "pointRedeems";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { changeBalance } from "lib/changeBalance"; import { changeBalance } from "lib/changeBalance";
import PointRedeem from "pointRedeems"; import { sendMessage } from "lib/commandUtils";
export default new PointRedeem({ export default new PointRedeem({
name: "qbucksredeem", name: "qbucksredeem",
title: "FREE MONEY", title: "FREE MONEY",
prompt: "GET 100 QBUCKS!", prompt: "GET 100 QBUCKS!",
color: '#00FF00', color: "#00FF00",
cost: 1000, cost: 1000,
execution: async (_msg, user) => { execution: async (_msg, user) => {
await changeBalance(user, await getUserRecord(user), 100); await changeBalance(user, await getUserRecord(user), 100);
await sendMessage(`${user.displayName} got 100 qbucks for their point redeem`); await sendMessage(
} `${user.displayName} got 100 qbucks for their point redeem`,
}) );
},
});

View File

@@ -2,15 +2,16 @@ import PointRedeem from "pointRedeems";
import { playAlert } from "web/alerts/serverFunctions"; import { playAlert } from "web/alerts/serverFunctions";
export default new PointRedeem({ export default new PointRedeem({
name: "sfxEddieScream", name: "sfxEddieScream",
title: "Eddie scream", title: "Eddie scream",
cost: 100, cost: 100,
color: "#A020F0", color: "#A020F0",
prompt: "Eddie screaming", prompt: "Eddie screaming",
sfxredeem: true, sfxredeem: true,
execution: async msg => await playAlert({ execution: async (msg) =>
name: 'sound', await playAlert({
user: msg.userDisplayName, name: "sound",
sound: 'eddiescream' user: msg.userDisplayName,
}) sound: "eddiescream",
}),
}); });

View File

@@ -2,15 +2,16 @@ import PointRedeem from "pointRedeems";
import { playAlert } from "web/alerts/serverFunctions"; import { playAlert } from "web/alerts/serverFunctions";
export default new PointRedeem({ export default new PointRedeem({
name: "sfxMrockMadhouse", name: "sfxMrockMadhouse",
title: "Welcome to the Madhouse", title: "Welcome to the Madhouse",
cost: 100, cost: 100,
color: "#A020F0", color: "#A020F0",
prompt: "mrockstar20 saying 'Welcome to the Madhouse'", prompt: "mrockstar20 saying 'Welcome to the Madhouse'",
sfxredeem: true, sfxredeem: true,
execution: async msg => await playAlert({ execution: async (msg) =>
name: 'sound', await playAlert({
user: msg.userDisplayName, name: "sound",
sound: 'mrockmadhouse' user: msg.userDisplayName,
}) sound: "mrockmadhouse",
}),
}); });

View File

@@ -2,15 +2,16 @@ import PointRedeem from "pointRedeems";
import { playAlert } from "web/alerts/serverFunctions"; import { playAlert } from "web/alerts/serverFunctions";
export default new PointRedeem({ export default new PointRedeem({
name: "sfxRipBozo", name: "sfxRipBozo",
title: "RIP BOZO", title: "RIP BOZO",
cost: 500, cost: 500,
color: "#A020F0", color: "#A020F0",
prompt: "Coffeezilla calls me a conman", prompt: "Coffeezilla calls me a conman",
sfxredeem: true, sfxredeem: true,
execution: async msg => await playAlert({ execution: async (msg) =>
name: 'sound', await playAlert({
user: msg.userDisplayName, name: "sound",
sound: 'ripbozo' user: msg.userDisplayName,
}) sound: "ripbozo",
}),
}); });

View File

@@ -1,9 +1,9 @@
import { redis } from "lib/redis"; import type { HelixUser } from "@twurple/api";
import { api } from "index"; import { api } from "index";
import { HelixUser } from "@twurple/api"
import logger from "lib/logger"; import logger from "lib/logger";
import { redis } from "lib/redis";
const EXPIRETIME = 60 * 60 // 60 minutes const EXPIRETIME = 60 * 60; // 60 minutes
// The objective of this class is to: // The objective of this class is to:
// store displayname, username and id to reduce api calls // store displayname, username and id to reduce api calls
@@ -16,108 +16,111 @@ const EXPIRETIME = 60 * 60 // 60 minutes
// vulnchatters only gets set when user chats // vulnchatters only gets set when user chats
export default class User { export default class User {
public username!: string; public username!: string;
public id!: string; public id!: string;
public displayName!: string; public displayName!: string;
static async initUsername(dirtyUsername: string): Promise<User | null> { static async initUsername(dirtyUsername: string): Promise<User | null> {
try { try {
const userObj = new User(); const userObj = new User();
const username = dirtyUsername.replaceAll(/@/gi, ''); const username = dirtyUsername.replaceAll(/@/gi, "");
userObj.username = username; userObj.username = username;
const userid = await redis.get(`userlookup:${username}`); const userid = await redis.get(`userlookup:${username}`);
if (!userid) { if (!userid) {
const userdata = await api.users.getUserByName(username); const userdata = await api.users.getUserByName(username);
if (!userdata) return null; if (!userdata) return null;
userObj._setCache(userdata); userObj._setCache(userdata);
userObj.id = userdata.id; userObj.id = userdata.id;
userObj.displayName = userdata.displayName; userObj.displayName = userdata.displayName;
} else { } else {
const displayname = await redis.get(`user:${userid}:displayName`); const displayname = await redis.get(`user:${userid}:displayName`);
userObj._setExpire(userid, username); userObj._setExpire(userid, username);
userObj.id = userid; userObj.id = userid;
userObj.displayName = displayname!; userObj.displayName = displayname!;
}; }
return userObj; return userObj;
} catch { } catch {
logger.err(`Failed to initialize user with name: ${dirtyUsername}`); logger.err(`Failed to initialize user with name: ${dirtyUsername}`);
return null; return null;
}; }
}; }
static async initUserId(userId: string): Promise<User | null> { static async initUserId(userId: string): Promise<User | null> {
try { try {
const userObj = new User(); const userObj = new User();
userObj.id = userId; userObj.id = userId;
if (!await redis.exists(`user:${userId}:displayName`)) { if (!(await redis.exists(`user:${userId}:displayName`))) {
const userdata = await api.users.getUserById(userId); const userdata = await api.users.getUserById(userId);
if (!userdata) return null; if (!userdata) return null;
userObj._setCache(userdata); userObj._setCache(userdata);
userObj.username = userdata.name; userObj.username = userdata.name;
userObj.displayName = userdata.displayName; userObj.displayName = userdata.displayName;
} else { } else {
const [displayName, username] = await Promise.all([ const [displayName, username] = await Promise.all([
redis.get(`user:${userId}:displayName`), redis.get(`user:${userId}:displayName`),
redis.get(`user:${userId}:username`) redis.get(`user:${userId}:username`),
]); ]);
userObj._setExpire(userId, username!); userObj._setExpire(userId, username!);
userObj.username = username!; userObj.username = username!;
userObj.displayName = displayName!; userObj.displayName = displayName!;
}; }
return userObj; return userObj;
} catch { } catch {
logger.err(`Failed to initializer user with id: ${userId}`); logger.err(`Failed to initializer user with id: ${userId}`);
return null; return null;
}; }
}; }
private async _setCache(userdata: HelixUser) { private async _setCache(userdata: HelixUser) {
await Promise.all([ await Promise.all([
redis.set(`user:${userdata.id}:displayName`, userdata.displayName), redis.set(`user:${userdata.id}:displayName`, userdata.displayName),
redis.set(`user:${userdata.id}:username`, userdata.name), redis.set(`user:${userdata.id}:username`, userdata.name),
redis.set(`userlookup:${userdata.name}`, userdata.id) redis.set(`userlookup:${userdata.name}`, userdata.id),
]); ]);
await this._setExpire(userdata.id, userdata.name); await this._setExpire(userdata.id, userdata.name);
}; }
private async _setExpire(userId: string, userName: string) { private async _setExpire(userId: string, userName: string) {
await Promise.all([ await Promise.all([
redis.expire(`user:${userId}:displayName`, EXPIRETIME), redis.expire(`user:${userId}:displayName`, EXPIRETIME),
redis.expire(`user:${userId}:username`, EXPIRETIME), redis.expire(`user:${userId}:username`, EXPIRETIME),
redis.expire(`userlookup:${userName}`, EXPIRETIME) redis.expire(`userlookup:${userName}`, EXPIRETIME),
]); ]);
}; }
public async itemLock(): Promise<boolean> { public async itemLock(): Promise<boolean> {
return await redis.exists(`user:${this.id}:itemlock`); return await redis.exists(`user:${this.id}:itemlock`);
}; }
public async setLock(): Promise<void> { public async setLock(): Promise<void> {
await redis.set(`user:${this.id}:itemlock`, '1'); await redis.set(`user:${this.id}:itemlock`, "1");
}; }
public async clearLock(): Promise<void> { public async clearLock(): Promise<void> {
await redis.del(`user:${this.id}:itemlock`); await redis.del(`user:${this.id}:itemlock`);
}; }
public async setVulnerable(): Promise<void> { public async setVulnerable(): Promise<void> {
await redis.set(`user:${this.id}:vulnerable`, '1'); await redis.set(`user:${this.id}:vulnerable`, "1");
await redis.expire(`user:${this.id}:vulnerable`, Math.floor(EXPIRETIME / 2)); // Vulnerable chatter gets removed from the pool after 30 minutes await redis.expire(
}; `user:${this.id}:vulnerable`,
Math.floor(EXPIRETIME / 2),
); // Vulnerable chatter gets removed from the pool after 30 minutes
}
public async clearVulnerable(): Promise<void> { public async clearVulnerable(): Promise<void> {
await redis.del(`user:${this.id}:vulnerable`); await redis.del(`user:${this.id}:vulnerable`);
}; }
public async setGreed(): Promise<void> { public async setGreed(): Promise<void> {
await redis.set(`user:${this.id}:greedy`, '1'); await redis.set(`user:${this.id}:greedy`, "1");
}; }
public async clearGreed(): Promise<void> { public async clearGreed(): Promise<void> {
await redis.del(`user:${this.id}:greedy`); await redis.del(`user:${this.id}:greedy`);
}; }
public async greedy(): Promise<boolean> { public async greedy(): Promise<boolean> {
return await redis.exists(`user:${this.id}:greedy`); return await redis.exists(`user:${this.id}:greedy`);
}; }
}; }

Some files were not shown because too many files have changed in this diff Show More