mirror of
https://github.com/qwerinope/qweribot.git
synced 2025-12-17 02:31:39 +01:00
proper formatting and linting YAY, change cheer constructor to take object
This commit is contained in:
43
biome.json
Normal file
43
biome.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
package.json
64
package.json
@@ -1,34 +1,34 @@
|
||||
{
|
||||
"name": "qweribot",
|
||||
"module": "src/index.ts",
|
||||
"devDependencies": {
|
||||
"@twurple/eventsub-ngrok": "^7.4.0",
|
||||
"@types/bun": "latest",
|
||||
"drizzle-kit": "^0.31.5",
|
||||
"pg": "^8.16.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=production bun src/index.ts",
|
||||
"start-dev": "NODE_ENV=development bun src/index.ts",
|
||||
"start-discord": "NODE_ENV=production 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-dev": "drizzle-kit push --config=drizzle-dev.config.ts",
|
||||
"studio": "drizzle-kit studio --config=drizzle-prod.config.ts",
|
||||
"studio-dev": "drizzle-kit studio --config=drizzle-dev.config.ts"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fontsource/jersey-15": "^5.2.8",
|
||||
"@twurple/api": "7.4.0",
|
||||
"@twurple/auth": "^7.4.0",
|
||||
"@twurple/eventsub-http": "^7.4.0",
|
||||
"discord.js": "^14.24.0",
|
||||
"drizzle-orm": "^0.44.6",
|
||||
"kleur": "^4.1.5"
|
||||
}
|
||||
"name": "qweribot",
|
||||
"module": "src/index.ts",
|
||||
"devDependencies": {
|
||||
"@twurple/eventsub-ngrok": "^7.4.0",
|
||||
"@types/bun": "latest",
|
||||
"drizzle-kit": "^0.31.5",
|
||||
"pg": "^8.16.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=production bun src/index.ts",
|
||||
"start-dev": "NODE_ENV=development bun src/index.ts",
|
||||
"start-discord": "NODE_ENV=production 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-dev": "drizzle-kit push --config=drizzle-dev.config.ts",
|
||||
"studio": "drizzle-kit studio --config=drizzle-prod.config.ts",
|
||||
"studio-dev": "drizzle-kit studio --config=drizzle-dev.config.ts"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fontsource/jersey-15": "^5.2.8",
|
||||
"@twurple/api": "7.4.0",
|
||||
"@twurple/auth": "^7.4.0",
|
||||
"@twurple/eventsub-http": "^7.4.0",
|
||||
"discord.js": "^14.24.0",
|
||||
"drizzle-orm": "^0.44.6",
|
||||
"kleur": "^4.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
219
src/auth.ts
219
src/auth.ts
@@ -1,103 +1,160 @@
|
||||
import { RefreshingAuthProvider, exchangeCode, type AccessToken } from "@twurple/auth";
|
||||
import { createAuthRecord, deleteAuthRecord, getAuthRecord, updateAuthRecord } from "db/dbAuth";
|
||||
|
||||
import logger from "lib/logger";
|
||||
import {
|
||||
type AccessToken,
|
||||
exchangeCode,
|
||||
RefreshingAuthProvider,
|
||||
} from "@twurple/auth";
|
||||
import {
|
||||
createAuthRecord,
|
||||
deleteAuthRecord,
|
||||
getAuthRecord,
|
||||
updateAuthRecord,
|
||||
} from "db/dbAuth";
|
||||
import kleur from "kleur";
|
||||
import logger from "lib/logger";
|
||||
|
||||
async function initAuth(userId: string, clientId: string, 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
|
||||
async function initAuth(
|
||||
userId: string,
|
||||
clientId: string,
|
||||
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();
|
||||
// Generate random state variable to prevent cross-site-scripting attacks
|
||||
const state = Bun.randomUUIDv7().replace(/-/g, "").slice(0, 32).toUpperCase();
|
||||
// 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.`
|
||||
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 instruction = `Visit this URL as ${kleur
|
||||
.red()
|
||||
.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 = () => {
|
||||
let resolver: (code: string) => void;
|
||||
const promise = new Promise<string>((resolve) => { resolver = resolve; });
|
||||
return { promise, resolver: resolver! };
|
||||
}
|
||||
const createCodePromise = () => {
|
||||
let resolver: (code: string) => void;
|
||||
const promise = new Promise<string>((resolve) => {
|
||||
resolver = resolve;
|
||||
});
|
||||
return { promise, resolver: resolver! };
|
||||
};
|
||||
|
||||
const { promise: codepromise, resolver } = createCodePromise();
|
||||
const { promise: codepromise, resolver } = createCodePromise();
|
||||
|
||||
const server = Bun.serve({
|
||||
port,
|
||||
fetch(request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
if (searchParams.has('code') && searchParams.has('state') && searchParams.get('state') === state) {
|
||||
// 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 });
|
||||
}
|
||||
}
|
||||
});
|
||||
const server = Bun.serve({
|
||||
port,
|
||||
fetch(request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
if (
|
||||
searchParams.has("code") &&
|
||||
searchParams.has("state") &&
|
||||
searchParams.get("state") === state
|
||||
) {
|
||||
// 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;
|
||||
server.stop(false);
|
||||
logger.info(`Authentication code received.`);
|
||||
const tokenData = await exchangeCode(clientId, clientSecret, code, redirectURL);
|
||||
logger.info(`Successfully authenticated code.`);
|
||||
await createAuthRecord(tokenData, userId);
|
||||
logger.ok(`Successfully saved auth data in the database.`);
|
||||
return tokenData;
|
||||
};
|
||||
const code = await codepromise;
|
||||
server.stop(false);
|
||||
logger.info(`Authentication code received.`);
|
||||
const tokenData = await exchangeCode(
|
||||
clientId,
|
||||
clientSecret,
|
||||
code,
|
||||
redirectURL,
|
||||
);
|
||||
logger.info(`Successfully authenticated code.`);
|
||||
await createAuthRecord(tokenData, userId);
|
||||
logger.ok(`Successfully saved auth data in the database.`);
|
||||
return tokenData;
|
||||
}
|
||||
|
||||
export type authProviderInstructions = {
|
||||
userId: string;
|
||||
intents: string[];
|
||||
streamer: boolean;
|
||||
userId: string;
|
||||
intents: string[];
|
||||
streamer: boolean;
|
||||
};
|
||||
|
||||
export async function createAuthProvider(data: authProviderInstructions[]): Promise<RefreshingAuthProvider> {
|
||||
const clientId = process.env.CLIENT_ID;
|
||||
if (!clientId) { logger.enverr("CLIENT_ID"); process.exit(1); };
|
||||
export async function createAuthProvider(
|
||||
data: authProviderInstructions[],
|
||||
): Promise<RefreshingAuthProvider> {
|
||||
const clientId = process.env.CLIENT_ID;
|
||||
if (!clientId) {
|
||||
logger.enverr("CLIENT_ID");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const clientSecret = process.env.CLIENT_SECRET;
|
||||
if (!clientSecret) { logger.enverr("CLIENT_SECRET"); process.exit(1); };
|
||||
const clientSecret = process.env.CLIENT_SECRET;
|
||||
if (!clientSecret) {
|
||||
logger.enverr("CLIENT_SECRET");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const authData = new RefreshingAuthProvider({
|
||||
clientId,
|
||||
clientSecret
|
||||
});
|
||||
const authData = new RefreshingAuthProvider({
|
||||
clientId,
|
||||
clientSecret,
|
||||
});
|
||||
|
||||
authData.onRefresh(async (user, token) => {
|
||||
logger.ok(`Successfully refreshed auth for user ${user}`);
|
||||
await updateAuthRecord(user, token);
|
||||
});
|
||||
authData.onRefresh(async (user, token) => {
|
||||
logger.ok(`Successfully refreshed auth for user ${user}`);
|
||||
await updateAuthRecord(user, token);
|
||||
});
|
||||
|
||||
authData.onRefreshFailure((user, err) => {
|
||||
logger.err(`Failed to refresh auth for user ${user}: ${err.name} ${err.message}`);
|
||||
});
|
||||
authData.onRefreshFailure((user, err) => {
|
||||
logger.err(
|
||||
`Failed to refresh auth for user ${user}: ${err.name} ${err.message}`,
|
||||
);
|
||||
});
|
||||
|
||||
for (const user of data) {
|
||||
const authRecord = await getAuthRecord(user.userId, user.intents);
|
||||
for (const user of data) {
|
||||
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 {
|
||||
await authData.addUserForToken(token, user.intents);
|
||||
} catch (err) {
|
||||
logger.err(`Failed to setup user auth. Please restart the bot and re-authenticate.`);
|
||||
await deleteAuthRecord(user.userId);
|
||||
process.exit(1);
|
||||
};
|
||||
try {
|
||||
await authData.addUserForToken(token, user.intents);
|
||||
} catch (_err) {
|
||||
logger.err(
|
||||
`Failed to setup user auth. Please restart the bot and re-authenticate.`,
|
||||
);
|
||||
await deleteAuthRecord(user.userId);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
await authData.refreshAccessTokenForUser(user.userId);
|
||||
} 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.`);
|
||||
await deleteAuthRecord(user.userId);
|
||||
process.exit(1);
|
||||
};
|
||||
};
|
||||
try {
|
||||
await authData.refreshAccessTokenForUser(user.userId);
|
||||
} 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.`,
|
||||
);
|
||||
await deleteAuthRecord(user.userId);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
return authData;
|
||||
};
|
||||
return authData;
|
||||
}
|
||||
|
||||
@@ -1,71 +1,92 @@
|
||||
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 { 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 { 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) => {
|
||||
const args = parseCheerArgs(msg.messageText);
|
||||
export default new Cheer({
|
||||
name: "execute",
|
||||
amount: 666,
|
||||
isItem: true,
|
||||
async execute(msg, user) {
|
||||
const args = parseCheerArgs(msg.messageText);
|
||||
|
||||
let target: User | null;
|
||||
if (!args[0]) {
|
||||
const vulnsids = await redis.keys('user:*:vulnerable');
|
||||
const baseusers = vulnsids.map(a => User.initUserId(a.slice(5, -11)));
|
||||
const users: User[] = [];
|
||||
for (const user of baseusers) {
|
||||
const a = await user;
|
||||
if (!a) continue;
|
||||
users.push(a);
|
||||
};
|
||||
if (users.length === 0) { await sendMessage('No vulnerable chatters'); await handleNoTarget(msg, user, ITEMNAME, true); return; };
|
||||
target = users[Math.floor(Math.random() * users.length)]!;
|
||||
await playAlert({
|
||||
name: 'blastinRoulette',
|
||||
user: user.displayName,
|
||||
targets: users.map(a => a.displayName),
|
||||
finaltarget: target.displayName
|
||||
});
|
||||
await new Promise((res, _) => setTimeout(res, 4000));
|
||||
} else {
|
||||
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 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;
|
||||
};
|
||||
};
|
||||
}, true);
|
||||
let target: User | null;
|
||||
if (!args[0]) {
|
||||
const vulnsids = await redis.keys("user:*:vulnerable");
|
||||
const baseusers = vulnsids.map((a) => User.initUserId(a.slice(5, -11)));
|
||||
const users: User[] = [];
|
||||
for (const user of baseusers) {
|
||||
const a = await user;
|
||||
if (!a) continue;
|
||||
users.push(a);
|
||||
}
|
||||
if (users.length === 0) {
|
||||
await sendMessage("No vulnerable chatters");
|
||||
await handleNoTarget(msg, user, ITEMNAME, true);
|
||||
return;
|
||||
}
|
||||
target = users[Math.floor(Math.random() * users.length)]!;
|
||||
await playAlert({
|
||||
name: "blastinRoulette",
|
||||
user: user.displayName,
|
||||
targets: users.map((a) => a.displayName),
|
||||
finaltarget: target.displayName,
|
||||
});
|
||||
await new Promise((res, _) => setTimeout(res, 4000));
|
||||
} else {
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 { redis } from "lib/redis";
|
||||
import { timeout } from "lib/timeout";
|
||||
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";
|
||||
|
||||
const ITEMNAME = 'grenade';
|
||||
const ITEMNAME = "grenade";
|
||||
|
||||
export default new Cheer('grenade', 99, async (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));
|
||||
export default new Cheer({
|
||||
name: "grenade",
|
||||
amount: 99,
|
||||
isItem: true,
|
||||
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([
|
||||
timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60),
|
||||
redis.del(selection),
|
||||
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`),
|
||||
createTimeoutRecord(user, target!, ITEMNAME),
|
||||
createCheerEventRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: 'grenadeExplosion',
|
||||
user: user.displayName,
|
||||
target: target?.displayName!
|
||||
})
|
||||
]);
|
||||
}, true);
|
||||
await Promise.all([
|
||||
timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60),
|
||||
redis.del(selection),
|
||||
sendMessage(
|
||||
`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`,
|
||||
),
|
||||
createTimeoutRecord(user, target!, ITEMNAME),
|
||||
createCheerEventRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: "grenadeExplosion",
|
||||
user: user.displayName,
|
||||
target: target?.displayName!,
|
||||
}),
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,47 +1,75 @@
|
||||
import User from 'user';
|
||||
import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
|
||||
import type { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
|
||||
import type User from "user";
|
||||
|
||||
export class Cheer {
|
||||
public readonly name: string;
|
||||
public readonly amount: number;
|
||||
public readonly execute: (msg: EventSubChannelChatMessageEvent, sender: User) => Promise<void>;
|
||||
public readonly isItem: boolean;
|
||||
constructor(name: string, amount: number, execution: (msg: EventSubChannelChatMessageEvent, sender: User) => Promise<void>, isItem = false) {
|
||||
this.name = name.toLowerCase();
|
||||
this.amount = amount;
|
||||
this.execute = execution;
|
||||
this.isItem = isItem;
|
||||
};
|
||||
type cheerOptions = {
|
||||
name: string;
|
||||
amount: number;
|
||||
isItem: boolean;
|
||||
execute: (
|
||||
msg: EventSubChannelChatMessageEvent,
|
||||
sender: User,
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
import { readdir } from 'node:fs/promises';
|
||||
const cheers = new Map<number, Cheer>;
|
||||
const namedcheers = new Map<string, Cheer>;
|
||||
export class Cheer {
|
||||
public readonly name: string;
|
||||
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);
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.ts')) continue;
|
||||
if (file === import.meta.file) continue;
|
||||
const cheer: Cheer = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default);
|
||||
cheers.set(cheer.amount, cheer);
|
||||
namedcheers.set(cheer.name, cheer);
|
||||
};
|
||||
if (!file.endsWith(".ts")) continue;
|
||||
if (file === import.meta.file) continue;
|
||||
const cheer: Cheer = await import(
|
||||
`${import.meta.dir}/${file.slice(0, -3)}`
|
||||
).then((a) => a.default);
|
||||
cheers.set(cheer.amount, cheer);
|
||||
namedcheers.set(cheer.name, cheer);
|
||||
}
|
||||
|
||||
export default cheers;
|
||||
export { namedcheers };
|
||||
|
||||
import { sendMessage } from 'lib/commandUtils';
|
||||
import { getUserRecord } from 'db/dbUser';
|
||||
import { changeItemCount, type items } from 'items';
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { changeItemCount, type items } from "items";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
|
||||
export async function handleNoTarget(msg: EventSubChannelChatMessageEvent, user: User, itemname: items, silent = true) {
|
||||
if (await user.itemLock()) {
|
||||
await sendMessage(`Cannot give ${user.displayName} a ${itemname} (itemlock)`, 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();
|
||||
export async function handleNoTarget(
|
||||
msg: EventSubChannelChatMessageEvent,
|
||||
user: User,
|
||||
itemname: items,
|
||||
silent = true,
|
||||
) {
|
||||
if (await user.itemLock()) {
|
||||
await sendMessage(
|
||||
`Cannot give ${user.displayName} a ${itemname} (itemlock)`,
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1,68 +1,87 @@
|
||||
import { Cheer } from "cheers";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { createGetLootRecord } from "db/dbGetLoot";
|
||||
import { getUserRecord, updateUserRecord } from "db/dbUser";
|
||||
import itemMap, { type inventory, type items } from "items";
|
||||
import { createGetLootRecord } from "db/dbGetLoot";
|
||||
import { timeout } from "lib/timeout";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { redis } from "lib/redis";
|
||||
import { timeout } from "lib/timeout";
|
||||
|
||||
export default new Cheer('superloot', 150, async (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);
|
||||
export default new Cheer({
|
||||
name: "superloot",
|
||||
amount: 150,
|
||||
isItem: true,
|
||||
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 new Promise(res => setTimeout(res, 1000 * 5));
|
||||
await sendMessage("HOLD");
|
||||
await new Promise((res) => setTimeout(res, 1000 * 5));
|
||||
|
||||
if (Math.random() > 0.5) {
|
||||
await Promise.all([
|
||||
sendMessage(`SUPERLOOT FAILED!!! KEKPOINT KEKPOINT KEKPOINT ${msg.chatterDisplayName.toUpperCase()} SEE YOU IN 5 MINUTES!!!`),
|
||||
timeout(user, `RIP BOZO! NO SUPERLOOT FOR YOU`, 60 * 5),
|
||||
user.clearLock()
|
||||
]);
|
||||
return;
|
||||
};
|
||||
if (Math.random() > 0.5) {
|
||||
await Promise.all([
|
||||
sendMessage(
|
||||
`SUPERLOOT FAILED!!! KEKPOINT KEKPOINT KEKPOINT ${msg.chatterDisplayName.toUpperCase()} SEE YOU IN 5 MINUTES!!!`,
|
||||
),
|
||||
timeout(user, `RIP BOZO! NO SUPERLOOT FOR YOU`, 60 * 5),
|
||||
user.clearLock(),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
const gainedqbucks = Math.floor(Math.random() * 250) + 150; // range from 150 to 400
|
||||
userData.balance += gainedqbucks;
|
||||
const gainedqbucks = Math.floor(Math.random() * 250) + 150; // range from 150 to 400
|
||||
userData.balance += gainedqbucks;
|
||||
|
||||
const itemDiff: inventory = {
|
||||
grenade: 0,
|
||||
blaster: 0,
|
||||
tnt: 0,
|
||||
silverbullet: 0
|
||||
};
|
||||
const itemDiff: inventory = {
|
||||
grenade: 0,
|
||||
blaster: 0,
|
||||
tnt: 0,
|
||||
silverbullet: 0,
|
||||
};
|
||||
|
||||
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.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.silverbullet! += 1; // 1 in 50
|
||||
};
|
||||
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.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.silverbullet! += 1; // 1 in 50
|
||||
}
|
||||
|
||||
for (const [item, amount] of Object.entries(itemDiff) as [items, number][]) {
|
||||
if (userData.inventory[item]) userData.inventory[item] += amount;
|
||||
else userData.inventory[item] = amount;
|
||||
};
|
||||
for (const [item, amount] of Object.entries(itemDiff) as [
|
||||
items,
|
||||
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)) {
|
||||
if (amount === 0) continue;
|
||||
const selection = itemMap.get(item);
|
||||
if (!selection) continue;
|
||||
itemstrings.push(`${amount} ${selection.prettyName + (amount === 1 ? '' : selection.plural)}`);
|
||||
};
|
||||
for (const [item, amount] of Object.entries(itemDiff)) {
|
||||
if (amount === 0) continue;
|
||||
const selection = itemMap.get(item);
|
||||
if (!selection) continue;
|
||||
itemstrings.push(
|
||||
`${amount} ${selection.prettyName + (amount === 1 ? "" : selection.plural)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const last = itemstrings.pop();
|
||||
const itemstring = itemstrings.length === 0 ? last : itemstrings.join(', ') + " and " + last;
|
||||
const message = `You got ${itemstring}`;
|
||||
const last = itemstrings.pop();
|
||||
const itemstring =
|
||||
itemstrings.length === 0 ? last : `${itemstrings.join(", ")} and ${last}`;
|
||||
const message = `You got ${itemstring}`;
|
||||
|
||||
await Promise.all([
|
||||
updateUserRecord(user, userData),
|
||||
sendMessage(message, msg.messageId),
|
||||
createGetLootRecord(user, gainedqbucks, itemDiff, 'superloot'),
|
||||
user.clearLock()
|
||||
]);
|
||||
}, true);
|
||||
await Promise.all([
|
||||
updateUserRecord(user, userData),
|
||||
sendMessage(message, msg.messageId),
|
||||
createGetLootRecord(user, gainedqbucks, itemDiff, "superloot"),
|
||||
user.clearLock(),
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,49 +1,69 @@
|
||||
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 { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { parseCheerArgs } from "lib/parseCommandArgs";
|
||||
import { timeout } from "lib/timeout";
|
||||
import User from "user";
|
||||
import { playAlert } from "web/alerts/serverFunctions";
|
||||
|
||||
const ITEMNAME = 'blaster';
|
||||
const ITEMNAME = "blaster";
|
||||
|
||||
export default new Cheer('timeout', 100, async (msg, user) => {
|
||||
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);
|
||||
export default new Cheer({
|
||||
name: "timeout",
|
||||
amount: 100,
|
||||
isItem: true,
|
||||
async execute(msg, user) {
|
||||
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);
|
||||
if (result.status) await Promise.all([
|
||||
sendMessage(`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`),
|
||||
createTimeoutRecord(user, target, ITEMNAME),
|
||||
createCheerEventRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: 'userBlast',
|
||||
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 blasted by ${user.displayName}!`,
|
||||
60,
|
||||
);
|
||||
if (result.status)
|
||||
await Promise.all([
|
||||
sendMessage(
|
||||
`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`,
|
||||
),
|
||||
createTimeoutRecord(user, target, ITEMNAME),
|
||||
createCheerEventRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: "userBlast",
|
||||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,40 +1,57 @@
|
||||
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 { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { getTNTTargets } from "items/tnt";
|
||||
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";
|
||||
|
||||
const ITEMNAME = 'tnt';
|
||||
const ITEMNAME = "tnt";
|
||||
|
||||
export default new Cheer('tnt', 1000, async (msg, user) => {
|
||||
const vulntargets = await redis.keys('user:*:vulnerable').then(a => a.map(b => b.slice(5, -11)));
|
||||
if (vulntargets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); await handleNoTarget(msg, user, ITEMNAME); return; };
|
||||
const targets = getTNTTargets(vulntargets);
|
||||
export default new Cheer({
|
||||
name: "tnt",
|
||||
amount: 1000,
|
||||
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 => {
|
||||
const target = await User.initUserId(targetid);
|
||||
await getUserRecord(target!); // make sure the user record exist in the database
|
||||
await Promise.all([
|
||||
timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60),
|
||||
redis.del(`user:${targetid}:vulnerable`),
|
||||
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`),
|
||||
createTimeoutRecord(user, target!, ITEMNAME)
|
||||
]);
|
||||
}));
|
||||
await Promise.all(
|
||||
targets.map(async (targetid) => {
|
||||
const target = await User.initUserId(targetid);
|
||||
await getUserRecord(target!); // make sure the user record exist in the database
|
||||
await Promise.all([
|
||||
timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60),
|
||||
redis.del(`user:${targetid}:vulnerable`),
|
||||
sendMessage(
|
||||
`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`,
|
||||
),
|
||||
createTimeoutRecord(user, target!, ITEMNAME),
|
||||
]);
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
createCheerEventRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: 'tntExplosion',
|
||||
user: user.displayName,
|
||||
targets
|
||||
})
|
||||
]);
|
||||
await Promise.all([
|
||||
createCheerEventRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: "tntExplosion",
|
||||
user: user.displayName,
|
||||
targets,
|
||||
}),
|
||||
]);
|
||||
|
||||
await sendMessage(`RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? '' : 's'} with their TNT RIPBOZO`);
|
||||
}, true);
|
||||
await sendMessage(
|
||||
`RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? "" : "s"} with their TNT RIPBOZO`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { addAdmin } from "lib/admins";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'addadmin',
|
||||
aliases: ['addadmin'],
|
||||
usertype: 'streamer',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; };
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||
const data = await 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);
|
||||
}
|
||||
name: "addadmin",
|
||||
aliases: ["addadmin"],
|
||||
usertype: "streamer",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a target", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) {
|
||||
await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
const data = await 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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,23 +1,40 @@
|
||||
import { redis } from "lib/redis";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { redis } from "lib/redis";
|
||||
import { streamerUsers } from "main";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'addbot',
|
||||
aliases: ['addbot'],
|
||||
usertype: 'streamer',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; };
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||
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);
|
||||
}
|
||||
name: "addbot",
|
||||
aliases: ["addbot"],
|
||||
usertype: "streamer",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a target", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) {
|
||||
await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
import { addInvuln } from "lib/invuln";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { addInvuln } from "lib/invuln";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'addinvuln',
|
||||
aliases: ['addinvuln'],
|
||||
usertype: 'moderator',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; };
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||
const data = await 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);
|
||||
}
|
||||
name: "addinvuln",
|
||||
aliases: ["addinvuln"],
|
||||
usertype: "moderator",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a target", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) {
|
||||
await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
const data = await 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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,30 +1,54 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { changeBalance } from "lib/changeBalance";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'admindonate',
|
||||
aliases: ['admindonate'],
|
||||
usertype: 'admin',
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a user', msg.messageId); return; };
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||
const userRecord = await getUserRecord(target);
|
||||
if (!args[1]) { await sendMessage('Please specify the amount qweribucks you want to give', msg.messageId); return; };
|
||||
const amount = parseInt(args[1]);
|
||||
if (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();
|
||||
}
|
||||
name: "admindonate",
|
||||
aliases: ["admindonate"],
|
||||
usertype: "admin",
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a user", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) {
|
||||
await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
const userRecord = await getUserRecord(target);
|
||||
if (!args[1]) {
|
||||
await sendMessage(
|
||||
"Please specify the amount qweribucks you want to give",
|
||||
msg.messageId,
|
||||
);
|
||||
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();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,34 +1,64 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import items, { changeItemCount } from "items";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'admingive',
|
||||
aliases: ['admingive'],
|
||||
usertype: 'admin',
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a user', msg.messageId); return; };
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||
const userRecord = 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]);
|
||||
if (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();
|
||||
}
|
||||
name: "admingive",
|
||||
aliases: ["admingive"],
|
||||
usertype: "admin",
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a user", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) {
|
||||
await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
const userRecord = 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)) {
|
||||
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();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,34 +1,38 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getKDLeaderboard } from "db/dbUser";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import User from "user";
|
||||
|
||||
type KD = { user: User; kd: number; };
|
||||
type KD = { user: User; kd: number };
|
||||
|
||||
export default new Command({
|
||||
name: 'alltimekdleaderboard',
|
||||
aliases: ['alltimeleaderboard', 'alltimekdleaderboard'],
|
||||
usertype: 'chatter',
|
||||
execution: async msg => {
|
||||
const rawKD = await getKDLeaderboard();
|
||||
if (rawKD.length === 0) {
|
||||
await sendMessage(`No users on leaderboard yet!`, msg.messageId);
|
||||
return;
|
||||
};
|
||||
name: "alltimekdleaderboard",
|
||||
aliases: ["alltimeleaderboard", "alltimekdleaderboard"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg) => {
|
||||
const rawKD = await getKDLeaderboard();
|
||||
if (rawKD.length === 0) {
|
||||
await sendMessage(`No users on leaderboard yet!`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
const userKDs: KD[] = [];
|
||||
await Promise.all(rawKD.map(async userRecord => {
|
||||
const user = await User.initUserId(userRecord.userId.toString());
|
||||
if (!user) return;
|
||||
userKDs.push({ user, kd: userRecord.KD })
|
||||
}));
|
||||
const userKDs: KD[] = [];
|
||||
await Promise.all(
|
||||
rawKD.map(async (userRecord) => {
|
||||
const user = await User.initUserId(userRecord.userId.toString());
|
||||
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[] = [];
|
||||
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)}`);
|
||||
};
|
||||
const txt: string[] = [];
|
||||
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)}`,
|
||||
);
|
||||
}
|
||||
|
||||
await sendMessage(`Alltime leaderboard: ${txt.join(' | ')}`, msg.messageId);
|
||||
}
|
||||
await sendMessage(`Alltime leaderboard: ${txt.join(" | ")}`, msg.messageId);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,33 +1,45 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getTimeoutStats, getItemStats } from "lib/getStats";
|
||||
import { getItemStats, getTimeoutStats } from "lib/getStats";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'alltimestats',
|
||||
aliases: ['alltime', 'alltimestats'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
let target: User | null = user;
|
||||
if (args[0]) {
|
||||
target = await User.initUsername(args[0]);
|
||||
if (!target) { await sendMessage(`User ${args[0]} doesn't exist!`, msg.messageId); return; };
|
||||
};
|
||||
name: "alltimestats",
|
||||
aliases: ["alltime", "alltimestats"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
let target: User | null = user;
|
||||
if (args[0]) {
|
||||
target = await User.initUsername(args[0]);
|
||||
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)]);
|
||||
if (!timeout || !item) { await sendMessage(`ERROR: Something went wrong!`, msg.messageId); return; };
|
||||
const [timeout, item] = await Promise.all([
|
||||
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}:
|
||||
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},
|
||||
TNT exploded: ${item.tnt}.
|
||||
Silver bullets fired: ${timeout.shot.silverbullet},
|
||||
Silver bullets taken: ${timeout.hit.silverbullet}.
|
||||
`, msg.messageId);
|
||||
}
|
||||
`,
|
||||
msg.messageId,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getAnivTimeouts } from "db/dbAnivTimeouts";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'anivtimeouts',
|
||||
aliases: ['anivtimeouts', 'anivtimeout'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
const target = args[0] ? await User.initUsername(args[0].toLowerCase()) : user;
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist!`, msg.messageId); return; };
|
||||
const { dodge, dead } = await getAnivTimeouts(target);
|
||||
const percentage = ((dodge / (dead + dodge)) * 100);
|
||||
const message = `Aniv timeouts of ${target.displayName}: Dodge: ${dodge}, Timeout: ${dead}. Dodge percentage: ${isNaN(percentage) ? "0" : percentage.toFixed(1)}%`;
|
||||
await sendMessage(message, msg.messageId);
|
||||
}
|
||||
name: "anivtimeouts",
|
||||
aliases: ["anivtimeouts", "anivtimeout"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
const target = args[0]
|
||||
? await User.initUsername(args[0].toLowerCase())
|
||||
: user;
|
||||
if (!target) {
|
||||
await sendMessage(`Chatter ${args[0]} doesn't exist!`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
const { dodge, dead } = await getAnivTimeouts(target);
|
||||
const percentage = (dodge / (dead + dodge)) * 100;
|
||||
const message = `Aniv timeouts of ${target.displayName}: Dodge: ${dodge}, Timeout: ${dead}. Dodge percentage: ${Number.isNaN(percentage) ? "0" : percentage.toFixed(1)}%`;
|
||||
await sendMessage(message, msg.messageId);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import User from "user";
|
||||
import { redis } from "lib/redis";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'backshot',
|
||||
aliases: ['backshot'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
const targets = await redis.keys(`user:*:haschatted`);
|
||||
const selection = targets[Math.floor(Math.random() * targets.length)]!;
|
||||
const target = await User.initUserId(selection.slice(5, -11));
|
||||
await sendMessage(`${user.displayName} backshotted ${target?.displayName}`, msg.messageId);
|
||||
}
|
||||
name: "backshot",
|
||||
aliases: ["backshot"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg, user) => {
|
||||
const targets = await redis.keys(`user:*:haschatted`);
|
||||
const selection = targets[Math.floor(Math.random() * targets.length)]!;
|
||||
const target = await User.initUserId(selection.slice(5, -11));
|
||||
await sendMessage(
|
||||
`${user.displayName} backshotted ${target?.displayName}`,
|
||||
msg.messageId,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,37 +1,62 @@
|
||||
import { getUserRecord, updateUserRecord } from "db/dbUser";
|
||||
import items from "items";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import items from "items";
|
||||
import { getUserRecord, updateUserRecord } from "db/dbUser";
|
||||
|
||||
export default new Command({
|
||||
name: 'buyitem',
|
||||
aliases: ['buyitem', 'buy', 'purchase'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage(`Specify the item you'd like to buy`, msg.messageId); return; };
|
||||
const selecteditem = items.get(args[0].toLowerCase());
|
||||
if (!selecteditem) { await sendMessage(`'${args[0]}' is not a valid item`, msg.messageId); 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 totalcost = amount * selecteditem.price;
|
||||
name: "buyitem",
|
||||
aliases: ["buyitem", "buy", "purchase"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage(`Specify the item you'd like to buy`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
const selecteditem = items.get(args[0].toLowerCase());
|
||||
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; };
|
||||
await user.setLock();
|
||||
if (await user.itemLock()) {
|
||||
await sendMessage("Cannot buy item (itemlock)", msg.messageId);
|
||||
return;
|
||||
}
|
||||
await user.setLock();
|
||||
|
||||
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; };
|
||||
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.inventory[selecteditem.name]) userRecord.inventory[selecteditem.name]! += amount
|
||||
else userRecord.inventory[selecteditem.name] = amount;
|
||||
if (userRecord.inventory[selecteditem.name])
|
||||
userRecord.inventory[selecteditem.name]! += amount;
|
||||
else userRecord.inventory[selecteditem.name] = amount;
|
||||
|
||||
userRecord.balance -= totalcost;
|
||||
userRecord.balance -= totalcost;
|
||||
|
||||
await Promise.all([
|
||||
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)
|
||||
]);
|
||||
await Promise.all([
|
||||
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,
|
||||
),
|
||||
]);
|
||||
|
||||
await user.clearLock();
|
||||
}
|
||||
await user.clearLock();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,20 +1,35 @@
|
||||
import { redis } from "lib/redis";
|
||||
import { namedcheers } from "cheers";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { namedcheers } from "cheers";
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
export default new Command({
|
||||
name: 'disablecheer',
|
||||
aliases: ['disablecheer'],
|
||||
usertype: 'moderator',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a cheer to disable', msg.messageId); return; };
|
||||
const selection = namedcheers.get(args[0].toLowerCase());
|
||||
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);
|
||||
}
|
||||
name: "disablecheer",
|
||||
aliases: ["disablecheer"],
|
||||
usertype: "moderator",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a cheer to disable", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const selection = namedcheers.get(args[0].toLowerCase());
|
||||
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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,21 +1,42 @@
|
||||
import { redis } from "lib/redis";
|
||||
import commands from "commands";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
export default new Command({
|
||||
name: 'disablecommand',
|
||||
aliases: ['disablecommand'],
|
||||
usertype: 'moderator',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a command to disable', msg.messageId); return; };
|
||||
const selection = commands.get(args[0].toLowerCase());
|
||||
if (!selection) { 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);
|
||||
}
|
||||
name: "disablecommand",
|
||||
aliases: ["disablecommand"],
|
||||
usertype: "moderator",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a command to disable", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const selection = commands.get(args[0].toLowerCase());
|
||||
if (!selection) {
|
||||
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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,30 +1,55 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { disableRedeem, idMap, namedRedeems, sfxRedeems } from "pointRedeems";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import logger from "lib/logger";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
|
||||
export default new Command({
|
||||
name: 'disableRedeem',
|
||||
aliases: ['disableredeem'],
|
||||
usertype: 'moderator',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage("Please specify a point redemption to disable", msg.messageId); return; };
|
||||
if (args[0] === 'sfx' || args[0] === 'sound') {
|
||||
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;
|
||||
};
|
||||
name: "disableRedeem",
|
||||
aliases: ["disableredeem"],
|
||||
usertype: "moderator",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage(
|
||||
"Please specify a point redemption to disable",
|
||||
msg.messageId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (args[0] === "sfx" || args[0] === "sound") {
|
||||
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]);
|
||||
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 disableRedeem(selection, id!);
|
||||
await sendMessage(`The ${selection.name} point redeem is now disabled`, msg.messageId);
|
||||
}
|
||||
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 disableRedeem(selection, id!);
|
||||
await sendMessage(
|
||||
`The ${selection.name} point redeem is now disabled`,
|
||||
msg.messageId,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,52 +1,81 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { changeBalance } from "lib/changeBalance";
|
||||
import User from "user";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import logger from "lib/logger";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'donate',
|
||||
aliases: ['donate'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a user', msg.messageId); return; };
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); 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]);
|
||||
if (isNaN(amount) || amount < 1) { await sendMessage(`'${args[1]}' is not a valid amount`); return; };
|
||||
name: "donate",
|
||||
aliases: ["donate"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a user", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) {
|
||||
await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
|
||||
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);
|
||||
if (userRecord.balance < amount) { await sendMessage(`You can't give qweribucks you don't have!`, msg.messageId); return; };
|
||||
const userRecord = await getUserRecord(user);
|
||||
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([
|
||||
user.setLock(),
|
||||
target.setLock()
|
||||
]);
|
||||
await Promise.all([user.setLock(), target.setLock()]);
|
||||
|
||||
const data = await Promise.all([
|
||||
await changeBalance(target, targetRecord, amount),
|
||||
await changeBalance(user, userRecord, -amount)
|
||||
]);
|
||||
const data = await Promise.all([
|
||||
await changeBalance(target, targetRecord, amount),
|
||||
await changeBalance(user, userRecord, -amount),
|
||||
]);
|
||||
|
||||
if (data[0] !== false && data[1] !== false) {
|
||||
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);
|
||||
} 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}`);
|
||||
};
|
||||
if (data[0] !== false && data[1] !== false) {
|
||||
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,
|
||||
);
|
||||
} 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([
|
||||
user.clearLock(),
|
||||
target.clearLock()
|
||||
]);
|
||||
}
|
||||
await Promise.all([user.clearLock(), target.clearLock()]);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,20 +1,35 @@
|
||||
import { redis } from "lib/redis";
|
||||
import { namedcheers } from "cheers";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { namedcheers } from "cheers";
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
export default new Command({
|
||||
name: 'enablecheer',
|
||||
aliases: ['enablecheer'],
|
||||
usertype: 'moderator',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a cheer to enable', msg.messageId); return; };
|
||||
const selection = namedcheers.get(args[0].toLowerCase());
|
||||
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);
|
||||
}
|
||||
name: "enablecheer",
|
||||
aliases: ["enablecheer"],
|
||||
usertype: "moderator",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a cheer to enable", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const selection = namedcheers.get(args[0].toLowerCase());
|
||||
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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,20 +1,35 @@
|
||||
import { redis } from "lib/redis";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import commands from "commands";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
export default new Command({
|
||||
name: 'enablecommand',
|
||||
aliases: ['enablecommand'],
|
||||
usertype: 'moderator',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a command to enable', msg.messageId); return; };
|
||||
const selection = commands.get(args[0].toLowerCase());
|
||||
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);
|
||||
}
|
||||
name: "enablecommand",
|
||||
aliases: ["enablecommand"],
|
||||
usertype: "moderator",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a command to enable", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const selection = commands.get(args[0].toLowerCase());
|
||||
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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,29 +1,54 @@
|
||||
import { enableRedeem, idMap, namedRedeems, sfxRedeems } from "pointRedeems";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import logger from "lib/logger";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { enableRedeem, idMap, namedRedeems, sfxRedeems } from "pointRedeems";
|
||||
|
||||
export default new Command({
|
||||
name: 'enableRedeem',
|
||||
aliases: ['enableredeem'],
|
||||
usertype: 'moderator',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage("Please specify a point redemption to enable", msg.messageId); return; };
|
||||
if (args[0] === 'sfx' || args[0] === 'sound') {
|
||||
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 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);
|
||||
}
|
||||
name: "enableRedeem",
|
||||
aliases: ["enableredeem"],
|
||||
usertype: "moderator",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage(
|
||||
"Please specify a point redemption to enable",
|
||||
msg.messageId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (args[0] === "sfx" || args[0] === "sound") {
|
||||
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 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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,13 +2,13 @@ import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { timeout } from "lib/timeout";
|
||||
|
||||
export default new Command({
|
||||
name: 'fakemodme',
|
||||
aliases: ['modme', 'mod'],
|
||||
usertype: 'chatter',
|
||||
execution: async (_msg, user) => {
|
||||
await Promise.all([
|
||||
timeout(user, "NO MODME", 60),
|
||||
sendMessage(`NO MODME COMMAND!!! UltraMad UltraMad UltraMad`)
|
||||
]);
|
||||
}
|
||||
name: "fakemodme",
|
||||
aliases: ["modme", "mod"],
|
||||
usertype: "chatter",
|
||||
execution: async (_msg, user) => {
|
||||
await Promise.all([
|
||||
timeout(user, "NO MODME", 60),
|
||||
sendMessage(`NO MODME COMMAND!!! UltraMad UltraMad UltraMad`),
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getAdmins } from "lib/admins";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'getadmins',
|
||||
aliases: ['getadmins'],
|
||||
usertype: 'chatter',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const admins = await getAdmins()
|
||||
const adminnames: string[] = [];
|
||||
for (const id of admins) {
|
||||
const admin = await User.initUserId(id);
|
||||
adminnames.push(admin?.displayName!);
|
||||
};
|
||||
await sendMessage(`Current admins: ${adminnames.join(', ')}`, msg.messageId);
|
||||
}
|
||||
name: "getadmins",
|
||||
aliases: ["getadmins"],
|
||||
usertype: "chatter",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const admins = await getAdmins();
|
||||
const adminnames: string[] = [];
|
||||
for (const id of admins) {
|
||||
const admin = await User.initUserId(id);
|
||||
adminnames.push(admin?.displayName!);
|
||||
}
|
||||
await sendMessage(
|
||||
`Current admins: ${adminnames.join(", ")}`,
|
||||
msg.messageId,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,17 +1,32 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'getbalance',
|
||||
aliases: ['getbalance', 'balance', 'qbucks', 'qweribucks', 'wallet', 'getwallet'],
|
||||
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);
|
||||
}
|
||||
name: "getbalance",
|
||||
aliases: [
|
||||
"getbalance",
|
||||
"balance",
|
||||
"qbucks",
|
||||
"qweribucks",
|
||||
"wallet",
|
||||
"getwallet",
|
||||
],
|
||||
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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,39 +1,63 @@
|
||||
import { redis } from "lib/redis";
|
||||
import { namedcheers } from "cheers";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { namedcheers } from "cheers";
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
export default new Command({
|
||||
name: 'getcheers',
|
||||
aliases: ['getcheers', 'getcheer'],
|
||||
usertype: 'chatter',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
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; };
|
||||
const disabledcheers = await redis.smembers('disabledcheers');
|
||||
const cheerstrings: string[] = [];
|
||||
name: "getcheers",
|
||||
aliases: ["getcheers", "getcheer"],
|
||||
usertype: "chatter",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
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;
|
||||
}
|
||||
const disabledcheers = await redis.smembers("disabledcheers");
|
||||
const cheerstrings: string[] = [];
|
||||
|
||||
if (args[0].toLowerCase() === "enabled") {
|
||||
for (const [name, cheer] of Array.from(namedcheers.entries())) {
|
||||
if (disabledcheers.includes(name)) continue;
|
||||
cheerstrings.push(`${cheer.amount}: ${name}`);
|
||||
};
|
||||
if (args[0].toLowerCase() === "enabled") {
|
||||
for (const [name, cheer] of Array.from(namedcheers.entries())) {
|
||||
if (disabledcheers.includes(name)) continue;
|
||||
cheerstrings.push(`${cheer.amount}: ${name}`);
|
||||
}
|
||||
|
||||
const last = cheerstrings.pop();
|
||||
if (!last) { await sendMessage("No enabled cheers", msg.messageId); return; };
|
||||
await sendMessage(cheerstrings.length === 0 ? last : cheerstrings.join(', ') + " and " + last, msg.messageId);
|
||||
const last = cheerstrings.pop();
|
||||
if (!last) {
|
||||
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") {
|
||||
for (const [name, cheer] of Array.from(namedcheers.entries())) {
|
||||
if (!disabledcheers.includes(name)) continue;
|
||||
cheerstrings.push(`${cheer.amount}: ${name}`);
|
||||
};
|
||||
|
||||
const last = cheerstrings.pop();
|
||||
if (!last) { await sendMessage("No disabled cheers", msg.messageId); return; };
|
||||
await sendMessage(cheerstrings.length === 0 ? last : cheerstrings.join(', ') + " and " + last, msg.messageId);
|
||||
|
||||
} else await sendMessage('Please specify if you want the enabled or disabled cheers', msg.messageId);
|
||||
}
|
||||
const last = cheerstrings.pop();
|
||||
if (!last) {
|
||||
await sendMessage("No disabled cheers", msg.messageId);
|
||||
return;
|
||||
}
|
||||
await sendMessage(
|
||||
cheerstrings.length === 0
|
||||
? last
|
||||
: `${cheerstrings.join(", ")} and ${last}`,
|
||||
msg.messageId,
|
||||
);
|
||||
} else
|
||||
await sendMessage(
|
||||
"Please specify if you want the enabled or disabled cheers",
|
||||
msg.messageId,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,30 +1,51 @@
|
||||
import { redis } from "lib/redis";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { basecommands } from "commands";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
export default new Command({
|
||||
name: 'getcommands',
|
||||
aliases: ['getcommands', 'getc', 'commands'],
|
||||
usertype: 'chatter',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
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; };
|
||||
const disabledcommands = await redis.smembers('disabledcommands');
|
||||
if (args[0].toLowerCase() === 'enabled') {
|
||||
const commandnames: string[] = [];
|
||||
for (const [name, command] of Array.from(basecommands.entries())) {
|
||||
if (command.usertype !== 'chatter') continue; // Admin only commands should be somewhat hidden
|
||||
if (disabledcommands.includes(name)) continue;
|
||||
commandnames.push(name);
|
||||
};
|
||||
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);
|
||||
}
|
||||
name: "getcommands",
|
||||
aliases: ["getcommands", "getc", "commands"],
|
||||
usertype: "chatter",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
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;
|
||||
}
|
||||
const disabledcommands = await redis.smembers("disabledcommands");
|
||||
if (args[0].toLowerCase() === "enabled") {
|
||||
const commandnames: string[] = [];
|
||||
for (const [name, command] of Array.from(basecommands.entries())) {
|
||||
if (command.usertype !== "chatter") continue; // Admin only commands should be somewhat hidden
|
||||
if (disabledcommands.includes(name)) continue;
|
||||
commandnames.push(name);
|
||||
}
|
||||
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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,31 +1,42 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import items from "items";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
import items from "items";
|
||||
|
||||
export default new Command({
|
||||
name: 'inventory',
|
||||
aliases: ['inv', 'inventory', 'pocket'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
let target: User = user;
|
||||
if (args[0]) {
|
||||
const obj = await User.initUsername(args[0].toLowerCase());
|
||||
if (!obj) { await sendMessage(`User ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||
target = obj;
|
||||
};
|
||||
name: "inventory",
|
||||
aliases: ["inv", "inventory", "pocket"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
let target: User = user;
|
||||
if (args[0]) {
|
||||
const obj = await User.initUsername(args[0].toLowerCase());
|
||||
if (!obj) {
|
||||
await sendMessage(`User ${args[0]} doesn't exist`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
target = obj;
|
||||
}
|
||||
|
||||
const data = await getUserRecord(target);
|
||||
const messagedata: string[] = [];
|
||||
for (const [key, amount] of Object.entries(data.inventory)) {
|
||||
if (amount === 0) continue;
|
||||
const itemselection = items.get(key);
|
||||
messagedata.push(`${itemselection?.prettyName}${amount === 1 ? '' : itemselection?.plural}: ${amount}`);
|
||||
};
|
||||
const data = await getUserRecord(target);
|
||||
const messagedata: string[] = [];
|
||||
for (const [key, amount] of Object.entries(data.inventory)) {
|
||||
if (amount === 0) continue;
|
||||
const itemselection = items.get(key);
|
||||
messagedata.push(
|
||||
`${itemselection?.prettyName}${amount === 1 ? "" : itemselection?.plural}: ${amount}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (messagedata.length === 0) { await sendMessage(`${target.displayName} has no items`, msg.messageId); return; };
|
||||
await sendMessage(`Inventory of ${target.displayName}: ${messagedata.join(', ')}`, msg.messageId);
|
||||
}
|
||||
if (messagedata.length === 0) {
|
||||
await sendMessage(`${target.displayName} has no items`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
await sendMessage(
|
||||
`Inventory of ${target.displayName}: ${messagedata.join(", ")}`,
|
||||
msg.messageId,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,17 +3,20 @@ import { getInvulns } from "lib/invuln";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'getinvulns',
|
||||
aliases: ['getinvulns'],
|
||||
usertype: 'chatter',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const invulns = await getInvulns()
|
||||
const invulnnames: string[] = [];
|
||||
for (const id of invulns) {
|
||||
const invuln = await User.initUserId(id);
|
||||
invulnnames.push(invuln?.displayName!);
|
||||
};
|
||||
await sendMessage(`Current invulnerable chatters: ${invulnnames.join(', ')}`, msg.messageId);
|
||||
}
|
||||
name: "getinvulns",
|
||||
aliases: ["getinvulns"],
|
||||
usertype: "chatter",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const invulns = await getInvulns();
|
||||
const invulnnames: string[] = [];
|
||||
for (const id of invulns) {
|
||||
const invuln = await User.initUserId(id);
|
||||
invulnnames.push(invuln?.displayName!);
|
||||
}
|
||||
await sendMessage(
|
||||
`Current invulnerable chatters: ${invulnnames.join(", ")}`,
|
||||
msg.messageId,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,103 +1,139 @@
|
||||
import { redis } from "lib/redis";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { createGetLootRecord } from "db/dbGetLoot";
|
||||
import { getUserRecord, updateUserRecord } from "db/dbUser";
|
||||
import itemMap, { type inventory, type items } from "items";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { buildTimeString } from "lib/dateManager";
|
||||
import { timeout } from "lib/timeout";
|
||||
import { isInvuln, removeInvuln } from "lib/invuln";
|
||||
import { redis } from "lib/redis";
|
||||
import { timeout } from "lib/timeout";
|
||||
import { streamerUsers } from "main";
|
||||
import { createGetLootRecord } from "db/dbGetLoot";
|
||||
import { playAlert } from "web/alerts/serverFunctions";
|
||||
|
||||
const COOLDOWN = 10 * 60; // 10 mins (s)
|
||||
|
||||
export default new Command({
|
||||
name: 'getloot',
|
||||
aliases: ['getloot', 'dig', 'loot', 'mine'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
if (!await redis.exists('streamIsLive')) { await sendMessage(`No loot while stream is offline`, msg.messageId); return; };
|
||||
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); };
|
||||
if (await user.itemLock()) { await sendMessage(`Cannot get loot (itemlock)`, msg.messageId); return; };
|
||||
await user.setLock();
|
||||
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;
|
||||
};
|
||||
};
|
||||
name: "getloot",
|
||||
aliases: ["getloot", "dig", "loot", "mine"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg, user) => {
|
||||
if (!(await redis.exists("streamIsLive"))) {
|
||||
await sendMessage(`No loot while stream is offline`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (await user.itemLock()) {
|
||||
await sendMessage(`Cannot get loot (itemlock)`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
await user.setLock();
|
||||
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.expire(`user:${user.id}:lootboxcooldown`, COOLDOWN);
|
||||
await redis.set(`user:${user.id}:lootboxcooldown`, "1");
|
||||
await redis.expire(`user:${user.id}:lootboxcooldown`, COOLDOWN);
|
||||
|
||||
if (!await redis.exists(`user:${user.id}:subbed`) && Math.random() < 0.1) {
|
||||
await Promise.all([
|
||||
user.clearLock(),
|
||||
updateUserRecord(user, userData),
|
||||
timeout(user, "THE LOOTBOX WAS TRAPPED!!!", 60),
|
||||
sendMessage(`wybuh wybuh ${user.displayName.toUpperCase()} FOUND A TRAPPED LOOTBOX!!! wybuh wybuh`),
|
||||
playAlert({
|
||||
name: 'grenadeExplosion',
|
||||
user: 'trapped lootbox',
|
||||
target: user.displayName
|
||||
})
|
||||
]);
|
||||
return;
|
||||
};
|
||||
const gainedqbucks = Math.floor(Math.random() * 100) + 50; // range from 50 to 150
|
||||
userData.balance += gainedqbucks;
|
||||
if (
|
||||
!(await redis.exists(`user:${user.id}:subbed`)) &&
|
||||
Math.random() < 0.1
|
||||
) {
|
||||
await Promise.all([
|
||||
user.clearLock(),
|
||||
updateUserRecord(user, userData),
|
||||
timeout(user, "THE LOOTBOX WAS TRAPPED!!!", 60),
|
||||
sendMessage(
|
||||
`wybuh wybuh ${user.displayName.toUpperCase()} FOUND A TRAPPED LOOTBOX!!! wybuh wybuh`,
|
||||
),
|
||||
playAlert({
|
||||
name: "grenadeExplosion",
|
||||
user: "trapped lootbox",
|
||||
target: user.displayName,
|
||||
}),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
const gainedqbucks = Math.floor(Math.random() * 100) + 50; // range from 50 to 150
|
||||
userData.balance += gainedqbucks;
|
||||
|
||||
const itemDiff: inventory = {
|
||||
grenade: 0,
|
||||
blaster: 0,
|
||||
tnt: 0,
|
||||
silverbullet: 0
|
||||
};
|
||||
const itemDiff: inventory = {
|
||||
grenade: 0,
|
||||
blaster: 0,
|
||||
tnt: 0,
|
||||
silverbullet: 0,
|
||||
};
|
||||
|
||||
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.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.silverbullet! += 1; // 1 in 50
|
||||
};
|
||||
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.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.silverbullet! += 1; // 1 in 50
|
||||
}
|
||||
|
||||
for (const [item, amount] of Object.entries(itemDiff) as [items, number][]) {
|
||||
if (userData.inventory[item]) userData.inventory[item] += amount;
|
||||
else userData.inventory[item] = amount;
|
||||
};
|
||||
for (const [item, amount] of Object.entries(itemDiff) as [
|
||||
items,
|
||||
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)) {
|
||||
if (amount === 0) continue;
|
||||
const selection = itemMap.get(item);
|
||||
if (!selection) continue;
|
||||
itemstrings.push(`${amount} ${selection.prettyName + (amount === 1 ? '' : selection.plural)}`);
|
||||
};
|
||||
for (const [item, amount] of Object.entries(itemDiff)) {
|
||||
if (amount === 0) continue;
|
||||
const selection = itemMap.get(item);
|
||||
if (!selection) continue;
|
||||
itemstrings.push(
|
||||
`${amount} ${selection.prettyName + (amount === 1 ? "" : selection.plural)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const last = itemstrings.pop();
|
||||
const itemstring = itemstrings.length === 0 ? last : itemstrings.join(', ') + " and " + last;
|
||||
const message = `You got ${itemstring}`;
|
||||
const last = itemstrings.pop();
|
||||
const itemstring =
|
||||
itemstrings.length === 0 ? last : `${itemstrings.join(", ")} and ${last}`;
|
||||
const message = `You got ${itemstring}`;
|
||||
|
||||
await Promise.all([
|
||||
updateUserRecord(user, userData),
|
||||
sendMessage(message, msg.messageId),
|
||||
createGetLootRecord(user, gainedqbucks, itemDiff, 'getloot'),
|
||||
user.clearLock()
|
||||
]);
|
||||
}
|
||||
await Promise.all([
|
||||
updateUserRecord(user, userData),
|
||||
sendMessage(message, msg.messageId),
|
||||
createGetLootRecord(user, gainedqbucks, itemDiff, "getloot"),
|
||||
user.clearLock(),
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { itemObjectArray } from "items";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
|
||||
export default new Command({
|
||||
name: 'getprices',
|
||||
aliases: ['getprices', 'prices', 'shop'],
|
||||
usertype: 'chatter',
|
||||
execution: async msg => {
|
||||
const txt = itemObjectArray.toSorted((a, b) => a.price - b.price).map(item => `${item.prettyName}: ${item.price}`);
|
||||
await sendMessage(`Prices: ${txt.join(' | ')}`, msg.messageId);
|
||||
}
|
||||
name: "getprices",
|
||||
aliases: ["getprices", "prices", "shop"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg) => {
|
||||
const txt = itemObjectArray
|
||||
.toSorted((a, b) => a.price - b.price)
|
||||
.map((item) => `${item.prettyName}: ${item.price}`);
|
||||
await sendMessage(`Prices: ${txt.join(" | ")}`, msg.messageId);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,21 +1,42 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { buildTimeString } from "lib/dateManager";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
import { timeoutDuration } from "lib/timeout";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'gettimeout',
|
||||
aliases: ['gett', 'gettimeout', 'releasetime'],
|
||||
usertype: 'chatter',
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; };
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||
const data = await 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);
|
||||
}
|
||||
name: "gettimeout",
|
||||
aliases: ["gett", "gettimeout", "releasetime"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a target", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) {
|
||||
await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
const data = await 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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,52 +1,87 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import items, { changeItemCount } from "items";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import logger from "lib/logger";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
import logger from "lib/logger";
|
||||
|
||||
export default new Command({
|
||||
name: 'give',
|
||||
aliases: ['give'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a user', msg.messageId); return; };
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||
if (target.username === user.username) { await sendMessage("You can't give yourself items", 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]);
|
||||
if (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; };
|
||||
name: "give",
|
||||
aliases: ["give"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a user", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) {
|
||||
await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
if (target.username === user.username) {
|
||||
await sendMessage("You can't give yourself items", 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([
|
||||
user.setLock(),
|
||||
target.setLock()
|
||||
]);
|
||||
await Promise.all([user.setLock(), target.setLock()]);
|
||||
|
||||
const data = await Promise.all([
|
||||
await changeItemCount(target, targetRecord, item.name, amount),
|
||||
await changeItemCount(user, userRecord, item.name, -amount)
|
||||
]);
|
||||
const data = await Promise.all([
|
||||
await changeItemCount(target, targetRecord, item.name, amount),
|
||||
await changeItemCount(user, userRecord, item.name, -amount),
|
||||
]);
|
||||
|
||||
if (data[0] !== false && data[1] !== false) {
|
||||
const tempdata = data[0];
|
||||
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);
|
||||
} else {
|
||||
// TODO: Rewrite this section
|
||||
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"}`);
|
||||
};
|
||||
await user.clearLock();
|
||||
await target.clearLock();
|
||||
}
|
||||
if (data[0] !== false && data[1] !== false) {
|
||||
const tempdata = data[0];
|
||||
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,
|
||||
);
|
||||
} else {
|
||||
// TODO: Rewrite this section
|
||||
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"}`,
|
||||
);
|
||||
}
|
||||
await user.clearLock();
|
||||
await target.clearLock();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
import { Command } from 'lib/commandUtils';
|
||||
import { readdir } from 'node:fs/promises';
|
||||
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 basecommands = new Map<string, Command>; // This map has all command names mapped to commands (one-to-one) (no items)
|
||||
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 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);
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.ts')) continue;
|
||||
if (file === import.meta.file) continue;
|
||||
const command: Command = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default);
|
||||
basecommands.set(command.name, command);
|
||||
for (const alias of command.aliases) {
|
||||
commands.set(alias, command); // Since it's not a primitive type the map is filled with references to the command, not the actual object
|
||||
};
|
||||
for (const alias of command.specialaliases) {
|
||||
specialAliasCommands.set(alias, command);
|
||||
};
|
||||
};
|
||||
if (!file.endsWith(".ts")) continue;
|
||||
if (file === import.meta.file) continue;
|
||||
const command: Command = await import(
|
||||
`${import.meta.dir}/${file.slice(0, -3)}`
|
||||
).then((a) => a.default);
|
||||
basecommands.set(command.name, command);
|
||||
for (const alias of command.aliases) {
|
||||
commands.set(alias, command); // Since it's not a primitive type the map is filled with references to the command, not the actual object
|
||||
}
|
||||
for (const alias of command.specialaliases) {
|
||||
specialAliasCommands.set(alias, command);
|
||||
}
|
||||
}
|
||||
|
||||
import items, { specialAliasItems } 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)) {
|
||||
specialAliasCommands.set(alias, item);
|
||||
};
|
||||
specialAliasCommands.set(alias, item);
|
||||
}
|
||||
|
||||
export default commands;
|
||||
export { specialAliasCommands, basecommands };
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import items from "items";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
|
||||
export default new Command({
|
||||
name: 'iteminfo',
|
||||
aliases: ['iteminfo', 'itemhelp', 'info'],
|
||||
usertype: 'chatter',
|
||||
execution: async msg => {
|
||||
const messagequery = parseCommandArgs(msg.messageText).join(' ');
|
||||
if (!messagequery) { await sendMessage('Please specify an item you would like to get info about', 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);
|
||||
}
|
||||
name: "iteminfo",
|
||||
aliases: ["iteminfo", "itemhelp", "info"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg) => {
|
||||
const messagequery = parseCommandArgs(msg.messageText).join(" ");
|
||||
if (!messagequery) {
|
||||
await sendMessage(
|
||||
"Please specify an item you would like to get info about",
|
||||
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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,17 +3,29 @@ import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'itemlock',
|
||||
aliases: ['itemlock'],
|
||||
usertype: 'moderator',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a chatter to toggle the lock for', 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);
|
||||
}
|
||||
name: "itemlock",
|
||||
aliases: ["itemlock"],
|
||||
usertype: "moderator",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage(
|
||||
"Please specify a chatter to toggle the lock for",
|
||||
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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,36 +1,40 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getKDLeaderboard } from "db/dbUser";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import User from "user";
|
||||
|
||||
type KD = { user: User; kd: number; };
|
||||
type KD = { user: User; kd: number };
|
||||
|
||||
export default new Command({
|
||||
name: 'monthlykdleaderboard',
|
||||
aliases: ['monthlyleaderboard', 'kdleaderboard', 'leaderboard'],
|
||||
usertype: 'chatter',
|
||||
execution: async msg => {
|
||||
const monthdata = new Date().toISOString().slice(0, 7);
|
||||
name: "monthlykdleaderboard",
|
||||
aliases: ["monthlyleaderboard", "kdleaderboard", "leaderboard"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg) => {
|
||||
const monthdata = new Date().toISOString().slice(0, 7);
|
||||
|
||||
const rawKD = await getKDLeaderboard(monthdata);
|
||||
if (rawKD.length === 0) {
|
||||
await sendMessage(`No users on leaderboard yet!`, msg.messageId);
|
||||
return;
|
||||
};
|
||||
const rawKD = await getKDLeaderboard(monthdata);
|
||||
if (rawKD.length === 0) {
|
||||
await sendMessage(`No users on leaderboard yet!`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
const userKDs: KD[] = [];
|
||||
await Promise.all(rawKD.map(async userRecord => {
|
||||
const user = await User.initUserId(userRecord.userId.toString());
|
||||
if (!user) return;
|
||||
userKDs.push({ user, kd: userRecord.KD })
|
||||
}));
|
||||
const userKDs: KD[] = [];
|
||||
await Promise.all(
|
||||
rawKD.map(async (userRecord) => {
|
||||
const user = await User.initUserId(userRecord.userId.toString());
|
||||
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[] = [];
|
||||
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)}`);
|
||||
};
|
||||
const txt: string[] = [];
|
||||
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)}`,
|
||||
);
|
||||
}
|
||||
|
||||
await sendMessage(`Monthly leaderboard: ${txt.join(' | ')}`, msg.messageId);
|
||||
}
|
||||
await sendMessage(`Monthly leaderboard: ${txt.join(" | ")}`, msg.messageId);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,33 +1,45 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getTimeoutStats, getItemStats } from "lib/getStats";
|
||||
import { getItemStats, getTimeoutStats } from "lib/getStats";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'monthlystats',
|
||||
aliases: ['stats', 'monthlystats'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
let target: User | null = user;
|
||||
if (args[0]) {
|
||||
target = await User.initUsername(args[0]);
|
||||
if (!target) { await sendMessage(`User ${args[0]} doesn't exist!`, msg.messageId); return; };
|
||||
};
|
||||
name: "monthlystats",
|
||||
aliases: ["stats", "monthlystats"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg, user) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
let target: User | null = user;
|
||||
if (args[0]) {
|
||||
target = await User.initUsername(args[0]);
|
||||
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)]);
|
||||
if (!timeout || !item) { await sendMessage(`ERROR: Something went wrong!`, msg.messageId); return; };
|
||||
const [timeout, item] = await Promise.all([
|
||||
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}:
|
||||
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},
|
||||
TNT exploded: ${item.tnt}.
|
||||
Silver bullets fired: ${timeout.shot.silverbullet},
|
||||
Silver bullets taken: ${timeout.hit.silverbullet}.
|
||||
`, msg.messageId);
|
||||
}
|
||||
`,
|
||||
msg.messageId,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { getBalanceLeaderboard } from "db/dbUser";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'qbucksleaderboard',
|
||||
aliases: ['qbucksleaderboard', 'baltop', 'moneyleaderboard'],
|
||||
usertype: 'chatter',
|
||||
execution: async msg => {
|
||||
const data = await getBalanceLeaderboard();
|
||||
if (!data) return;
|
||||
name: "qbucksleaderboard",
|
||||
aliases: ["qbucksleaderboard", "baltop", "moneyleaderboard"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg) => {
|
||||
const data = await getBalanceLeaderboard();
|
||||
if (!data) return;
|
||||
|
||||
let index = 1;
|
||||
const txt: string[] = [];
|
||||
for (const userRecord of data) {
|
||||
if (userRecord.balance === 0) continue;
|
||||
const user = await User.initUserId(userRecord.id.toString());
|
||||
if (!user) continue;
|
||||
txt.push(`${index}. ${user.displayName}: ${userRecord.balance}`);
|
||||
index++;
|
||||
};
|
||||
let index = 1;
|
||||
const txt: string[] = [];
|
||||
for (const userRecord of data) {
|
||||
if (userRecord.balance === 0) continue;
|
||||
const user = await User.initUserId(userRecord.id.toString());
|
||||
if (!user) continue;
|
||||
txt.push(`${index}. ${user.displayName}: ${userRecord.balance}`);
|
||||
index++;
|
||||
}
|
||||
|
||||
await sendMessage(`Balance leaderboard: ${txt.join(' | ')}`, msg.messageId);
|
||||
}
|
||||
await sendMessage(`Balance leaderboard: ${txt.join(" | ")}`, msg.messageId);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,29 +4,40 @@ import { streamerId } from "main";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'racetime',
|
||||
aliases: ['racetime', 'raceroom'],
|
||||
usertype: 'chatter',
|
||||
execution: async msg => {
|
||||
try { // this might be some of the worst http code ever
|
||||
const streamer = await User.initUserId(streamerId);
|
||||
name: "racetime",
|
||||
aliases: ["racetime", "raceroom"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg) => {
|
||||
try {
|
||||
// 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);
|
||||
if (races.current_races.length < 1) { await sendMessage(`No Super Metroid Randomizer races active`, msg.messageId); return; };
|
||||
const races = await fetch(`https://racetime.gg/smr/data`).then(
|
||||
(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) {
|
||||
const data = await fetch(`https://racetime.gg${race.data_url}`).then(a => a.json() as any);
|
||||
for (const racer of data.entrants) {
|
||||
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);
|
||||
logger.err(err as string);
|
||||
};
|
||||
}
|
||||
for (const race of races.current_races) {
|
||||
const data = await fetch(`https://racetime.gg${race.data_url}`).then(
|
||||
(a) => a.json() as any,
|
||||
);
|
||||
for (const racer of data.entrants) {
|
||||
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);
|
||||
logger.err(err as string);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,19 +4,19 @@ import { streamerId } from "main";
|
||||
import { playAlert } from "web/alerts/serverFunctions";
|
||||
|
||||
export default new Command({
|
||||
name: 'randomchatter',
|
||||
aliases: ['randomchatter'],
|
||||
usertype: 'moderator',
|
||||
execution: async (msg) => {
|
||||
const data = await api.chat.getChatters(streamerId).then(a => a.data);
|
||||
const target = data[Math.floor(Math.random() * data.length)];
|
||||
await playAlert({
|
||||
name: 'blastinRoulette',
|
||||
user: msg.chatterName,
|
||||
targets: data.map(a => a.userDisplayName),
|
||||
finaltarget: target?.userDisplayName
|
||||
});
|
||||
await new Promise((res, _) => setTimeout(res, 4000));
|
||||
await sendMessage(`${target?.userDisplayName}`, msg.messageId);
|
||||
}
|
||||
name: "randomchatter",
|
||||
aliases: ["randomchatter"],
|
||||
usertype: "moderator",
|
||||
execution: async (msg) => {
|
||||
const data = await api.chat.getChatters(streamerId).then((a) => a.data);
|
||||
const target = data[Math.floor(Math.random() * data.length)];
|
||||
await playAlert({
|
||||
name: "blastinRoulette",
|
||||
user: msg.chatterName,
|
||||
targets: data.map((a) => a.userDisplayName),
|
||||
finaltarget: target?.userDisplayName,
|
||||
});
|
||||
await new Promise((res, _) => setTimeout(res, 4000));
|
||||
await sendMessage(`${target?.userDisplayName}`, msg.messageId);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,22 +1,39 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { streamerUsers } from "main";
|
||||
import { removeAdmin } from "lib/admins";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { streamerUsers } from "main";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'removeadmin',
|
||||
aliases: ['removeadmin'],
|
||||
usertype: 'streamer',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; };
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||
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);
|
||||
}
|
||||
name: "removeadmin",
|
||||
aliases: ["removeadmin"],
|
||||
usertype: "streamer",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a target", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) {
|
||||
await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,22 +1,38 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { streamerUsers } from "main";
|
||||
import { redis } from "lib/redis";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { redis } from "lib/redis";
|
||||
import { streamerUsers } from "main";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'removebot',
|
||||
aliases: ['removebot'],
|
||||
usertype: 'streamer',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; };
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||
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);
|
||||
}
|
||||
name: "removebot",
|
||||
aliases: ["removebot"],
|
||||
usertype: "streamer",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a target", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) {
|
||||
await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,22 +1,42 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { streamerUsers } from "main";
|
||||
import { removeInvuln } from "lib/invuln";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { streamerUsers } from "main";
|
||||
import User from "user";
|
||||
|
||||
export default new Command({
|
||||
name: 'removeinvuln',
|
||||
aliases: ['removeinvuln'],
|
||||
usertype: 'moderator',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; };
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||
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);
|
||||
}
|
||||
name: "removeinvuln",
|
||||
aliases: ["removeinvuln"],
|
||||
usertype: "moderator",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await sendMessage("Please specify a target", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) {
|
||||
await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId);
|
||||
return;
|
||||
}
|
||||
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,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,21 +5,24 @@ import { timeout } from "lib/timeout";
|
||||
const barrelCount = 6;
|
||||
|
||||
export default new Command({
|
||||
name: 'roulette',
|
||||
aliases: ['roulette'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
if (!await redis.exists('rouletteCount')) await redis.set('rouletteCount', "0");
|
||||
const currentChamber = Number(await redis.get('rouletteCount'));
|
||||
const shot = Math.random() < 1 / (barrelCount - currentChamber);
|
||||
if (!shot) await Promise.all([
|
||||
redis.incr('rouletteCount'),
|
||||
sendMessage("SWEAT Click SWEAT", msg.messageId)
|
||||
]);
|
||||
else await Promise.all([
|
||||
redis.set('rouletteCount', "0"),
|
||||
sendMessage("wybuh BANG!! wybuh"),
|
||||
timeout(user, "You lost at russian roulette!", 5 * 60)
|
||||
]);
|
||||
}
|
||||
name: "roulette",
|
||||
aliases: ["roulette"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg, user) => {
|
||||
if (!(await redis.exists("rouletteCount")))
|
||||
await redis.set("rouletteCount", "0");
|
||||
const currentChamber = Number(await redis.get("rouletteCount"));
|
||||
const shot = Math.random() < 1 / (barrelCount - currentChamber);
|
||||
if (!shot)
|
||||
await Promise.all([
|
||||
redis.incr("rouletteCount"),
|
||||
sendMessage("SWEAT Click SWEAT", msg.messageId),
|
||||
]);
|
||||
else
|
||||
await Promise.all([
|
||||
redis.set("rouletteCount", "0"),
|
||||
sendMessage("wybuh BANG!! wybuh"),
|
||||
timeout(user, "You lost at russian roulette!", 5 * 60),
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,23 +2,28 @@ import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { timeout } from "lib/timeout";
|
||||
|
||||
export default new Command({
|
||||
name: 'seiso',
|
||||
aliases: ['seiso'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
const rand = Math.floor(Math.random() * 101);
|
||||
if (rand > 75) await sendMessage(`${rand}% seiso YAAAA`, msg.messageId);
|
||||
else if (rand === 67) await Promise.all([
|
||||
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 > 30) await sendMessage(`${rand}% seiso SWEAT`, msg.messageId);
|
||||
else if (rand > 10) await sendMessage(`${rand}% seiso catErm`, msg.messageId);
|
||||
else await Promise.all([
|
||||
sendMessage(`${rand}% seiso RIPBOZO`),
|
||||
timeout(user, 'TOO YABAI!', 60)
|
||||
]);
|
||||
}
|
||||
name: "seiso",
|
||||
aliases: ["seiso"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg, user) => {
|
||||
const rand = Math.floor(Math.random() * 101);
|
||||
if (rand > 75) await sendMessage(`${rand}% seiso YAAAA`, msg.messageId);
|
||||
else if (rand === 67)
|
||||
await Promise.all([
|
||||
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 > 30)
|
||||
await sendMessage(`${rand}% seiso SWEAT`, msg.messageId);
|
||||
else if (rand > 10)
|
||||
await sendMessage(`${rand}% seiso catErm`, msg.messageId);
|
||||
else
|
||||
await Promise.all([
|
||||
sendMessage(`${rand}% seiso RIPBOZO`),
|
||||
timeout(user, "TOO YABAI!", 60),
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
import { redis } from "lib/redis";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { isAdmin } from "lib/admins";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
export default new Command({
|
||||
name: 'stacking',
|
||||
aliases: ['stacking'],
|
||||
usertype: 'chatter',
|
||||
disableable: false,
|
||||
execution: async msg => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0] || !await isAdmin(msg.chatterId)) { await sendMessage(`Timeout stacking is currently ${await redis.exists('timeoutStacking') ? "on" : "off"}`, msg.messageId); return; };
|
||||
// Only admins can reach this part of code
|
||||
switch (args[0]) {
|
||||
case 'enable':
|
||||
case 'on':
|
||||
await redis.set('timeoutStacking', '1');
|
||||
await sendMessage('Timeout stacking is now on')
|
||||
break;
|
||||
case 'disable':
|
||||
case 'off':
|
||||
await redis.del('timeoutStacking');
|
||||
await sendMessage('Timeout stacking is now off')
|
||||
break;
|
||||
};
|
||||
}
|
||||
name: "stacking",
|
||||
aliases: ["stacking"],
|
||||
usertype: "chatter",
|
||||
disableable: false,
|
||||
execution: async (msg) => {
|
||||
const args = parseCommandArgs(msg.messageText);
|
||||
if (!args[0] || !(await isAdmin(msg.chatterId))) {
|
||||
await sendMessage(
|
||||
`Timeout stacking is currently ${(await redis.exists("timeoutStacking")) ? "on" : "off"}`,
|
||||
msg.messageId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Only admins can reach this part of code
|
||||
switch (args[0]) {
|
||||
case "enable":
|
||||
case "on":
|
||||
await redis.set("timeoutStacking", "1");
|
||||
await sendMessage("Timeout stacking is now on");
|
||||
break;
|
||||
case "disable":
|
||||
case "off":
|
||||
await redis.del("timeoutStacking");
|
||||
await sendMessage("Timeout stacking is now off");
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { handleCheer } from "events/message";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
|
||||
export default new Command({
|
||||
name: 'testcheer',
|
||||
aliases: ['testcheer'],
|
||||
usertype: 'streamer',
|
||||
disableable: false,
|
||||
execution: async (msg, user) => {
|
||||
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 (isNaN(parseInt(args[0]))) { 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);
|
||||
}
|
||||
name: "testcheer",
|
||||
aliases: ["testcheer"],
|
||||
usertype: "streamer",
|
||||
disableable: false,
|
||||
execution: async (msg, user) => {
|
||||
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 (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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 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({
|
||||
name: 'use',
|
||||
aliases: ['use'],
|
||||
usertype: 'chatter',
|
||||
disableable: false,
|
||||
specialaliases: ['i'],
|
||||
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 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; };
|
||||
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);
|
||||
}
|
||||
name: "use",
|
||||
aliases: ["use"],
|
||||
usertype: "chatter",
|
||||
disableable: false,
|
||||
specialaliases: ["i"],
|
||||
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 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;
|
||||
}
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { redis } from "lib/redis";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
export default new Command({
|
||||
name: 'vulnchatters',
|
||||
aliases: ['vulnchatters', 'vulnc', 'vc'],
|
||||
usertype: 'chatter',
|
||||
execution: async msg => {
|
||||
const data = await redis.keys('user:*:vulnerable');
|
||||
const one = data.length === 1;
|
||||
await sendMessage(`There ${one ? 'is' : 'are'} ${data.length} vulnerable chatter${one ? '' : 's'}`, msg.messageId);
|
||||
}
|
||||
name: "vulnchatters",
|
||||
aliases: ["vulnchatters", "vulnc", "vc"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg) => {
|
||||
const data = await redis.keys("user:*:vulnerable");
|
||||
const one = data.length === 1;
|
||||
await sendMessage(
|
||||
`There ${one ? "is" : "are"} ${data.length} vulnerable chatter${one ? "" : "s"}`,
|
||||
msg.messageId,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,22 +3,24 @@ import { timeout } from "lib/timeout";
|
||||
|
||||
// Remake of the !yabai command in ttv/kiara_tv
|
||||
export default new Command({
|
||||
name: 'yabai',
|
||||
aliases: ['yabai', 'goon'],
|
||||
usertype: 'chatter',
|
||||
execution: async (msg, user) => {
|
||||
const rand = Math.floor(Math.random() * 101);
|
||||
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 ok`, msg.messageId);
|
||||
else if (rand === 67) await Promise.all([
|
||||
sendMessage(`KOKPEG 67 KOKPEG`),
|
||||
timeout(user, 'SIX SEVEN', 67)
|
||||
])
|
||||
else if (rand < 90) sendMessage(`${rand}% yabai AINTNOWAY`, msg.messageId);
|
||||
else await Promise.all([
|
||||
sendMessage(`${msg.chatterDisplayName} is ${rand}% yabai CAUGHT`),
|
||||
timeout(user, "TOO YABAI!", 60)
|
||||
]);
|
||||
}
|
||||
name: "yabai",
|
||||
aliases: ["yabai", "goon"],
|
||||
usertype: "chatter",
|
||||
execution: async (msg, user) => {
|
||||
const rand = Math.floor(Math.random() * 101);
|
||||
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 ok`, msg.messageId);
|
||||
else if (rand === 67)
|
||||
await Promise.all([
|
||||
sendMessage(`KOKPEG 67 KOKPEG`),
|
||||
timeout(user, "SIX SEVEN", 67),
|
||||
]);
|
||||
else if (rand < 90) sendMessage(`${rand}% yabai AINTNOWAY`, msg.messageId);
|
||||
else
|
||||
await Promise.all([
|
||||
sendMessage(`${msg.chatterDisplayName} is ${rand}% yabai CAUGHT`),
|
||||
timeout(user, "TOO YABAI!", 60),
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,22 +4,28 @@ import { users } from "db/schema";
|
||||
import logger from "lib/logger";
|
||||
|
||||
export async function connectionCheck() {
|
||||
let pgstatus = false;
|
||||
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
|
||||
pgstatus = true;
|
||||
} catch { };
|
||||
const tempclient = new RedisClient(undefined, {
|
||||
connectionTimeout: 200,
|
||||
maxRetries: 1,
|
||||
});
|
||||
let redisstatus = false;
|
||||
try {
|
||||
await tempclient.connect();
|
||||
redisstatus = true;
|
||||
} catch { };
|
||||
logger.info(`Currently using the "${process.env.NODE_ENV ?? "production"}" database`);
|
||||
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);
|
||||
};
|
||||
let pgstatus = false;
|
||||
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
|
||||
pgstatus = true;
|
||||
} catch {}
|
||||
const tempclient = new RedisClient(undefined, {
|
||||
connectionTimeout: 200,
|
||||
maxRetries: 1,
|
||||
});
|
||||
let redisstatus = false;
|
||||
try {
|
||||
await tempclient.connect();
|
||||
redisstatus = true;
|
||||
} catch {}
|
||||
logger.info(
|
||||
`Currently using the "${process.env.NODE_ENV ?? "production"}" database`,
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@ export const password = process.env.POSTGRES_PASSWORD ?? "";
|
||||
export const database = process.env.POSTGRES_DB ?? "";
|
||||
export const url = `postgresql://${user}:${password}@${host}/${database}`;
|
||||
|
||||
import { drizzle } from 'drizzle-orm/bun-sql';
|
||||
export default drizzle(url, { schema })
|
||||
import { drizzle } from "drizzle-orm/bun-sql";
|
||||
export default drizzle(url, { schema });
|
||||
|
||||
@@ -1,32 +1,55 @@
|
||||
import db from "db/connection";
|
||||
import User from "user";
|
||||
import { anivTimeouts } from "db/schema";
|
||||
import { type anivBots } from "lib/handleAnivMessage";
|
||||
import { count, eq, and } from "drizzle-orm";
|
||||
import { and, count, eq } from "drizzle-orm";
|
||||
import type { anivBots } from "lib/handleAnivMessage";
|
||||
import type User from "user";
|
||||
|
||||
/** To create a dodge record, set the duration to 0 */
|
||||
export async function createAnivTimeoutRecord(message: string, anivBot: anivBots, user: User, duration: number) {
|
||||
await db.insert(anivTimeouts).values({
|
||||
message,
|
||||
anivBot,
|
||||
user: parseInt(user.id),
|
||||
duration,
|
||||
timeout: duration !== 0
|
||||
});
|
||||
};
|
||||
export async function createAnivTimeoutRecord(
|
||||
message: string,
|
||||
anivBot: anivBots,
|
||||
user: User,
|
||||
duration: number,
|
||||
) {
|
||||
await db.insert(anivTimeouts).values({
|
||||
message,
|
||||
anivBot,
|
||||
user: parseInt(user.id, 10),
|
||||
duration,
|
||||
timeout: duration !== 0,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAnivTimeouts(user: User) {
|
||||
let [dodge, dead] = await Promise.all([
|
||||
db.select({
|
||||
dodge: count()
|
||||
}).from(anivTimeouts).where(and(eq(anivTimeouts.user, parseInt(user.id)), eq(anivTimeouts.timeout, false))).then(a => a[0]?.dodge),
|
||||
db.select({
|
||||
dead: count()
|
||||
}).from(anivTimeouts).where(and(eq(anivTimeouts.user, parseInt(user.id)), eq(anivTimeouts.timeout, true))).then(a => a[0]?.dead)
|
||||
]);
|
||||
let [dodge, dead] = await Promise.all([
|
||||
db
|
||||
.select({
|
||||
dodge: count(),
|
||||
})
|
||||
.from(anivTimeouts)
|
||||
.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 (!dead) dead = 0;
|
||||
if (!dodge) dodge = 0;
|
||||
if (!dead) dead = 0;
|
||||
|
||||
return { dodge, dead };
|
||||
};
|
||||
return { dodge, dead };
|
||||
}
|
||||
|
||||
@@ -4,25 +4,31 @@ import { auth } from "db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export async function createAuthRecord(token: AccessToken, userId: string) {
|
||||
await db.insert(auth).values({
|
||||
id: parseInt(userId),
|
||||
accesstoken: token
|
||||
});
|
||||
};
|
||||
await db.insert(auth).values({
|
||||
id: parseInt(userId, 10),
|
||||
accesstoken: token,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAuthRecord(userId: string, requiredIntents: string[]) {
|
||||
const data = await db.query.auth.findFirst({
|
||||
where: eq(auth.id, parseInt(userId))
|
||||
});
|
||||
if (!data) return undefined;
|
||||
if (!requiredIntents.every(intent => data.accesstoken.scope.includes(intent))) return undefined;
|
||||
return { accesstoken: data.accesstoken };
|
||||
};
|
||||
const data = await db.query.auth.findFirst({
|
||||
where: eq(auth.id, parseInt(userId, 10)),
|
||||
});
|
||||
if (!data) return undefined;
|
||||
if (
|
||||
!requiredIntents.every((intent) => data.accesstoken.scope.includes(intent))
|
||||
)
|
||||
return undefined;
|
||||
return { accesstoken: data.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> {
|
||||
await db.delete(auth).where(eq(auth.id, parseInt(userId)));
|
||||
};
|
||||
await db.delete(auth).where(eq(auth.id, parseInt(userId, 10)));
|
||||
}
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
import db from "db/connection";
|
||||
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 User from "user";
|
||||
import type User from "user";
|
||||
|
||||
export async function createCheerEventRecord(user: User, cheer: items): Promise<void> {
|
||||
await db.insert(cheerEvents).values({ user: parseInt(user.id), event: cheer });
|
||||
};
|
||||
export async function createCheerEventRecord(
|
||||
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) {
|
||||
let condition: SQL<unknown> | undefined = eq(cheerEvents.user, parseInt(user.id));
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(condition, between(cheerEvents.created, new Date(begin), new Date(end)));
|
||||
};
|
||||
const data = await db.select().from(cheerEvents).where(condition);
|
||||
return data;
|
||||
};
|
||||
let condition: SQL<unknown> | undefined = eq(
|
||||
cheerEvents.user,
|
||||
parseInt(user.id, 10),
|
||||
);
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(
|
||||
condition,
|
||||
between(cheerEvents.created, new Date(begin), new Date(end)),
|
||||
);
|
||||
}
|
||||
const data = await db.select().from(cheerEvents).where(condition);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
import db from "db/connection";
|
||||
import { cheers } from "db/schema";
|
||||
import User from "user";
|
||||
import { and, between, eq, SQL } from "drizzle-orm";
|
||||
import { and, between, eq, type SQL } from "drizzle-orm";
|
||||
import type User from "user";
|
||||
|
||||
export async function createCheerRecord(user: User, amount: number): Promise<void> {
|
||||
await db.insert(cheers).values({ user: parseInt(user.id), amount });
|
||||
};
|
||||
export async function createCheerRecord(
|
||||
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) {
|
||||
let condition: SQL<unknown> | undefined = eq(cheers.user, parseInt(user.id));
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(condition, between(cheers.created, new Date(begin), new Date(end)));
|
||||
};
|
||||
const data = await db.select().from(cheers).where(condition);
|
||||
return data;
|
||||
};
|
||||
let condition: SQL<unknown> | undefined = eq(
|
||||
cheers.user,
|
||||
parseInt(user.id, 10),
|
||||
);
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(
|
||||
condition,
|
||||
between(cheers.created, new Date(begin), new Date(end)),
|
||||
);
|
||||
}
|
||||
const data = await db.select().from(cheers).where(condition);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,16 @@ import { getLoots, type lootTriggers } from "db/schema";
|
||||
import type { inventory } from "items";
|
||||
import type User from "user";
|
||||
|
||||
export async function createGetLootRecord(user: User, qbucks: number, inventory: inventory, trigger: lootTriggers) {
|
||||
await db.insert(getLoots).values({
|
||||
user: parseInt(user.id),
|
||||
qbucks: qbucks,
|
||||
items: inventory,
|
||||
trigger
|
||||
});
|
||||
};
|
||||
export async function createGetLootRecord(
|
||||
user: User,
|
||||
qbucks: number,
|
||||
inventory: inventory,
|
||||
trigger: lootTriggers,
|
||||
) {
|
||||
await db.insert(getLoots).values({
|
||||
user: parseInt(user.id, 10),
|
||||
qbucks: qbucks,
|
||||
items: inventory,
|
||||
trigger,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,35 +1,51 @@
|
||||
import db from "db/connection";
|
||||
import { timeouts } from "db/schema";
|
||||
import User from "user";
|
||||
import type { items } from "items";
|
||||
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> {
|
||||
await db.insert(timeouts).values({
|
||||
user: parseInt(user.id),
|
||||
target: parseInt(target.id),
|
||||
item
|
||||
});
|
||||
};
|
||||
export async function createTimeoutRecord(
|
||||
user: User,
|
||||
target: User,
|
||||
item: items,
|
||||
): 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) {
|
||||
let condition: SQL<unknown> | undefined = eq(timeouts.user, parseInt(user.id));
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(condition, between(timeouts.created, new Date(begin), new Date(end)));
|
||||
};
|
||||
const data = await db.select().from(timeouts).where(condition);
|
||||
return data;
|
||||
};
|
||||
let condition: SQL<unknown> | undefined = eq(
|
||||
timeouts.user,
|
||||
parseInt(user.id, 10),
|
||||
);
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
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) {
|
||||
let condition: SQL<unknown> | undefined = eq(timeouts.target, parseInt(user.id));
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(condition, between(timeouts.created, new Date(begin), new Date(end)));
|
||||
};
|
||||
const data = await db.select().from(timeouts).where(condition);
|
||||
return data;
|
||||
};
|
||||
let condition: SQL<unknown> | undefined = eq(
|
||||
timeouts.target,
|
||||
parseInt(user.id, 10),
|
||||
);
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(
|
||||
condition,
|
||||
between(timeouts.created, new Date(begin), new Date(end)),
|
||||
);
|
||||
}
|
||||
const data = await db.select().from(timeouts).where(condition);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
import db from "db/connection";
|
||||
import { usedItems } from "db/schema";
|
||||
import User from "user";
|
||||
import type { items } from "items";
|
||||
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> {
|
||||
await db.insert(usedItems).values({ user: parseInt(user.id), item });
|
||||
};
|
||||
export async function createUsedItemRecord(
|
||||
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) {
|
||||
let condition: SQL<unknown> | undefined = eq(usedItems.user, parseInt(user.id));
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(condition, between(usedItems.created, new Date(begin), new Date(end)));
|
||||
};
|
||||
const data = await db.select().from(usedItems).where(condition);
|
||||
return data;
|
||||
};
|
||||
let condition: SQL<unknown> | undefined = eq(
|
||||
usedItems.user,
|
||||
parseInt(user.id, 10),
|
||||
);
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(
|
||||
condition,
|
||||
between(usedItems.created, new Date(begin), new Date(end)),
|
||||
);
|
||||
}
|
||||
const data = await db.select().from(usedItems).where(condition);
|
||||
return data;
|
||||
}
|
||||
|
||||
160
src/db/dbUser.ts
160
src/db/dbUser.ts
@@ -1,90 +1,122 @@
|
||||
import db from "db/connection";
|
||||
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 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 */
|
||||
export async function getUserRecord(user: User) {
|
||||
const data = await db.query.users.findFirst({ where: eq(users.id, parseInt(user.id)) });
|
||||
if (!data) return createUserRecord(user);
|
||||
const data = await db.query.users.findFirst({
|
||||
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.
|
||||
itemarray.forEach(key => {
|
||||
if (!(key in data.inventory)) data.inventory[key] = 0;
|
||||
});
|
||||
};
|
||||
if (
|
||||
Object.keys(data.inventory).sort().toString() !==
|
||||
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() {
|
||||
return await db.select().from(users);
|
||||
};
|
||||
return await db.select().from(users);
|
||||
}
|
||||
|
||||
async function createUserRecord(user: User) {
|
||||
return await db.insert(users).values({
|
||||
id: parseInt(user.id),
|
||||
username: user.username
|
||||
}).returning().then(a => {
|
||||
if (!a[0]) throw Error('Something went horribly wrong');
|
||||
return a[0]
|
||||
});
|
||||
};
|
||||
return await db
|
||||
.insert(users)
|
||||
.values({
|
||||
id: parseInt(user.id, 10),
|
||||
username: user.username,
|
||||
})
|
||||
.returning()
|
||||
.then((a) => {
|
||||
if (!a[0]) throw Error("Something went horribly wrong");
|
||||
return a[0];
|
||||
});
|
||||
}
|
||||
|
||||
export type UserRecord = InferSelectModel<typeof users>;
|
||||
|
||||
export async function updateUserRecord(user: User, newData: UserRecord) {
|
||||
await db.update(users).set(newData).where(eq(users.id, parseInt(user.id)));
|
||||
return true;
|
||||
};
|
||||
await db
|
||||
.update(users)
|
||||
.set(newData)
|
||||
.where(eq(users.id, parseInt(user.id, 10)));
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
let condition: SQL<unknown> | undefined = ne(timeouts.item, 'silverbullet');
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(condition, between(timeouts.created, new Date(begin), new Date(end)));
|
||||
};
|
||||
let condition: SQL<unknown> | undefined = ne(timeouts.item, "silverbullet");
|
||||
if (monthData) {
|
||||
const begin = Date.parse(monthData);
|
||||
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
|
||||
condition = and(
|
||||
condition,
|
||||
between(timeouts.created, new Date(begin), new Date(end)),
|
||||
);
|
||||
}
|
||||
|
||||
const usersGotShot = await db.select({
|
||||
userId: users.id,
|
||||
amount: count(timeouts.target),
|
||||
})
|
||||
.from(users)
|
||||
.innerJoin(timeouts, eq(users.id, timeouts.target))
|
||||
.groupBy(users.id)
|
||||
.having(sql`count(${timeouts.id}) > 5`)
|
||||
.where(condition);
|
||||
const usersGotShot = await db
|
||||
.select({
|
||||
userId: users.id,
|
||||
amount: count(timeouts.target),
|
||||
})
|
||||
.from(users)
|
||||
.innerJoin(timeouts, eq(users.id, timeouts.target))
|
||||
.groupBy(users.id)
|
||||
.having(sql`count(${timeouts.id}) > 5`)
|
||||
.where(condition);
|
||||
|
||||
const usersThatShot = await db.select({
|
||||
userId: users.id,
|
||||
amount: count(timeouts.user)
|
||||
})
|
||||
.from(users)
|
||||
.innerJoin(timeouts, eq(users.id, timeouts.user))
|
||||
.groupBy(users.id)
|
||||
.where(
|
||||
and(
|
||||
condition,
|
||||
inArray(users.id, usersGotShot.map(a => a.userId))
|
||||
)
|
||||
);
|
||||
const usersThatShot = await db
|
||||
.select({
|
||||
userId: users.id,
|
||||
amount: count(timeouts.user),
|
||||
})
|
||||
.from(users)
|
||||
.innerJoin(timeouts, eq(users.id, timeouts.user))
|
||||
.groupBy(users.id)
|
||||
.where(
|
||||
and(
|
||||
condition,
|
||||
inArray(
|
||||
users.id,
|
||||
usersGotShot.map((a) => a.userId),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const lookup = new Map(usersThatShot.map(a => [a.userId, a.amount]));
|
||||
const result = usersGotShot.map(user => ({
|
||||
userId: user.userId,
|
||||
KD: lookup.get(user.userId)! / user.amount
|
||||
}));
|
||||
const lookup = new Map(usersThatShot.map((a) => [a.userId, a.amount]));
|
||||
const result = usersGotShot.map((user) => ({
|
||||
userId: user.userId,
|
||||
KD: lookup.get(user.userId)! / user.amount,
|
||||
}));
|
||||
|
||||
result.map(user => {
|
||||
if (isNaN(user.KD)) user.KD = 0;
|
||||
return user
|
||||
});
|
||||
result.map((user) => {
|
||||
if (Number.isNaN(user.KD)) user.KD = 0;
|
||||
return user;
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
192
src/db/schema.ts
192
src/db/schema.ts
@@ -1,125 +1,147 @@
|
||||
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 {
|
||||
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', {
|
||||
id: integer().primaryKey(),
|
||||
accesstoken: jsonb().$type<AccessToken>().notNull()
|
||||
export const auth = pgTable("auth", {
|
||||
id: integer().primaryKey(),
|
||||
accesstoken: jsonb().$type<AccessToken>().notNull(),
|
||||
});
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: integer().primaryKey().notNull(),
|
||||
username: varchar().notNull(),
|
||||
balance: integer().default(0).notNull(),
|
||||
inventory: jsonb().$type<inventory>().default({}).notNull()
|
||||
export const users = pgTable("users", {
|
||||
id: integer().primaryKey().notNull(),
|
||||
username: varchar().notNull(),
|
||||
balance: integer().default(0).notNull(),
|
||||
inventory: jsonb().$type<inventory>().default({}).notNull(),
|
||||
});
|
||||
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
timeouts_target: many(timeouts),
|
||||
timeouts_shooter: many(timeouts),
|
||||
usedItems: many(usedItems),
|
||||
cheerEvents: many(cheerEvents),
|
||||
cheers: many(cheers),
|
||||
anivTimeouts: many(anivTimeouts),
|
||||
getLoots: many(getLoots)
|
||||
timeouts_target: many(timeouts),
|
||||
timeouts_shooter: many(timeouts),
|
||||
usedItems: many(usedItems),
|
||||
cheerEvents: many(cheerEvents),
|
||||
cheers: many(cheers),
|
||||
anivTimeouts: many(anivTimeouts),
|
||||
getLoots: many(getLoots),
|
||||
}));
|
||||
|
||||
export const timeouts = pgTable('timeouts', {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer().notNull().references(() => users.id),
|
||||
target: integer().notNull().references(() => users.id),
|
||||
item: varchar().$type<items>().notNull(),
|
||||
created: timestamp().defaultNow().notNull()
|
||||
export const timeouts = pgTable("timeouts", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer()
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
target: integer()
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
item: varchar().$type<items>().notNull(),
|
||||
created: timestamp().defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const timeoutsRelations = relations(timeouts, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [timeouts.user],
|
||||
references: [users.id],
|
||||
relationName: 'shooter'
|
||||
}),
|
||||
target: one(users, {
|
||||
fields: [timeouts.target],
|
||||
references: [users.id],
|
||||
relationName: 'target'
|
||||
})
|
||||
}))
|
||||
user: one(users, {
|
||||
fields: [timeouts.user],
|
||||
references: [users.id],
|
||||
relationName: "shooter",
|
||||
}),
|
||||
target: one(users, {
|
||||
fields: [timeouts.target],
|
||||
references: [users.id],
|
||||
relationName: "target",
|
||||
}),
|
||||
}));
|
||||
|
||||
export const usedItems = pgTable('usedItems', {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer().notNull().references(() => users.id),
|
||||
item: varchar().$type<items>().notNull(),
|
||||
created: timestamp().defaultNow().notNull()
|
||||
export const usedItems = pgTable("usedItems", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer()
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
item: varchar().$type<items>().notNull(),
|
||||
created: timestamp().defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const usedItemsRelations = relations(usedItems, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [usedItems.user],
|
||||
references: [users.id]
|
||||
})
|
||||
user: one(users, {
|
||||
fields: [usedItems.user],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const cheerEvents = pgTable('cheerEvents', {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer().notNull().references(() => users.id),
|
||||
event: varchar().$type<items>().notNull(),
|
||||
created: timestamp().defaultNow().notNull()
|
||||
export const cheerEvents = pgTable("cheerEvents", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer()
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
event: varchar().$type<items>().notNull(),
|
||||
created: timestamp().defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const cheerEventsRelations = relations(cheerEvents, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [cheerEvents.user],
|
||||
references: [users.id]
|
||||
})
|
||||
user: one(users, {
|
||||
fields: [cheerEvents.user],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const cheers = pgTable('cheers', {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer().notNull().references(() => users.id),
|
||||
amount: integer().notNull(),
|
||||
created: timestamp().defaultNow().notNull()
|
||||
export const cheers = pgTable("cheers", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer()
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
amount: integer().notNull(),
|
||||
created: timestamp().defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const cheersRelations = relations(cheers, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [cheers.user],
|
||||
references: [users.id]
|
||||
})
|
||||
user: one(users, {
|
||||
fields: [cheers.user],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const anivTimeouts = pgTable('anivTimeouts', {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer().notNull().references(() => users.id),
|
||||
message: varchar().notNull(),
|
||||
anivBot: varchar().$type<anivBots>().notNull(),
|
||||
duration: integer(),
|
||||
created: timestamp().defaultNow().notNull(),
|
||||
timeout: boolean().default(true)
|
||||
export const anivTimeouts = pgTable("anivTimeouts", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer()
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
message: varchar().notNull(),
|
||||
anivBot: varchar().$type<anivBots>().notNull(),
|
||||
duration: integer(),
|
||||
created: timestamp().defaultNow().notNull(),
|
||||
timeout: boolean().default(true),
|
||||
});
|
||||
|
||||
export const anivTimeoutsRelations = relations(anivTimeouts, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [anivTimeouts.user],
|
||||
references: [users.id]
|
||||
})
|
||||
user: one(users, {
|
||||
fields: [anivTimeouts.user],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export type lootTriggers = "getloot" | "superloot";
|
||||
|
||||
export const getLoots = pgTable('getLoots', {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer().notNull().references(() => users.id),
|
||||
qbucks: integer().notNull(),
|
||||
items: jsonb().$type<inventory>().notNull(),
|
||||
trigger: varchar().$type<lootTriggers>().notNull(),
|
||||
created: timestamp().defaultNow().notNull()
|
||||
export const getLoots = pgTable("getLoots", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer()
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
qbucks: integer().notNull(),
|
||||
items: jsonb().$type<inventory>().notNull(),
|
||||
trigger: varchar().$type<lootTriggers>().notNull(),
|
||||
created: timestamp().defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const getLootsRelations = relations(getLoots, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [getLoots.user],
|
||||
references: [users.id]
|
||||
})
|
||||
user: one(users, {
|
||||
fields: [getLoots.user],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import { api, eventSub } from "index";
|
||||
import { redis } from "lib/redis";
|
||||
import { streamerId } from "main";
|
||||
import { deleteBannedUserMessagesFromChatWidget } from "web/chatWidget/message";
|
||||
import { eventSub, api } from "index";
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
eventSub.onChannelBan(streamerId, async msg => {
|
||||
deleteBannedUserMessagesFromChatWidget(msg);
|
||||
const welcomemessageid = await redis.get(`user:${msg.userId}:welcomemessageid`);
|
||||
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.onChannelBan(streamerId, async (msg) => {
|
||||
deleteBannedUserMessagesFromChatWidget(msg);
|
||||
const welcomemessageid = await redis.get(
|
||||
`user:${msg.userId}:welcomemessageid`,
|
||||
);
|
||||
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 => {
|
||||
await redis.del(`user:${msg.userId}:timeout`);
|
||||
await redis.del(`user:${msg.userId}:remod`);
|
||||
eventSub.onChannelUnban(streamerId, async (msg) => {
|
||||
await redis.del(`user:${msg.userId}:timeout`);
|
||||
await redis.del(`user:${msg.userId}:remod`);
|
||||
});
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { activeRedeems } from "pointRedeems";
|
||||
import { eventSub } from "index";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import logger from "lib/logger";
|
||||
import { streamerId } from "main";
|
||||
import { activeRedeems } from "pointRedeems";
|
||||
import User from "user";
|
||||
|
||||
eventSub.onChannelRedemptionAdd(streamerId, async msg => {
|
||||
const selection = activeRedeems.get(msg.rewardId);
|
||||
if (!selection) { logger.warn(`Can't find the ${msg.rewardTitle} redeem`); return; };
|
||||
const user = await User.initUsername(msg.userName);
|
||||
try {
|
||||
await selection.execute(msg, user!);
|
||||
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);
|
||||
};
|
||||
eventSub.onChannelRedemptionAdd(streamerId, async (msg) => {
|
||||
const selection = activeRedeems.get(msg.rewardId);
|
||||
if (!selection) {
|
||||
logger.warn(`Can't find the ${msg.rewardTitle} redeem`);
|
||||
return;
|
||||
}
|
||||
const user = await User.initUsername(msg.userName);
|
||||
try {
|
||||
await selection.execute(msg, user!);
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,6 @@ import { eventSub } from "index";
|
||||
import { chatterId, streamerId } from "main";
|
||||
import { deleteMessageFromChatWidget } from "web/chatWidget/message";
|
||||
|
||||
eventSub.onChannelChatMessageDelete(streamerId, chatterId, async msg => {
|
||||
deleteMessageFromChatWidget(msg);
|
||||
eventSub.onChannelChatMessageDelete(streamerId, chatterId, async (msg) => {
|
||||
deleteMessageFromChatWidget(msg);
|
||||
});
|
||||
|
||||
@@ -2,34 +2,45 @@ import { api, eventSub } from "index";
|
||||
import kleur from "kleur";
|
||||
import logger from "lib/logger";
|
||||
|
||||
eventSub.onRevoke(event => {
|
||||
logger.ok(`Successfully revoked EventSub subscription: ${kleur.underline(event.id)}`);
|
||||
eventSub.onRevoke((event) => {
|
||||
logger.ok(
|
||||
`Successfully revoked EventSub subscription: ${kleur.underline(event.id)}`,
|
||||
);
|
||||
});
|
||||
|
||||
eventSub.onSubscriptionCreateSuccess(event => {
|
||||
logger.ok(`Successfully created EventSub subscription: ${kleur.underline(event.id)}`);
|
||||
eventSub.onSubscriptionCreateSuccess((event) => {
|
||||
logger.ok(
|
||||
`Successfully created EventSub subscription: ${kleur.underline(event.id)}`,
|
||||
);
|
||||
});
|
||||
|
||||
eventSub.onSubscriptionCreateFailure(event => {
|
||||
logger.err(`Failed to create EventSub subscription: ${kleur.underline(event.id)}`);
|
||||
eventSub.onSubscriptionCreateFailure((event) => {
|
||||
logger.err(
|
||||
`Failed to create EventSub subscription: ${kleur.underline(event.id)}`,
|
||||
);
|
||||
});
|
||||
|
||||
eventSub.onSubscriptionDeleteSuccess(event => {
|
||||
logger.ok(`Successfully deleted EventSub subscription: ${kleur.underline(event.id)}`);
|
||||
eventSub.onSubscriptionDeleteSuccess((event) => {
|
||||
logger.ok(
|
||||
`Successfully deleted EventSub subscription: ${kleur.underline(event.id)}`,
|
||||
);
|
||||
});
|
||||
|
||||
eventSub.onSubscriptionDeleteFailure(event => {
|
||||
logger.err(`Failed to delete EventSub subscription: ${kleur.underline(event.id)}`);
|
||||
eventSub.onSubscriptionDeleteFailure((event) => {
|
||||
logger.err(
|
||||
`Failed to delete EventSub subscription: ${kleur.underline(event.id)}`,
|
||||
);
|
||||
});
|
||||
|
||||
await api.eventSub.deleteAllSubscriptions();
|
||||
|
||||
import { readdir } from 'node:fs/promises';
|
||||
import { readdir } from "node:fs/promises";
|
||||
|
||||
const files = await readdir(import.meta.dir);
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.ts')) continue;
|
||||
if (file === import.meta.file) continue;
|
||||
await import(import.meta.dir + '/' + file.slice(0, -3));
|
||||
};
|
||||
if (!file.endsWith(".ts")) continue;
|
||||
if (file === import.meta.file) continue;
|
||||
await import(`${import.meta.dir}/${file.slice(0, -3)}`);
|
||||
}
|
||||
|
||||
eventSub.start();
|
||||
|
||||
@@ -1,115 +1,172 @@
|
||||
import { 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 type { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
|
||||
import cheers from "cheers";
|
||||
import logger from "lib/logger";
|
||||
import { addMessageToChatWidget } from "web/chatWidget/message";
|
||||
import { isInvuln, removeInvuln, setTemporaryInvuln } from "lib/invuln";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import commands, { specialAliasCommands } from "commands";
|
||||
import { createCheerRecord } from "db/dbCheers";
|
||||
import handleAnivMessage from "lib/handleAnivMessage";
|
||||
import { Item } from "items";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
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 { chatterId, commandPrefix, streamerId, streamerUsers } from "main";
|
||||
import User from "user";
|
||||
import { addMessageToChatWidget } from "web/chatWidget/message";
|
||||
|
||||
eventSub.onChannelChatMessage(streamerId, chatterId, parseChatMessage);
|
||||
|
||||
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
|
||||
// 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.
|
||||
// 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)
|
||||
// The only problem would be if a user changed their name and someone else took their name right after
|
||||
// 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
|
||||
//
|
||||
// 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)
|
||||
// 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
|
||||
|
||||
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
|
||||
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 redis.exists(`user:${user?.id}:haschatted`)) &&
|
||||
!msg.sourceMessageId
|
||||
) {
|
||||
// The msg.sourceMessageId checks if the message is from shared chat. shared chat should be ignored
|
||||
const message = await sendMessage(
|
||||
`Welcome ${user?.displayName}. Please note: This chat has PvP, if you get timed out that's part of the qwerinope experience. You have 10 minutes of invincibility. A full list of commands and items can be found here: https://github.com/qwerinope/qweribot/#qweribot`,
|
||||
);
|
||||
await redis.set(`user:${user?.id}:haschatted`, "1");
|
||||
await redis.set(`user:${user?.id}:welcomemessageid`, message.id);
|
||||
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!)
|
||||
else if (msg.isCheer && !msg.isRedemption) await handleCheer(msg, msg.bits, user!);
|
||||
};
|
||||
if (!msg.isCheer && !msg.isRedemption) await handleChatMessage(msg, user!);
|
||||
else if (msg.isCheer && !msg.isRedemption)
|
||||
await handleCheer(msg, msg.bits, user!);
|
||||
}
|
||||
|
||||
async function handleChatMessage(msg: EventSubChannelChatMessageEvent, user: User) {
|
||||
// Aniv message filter
|
||||
handleAnivMessage(msg, user);
|
||||
async function handleChatMessage(
|
||||
msg: EventSubChannelChatMessageEvent,
|
||||
user: User,
|
||||
) {
|
||||
// Aniv message filter
|
||||
handleAnivMessage(msg, user);
|
||||
|
||||
// Parse commands:
|
||||
const selected = selectCommand(msg.messageText);
|
||||
if (!selected) return;
|
||||
const { cmd: selection, activation, isitem } = selected;
|
||||
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); };
|
||||
// Parse commands:
|
||||
const selected = selectCommand(msg.messageText);
|
||||
if (!selected) return;
|
||||
const { cmd: selection, activation, isitem } = selected;
|
||||
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);
|
||||
}
|
||||
|
||||
switch (selection.usertype) {
|
||||
case "admin":
|
||||
if (!await isAdmin(user.id)) return;
|
||||
break;
|
||||
case "streamer":
|
||||
if (!streamerUsers.includes(msg.chatterId)) return;
|
||||
break;
|
||||
case "moderator":
|
||||
if (!(await redis.exists(`user:${user.id}:mod`) || await isAdmin(user.id))) return;
|
||||
break;
|
||||
};
|
||||
switch (selection.usertype) {
|
||||
case "admin":
|
||||
if (!(await isAdmin(user.id))) return;
|
||||
break;
|
||||
case "streamer":
|
||||
if (!streamerUsers.includes(msg.chatterId)) return;
|
||||
break;
|
||||
case "moderator":
|
||||
if (
|
||||
!(
|
||||
(await redis.exists(`user:${user.id}:mod`)) ||
|
||||
(await isAdmin(user.id))
|
||||
)
|
||||
)
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
await selection.execute(msg, user, { activation });
|
||||
} catch (err) {
|
||||
logger.err(err as string);
|
||||
await sendMessage('ERROR: Something went wrong', msg.messageId);
|
||||
await user.clearLock();
|
||||
};
|
||||
};
|
||||
try {
|
||||
await selection.execute(msg, user, { activation });
|
||||
} catch (err) {
|
||||
logger.err(err as string);
|
||||
await sendMessage("ERROR: Something went wrong", msg.messageId);
|
||||
await user.clearLock();
|
||||
}
|
||||
}
|
||||
|
||||
type selectedCommand = {
|
||||
cmd: Command;
|
||||
activation: string;
|
||||
isitem: boolean;
|
||||
cmd: Command;
|
||||
activation: string;
|
||||
isitem: boolean;
|
||||
};
|
||||
|
||||
function selectCommand(message: string): selectedCommand | false {
|
||||
const specialcmdselector = message.trim().toLowerCase().split(' ')[0]!;
|
||||
const specialcmd = specialAliasCommands.get(specialcmdselector);
|
||||
if (specialcmd) return { cmd: specialcmd, activation: specialcmdselector, isitem: specialcmd instanceof Item };
|
||||
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;
|
||||
};
|
||||
const specialcmdselector = message.trim().toLowerCase().split(" ")[0]!;
|
||||
const specialcmd = specialAliasCommands.get(specialcmdselector);
|
||||
if (specialcmd)
|
||||
return {
|
||||
cmd: specialcmd,
|
||||
activation: specialcmdselector,
|
||||
isitem: specialcmd instanceof Item,
|
||||
};
|
||||
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) {
|
||||
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
|
||||
export async function handleCheer(
|
||||
msg: EventSubChannelChatMessageEvent,
|
||||
bits: number,
|
||||
user: User,
|
||||
) {
|
||||
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);
|
||||
if (!selection) return;
|
||||
const selection = cheers.get(bits);
|
||||
if (!selection) return;
|
||||
|
||||
if (await redis.sismember('disabledcheers', selection.name)) { await sendMessage(`The ${selection.name} cheer is disabled! Sorry!`, msg.messageId); return; };
|
||||
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);
|
||||
};
|
||||
};
|
||||
if (await redis.sismember("disabledcheers", selection.name)) {
|
||||
await sendMessage(
|
||||
`The ${selection.name} cheer is disabled! Sorry!`,
|
||||
msg.messageId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ import { eventSub } from "index";
|
||||
import { redis } from "lib/redis";
|
||||
import { streamerId } from "main";
|
||||
|
||||
eventSub.onChannelModeratorAdd(streamerId, async mod => {
|
||||
await redis.set(`user:${mod.userId}:mod`, '1');
|
||||
eventSub.onChannelModeratorAdd(streamerId, async (mod) => {
|
||||
await redis.set(`user:${mod.userId}:mod`, "1");
|
||||
});
|
||||
|
||||
eventSub.onChannelModeratorRemove(streamerId, async mod => {
|
||||
await redis.del(`user:${mod.userId}:mod`);
|
||||
eventSub.onChannelModeratorRemove(streamerId, async (mod) => {
|
||||
await redis.del(`user:${mod.userId}:mod`);
|
||||
});
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { eventSub, api } from "index";
|
||||
import { api, eventSub } from "index";
|
||||
import { changeItemCount } from "items";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import logger from "lib/logger";
|
||||
import { redis } from "lib/redis";
|
||||
import { streamerId } from "main";
|
||||
import User from "user";
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
eventSub.onChannelRaidTo(streamerId, async msg => {
|
||||
if (await redis.exists(`user:${msg.raidingBroadcasterId}:recentraid`)) { await sendMessage(`Another raid from ${msg.raidedBroadcasterDisplayName}??? SMH`); return; };
|
||||
await redis.set(`user:${msg.raidingBroadcasterId}:recentraid`, '1');
|
||||
await redis.expire(`user:${msg.raidingBroadcasterId}:recentraid`, 60 * 30); // raid cooldown is 30 minutes
|
||||
await sendMessage(`Ty for raiding ${msg.raidingBroadcasterDisplayName}. You get 3 pieces of TNT. Enjoy!`);
|
||||
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!");
|
||||
eventSub.onChannelRaidTo(streamerId, async (msg) => {
|
||||
if (await redis.exists(`user:${msg.raidingBroadcasterId}:recentraid`)) {
|
||||
await sendMessage(
|
||||
`Another raid from ${msg.raidedBroadcasterDisplayName}??? SMH`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
await redis.set(`user:${msg.raidingBroadcasterId}:recentraid`, "1");
|
||||
await redis.expire(`user:${msg.raidingBroadcasterId}:recentraid`, 60 * 30); // raid cooldown is 30 minutes
|
||||
await sendMessage(
|
||||
`Ty for raiding ${msg.raidingBroadcasterDisplayName}. You get 3 pieces of TNT. Enjoy!`,
|
||||
);
|
||||
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!");
|
||||
});
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { eventSub } from "index";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { redis } from "lib/redis";
|
||||
import { streamerId } from "main";
|
||||
import { sendDiscordMessage } from "web/discordConnection";
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
eventSub.onStreamOnline(streamerId, async msg => {
|
||||
await redis.set('streamIsLive', '1');
|
||||
await sendMessage(`${msg.broadcasterDisplayName.toUpperCase()} IS LIVE! START DIGGING!`);
|
||||
await sendDiscordMessage({ message: 'live' });
|
||||
eventSub.onStreamOnline(streamerId, async (msg) => {
|
||||
await redis.set("streamIsLive", "1");
|
||||
await sendMessage(
|
||||
`${msg.broadcasterDisplayName.toUpperCase()} IS LIVE! START DIGGING!`,
|
||||
);
|
||||
await sendDiscordMessage({ message: "live" });
|
||||
});
|
||||
|
||||
eventSub.onStreamOffline(streamerId, async msg => {
|
||||
await redis.del('streamIsLive');
|
||||
await sendMessage(`${msg.broadcasterDisplayName.toUpperCase()} IS OFFLINE! NO MORE FREE LOOT!`);
|
||||
eventSub.onStreamOffline(streamerId, async (msg) => {
|
||||
await redis.del("streamIsLive");
|
||||
await sendMessage(
|
||||
`${msg.broadcasterDisplayName.toUpperCase()} IS OFFLINE! NO MORE FREE LOOT!`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,123 +1,153 @@
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { getUserRecord, updateUserRecord } from "db/dbUser";
|
||||
import { eventSub } from "index";
|
||||
import { changeBalance } from "lib/changeBalance";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { redis } from "lib/redis";
|
||||
import { streamerId } from "main";
|
||||
import User from "user";
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
eventSub.onChannelSubscription(streamerId, async msg => {
|
||||
await redis.set(`user:${msg.userId}:subbed`, msg.tier.slice(0, 1));
|
||||
if (msg.isGift) return;
|
||||
const user = await User.initUsername(msg.userName);
|
||||
const userRecord = await getUserRecord(user!);
|
||||
switch (msg.tier) {
|
||||
case "1000":
|
||||
await Promise.all([
|
||||
sendMessage(`YO THANKS FOR THE SUB ${msg.userDisplayName}! YOU GET 500 QBUCKS`),
|
||||
changeBalance(user!, userRecord, 500)
|
||||
]);
|
||||
break;
|
||||
case "2000":
|
||||
userRecord.balance += 1500;
|
||||
if (userRecord.inventory.silverbullet) userRecord.inventory.silverbullet += 1
|
||||
else userRecord.inventory.silverbullet = 1
|
||||
await Promise.all([
|
||||
sendMessage(`YO THANKS FOR THE TIER 2 SUB ${msg.userDisplayName}! YOU GET 1500 QBUCKS AND A SILVER BULLET`),
|
||||
updateUserRecord(user!, userRecord)
|
||||
]);
|
||||
break;
|
||||
case "3000":
|
||||
userRecord.balance += 3000;
|
||||
if (userRecord.inventory.silverbullet) 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.onChannelSubscription(streamerId, async (msg) => {
|
||||
await redis.set(`user:${msg.userId}:subbed`, msg.tier.slice(0, 1));
|
||||
if (msg.isGift) return;
|
||||
const user = await User.initUsername(msg.userName);
|
||||
const userRecord = await getUserRecord(user!);
|
||||
switch (msg.tier) {
|
||||
case "1000":
|
||||
await Promise.all([
|
||||
sendMessage(
|
||||
`YO THANKS FOR THE SUB ${msg.userDisplayName}! YOU GET 500 QBUCKS`,
|
||||
),
|
||||
changeBalance(user!, userRecord, 500),
|
||||
]);
|
||||
break;
|
||||
case "2000":
|
||||
userRecord.balance += 1500;
|
||||
if (userRecord.inventory.silverbullet)
|
||||
userRecord.inventory.silverbullet += 1;
|
||||
else userRecord.inventory.silverbullet = 1;
|
||||
await Promise.all([
|
||||
sendMessage(
|
||||
`YO THANKS FOR THE TIER 2 SUB ${msg.userDisplayName}! YOU GET 1500 QBUCKS AND A SILVER BULLET`,
|
||||
),
|
||||
updateUserRecord(user!, userRecord),
|
||||
]);
|
||||
break;
|
||||
case "3000":
|
||||
userRecord.balance += 3000;
|
||||
if (userRecord.inventory.silverbullet)
|
||||
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 => {
|
||||
if (msg.isAnonymous) {
|
||||
switch (msg.tier) {
|
||||
case "1000":
|
||||
await sendMessage(`YO THANKS ANON FOR THE SCAM SUB${msg.amount === 1 ? '' : 'S'}`);
|
||||
break;
|
||||
case "2000":
|
||||
await sendMessage(`YO THANKS ANON FOR THE ${msg.amount} TIER 2 SCAM SUB${msg.amount === 1 ? '' : 'S'}`);
|
||||
break;
|
||||
case "3000":
|
||||
await sendMessage(`YO THANKS ANON FOR THE ${msg.amount} TIER 3 SCAM SUB${msg.amount === 1 ? '' : 'S'}`);
|
||||
break;
|
||||
};
|
||||
return;
|
||||
};
|
||||
eventSub.onChannelSubscriptionGift(streamerId, async (msg) => {
|
||||
if (msg.isAnonymous) {
|
||||
switch (msg.tier) {
|
||||
case "1000":
|
||||
await sendMessage(
|
||||
`YO THANKS ANON FOR THE SCAM SUB${msg.amount === 1 ? "" : "S"}`,
|
||||
);
|
||||
break;
|
||||
case "2000":
|
||||
await sendMessage(
|
||||
`YO THANKS ANON FOR THE ${msg.amount} TIER 2 SCAM SUB${msg.amount === 1 ? "" : "S"}`,
|
||||
);
|
||||
break;
|
||||
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 amount = msg.amount;
|
||||
const userRecord = await getUserRecord(user!);
|
||||
switch (msg.tier) {
|
||||
case "1000":
|
||||
await Promise.all([
|
||||
sendMessage(`YO THANKS FOR THE SCAM GIFTS ${msg.gifterDisplayName}! YOU GET ${amount * 500} QBUCKS`),
|
||||
changeBalance(user!, userRecord, amount * 500)
|
||||
]);
|
||||
break;
|
||||
case "2000":
|
||||
userRecord.balance += 1500 * amount;
|
||||
if (userRecord.inventory.silverbullet) userRecord.inventory.silverbullet += amount
|
||||
else userRecord.inventory.silverbullet = amount;
|
||||
await Promise.all([
|
||||
sendMessage(`YO THANKS FOR THE SCAM TIER 2 GIFTS ${msg.gifterDisplayName}! YOU GET ${amount * 1500} QBUCKS AND ${amount} SILVER BULLET${amount === 1 ? '' : 'S'}`),
|
||||
updateUserRecord(user!, userRecord)
|
||||
]);
|
||||
break;
|
||||
case "3000":
|
||||
userRecord.balance += 3000 * amount;
|
||||
if (userRecord.inventory.silverbullet) 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;
|
||||
}
|
||||
const user = await User.initUsername(msg.gifterName);
|
||||
const amount = msg.amount;
|
||||
const userRecord = await getUserRecord(user!);
|
||||
switch (msg.tier) {
|
||||
case "1000":
|
||||
await Promise.all([
|
||||
sendMessage(
|
||||
`YO THANKS FOR THE SCAM GIFTS ${msg.gifterDisplayName}! YOU GET ${amount * 500} QBUCKS`,
|
||||
),
|
||||
changeBalance(user!, userRecord, amount * 500),
|
||||
]);
|
||||
break;
|
||||
case "2000":
|
||||
userRecord.balance += 1500 * amount;
|
||||
if (userRecord.inventory.silverbullet)
|
||||
userRecord.inventory.silverbullet += amount;
|
||||
else userRecord.inventory.silverbullet = amount;
|
||||
await Promise.all([
|
||||
sendMessage(
|
||||
`YO THANKS FOR THE SCAM TIER 2 GIFTS ${msg.gifterDisplayName}! YOU GET ${amount * 1500} QBUCKS AND ${amount} SILVER BULLET${amount === 1 ? "" : "S"}`,
|
||||
),
|
||||
updateUserRecord(user!, userRecord),
|
||||
]);
|
||||
break;
|
||||
case "3000":
|
||||
userRecord.balance += 3000 * amount;
|
||||
if (userRecord.inventory.silverbullet)
|
||||
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 => {
|
||||
await redis.del(`user:${msg.userId}:subbed`);
|
||||
eventSub.onChannelSubscriptionEnd(streamerId, async (msg) => {
|
||||
await redis.del(`user:${msg.userId}:subbed`);
|
||||
});
|
||||
|
||||
eventSub.onChannelSubscriptionMessage(streamerId, async msg => {
|
||||
await redis.set(`user:${msg.userId}:subbed`, msg.tier.slice(0, 1));
|
||||
const user = await User.initUsername(msg.userName);
|
||||
const userRecord = await getUserRecord(user!);
|
||||
switch (msg.tier) {
|
||||
case "1000":
|
||||
await Promise.all([
|
||||
sendMessage(`YO THANKS FOR THE RESUB ${msg.userDisplayName}! YOU GET 500 QBUCKS`),
|
||||
changeBalance(user!, userRecord, 500)
|
||||
]);
|
||||
break;
|
||||
case "2000":
|
||||
userRecord.balance += 1500;
|
||||
if (userRecord.inventory.silverbullet) userRecord.inventory.silverbullet += 1
|
||||
else userRecord.inventory.silverbullet = 1
|
||||
await Promise.all([
|
||||
sendMessage(`YO THANKS FOR THE TIER 2 RESUB ${msg.userDisplayName}! YOU GET 1500 QBUCKS AND A SILVER BULLET`),
|
||||
updateUserRecord(user!, userRecord)
|
||||
]);
|
||||
break;
|
||||
case "3000":
|
||||
userRecord.balance += 3000;
|
||||
if (userRecord.inventory.silverbullet) 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;
|
||||
};
|
||||
eventSub.onChannelSubscriptionMessage(streamerId, async (msg) => {
|
||||
await redis.set(`user:${msg.userId}:subbed`, msg.tier.slice(0, 1));
|
||||
const user = await User.initUsername(msg.userName);
|
||||
const userRecord = await getUserRecord(user!);
|
||||
switch (msg.tier) {
|
||||
case "1000":
|
||||
await Promise.all([
|
||||
sendMessage(
|
||||
`YO THANKS FOR THE RESUB ${msg.userDisplayName}! YOU GET 500 QBUCKS`,
|
||||
),
|
||||
changeBalance(user!, userRecord, 500),
|
||||
]);
|
||||
break;
|
||||
case "2000":
|
||||
userRecord.balance += 1500;
|
||||
if (userRecord.inventory.silverbullet)
|
||||
userRecord.inventory.silverbullet += 1;
|
||||
else userRecord.inventory.silverbullet = 1;
|
||||
await Promise.all([
|
||||
sendMessage(
|
||||
`YO THANKS FOR THE TIER 2 RESUB ${msg.userDisplayName}! YOU GET 1500 QBUCKS AND A SILVER BULLET`,
|
||||
),
|
||||
updateUserRecord(user!, userRecord),
|
||||
]);
|
||||
break;
|
||||
case "3000":
|
||||
userRecord.balance += 3000;
|
||||
if (userRecord.inventory.silverbullet)
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,36 +1,80 @@
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { api, eventSub } from "index";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { buildTimeString } from "lib/dateManager";
|
||||
import { chatterId, commandPrefix } from "main";
|
||||
import { redis } from "lib/redis";
|
||||
import { chatterId, commandPrefix } from "main";
|
||||
|
||||
const WHISPERCOOLDOWN = 60 * 5; // 5 minutes
|
||||
|
||||
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; };
|
||||
const cmd = msg.messageText.slice(commandPrefix.length).trim().toLowerCase().split(' ')[0]!;
|
||||
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;
|
||||
}
|
||||
const cmd = msg.messageText
|
||||
.slice(commandPrefix.length)
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.split(" ")[0]!;
|
||||
|
||||
switch (cmd) {
|
||||
case 'help':
|
||||
case 'h':
|
||||
await whisper(msg.senderUserId, `All whisper commands can be found here: https://github.com/qwerinope/qweribot#whisper-commands-1`);
|
||||
break;
|
||||
case 'ghostwhisper':
|
||||
case 'ghost':
|
||||
case 'g':
|
||||
if (await redis.ttl(`user:${msg.senderUserId}:timeout`) < 0) { await whisper(msg.senderUserId, 'Cannot send ghost whisper while not timed out'); return; };
|
||||
const cooldown = await redis.expiretime(`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;
|
||||
};
|
||||
switch (cmd) {
|
||||
case "help":
|
||||
case "h":
|
||||
await whisper(
|
||||
msg.senderUserId,
|
||||
`All whisper commands can be found here: https://github.com/qwerinope/qweribot#whisper-commands-1`,
|
||||
);
|
||||
break;
|
||||
case "ghostwhisper":
|
||||
case "ghost":
|
||||
case "g": {
|
||||
if ((await redis.ttl(`user:${msg.senderUserId}:timeout`)) < 0) {
|
||||
await whisper(
|
||||
msg.senderUserId,
|
||||
"Cannot send ghost whisper while not timed out",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const cooldown = await redis.expiretime(
|
||||
`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);
|
||||
|
||||
254
src/index.ts
254
src/index.ts
@@ -1,128 +1,212 @@
|
||||
import { chatterId, streamerId, singleUserMode, streamerUsers } from "main";
|
||||
import { ApiClient } from "@twurple/api";
|
||||
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 { redis } from "lib/redis";
|
||||
import { createAuthProvider, type authProviderInstructions } from "auth";
|
||||
import { user, password, database, host } from "db/connection";
|
||||
import { ConnectionAdapter, EventSubHttpListener, ReverseProxyAdapter } from "@twurple/eventsub-http";
|
||||
import { NgrokAdapter } from "@twurple/eventsub-ngrok";
|
||||
import { chatterId, singleUserMode, streamerId, streamerUsers } from "main";
|
||||
|
||||
if (chatterId === "") { logger.enverr('CHATTER_ID'); process.exit(1); };
|
||||
if (streamerId === "") { 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); };
|
||||
if (chatterId === "") {
|
||||
logger.enverr("CHATTER_ID");
|
||||
process.exit(1);
|
||||
}
|
||||
if (streamerId === "") {
|
||||
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 ?? (() => {
|
||||
logger.enverr('EVENTSUB_HOSTNAME');
|
||||
process.exit(1);
|
||||
})();
|
||||
const eventSubHostName =
|
||||
process.env.EVENTSUB_HOSTNAME ??
|
||||
(() => {
|
||||
logger.enverr("EVENTSUB_HOSTNAME");
|
||||
process.exit(1);
|
||||
})();
|
||||
|
||||
const eventSubPort = process.env.EVENTSUB_PORT ?? (() => {
|
||||
logger.enverr('EVENTSUB_PORT');
|
||||
process.exit(1);
|
||||
})();
|
||||
const eventSubPort =
|
||||
process.env.EVENTSUB_PORT ??
|
||||
(() => {
|
||||
logger.enverr("EVENTSUB_PORT");
|
||||
process.exit(1);
|
||||
})();
|
||||
|
||||
const eventSubSecret = process.env.EVENTSUB_SECRET ?? (() => {
|
||||
logger.enverr('EVENTSUB_SECRET');
|
||||
process.exit(1);
|
||||
})();
|
||||
const eventSubSecret =
|
||||
process.env.EVENTSUB_SECRET ??
|
||||
(() => {
|
||||
logger.enverr("EVENTSUB_SECRET");
|
||||
process.exit(1);
|
||||
})();
|
||||
|
||||
const eventSubPath = process.env.EVENTSUB_PATH;
|
||||
|
||||
await connectionCheck();
|
||||
|
||||
const CHATTERINTENTS = ["user:read:chat", "user:write:chat", "user:bot", "user:manage:whispers"];
|
||||
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"];
|
||||
|
||||
const users: authProviderInstructions[] = [
|
||||
{
|
||||
userId: streamerId,
|
||||
intents: singleUserMode ? CHATTERINTENTS.concat(STREAMERINTENTS) : STREAMERINTENTS,
|
||||
streamer: true
|
||||
}
|
||||
const CHATTERINTENTS = [
|
||||
"user:read:chat",
|
||||
"user:write:chat",
|
||||
"user:bot",
|
||||
"user:manage:whispers",
|
||||
];
|
||||
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",
|
||||
];
|
||||
|
||||
if (!singleUserMode) users.push({
|
||||
userId: chatterId,
|
||||
intents: CHATTERINTENTS,
|
||||
streamer: false
|
||||
});
|
||||
const users: authProviderInstructions[] = [
|
||||
{
|
||||
userId: streamerId,
|
||||
intents: singleUserMode
|
||||
? CHATTERINTENTS.concat(STREAMERINTENTS)
|
||||
: STREAMERINTENTS,
|
||||
streamer: true,
|
||||
},
|
||||
];
|
||||
|
||||
const adapter: ConnectionAdapter = 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)
|
||||
});
|
||||
if (!singleUserMode)
|
||||
users.push({
|
||||
userId: chatterId,
|
||||
intents: CHATTERINTENTS,
|
||||
streamer: false,
|
||||
});
|
||||
|
||||
const adapter: ConnectionAdapter =
|
||||
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);
|
||||
|
||||
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 { addInvuln } from "lib/invuln";
|
||||
import User from "user";
|
||||
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) {
|
||||
await redis.set(`user:${ban.userId}:timeout`, '1');
|
||||
const banlength = ban.expiryDate;
|
||||
if (banlength) {
|
||||
redis.expire(`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.`);
|
||||
};
|
||||
};
|
||||
await redis.set(`user:${ban.userId}:timeout`, "1");
|
||||
const banlength = ban.expiryDate;
|
||||
if (banlength) {
|
||||
redis.expire(
|
||||
`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) {
|
||||
await redis.set(`user:${mod.userId}:mod`, '1');
|
||||
logger.info(`Set the mod status of ${mod.userDisplayName} in the Redis/Valkey database.`);
|
||||
};
|
||||
await redis.set(`user:${mod.userId}:mod`, "1");
|
||||
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) {
|
||||
const target = await User.initUserId(remod);
|
||||
const durationdata = await timeoutDuration(target!);
|
||||
let duration = 0;
|
||||
if (durationdata) duration = Math.floor((durationdata * 1000 - Date.now()) / 1000);
|
||||
remodMod(target!, duration);
|
||||
logger.info(`Set the remod timer for ${target?.displayName} to ${duration} seconds.`);
|
||||
};
|
||||
const target = await User.initUserId(remod);
|
||||
const durationdata = await timeoutDuration(target!);
|
||||
let duration = 0;
|
||||
if (durationdata)
|
||||
duration = Math.floor((durationdata * 1000 - Date.now()) / 1000);
|
||||
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 redisSubs = await redis.keys('user:*:subbed').then(a => a.map(b => b.slice(5, -7)));
|
||||
const subs = await api.subscriptions
|
||||
.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) {
|
||||
if (redisSubs.includes(sub.userId)) {
|
||||
const index = redisSubs.indexOf(sub.userId);
|
||||
redisSubs.splice(index, 1);
|
||||
continue;
|
||||
};
|
||||
await redis.set(`user:${sub.userId}:subbed`, sub.tier.slice(0, 1));
|
||||
};
|
||||
if (redisSubs.includes(sub.userId)) {
|
||||
const index = redisSubs.indexOf(sub.userId);
|
||||
redisSubs.splice(index, 1);
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
if (streamdata) await redis.set('streamIsLive', '1')
|
||||
else await redis.del('streamIsLive');
|
||||
if (streamdata) await redis.set("streamIsLive", "1");
|
||||
else await redis.del("streamIsLive");
|
||||
|
||||
await import("./events");
|
||||
|
||||
await import("./pointRedeems");
|
||||
|
||||
await import("./web");
|
||||
|
||||
|
||||
@@ -1,63 +1,89 @@
|
||||
import { changeItemCount, Item } from "items";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import { createUsedItemRecord } from "db/dbUsedItems";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { changeItemCount, Item } from "items";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
import { timeout } from "lib/timeout";
|
||||
import User from "user";
|
||||
import { playAlert } from "web/alerts/serverFunctions";
|
||||
|
||||
const ITEMNAME = 'blaster';
|
||||
const ITEMNAME = "blaster";
|
||||
|
||||
export default new Item({
|
||||
name: ITEMNAME,
|
||||
prettyName: 'Blaster',
|
||||
plural: 's',
|
||||
description: 'Times a specific person out for 60 seconds',
|
||||
aliases: ['blaster', 'blast'],
|
||||
price: 100,
|
||||
execution: async (msg, user, specialargs) => {
|
||||
const messagequery = parseCommandArgs(msg.messageText, specialargs?.activation);
|
||||
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
|
||||
name: ITEMNAME,
|
||||
prettyName: "Blaster",
|
||||
plural: "s",
|
||||
description: "Times a specific person out for 60 seconds",
|
||||
aliases: ["blaster", "blast"],
|
||||
price: 100,
|
||||
execution: async (msg, user, specialargs) => {
|
||||
const messagequery = parseCommandArgs(
|
||||
msg.messageText,
|
||||
specialargs?.activation,
|
||||
);
|
||||
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; };
|
||||
await user.setLock();
|
||||
if (await user.itemLock()) {
|
||||
await sendMessage("Cannot use an item (itemlock)", msg.messageId);
|
||||
return;
|
||||
}
|
||||
await user.setLock();
|
||||
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any blasters!`, msg.messageId); await user.clearLock(); return; };
|
||||
const userObj = await getUserRecord(user);
|
||||
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);
|
||||
if (result.status) await Promise.all([
|
||||
sendMessage(`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`),
|
||||
changeItemCount(user, userObj, ITEMNAME),
|
||||
createTimeoutRecord(user, target, ITEMNAME),
|
||||
createUsedItemRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: 'userBlast',
|
||||
user: user.displayName,
|
||||
target: target.displayName
|
||||
})
|
||||
]);
|
||||
else {
|
||||
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;
|
||||
};
|
||||
};
|
||||
await user.clearLock();
|
||||
}
|
||||
const result = await timeout(
|
||||
target,
|
||||
`You got blasted by ${user.displayName}!`,
|
||||
60,
|
||||
);
|
||||
if (result.status)
|
||||
await Promise.all([
|
||||
sendMessage(
|
||||
`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`,
|
||||
),
|
||||
changeItemCount(user, userObj, ITEMNAME),
|
||||
createTimeoutRecord(user, target, ITEMNAME),
|
||||
createUsedItemRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: "userBlast",
|
||||
user: user.displayName,
|
||||
target: target.displayName,
|
||||
}),
|
||||
]);
|
||||
else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
await user.clearLock();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 { 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";
|
||||
|
||||
const ITEMNAME = 'grenade';
|
||||
const ITEMNAME = "grenade";
|
||||
|
||||
export default new Item({
|
||||
name: ITEMNAME,
|
||||
prettyName: 'Grenade',
|
||||
plural: 's',
|
||||
description: 'Give a random chatter a 60s timeout',
|
||||
aliases: ['grenade'],
|
||||
price: 99,
|
||||
execution: async (msg, user) => {
|
||||
const targets = await redis.keys(`user:*:vulnerable`);
|
||||
if (targets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; };
|
||||
const selection = targets[Math.floor(Math.random() * targets.length)]!;
|
||||
const target = await User.initUserId(selection.slice(5, -11));
|
||||
name: ITEMNAME,
|
||||
prettyName: "Grenade",
|
||||
plural: "s",
|
||||
description: "Give a random chatter a 60s timeout",
|
||||
aliases: ["grenade"],
|
||||
price: 99,
|
||||
execution: async (msg, user) => {
|
||||
const targets = await redis.keys(`user:*:vulnerable`);
|
||||
if (targets.length === 0) {
|
||||
await sendMessage("No vulnerable chatters to blow up", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const selection = targets[Math.floor(Math.random() * targets.length)]!;
|
||||
const target = await User.initUserId(selection.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; };
|
||||
await user.setLock();
|
||||
if (await user.itemLock()) {
|
||||
await sendMessage("Cannot use an item (itemlock)", msg.messageId);
|
||||
return;
|
||||
}
|
||||
await user.setLock();
|
||||
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any grenades!`, msg.messageId); await user.clearLock(); return; };
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.inventory[ITEMNAME]! < 1) {
|
||||
await sendMessage(`You don't have any grenades!`, msg.messageId);
|
||||
await user.clearLock();
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60),
|
||||
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`),
|
||||
changeItemCount(user, userObj, ITEMNAME),
|
||||
createTimeoutRecord(user, target!, ITEMNAME),
|
||||
createUsedItemRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: 'grenadeExplosion',
|
||||
user: user.displayName,
|
||||
target: target?.displayName!
|
||||
})
|
||||
]);
|
||||
await user.clearLock();
|
||||
}
|
||||
await Promise.all([
|
||||
timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60),
|
||||
sendMessage(
|
||||
`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`,
|
||||
),
|
||||
changeItemCount(user, userObj, ITEMNAME),
|
||||
createTimeoutRecord(user, target!, ITEMNAME),
|
||||
createUsedItemRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: "grenadeExplosion",
|
||||
user: user.displayName,
|
||||
target: target?.displayName!,
|
||||
}),
|
||||
]);
|
||||
await user.clearLock();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,80 +1,96 @@
|
||||
import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
|
||||
import User from "user";
|
||||
import { type userType, type specialExecuteArgs } from "lib/commandUtils";
|
||||
import type { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
|
||||
import type { specialExecuteArgs, userType } from "lib/commandUtils";
|
||||
import type User from "user";
|
||||
|
||||
type itemOptions = {
|
||||
name: items;
|
||||
aliases: string[];
|
||||
prettyName: string;
|
||||
plural: string;
|
||||
description: string;
|
||||
execution: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>;
|
||||
specialaliases?: string[];
|
||||
price: number;
|
||||
name: items;
|
||||
aliases: string[];
|
||||
prettyName: string;
|
||||
plural: string;
|
||||
description: string;
|
||||
execution: (
|
||||
message: EventSubChannelChatMessageEvent,
|
||||
sender: User,
|
||||
args?: specialExecuteArgs,
|
||||
) => Promise<void>;
|
||||
specialaliases?: string[];
|
||||
price: number;
|
||||
};
|
||||
|
||||
export class Item {
|
||||
public readonly name: items;
|
||||
public readonly prettyName: string;
|
||||
public readonly plural: string;
|
||||
public readonly description: string;
|
||||
public readonly aliases: string[];
|
||||
public readonly specialaliases: string[];
|
||||
public readonly usertype: userType;
|
||||
public readonly price: number;
|
||||
public readonly execute: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>;
|
||||
public readonly disableable: boolean;
|
||||
public readonly name: items;
|
||||
public readonly prettyName: string;
|
||||
public readonly plural: string;
|
||||
public readonly description: string;
|
||||
public readonly aliases: string[];
|
||||
public readonly specialaliases: string[];
|
||||
public readonly usertype: userType;
|
||||
public readonly price: number;
|
||||
public readonly execute: (
|
||||
message: EventSubChannelChatMessageEvent,
|
||||
sender: User,
|
||||
args?: specialExecuteArgs,
|
||||
) => Promise<void>;
|
||||
public readonly disableable: boolean;
|
||||
|
||||
/** Creates an item object */
|
||||
constructor(options: itemOptions) {
|
||||
this.name = options.name;
|
||||
this.prettyName = options.prettyName;
|
||||
this.plural = options.plural;
|
||||
this.description = options.description;
|
||||
this.aliases = options.aliases;
|
||||
this.usertype = 'chatter'; // Items are usable by everyone
|
||||
this.execute = options.execution;
|
||||
this.disableable = true;
|
||||
this.specialaliases = options.specialaliases ?? [];
|
||||
this.price = options.price;
|
||||
};
|
||||
};
|
||||
/** Creates an item object */
|
||||
constructor(options: itemOptions) {
|
||||
this.name = options.name;
|
||||
this.prettyName = options.prettyName;
|
||||
this.plural = options.plural;
|
||||
this.description = options.description;
|
||||
this.aliases = options.aliases;
|
||||
this.usertype = "chatter"; // Items are usable by everyone
|
||||
this.execute = options.execution;
|
||||
this.disableable = true;
|
||||
this.specialaliases = options.specialaliases ?? [];
|
||||
this.price = options.price;
|
||||
}
|
||||
}
|
||||
|
||||
import { readdir } from 'node:fs/promises';
|
||||
import { updateUserRecord, type UserRecord } from "db/dbUser";
|
||||
const itemAliasMap = new Map<string, Item>;
|
||||
const itemObjectArray: Item[] = []
|
||||
const specialAliasItems = new Map<string, Item>;
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { type UserRecord, updateUserRecord } from "db/dbUser";
|
||||
|
||||
const itemAliasMap = new Map<string, Item>();
|
||||
const itemObjectArray: Item[] = [];
|
||||
const specialAliasItems = new Map<string, Item>();
|
||||
const emptyInventory: inventory = {};
|
||||
const itemarray: items[] = [];
|
||||
|
||||
const files = await readdir(import.meta.dir);
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.ts')) continue;
|
||||
if (file === import.meta.file) continue;
|
||||
const item: Item = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default);
|
||||
emptyInventory[item.name] = 0;
|
||||
itemarray.push(item.name);
|
||||
itemObjectArray.push(item);
|
||||
for (const alias of item.aliases) {
|
||||
itemAliasMap.set(alias, item); // Since it's not a primitive type the map is filled with references to the item, not the actual object
|
||||
};
|
||||
for (const alias of item.specialaliases) {
|
||||
specialAliasItems.set(alias, item);
|
||||
};
|
||||
};
|
||||
if (!file.endsWith(".ts")) continue;
|
||||
if (file === import.meta.file) continue;
|
||||
const item: Item = await import(
|
||||
`${import.meta.dir}/${file.slice(0, -3)}`
|
||||
).then((a) => a.default);
|
||||
emptyInventory[item.name] = 0;
|
||||
itemarray.push(item.name);
|
||||
itemObjectArray.push(item);
|
||||
for (const alias of item.aliases) {
|
||||
itemAliasMap.set(alias, item); // Since it's not a primitive type the map is filled with references to the item, not the actual object
|
||||
}
|
||||
for (const alias of item.specialaliases) {
|
||||
specialAliasItems.set(alias, item);
|
||||
}
|
||||
}
|
||||
|
||||
export default itemAliasMap;
|
||||
export { emptyInventory, itemarray, specialAliasItems, itemObjectArray };
|
||||
|
||||
export type items = "blaster" | "silverbullet" | "grenade" | "tnt";
|
||||
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> {
|
||||
userRecord.inventory[itemname] = userRecord.inventory[itemname]! += amount;
|
||||
if (userRecord.inventory[itemname] < 0) return false;
|
||||
await updateUserRecord(user, userRecord);
|
||||
return userRecord;
|
||||
};
|
||||
export async function changeItemCount(
|
||||
user: User,
|
||||
userRecord: UserRecord,
|
||||
itemname: items,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,88 +1,116 @@
|
||||
import { changeItemCount, Item } from "items";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import { createUsedItemRecord } from "db/dbUsedItems";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { changeItemCount, Item } from "items";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
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 { timeout } from "lib/timeout";
|
||||
import { streamerId } from "main";
|
||||
import User from "user";
|
||||
import { playAlert } from "web/alerts/serverFunctions";
|
||||
|
||||
const ITEMNAME = 'silverbullet';
|
||||
const ITEMNAME = "silverbullet";
|
||||
|
||||
export default new Item({
|
||||
name: ITEMNAME,
|
||||
prettyName: 'Silver bullet',
|
||||
plural: 's',
|
||||
description: 'Times targeted or random vulnerable user out for 30 minutes',
|
||||
aliases: ['execute', 'silverbullet'],
|
||||
specialaliases: ['blastin'],
|
||||
price: 666,
|
||||
execution: async (msg, user, specialargs) => {
|
||||
const messagequery = parseCommandArgs(msg.messageText, specialargs?.activation);
|
||||
name: ITEMNAME,
|
||||
prettyName: "Silver bullet",
|
||||
plural: "s",
|
||||
description: "Times targeted or random vulnerable user out for 30 minutes",
|
||||
aliases: ["execute", "silverbullet"],
|
||||
specialaliases: ["blastin"],
|
||||
price: 666,
|
||||
execution: async (msg, user, specialargs) => {
|
||||
const messagequery = parseCommandArgs(
|
||||
msg.messageText,
|
||||
specialargs?.activation,
|
||||
);
|
||||
|
||||
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; };
|
||||
await user.setLock();
|
||||
if (await user.itemLock()) {
|
||||
await sendMessage("Cannot use an item (itemlock)", msg.messageId);
|
||||
return;
|
||||
}
|
||||
await user.setLock();
|
||||
|
||||
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; };
|
||||
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;
|
||||
}
|
||||
|
||||
let target: User | null;
|
||||
if (!messagequery[0]) {
|
||||
const vulnsids = await redis.keys('user:*:vulnerable');
|
||||
const baseusers = vulnsids.map(a => User.initUserId(a.slice(5, -11)));
|
||||
const users: User[] = [];
|
||||
for (const user of baseusers) {
|
||||
const a = await user;
|
||||
if (!a) continue;
|
||||
users.push(a);
|
||||
};
|
||||
if (users.length === 0) { await user.clearLock(); await sendMessage('No vulnerable chatters', msg.messageId); return; };
|
||||
target = users[Math.floor(Math.random() * users.length)]!;
|
||||
await playAlert({
|
||||
name: 'blastinRoulette',
|
||||
user: user.displayName,
|
||||
targets: users.map(a => a.displayName),
|
||||
finaltarget: target.displayName
|
||||
});
|
||||
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; };
|
||||
let target: User | null;
|
||||
if (!messagequery[0]) {
|
||||
const vulnsids = await redis.keys("user:*:vulnerable");
|
||||
const baseusers = vulnsids.map((a) => User.initUserId(a.slice(5, -11)));
|
||||
const users: User[] = [];
|
||||
for (const user of baseusers) {
|
||||
const a = await user;
|
||||
if (!a) continue;
|
||||
users.push(a);
|
||||
}
|
||||
if (users.length === 0) {
|
||||
await user.clearLock();
|
||||
await sendMessage("No vulnerable chatters", msg.messageId);
|
||||
return;
|
||||
}
|
||||
target = users[Math.floor(Math.random() * users.length)]!;
|
||||
await playAlert({
|
||||
name: "blastinRoulette",
|
||||
user: user.displayName,
|
||||
targets: users.map((a) => a.displayName),
|
||||
finaltarget: target.displayName,
|
||||
});
|
||||
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);
|
||||
if (result.status) await Promise.all([
|
||||
sendMessage(`KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`),
|
||||
changeItemCount(user, userObj, ITEMNAME),
|
||||
createTimeoutRecord(user, target, ITEMNAME),
|
||||
createUsedItemRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: 'userExecution',
|
||||
user: user.displayName,
|
||||
target: target.displayName
|
||||
})
|
||||
]);
|
||||
else {
|
||||
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;
|
||||
};
|
||||
};
|
||||
await user.clearLock();
|
||||
}
|
||||
const result = await timeout(
|
||||
target,
|
||||
`You got blasted 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`,
|
||||
),
|
||||
changeItemCount(user, userObj, ITEMNAME),
|
||||
createTimeoutRecord(user, target, ITEMNAME),
|
||||
createUsedItemRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: "userExecution",
|
||||
user: user.displayName,
|
||||
target: target.displayName,
|
||||
}),
|
||||
]);
|
||||
else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
await user.clearLock();
|
||||
},
|
||||
});
|
||||
|
||||
116
src/items/tnt.ts
116
src/items/tnt.ts
@@ -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 { 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";
|
||||
|
||||
const ITEMNAME = 'tnt';
|
||||
const ITEMNAME = "tnt";
|
||||
|
||||
export default new Item({
|
||||
name: ITEMNAME,
|
||||
prettyName: 'TNT',
|
||||
plural: 's',
|
||||
description: 'Give 5-10 random chatters 60 second timeouts',
|
||||
aliases: ['tnt'],
|
||||
price: 1000,
|
||||
execution: async (msg, user) => {
|
||||
const vulntargets = await redis.keys('user:*:vulnerable').then(a => a.map(b => b.slice(5, -11)));
|
||||
if (vulntargets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; };
|
||||
const targets = getTNTTargets(vulntargets);
|
||||
name: ITEMNAME,
|
||||
prettyName: "TNT",
|
||||
plural: "s",
|
||||
description: "Give 5-10 random chatters 60 second timeouts",
|
||||
aliases: ["tnt"],
|
||||
price: 1000,
|
||||
execution: async (msg, user) => {
|
||||
const vulntargets = await redis
|
||||
.keys("user:*:vulnerable")
|
||||
.then((a) => a.map((b) => b.slice(5, -11)));
|
||||
if (vulntargets.length === 0) {
|
||||
await sendMessage("No vulnerable chatters to blow up", msg.messageId);
|
||||
return;
|
||||
}
|
||||
const targets = getTNTTargets(vulntargets);
|
||||
|
||||
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; };
|
||||
await user.setLock();
|
||||
if (await user.itemLock()) {
|
||||
await sendMessage("Cannot use an item (itemlock)", msg.messageId);
|
||||
return;
|
||||
}
|
||||
await user.setLock();
|
||||
|
||||
const userObj = await getUserRecord(user);
|
||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any TNTs!`, msg.messageId); await user.clearLock(); return; };
|
||||
const userObj = await getUserRecord(user);
|
||||
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 => {
|
||||
const target = await User.initUserId(targetid);
|
||||
await getUserRecord(target!); // make sure the user record exist in the database
|
||||
await Promise.all([
|
||||
timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60),
|
||||
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`),
|
||||
createTimeoutRecord(user, target!, ITEMNAME),
|
||||
]);
|
||||
}));
|
||||
await Promise.all(
|
||||
targets.map(async (targetid) => {
|
||||
const target = await User.initUserId(targetid);
|
||||
await getUserRecord(target!); // make sure the user record exist in the database
|
||||
await Promise.all([
|
||||
timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60),
|
||||
sendMessage(
|
||||
`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`,
|
||||
),
|
||||
createTimeoutRecord(user, target!, ITEMNAME),
|
||||
]);
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
createUsedItemRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: 'tntExplosion',
|
||||
user: user.displayName,
|
||||
targets
|
||||
}),
|
||||
changeItemCount(user, userObj, ITEMNAME)
|
||||
]);
|
||||
await Promise.all([
|
||||
createUsedItemRecord(user, ITEMNAME),
|
||||
playAlert({
|
||||
name: "tntExplosion",
|
||||
user: user.displayName,
|
||||
targets,
|
||||
}),
|
||||
changeItemCount(user, userObj, ITEMNAME),
|
||||
]);
|
||||
|
||||
await user.clearLock();
|
||||
await sendMessage(`RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? '' : 's'} with their TNT RIPBOZO`);
|
||||
}
|
||||
await user.clearLock();
|
||||
await sendMessage(
|
||||
`RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? "" : "s"} with their TNT RIPBOZO`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export function getTNTTargets<T>(arr: T[]): T[] {
|
||||
if (arr.length <= 5) {
|
||||
return arr;
|
||||
};
|
||||
if (arr.length <= 5) {
|
||||
return arr;
|
||||
}
|
||||
|
||||
const count = Math.floor(Math.random() * 6) + 5; // Random number between 5 and 10
|
||||
const shuffled = [...arr].sort(() => 0.5 - Math.random()); // Shuffle array
|
||||
return shuffled.slice(0, Math.min(count, arr.length)); // Return up to `count` entries
|
||||
};
|
||||
const count = Math.floor(Math.random() * 6) + 5; // Random number between 5 and 10
|
||||
const shuffled = [...arr].sort(() => 0.5 - Math.random()); // Shuffle array
|
||||
return shuffled.slice(0, Math.min(count, arr.length)); // Return up to `count` entries
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { redis } from "lib/redis";
|
||||
|
||||
export async function getAdmins() {
|
||||
const data = await redis.keys('user:*:admin');
|
||||
return data.map(a => a.slice(5, -6));
|
||||
};
|
||||
const data = await redis.keys("user:*:admin");
|
||||
return data.map((a) => a.slice(5, -6));
|
||||
}
|
||||
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) {
|
||||
return await redis.set(`user:${userid}:admin`, '1');
|
||||
};
|
||||
return await redis.set(`user:${userid}:admin`, "1");
|
||||
}
|
||||
export async function removeAdmin(userid: string) {
|
||||
return await redis.del(`user:${userid}:admin`);
|
||||
};
|
||||
return await redis.del(`user:${userid}:admin`);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { updateUserRecord, type UserRecord } from "db/dbUser";
|
||||
import User from "user";
|
||||
import { type UserRecord, updateUserRecord } from "db/dbUser";
|
||||
import type User from "user";
|
||||
|
||||
export async function changeBalance(user: User, userRecord: UserRecord, amount: number): Promise<false | UserRecord> {
|
||||
userRecord.balance = userRecord.balance += amount;
|
||||
if (userRecord.balance < 0) return false;
|
||||
await updateUserRecord(user, userRecord);
|
||||
return userRecord;
|
||||
};
|
||||
export async function changeBalance(
|
||||
user: User,
|
||||
userRecord: UserRecord,
|
||||
amount: number,
|
||||
): Promise<false | UserRecord> {
|
||||
userRecord.balance = userRecord.balance += amount;
|
||||
if (userRecord.balance < 0) return false;
|
||||
await updateUserRecord(user, userRecord);
|
||||
return userRecord;
|
||||
}
|
||||
|
||||
@@ -1,46 +1,59 @@
|
||||
import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
|
||||
import User from "user";
|
||||
import { chatterId, streamerId } from "main";
|
||||
import type { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
|
||||
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 = {
|
||||
activation?: string;
|
||||
activation?: string;
|
||||
};
|
||||
|
||||
export type commandOptions = {
|
||||
name: string;
|
||||
aliases: string[];
|
||||
usertype: userType;
|
||||
execution: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>;
|
||||
disableable?: boolean;
|
||||
specialaliases?: string[];
|
||||
name: string;
|
||||
aliases: string[];
|
||||
usertype: userType;
|
||||
execution: (
|
||||
message: EventSubChannelChatMessageEvent,
|
||||
sender: User,
|
||||
args?: specialExecuteArgs,
|
||||
) => Promise<void>;
|
||||
disableable?: boolean;
|
||||
specialaliases?: string[];
|
||||
};
|
||||
|
||||
/** The Command class represents a command */
|
||||
export class Command {
|
||||
public readonly name: string;
|
||||
public readonly aliases: string[];
|
||||
public readonly usertype: userType;
|
||||
public readonly disableable: boolean;
|
||||
public readonly specialaliases: string[];
|
||||
public readonly execute: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>;
|
||||
constructor(options: commandOptions) {
|
||||
this.name = options.name.toLowerCase();
|
||||
this.aliases = options.aliases;
|
||||
this.usertype = options.usertype;
|
||||
this.execute = options.execution;
|
||||
this.disableable = options.disableable ?? true;
|
||||
this.specialaliases = options.specialaliases ?? [];
|
||||
};
|
||||
};
|
||||
public readonly name: string;
|
||||
public readonly aliases: string[];
|
||||
public readonly usertype: userType;
|
||||
public readonly disableable: boolean;
|
||||
public readonly specialaliases: string[];
|
||||
public readonly execute: (
|
||||
message: EventSubChannelChatMessageEvent,
|
||||
sender: User,
|
||||
args?: specialExecuteArgs,
|
||||
) => Promise<void>;
|
||||
constructor(options: commandOptions) {
|
||||
this.name = options.name.toLowerCase();
|
||||
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 */
|
||||
export const sendMessage = async (message: string, replyParentMessageId?: string) => {
|
||||
try {
|
||||
return await api.chat.sendChatMessageAsApp(chatterId, streamerId, message, { replyParentMessageId });
|
||||
} catch (e) {
|
||||
return await api.chat.sendChatMessageAsApp(chatterId, streamerId, message);
|
||||
};
|
||||
export const sendMessage = async (
|
||||
message: string,
|
||||
replyParentMessageId?: string,
|
||||
) => {
|
||||
try {
|
||||
return await api.chat.sendChatMessageAsApp(chatterId, streamerId, message, {
|
||||
replyParentMessageId,
|
||||
});
|
||||
} catch (_e) {
|
||||
return await api.chat.sendChatMessageAsApp(chatterId, streamerId, message);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
export function buildTimeString(time1: number, time2: number): string {
|
||||
const diff = Math.abs(time1 - time2);
|
||||
const timeobj = {
|
||||
day: Math.floor(diff / (1000 * 60 * 60 * 24)),
|
||||
hour: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
|
||||
minute: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
|
||||
second: Math.floor((diff % (1000 * 60)) / 1000)
|
||||
};
|
||||
const stringarray: string[] = [];
|
||||
for (const [unit, value] of Object.entries(timeobj)) {
|
||||
if (value === 0) continue;
|
||||
if (unit === 'second' && timeobj.day > 0) continue;
|
||||
stringarray.push(`${value} ${unit}${value === 1 ? '' : 's'}`);
|
||||
};
|
||||
const last = stringarray.pop();
|
||||
return stringarray.length === 0 ? last! : stringarray.join(', ') + " and " + last;
|
||||
};
|
||||
const diff = Math.abs(time1 - time2);
|
||||
const timeobj = {
|
||||
day: Math.floor(diff / (1000 * 60 * 60 * 24)),
|
||||
hour: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
|
||||
minute: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
|
||||
second: Math.floor((diff % (1000 * 60)) / 1000),
|
||||
};
|
||||
const stringarray: string[] = [];
|
||||
for (const [unit, value] of Object.entries(timeobj)) {
|
||||
if (value === 0) continue;
|
||||
if (unit === "second" && timeobj.day > 0) continue;
|
||||
stringarray.push(`${value} ${unit}${value === 1 ? "" : "s"}`);
|
||||
}
|
||||
const last = stringarray.pop();
|
||||
return stringarray.length === 0
|
||||
? last!
|
||||
: `${stringarray.join(", ")} and ${last}`;
|
||||
}
|
||||
|
||||
@@ -5,57 +5,63 @@ import type { inventory } from "items";
|
||||
import type User from "user";
|
||||
|
||||
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([
|
||||
getTimeoutsAsUser(target, monthdata),
|
||||
getTimeoutsAsTarget(target, monthdata)
|
||||
]);
|
||||
const [shot, hit] = await Promise.all([
|
||||
getTimeoutsAsUser(target, monthdata),
|
||||
getTimeoutsAsTarget(target, monthdata),
|
||||
]);
|
||||
|
||||
if (!shot || !hit) return;
|
||||
if (!shot || !hit) return;
|
||||
|
||||
const blasterhit = hit.filter(item => item.item !== 'silverbullet').length;
|
||||
const silverbullethit = hit.length - blasterhit;
|
||||
const blastershot = shot.filter(item => item.item !== 'silverbullet').length;
|
||||
const silverbulletshot = shot.length - blastershot;
|
||||
const blasterhit = hit.filter((item) => item.item !== "silverbullet").length;
|
||||
const silverbullethit = hit.length - blasterhit;
|
||||
const blastershot = shot.filter(
|
||||
(item) => item.item !== "silverbullet",
|
||||
).length;
|
||||
const silverbulletshot = shot.length - blastershot;
|
||||
|
||||
return {
|
||||
hit: {
|
||||
blaster: blasterhit,
|
||||
silverbullet: silverbullethit
|
||||
},
|
||||
shot: {
|
||||
blaster: blastershot,
|
||||
silverbullet: silverbulletshot
|
||||
}
|
||||
};
|
||||
};
|
||||
return {
|
||||
hit: {
|
||||
blaster: blasterhit,
|
||||
silverbullet: silverbullethit,
|
||||
},
|
||||
shot: {
|
||||
blaster: blastershot,
|
||||
silverbullet: silverbulletshot,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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([
|
||||
getItemsUsed(target, monthdata),
|
||||
getCheerEvents(target, monthdata)
|
||||
]);
|
||||
if (!items || !cheers) return;
|
||||
const [items, cheers] = await Promise.all([
|
||||
getItemsUsed(target, monthdata),
|
||||
getCheerEvents(target, monthdata),
|
||||
]);
|
||||
if (!items || !cheers) return;
|
||||
|
||||
const returnObj: inventory = {
|
||||
blaster: 0,
|
||||
silverbullet: 0,
|
||||
grenade: 0,
|
||||
tnt: 0,
|
||||
};
|
||||
const returnObj: inventory = {
|
||||
blaster: 0,
|
||||
silverbullet: 0,
|
||||
grenade: 0,
|
||||
tnt: 0,
|
||||
};
|
||||
|
||||
for (const item of items) {
|
||||
if (!returnObj[item.item]) returnObj[item.item] = 0;
|
||||
returnObj[item.item]! += 1;
|
||||
};
|
||||
for (const item of items) {
|
||||
if (!returnObj[item.item]) returnObj[item.item] = 0;
|
||||
returnObj[item.item]! += 1;
|
||||
}
|
||||
|
||||
for (const cheer of cheers) {
|
||||
if (!returnObj[cheer.event]) returnObj[cheer.event] = 0;
|
||||
returnObj[cheer.event]! += 1
|
||||
};
|
||||
for (const cheer of cheers) {
|
||||
if (!returnObj[cheer.event]) returnObj[cheer.event] = 0;
|
||||
returnObj[cheer.event]! += 1;
|
||||
}
|
||||
|
||||
return returnObj;
|
||||
};
|
||||
return returnObj;
|
||||
}
|
||||
|
||||
@@ -1,60 +1,71 @@
|
||||
import { 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 type { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base";
|
||||
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 = {
|
||||
[key: string]: string;
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
type IsAnivMessage = {
|
||||
isAnivMessage: true;
|
||||
message: string;
|
||||
anivbot: anivBots;
|
||||
isAnivMessage: true;
|
||||
message: string;
|
||||
anivbot: anivBots;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
async function isAnivMessage(message: string): Promise<anivMessageResult> {
|
||||
const data: anivMessageStore = await redis.get('anivmessages').then(a => a === null ? {} : JSON.parse(a));
|
||||
for (const clanker of ANIVNAMES) {
|
||||
const anivmessage = data[clanker];
|
||||
if (!anivmessage) continue;
|
||||
if (anivmessage === message) return { isAnivMessage: true, message, anivbot: clanker };
|
||||
};
|
||||
return { isAnivMessage: false };
|
||||
};
|
||||
const data: anivMessageStore = await redis
|
||||
.get("anivmessages")
|
||||
.then((a) => (a === null ? {} : JSON.parse(a)));
|
||||
for (const clanker of ANIVNAMES) {
|
||||
const anivmessage = data[clanker];
|
||||
if (!anivmessage) continue;
|
||||
if (anivmessage === message)
|
||||
return { isAnivMessage: true, message, anivbot: clanker };
|
||||
}
|
||||
return { isAnivMessage: false };
|
||||
}
|
||||
|
||||
export default async function handleMessage(msg: EventSubChannelChatMessageEvent, user: User) {
|
||||
if (ANIVNAMES.map(a => a.toLowerCase()).includes(user.username)) {
|
||||
const data: anivMessageStore = await redis.get('anivmessages').then(a => a === null ? {} : JSON.parse(a));
|
||||
data[user.displayName] = msg.messageText;
|
||||
await redis.set('anivmessages', JSON.stringify(data));
|
||||
} 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;
|
||||
};
|
||||
export default async function handleMessage(
|
||||
msg: EventSubChannelChatMessageEvent,
|
||||
user: User,
|
||||
) {
|
||||
if (ANIVNAMES.map((a) => a.toLowerCase()).includes(user.username)) {
|
||||
const data: anivMessageStore = await redis
|
||||
.get("anivmessages")
|
||||
.then((a) => (a === null ? {} : JSON.parse(a)));
|
||||
data[user.displayName] = msg.messageText;
|
||||
await redis.set("anivmessages", JSON.stringify(data));
|
||||
} 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([
|
||||
timeout(user, 'copied an aniv message', 30),
|
||||
sendMessage(`${user.displayName} got timed out for copying an ${data.anivbot} message`),
|
||||
createAnivTimeoutRecord(msg.messageText, data.anivbot, user, duration)
|
||||
]);
|
||||
};
|
||||
};
|
||||
};
|
||||
await Promise.all([
|
||||
timeout(user, "copied an aniv message", 30),
|
||||
sendMessage(
|
||||
`${user.displayName} got timed out for copying an ${data.anivbot} message`,
|
||||
),
|
||||
createAnivTimeoutRecord(msg.messageText, data.anivbot, user, duration),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,23 @@ import { redis } from "lib/redis";
|
||||
import { streamerUsers } from "main";
|
||||
|
||||
export async function getInvulns() {
|
||||
const data = await redis.keys('user:*:invulnerable');
|
||||
return data.map(a => a.slice(5, -13));
|
||||
};
|
||||
const data = await redis.keys("user:*:invulnerable");
|
||||
return data.map((a) => a.slice(5, -13));
|
||||
}
|
||||
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) {
|
||||
await redis.del(`user:${userid}:vulnerable`);
|
||||
if (await redis.ttl(`user:${userid}:invulnerable`) > 0) await redis.del(`user:${userid}:invulnerable`);
|
||||
return await redis.set(`user:${userid}:invulnerable`, '1');
|
||||
};
|
||||
await redis.del(`user:${userid}:vulnerable`);
|
||||
if ((await redis.ttl(`user:${userid}:invulnerable`)) > 0)
|
||||
await redis.del(`user:${userid}:invulnerable`);
|
||||
return await redis.set(`user:${userid}:invulnerable`, "1");
|
||||
}
|
||||
export async function removeInvuln(userid: string) {
|
||||
if (streamerUsers.includes(userid)) return;
|
||||
return await redis.del(`user:${userid}:invulnerable`);
|
||||
};
|
||||
if (streamerUsers.includes(userid)) return;
|
||||
return await redis.del(`user:${userid}:invulnerable`);
|
||||
}
|
||||
export async function setTemporaryInvuln(userid: string, duration = 600) {
|
||||
await redis.set(`user:${userid}:invulnerable`, '1');
|
||||
await redis.expire(`user:${userid}:invulnerable`, duration);
|
||||
};
|
||||
await redis.set(`user:${userid}:invulnerable`, "1");
|
||||
await redis.expire(`user:${userid}:invulnerable`, duration);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import kleur from "kleur";
|
||||
|
||||
const logger = {
|
||||
err: (arg: string) => console.error(kleur.red().bold().italic('[ERROR] ') + kleur.red().bold(arg)),
|
||||
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`)
|
||||
err: (arg: string) =>
|
||||
console.error(
|
||||
kleur.red().bold().italic("[ERROR] ") + kleur.red().bold(arg),
|
||||
),
|
||||
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;
|
||||
|
||||
@@ -2,24 +2,40 @@ import { commandPrefix } from "main";
|
||||
|
||||
/** Helper function to extract arguments from commands */
|
||||
export default function parseCommandArgs(input: string, specialAlias?: string) {
|
||||
let nice = '';
|
||||
let sliceLength = 0;
|
||||
if (specialAlias) {
|
||||
nice = input.toLowerCase().slice(specialAlias.length).replace(/[^\x00-\x7F]/g, '').trim();
|
||||
sliceLength = input.toLowerCase().startsWith('i') ? 1 : 0;
|
||||
} 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, ''));
|
||||
};
|
||||
let nice = "";
|
||||
let sliceLength = 0;
|
||||
if (specialAlias) {
|
||||
nice = input
|
||||
.toLowerCase()
|
||||
.slice(specialAlias.length)
|
||||
.replace(/[^\x00-\x7F]/g, "")
|
||||
.trim();
|
||||
sliceLength = input.toLowerCase().startsWith("i") ? 1 : 0;
|
||||
} 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) {
|
||||
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
|
||||
if (nice.startsWith(commandPrefix + 'testcheer')) return nice.slice(commandPrefix.length + 'testcheer'.length + 1).replace(/[^\x00-\x7F]/g, '').split(' ').slice(1);
|
||||
// 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);
|
||||
|
||||
// This is for actual cheers. Remove all 'cheerx' parts of the message
|
||||
return nice.split(' ').filter(a => !/cheer[0-9]+/i.test(a));
|
||||
};
|
||||
// This is for actual cheers. Remove all 'cheerx' parts of the message
|
||||
return nice.split(" ").filter((a) => !/cheer[0-9]+/i.test(a));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { RedisClient } from "bun";
|
||||
|
||||
export const redis = new RedisClient();
|
||||
export const redis = new RedisClient();
|
||||
|
||||
@@ -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 { isInvuln } from "lib/invuln";
|
||||
import logger from "lib/logger";
|
||||
import { redis } from "lib/redis";
|
||||
import { streamerId } from "main";
|
||||
import type User from "user";
|
||||
|
||||
type SuccessfulTimeout = { status: true; };
|
||||
type UnSuccessfulTimeout = { status: false; reason: 'banned' | 'unknown' | 'illegal'; };
|
||||
type SuccessfulTimeout = { status: true };
|
||||
type UnSuccessfulTimeout = {
|
||||
status: false;
|
||||
reason: "banned" | "unknown" | "illegal";
|
||||
};
|
||||
type TimeoutResult = SuccessfulTimeout | UnSuccessfulTimeout;
|
||||
|
||||
/** Give a user a timeout/ban
|
||||
* @param user - user class of target to timeout/ban
|
||||
* @param reason - reason for timeout/ban
|
||||
* @param duration - duration of timeout. don't specifiy for ban */
|
||||
export const timeout = async (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
|
||||
export const timeout = async (
|
||||
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
|
||||
const banStatus = await timeoutDuration(user);
|
||||
if (banStatus) {
|
||||
if (await redis.exists('timeoutStacking')) {
|
||||
if (duration) duration += Math.floor((banStatus * 1000 - Date.now()) / 1000); // the target is timed out and stacking is on
|
||||
} else return { status: false, reason: 'banned' }; // the target is timed out, but stacking is off
|
||||
} else if (banStatus === null) return { status: false, reason: 'banned' }; // target is perma banned
|
||||
// Check if user already has a timeout and handle stacking
|
||||
const banStatus = await timeoutDuration(user);
|
||||
if (banStatus) {
|
||||
if (await redis.exists("timeoutStacking")) {
|
||||
if (duration)
|
||||
duration += Math.floor((banStatus * 1000 - Date.now()) / 1000); // the target is timed out and stacking is on
|
||||
} else return { status: false, reason: "banned" }; // the target is timed out, but stacking is off
|
||||
} else if (banStatus === null) return { status: false, reason: "banned" }; // target is perma banned
|
||||
|
||||
if (await redis.exists(`user:${user.id}:mod`)) {
|
||||
if (!duration) duration = 60; // make sure that mods don't get perma-banned
|
||||
await redis.set(`user:${user.id}:remod`, '1');
|
||||
remodMod(user, duration);
|
||||
await api.moderation.removeModerator(streamerId, user.id!);
|
||||
};
|
||||
if (await redis.exists(`user:${user.id}:mod`)) {
|
||||
if (!duration) duration = 60; // make sure that mods don't get perma-banned
|
||||
await redis.set(`user:${user.id}:remod`, "1");
|
||||
remodMod(user, duration);
|
||||
await api.moderation.removeModerator(streamerId, user.id!);
|
||||
}
|
||||
|
||||
try {
|
||||
await api.moderation.banUser(streamerId, { user: user.id, reason, duration });
|
||||
} catch (err) {
|
||||
logger.err(err as string);
|
||||
return { status: false, reason: 'unknown' };
|
||||
};
|
||||
try {
|
||||
await api.moderation.banUser(streamerId, {
|
||||
user: user.id,
|
||||
reason,
|
||||
duration,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.err(err as string);
|
||||
return { status: false, reason: "unknown" };
|
||||
}
|
||||
|
||||
await user.clearVulnerable();
|
||||
await redis.set(`user:${user.id}:timeout`, '1');
|
||||
if (duration) await redis.expire(`user:${user.id}:timeout`, duration);
|
||||
await user.clearVulnerable();
|
||||
await redis.set(`user:${user.id}:timeout`, "1");
|
||||
if (duration) await redis.expire(`user:${user.id}:timeout`, duration);
|
||||
|
||||
return { status: true };
|
||||
return { status: true };
|
||||
};
|
||||
|
||||
/** Give the target mod status back after timeout */
|
||||
export function remodMod(target: User, duration: number) {
|
||||
setTimeout(async () => {
|
||||
const bandata = await timeoutDuration(target);
|
||||
if (bandata) { // If the target is still timed out, try again when new timeout expires
|
||||
const timeoutleft = bandata * 1000 - Date.now(); // date when timeout expires - current date
|
||||
remodMod(target, timeoutleft); // Call the current function with new time (recursion)
|
||||
} else {
|
||||
try {
|
||||
await api.moderation.addModerator(streamerId, target.id);
|
||||
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
|
||||
};
|
||||
setTimeout(async () => {
|
||||
const bandata = await timeoutDuration(target);
|
||||
if (bandata) {
|
||||
// If the target is still timed out, try again when new timeout expires
|
||||
const timeoutleft = bandata * 1000 - Date.now(); // date when timeout expires - current date
|
||||
remodMod(target, timeoutleft); // Call the current function with new time (recursion)
|
||||
} else {
|
||||
try {
|
||||
await api.moderation.addModerator(streamerId, target.id);
|
||||
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
|
||||
}
|
||||
|
||||
/** This returns number if there is a duration of time for the timeout, false if not banned and null if perma banned */
|
||||
export async function timeoutDuration(user: User): Promise<number | null | false> {
|
||||
const data = await redis.expiretime(`user:${user.id}:timeout`);
|
||||
if (data === -1) return null; // Perma banned
|
||||
else if (data === -2) return false; // Not banned
|
||||
return data;
|
||||
};
|
||||
export async function timeoutDuration(
|
||||
user: User,
|
||||
): Promise<number | null | false> {
|
||||
const data = await redis.expiretime(`user:${user.id}:timeout`);
|
||||
if (data === -1)
|
||||
return null; // Perma banned
|
||||
else if (data === -2) return false; // Not banned
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -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 streamerId = process.env.STREAMER_ID ?? "";
|
||||
export const commandPrefix = process.env.COMMAND_PREFIX ?? "!";
|
||||
|
||||
@@ -1,112 +1,123 @@
|
||||
import { EventSubChannelRedemptionAddEvent } from "@twurple/eventsub-base";
|
||||
import User from "user";
|
||||
import type { EventSubChannelRedemptionAddEvent } from "@twurple/eventsub-base";
|
||||
import type User from "user";
|
||||
|
||||
export type pointRedeemOptions = {
|
||||
name: string;
|
||||
title: string;
|
||||
prompt?: string;
|
||||
cost: number;
|
||||
color?: string;
|
||||
sfxredeem?: boolean;
|
||||
input?: boolean;
|
||||
execution: (message: EventSubChannelRedemptionAddEvent, sender: User) => Promise<void>;
|
||||
name: string;
|
||||
title: string;
|
||||
prompt?: string;
|
||||
cost: number;
|
||||
color?: string;
|
||||
sfxredeem?: boolean;
|
||||
input?: boolean;
|
||||
execution: (
|
||||
message: EventSubChannelRedemptionAddEvent,
|
||||
sender: User,
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
/** The Command class represents a command */
|
||||
export default class PointRedeem {
|
||||
public readonly name: string;
|
||||
public readonly title: string;
|
||||
public readonly prompt?: string;
|
||||
public readonly cost: number;
|
||||
public readonly color?: string;
|
||||
public readonly sfxredeem?: boolean;
|
||||
public readonly input?: boolean;
|
||||
public readonly execute: (message: EventSubChannelRedemptionAddEvent, sender: User) => Promise<void>;
|
||||
constructor(options: pointRedeemOptions) {
|
||||
this.name = options.name.toLowerCase();
|
||||
this.title = options.title;
|
||||
this.prompt = options.prompt;
|
||||
this.cost = options.cost;
|
||||
this.color = options.color;
|
||||
this.execute = options.execution;
|
||||
this.sfxredeem = options.sfxredeem;
|
||||
this.input = options.input;
|
||||
};
|
||||
};
|
||||
public readonly name: string;
|
||||
public readonly title: string;
|
||||
public readonly prompt?: string;
|
||||
public readonly cost: number;
|
||||
public readonly color?: string;
|
||||
public readonly sfxredeem?: boolean;
|
||||
public readonly input?: boolean;
|
||||
public readonly execute: (
|
||||
message: EventSubChannelRedemptionAddEvent,
|
||||
sender: User,
|
||||
) => Promise<void>;
|
||||
constructor(options: pointRedeemOptions) {
|
||||
this.name = options.name.toLowerCase();
|
||||
this.title = options.title;
|
||||
this.prompt = options.prompt;
|
||||
this.cost = options.cost;
|
||||
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 */
|
||||
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);
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.ts')) continue;
|
||||
if (file === import.meta.file) continue;
|
||||
const redeem: PointRedeem = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default);
|
||||
namedRedeems.set(redeem.name, redeem);
|
||||
if (redeem.sfxredeem) sfxRedeems.set(redeem.name, redeem);
|
||||
};
|
||||
if (!file.endsWith(".ts")) continue;
|
||||
if (file === import.meta.file) continue;
|
||||
const redeem: PointRedeem = await import(
|
||||
`${import.meta.dir}/${file.slice(0, -3)}`
|
||||
).then((a) => a.default);
|
||||
namedRedeems.set(redeem.name, redeem);
|
||||
if (redeem.sfxredeem) sfxRedeems.set(redeem.name, redeem);
|
||||
}
|
||||
|
||||
export { namedRedeems, sfxRedeems };
|
||||
|
||||
const activeRedeems = new Map<string, PointRedeem>;
|
||||
const activeRedeems = new Map<string, PointRedeem>();
|
||||
|
||||
/** 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 logger from "lib/logger";
|
||||
import { streamerId } from "main";
|
||||
|
||||
const currentRedeems = new Map<string, string>;
|
||||
await api.channelPoints.getCustomRewards(streamerId).then(a => a.map(b => currentRedeems.set(b.title, b.id)));
|
||||
const currentRedeems = new Map<string, string>();
|
||||
await api.channelPoints
|
||||
.getCustomRewards(streamerId)
|
||||
.then((a) => a.map((b) => currentRedeems.set(b.title, b.id)));
|
||||
for (const [_, redeem] of Array.from(namedRedeems)) {
|
||||
const selection = currentRedeems.get(redeem.title);
|
||||
if (selection) {
|
||||
currentRedeems.delete(redeem.title);
|
||||
idMap.set(redeem.name, selection);
|
||||
activeRedeems.set(selection, redeem);
|
||||
} else {
|
||||
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, {
|
||||
title: redeem.title,
|
||||
prompt: redeem.prompt,
|
||||
cost: redeem.cost,
|
||||
backgroundColor: redeem.color,
|
||||
userInputRequired: redeem.input
|
||||
});
|
||||
logger.ok(`Created custom point redeem ${redeem.title}`);
|
||||
idMap.set(redeem.name, creation.id);
|
||||
activeRedeems.set(creation.id, redeem);
|
||||
};
|
||||
};
|
||||
const selection = currentRedeems.get(redeem.title);
|
||||
if (selection) {
|
||||
currentRedeems.delete(redeem.title);
|
||||
idMap.set(redeem.name, selection);
|
||||
activeRedeems.set(selection, redeem);
|
||||
} else {
|
||||
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, {
|
||||
title: redeem.title,
|
||||
prompt: redeem.prompt,
|
||||
cost: redeem.cost,
|
||||
backgroundColor: redeem.color,
|
||||
userInputRequired: redeem.input,
|
||||
});
|
||||
logger.ok(`Created custom point redeem ${redeem.title}`);
|
||||
idMap.set(redeem.name, creation.id);
|
||||
activeRedeems.set(creation.id, redeem);
|
||||
}
|
||||
}
|
||||
|
||||
Array.from(currentRedeems).map(async ([title, redeem]) => {
|
||||
if (process.env.NODE_ENV !== 'production') return;
|
||||
await api.channelPoints.deleteCustomReward(streamerId, redeem); logger.ok(`Deleted custom point redeem ${title}`);
|
||||
if (process.env.NODE_ENV !== "production") return;
|
||||
await api.channelPoints.deleteCustomReward(streamerId, redeem);
|
||||
logger.ok(`Deleted custom point redeem ${title}`);
|
||||
});
|
||||
|
||||
logger.ok("Successfully synced all custom point redeems");
|
||||
|
||||
export async function enableRedeem(redeem: PointRedeem, id: string) {
|
||||
if (process.env.NODE_ENV !== 'production') return;
|
||||
await api.channelPoints.updateCustomReward(streamerId, id, {
|
||||
isEnabled: true
|
||||
});
|
||||
activeRedeems.set(id, redeem);
|
||||
logger.ok(`Enabled the ${redeem.name} point redeem`);
|
||||
};
|
||||
if (process.env.NODE_ENV !== "production") return;
|
||||
await api.channelPoints.updateCustomReward(streamerId, id, {
|
||||
isEnabled: true,
|
||||
});
|
||||
activeRedeems.set(id, redeem);
|
||||
logger.ok(`Enabled the ${redeem.name} point redeem`);
|
||||
}
|
||||
|
||||
export async function disableRedeem(redeem: PointRedeem, id: string) {
|
||||
if (process.env.NODE_ENV !== 'production') return;
|
||||
await api.channelPoints.updateCustomReward(streamerId, id, {
|
||||
isEnabled: false
|
||||
});
|
||||
activeRedeems.delete(id);
|
||||
logger.ok(`Disabled the ${redeem.name} point redeem`);
|
||||
};
|
||||
if (process.env.NODE_ENV !== "production") return;
|
||||
await api.channelPoints.updateCustomReward(streamerId, id, {
|
||||
isEnabled: false,
|
||||
});
|
||||
activeRedeems.delete(id);
|
||||
logger.ok(`Disabled the ${redeem.name} point redeem`);
|
||||
}
|
||||
|
||||
export { activeRedeems, idMap };
|
||||
export { activeRedeems, idMap };
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import PointRedeem from "pointRedeems";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { changeBalance } from "lib/changeBalance";
|
||||
import PointRedeem from "pointRedeems";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
|
||||
export default new PointRedeem({
|
||||
name: "qbucksredeem",
|
||||
title: "FREE MONEY",
|
||||
prompt: "GET 100 QBUCKS!",
|
||||
color: '#00FF00',
|
||||
cost: 1000,
|
||||
execution: async (_msg, user) => {
|
||||
await changeBalance(user, await getUserRecord(user), 100);
|
||||
await sendMessage(`${user.displayName} got 100 qbucks for their point redeem`);
|
||||
}
|
||||
})
|
||||
name: "qbucksredeem",
|
||||
title: "FREE MONEY",
|
||||
prompt: "GET 100 QBUCKS!",
|
||||
color: "#00FF00",
|
||||
cost: 1000,
|
||||
execution: async (_msg, user) => {
|
||||
await changeBalance(user, await getUserRecord(user), 100);
|
||||
await sendMessage(
|
||||
`${user.displayName} got 100 qbucks for their point redeem`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,15 +2,16 @@ import PointRedeem from "pointRedeems";
|
||||
import { playAlert } from "web/alerts/serverFunctions";
|
||||
|
||||
export default new PointRedeem({
|
||||
name: "sfxEddieScream",
|
||||
title: "Eddie scream",
|
||||
cost: 100,
|
||||
color: "#A020F0",
|
||||
prompt: "Eddie screaming",
|
||||
sfxredeem: true,
|
||||
execution: async msg => await playAlert({
|
||||
name: 'sound',
|
||||
user: msg.userDisplayName,
|
||||
sound: 'eddiescream'
|
||||
})
|
||||
name: "sfxEddieScream",
|
||||
title: "Eddie scream",
|
||||
cost: 100,
|
||||
color: "#A020F0",
|
||||
prompt: "Eddie screaming",
|
||||
sfxredeem: true,
|
||||
execution: async (msg) =>
|
||||
await playAlert({
|
||||
name: "sound",
|
||||
user: msg.userDisplayName,
|
||||
sound: "eddiescream",
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -2,15 +2,16 @@ import PointRedeem from "pointRedeems";
|
||||
import { playAlert } from "web/alerts/serverFunctions";
|
||||
|
||||
export default new PointRedeem({
|
||||
name: "sfxMrockMadhouse",
|
||||
title: "Welcome to the Madhouse",
|
||||
cost: 100,
|
||||
color: "#A020F0",
|
||||
prompt: "mrockstar20 saying 'Welcome to the Madhouse'",
|
||||
sfxredeem: true,
|
||||
execution: async msg => await playAlert({
|
||||
name: 'sound',
|
||||
user: msg.userDisplayName,
|
||||
sound: 'mrockmadhouse'
|
||||
})
|
||||
name: "sfxMrockMadhouse",
|
||||
title: "Welcome to the Madhouse",
|
||||
cost: 100,
|
||||
color: "#A020F0",
|
||||
prompt: "mrockstar20 saying 'Welcome to the Madhouse'",
|
||||
sfxredeem: true,
|
||||
execution: async (msg) =>
|
||||
await playAlert({
|
||||
name: "sound",
|
||||
user: msg.userDisplayName,
|
||||
sound: "mrockmadhouse",
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -2,15 +2,16 @@ import PointRedeem from "pointRedeems";
|
||||
import { playAlert } from "web/alerts/serverFunctions";
|
||||
|
||||
export default new PointRedeem({
|
||||
name: "sfxRipBozo",
|
||||
title: "RIP BOZO",
|
||||
cost: 500,
|
||||
color: "#A020F0",
|
||||
prompt: "Coffeezilla calls me a conman",
|
||||
sfxredeem: true,
|
||||
execution: async msg => await playAlert({
|
||||
name: 'sound',
|
||||
user: msg.userDisplayName,
|
||||
sound: 'ripbozo'
|
||||
})
|
||||
name: "sfxRipBozo",
|
||||
title: "RIP BOZO",
|
||||
cost: 500,
|
||||
color: "#A020F0",
|
||||
prompt: "Coffeezilla calls me a conman",
|
||||
sfxredeem: true,
|
||||
execution: async (msg) =>
|
||||
await playAlert({
|
||||
name: "sound",
|
||||
user: msg.userDisplayName,
|
||||
sound: "ripbozo",
|
||||
}),
|
||||
});
|
||||
|
||||
195
src/user.ts
195
src/user.ts
@@ -1,9 +1,9 @@
|
||||
import { redis } from "lib/redis";
|
||||
import type { HelixUser } from "@twurple/api";
|
||||
import { api } from "index";
|
||||
import { HelixUser } from "@twurple/api"
|
||||
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:
|
||||
// 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
|
||||
|
||||
export default class User {
|
||||
public username!: string;
|
||||
public id!: string;
|
||||
public displayName!: string;
|
||||
public username!: string;
|
||||
public id!: string;
|
||||
public displayName!: string;
|
||||
|
||||
static async initUsername(dirtyUsername: string): Promise<User | null> {
|
||||
try {
|
||||
const userObj = new User();
|
||||
const username = dirtyUsername.replaceAll(/@/gi, '');
|
||||
userObj.username = username;
|
||||
const userid = await redis.get(`userlookup:${username}`);
|
||||
if (!userid) {
|
||||
const userdata = await api.users.getUserByName(username);
|
||||
if (!userdata) return null;
|
||||
userObj._setCache(userdata);
|
||||
userObj.id = userdata.id;
|
||||
userObj.displayName = userdata.displayName;
|
||||
} else {
|
||||
const displayname = await redis.get(`user:${userid}:displayName`);
|
||||
userObj._setExpire(userid, username);
|
||||
userObj.id = userid;
|
||||
userObj.displayName = displayname!;
|
||||
};
|
||||
return userObj;
|
||||
} catch {
|
||||
logger.err(`Failed to initialize user with name: ${dirtyUsername}`);
|
||||
return null;
|
||||
};
|
||||
};
|
||||
static async initUsername(dirtyUsername: string): Promise<User | null> {
|
||||
try {
|
||||
const userObj = new User();
|
||||
const username = dirtyUsername.replaceAll(/@/gi, "");
|
||||
userObj.username = username;
|
||||
const userid = await redis.get(`userlookup:${username}`);
|
||||
if (!userid) {
|
||||
const userdata = await api.users.getUserByName(username);
|
||||
if (!userdata) return null;
|
||||
userObj._setCache(userdata);
|
||||
userObj.id = userdata.id;
|
||||
userObj.displayName = userdata.displayName;
|
||||
} else {
|
||||
const displayname = await redis.get(`user:${userid}:displayName`);
|
||||
userObj._setExpire(userid, username);
|
||||
userObj.id = userid;
|
||||
userObj.displayName = displayname!;
|
||||
}
|
||||
return userObj;
|
||||
} catch {
|
||||
logger.err(`Failed to initialize user with name: ${dirtyUsername}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static async initUserId(userId: string): Promise<User | null> {
|
||||
try {
|
||||
const userObj = new User();
|
||||
userObj.id = userId;
|
||||
if (!await redis.exists(`user:${userId}:displayName`)) {
|
||||
const userdata = await api.users.getUserById(userId);
|
||||
if (!userdata) return null;
|
||||
userObj._setCache(userdata);
|
||||
userObj.username = userdata.name;
|
||||
userObj.displayName = userdata.displayName;
|
||||
} else {
|
||||
const [displayName, username] = await Promise.all([
|
||||
redis.get(`user:${userId}:displayName`),
|
||||
redis.get(`user:${userId}:username`)
|
||||
]);
|
||||
userObj._setExpire(userId, username!);
|
||||
userObj.username = username!;
|
||||
userObj.displayName = displayName!;
|
||||
};
|
||||
return userObj;
|
||||
} catch {
|
||||
logger.err(`Failed to initializer user with id: ${userId}`);
|
||||
return null;
|
||||
};
|
||||
};
|
||||
static async initUserId(userId: string): Promise<User | null> {
|
||||
try {
|
||||
const userObj = new User();
|
||||
userObj.id = userId;
|
||||
if (!(await redis.exists(`user:${userId}:displayName`))) {
|
||||
const userdata = await api.users.getUserById(userId);
|
||||
if (!userdata) return null;
|
||||
userObj._setCache(userdata);
|
||||
userObj.username = userdata.name;
|
||||
userObj.displayName = userdata.displayName;
|
||||
} else {
|
||||
const [displayName, username] = await Promise.all([
|
||||
redis.get(`user:${userId}:displayName`),
|
||||
redis.get(`user:${userId}:username`),
|
||||
]);
|
||||
userObj._setExpire(userId, username!);
|
||||
userObj.username = username!;
|
||||
userObj.displayName = displayName!;
|
||||
}
|
||||
return userObj;
|
||||
} catch {
|
||||
logger.err(`Failed to initializer user with id: ${userId}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async _setCache(userdata: HelixUser) {
|
||||
await Promise.all([
|
||||
redis.set(`user:${userdata.id}:displayName`, userdata.displayName),
|
||||
redis.set(`user:${userdata.id}:username`, userdata.name),
|
||||
redis.set(`userlookup:${userdata.name}`, userdata.id)
|
||||
]);
|
||||
await this._setExpire(userdata.id, userdata.name);
|
||||
};
|
||||
private async _setCache(userdata: HelixUser) {
|
||||
await Promise.all([
|
||||
redis.set(`user:${userdata.id}:displayName`, userdata.displayName),
|
||||
redis.set(`user:${userdata.id}:username`, userdata.name),
|
||||
redis.set(`userlookup:${userdata.name}`, userdata.id),
|
||||
]);
|
||||
await this._setExpire(userdata.id, userdata.name);
|
||||
}
|
||||
|
||||
private async _setExpire(userId: string, userName: string) {
|
||||
await Promise.all([
|
||||
redis.expire(`user:${userId}:displayName`, EXPIRETIME),
|
||||
redis.expire(`user:${userId}:username`, EXPIRETIME),
|
||||
redis.expire(`userlookup:${userName}`, EXPIRETIME)
|
||||
]);
|
||||
};
|
||||
private async _setExpire(userId: string, userName: string) {
|
||||
await Promise.all([
|
||||
redis.expire(`user:${userId}:displayName`, EXPIRETIME),
|
||||
redis.expire(`user:${userId}:username`, EXPIRETIME),
|
||||
redis.expire(`userlookup:${userName}`, EXPIRETIME),
|
||||
]);
|
||||
}
|
||||
|
||||
public async itemLock(): Promise<boolean> {
|
||||
return await redis.exists(`user:${this.id}:itemlock`);
|
||||
};
|
||||
public async itemLock(): Promise<boolean> {
|
||||
return await redis.exists(`user:${this.id}:itemlock`);
|
||||
}
|
||||
|
||||
public async setLock(): Promise<void> {
|
||||
await redis.set(`user:${this.id}:itemlock`, '1');
|
||||
};
|
||||
public async setLock(): Promise<void> {
|
||||
await redis.set(`user:${this.id}:itemlock`, "1");
|
||||
}
|
||||
|
||||
public async clearLock(): Promise<void> {
|
||||
await redis.del(`user:${this.id}:itemlock`);
|
||||
};
|
||||
public async clearLock(): Promise<void> {
|
||||
await redis.del(`user:${this.id}:itemlock`);
|
||||
}
|
||||
|
||||
public async setVulnerable(): Promise<void> {
|
||||
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
|
||||
};
|
||||
public async setVulnerable(): Promise<void> {
|
||||
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
|
||||
}
|
||||
|
||||
public async clearVulnerable(): Promise<void> {
|
||||
await redis.del(`user:${this.id}:vulnerable`);
|
||||
};
|
||||
public async clearVulnerable(): Promise<void> {
|
||||
await redis.del(`user:${this.id}:vulnerable`);
|
||||
}
|
||||
|
||||
public async setGreed(): Promise<void> {
|
||||
await redis.set(`user:${this.id}:greedy`, '1');
|
||||
};
|
||||
public async setGreed(): Promise<void> {
|
||||
await redis.set(`user:${this.id}:greedy`, "1");
|
||||
}
|
||||
|
||||
public async clearGreed(): Promise<void> {
|
||||
await redis.del(`user:${this.id}:greedy`);
|
||||
};
|
||||
public async clearGreed(): Promise<void> {
|
||||
await redis.del(`user:${this.id}:greedy`);
|
||||
}
|
||||
|
||||
public async greedy(): Promise<boolean> {
|
||||
return await redis.exists(`user:${this.id}:greedy`);
|
||||
};
|
||||
};
|
||||
public async greedy(): Promise<boolean> {
|
||||
return await redis.exists(`user:${this.id}:greedy`);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user