mirror of
https://gitlab.com/qwerinope/qweribot.git
synced 2026-02-04 07:56:58 +01:00
add events to database, remove kleur dependency/slightly nicer logging, update twurple
This commit is contained in:
20
src/auth.ts
20
src/auth.ts
@@ -9,7 +9,6 @@ import {
|
||||
getAuthRecord,
|
||||
updateAuthRecord,
|
||||
} from "db/dbAuth";
|
||||
import kleur from "kleur";
|
||||
import logger from "lib/logger";
|
||||
|
||||
async function initAuth(
|
||||
@@ -26,15 +25,10 @@ async function initAuth(
|
||||
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.`;
|
||||
const instruction = `Visit this URL as \x1b[3;4;1;95m${streamer ? "the streamer" : "the chatter"}\x1b[0;97m 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}`,
|
||||
`\x1b[3;4;1;95mhttps://id.twitch.tv/oauth2/authorize?client_id=${clientId}&redirect_uri=${redirectURL}&response_type=code&scope=${requestedIntents.join("+")}&state=${state}\x1b[0;97m`,
|
||||
);
|
||||
|
||||
const createCodePromise = () => {
|
||||
@@ -61,7 +55,7 @@ async function initAuth(
|
||||
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.`,
|
||||
`Authentication attempt unsuccessful, please make sure the redirect url in the twitch developer console is set to \x1b[3;4;1;95m${redirectURL}\x1b[0;97m and that the bot is listening to that url & port.`,
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
@@ -112,13 +106,15 @@ export async function createAuthProvider(
|
||||
});
|
||||
|
||||
authData.onRefresh(async (user, token) => {
|
||||
logger.ok(`Successfully refreshed auth for user ${user}`);
|
||||
logger.ok(
|
||||
`Successfully refreshed auth for user \x1b[3;4;1;95m${user}\x1b[0;97m`,
|
||||
);
|
||||
await updateAuthRecord(user, token);
|
||||
});
|
||||
|
||||
authData.onRefreshFailure((user, err) => {
|
||||
logger.err(
|
||||
`Failed to refresh auth for user ${user}: ${err.name} ${err.message}`,
|
||||
`Failed to refresh auth for user \x1b[3;4;1;95m${user}\x1b[0;97m: ${err.name} ${err.message}`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -149,7 +145,7 @@ export async function createAuthProvider(
|
||||
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.`,
|
||||
`Failed to refresh user \x1b[3;4;1;95m${user.userId}\x1b[0;97m. 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);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Cheer, handleNoTarget } from "cheers";
|
||||
import { createCheerEventRecord } from "db/dbCheerEvents";
|
||||
import { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import {
|
||||
createCompensatedItemCheer,
|
||||
createTimeoutEventCheer,
|
||||
} from "db/CheerEvents";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { parseCheerArgs } from "lib/parseCommandArgs";
|
||||
@@ -30,7 +32,8 @@ export default new Cheer({
|
||||
}
|
||||
if (users.length === 0) {
|
||||
await sendMessage("No vulnerable chatters");
|
||||
await handleNoTarget(msg, user, ITEMNAME, true);
|
||||
const compensated = await handleNoTarget(msg, user, ITEMNAME, true);
|
||||
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
|
||||
return;
|
||||
}
|
||||
target = users[Math.floor(Math.random() * users.length)]!;
|
||||
@@ -45,7 +48,8 @@ export default new Cheer({
|
||||
target = await User.initUsername(args[0].toLowerCase());
|
||||
}
|
||||
if (!target) {
|
||||
await handleNoTarget(msg, user, ITEMNAME, false);
|
||||
const compensated = await handleNoTarget(msg, user, ITEMNAME);
|
||||
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
|
||||
return;
|
||||
}
|
||||
await getUserRecord(target);
|
||||
@@ -60,8 +64,7 @@ export default new Cheer({
|
||||
sendMessage(
|
||||
`KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`,
|
||||
),
|
||||
createTimeoutRecord(user, target, ITEMNAME),
|
||||
createCheerEventRecord(user, ITEMNAME),
|
||||
createTimeoutEventCheer(user, target, "execute"),
|
||||
playAlert({
|
||||
name: "userExecution",
|
||||
user: user.displayName,
|
||||
@@ -69,7 +72,8 @@ export default new Cheer({
|
||||
}),
|
||||
]);
|
||||
else {
|
||||
await handleNoTarget(msg, user, ITEMNAME);
|
||||
const compensated = await handleNoTarget(msg, user, ITEMNAME);
|
||||
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
|
||||
switch (result.reason) {
|
||||
case "banned":
|
||||
await sendMessage(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Cheer, handleNoTarget } from "cheers";
|
||||
import { createCheerEventRecord } from "db/dbCheerEvents";
|
||||
import { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import {
|
||||
createCompensatedItemCheer,
|
||||
createTimeoutEventCheer,
|
||||
} from "db/CheerEvents";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { redis } from "lib/redis";
|
||||
@@ -18,7 +20,8 @@ export default new Cheer({
|
||||
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);
|
||||
const compensated = await handleNoTarget(msg, user, ITEMNAME, true);
|
||||
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
|
||||
return;
|
||||
}
|
||||
const selection = targets[Math.floor(Math.random() * targets.length)]!;
|
||||
@@ -32,8 +35,7 @@ export default new Cheer({
|
||||
sendMessage(
|
||||
`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`,
|
||||
),
|
||||
createTimeoutRecord(user, target!, ITEMNAME),
|
||||
createCheerEventRecord(user, ITEMNAME),
|
||||
createTimeoutEventCheer(user, target!, "grenade"),
|
||||
playAlert({
|
||||
name: "grenadeExplosion",
|
||||
user: user.displayName,
|
||||
|
||||
@@ -63,13 +63,13 @@ export async function handleNoTarget(
|
||||
user: User,
|
||||
itemname: items,
|
||||
silent = true,
|
||||
) {
|
||||
): Promise<boolean> {
|
||||
if (await user.itemLock()) {
|
||||
await sendMessage(
|
||||
`Cannot give ${user.displayName} a ${itemname} (itemlock)`,
|
||||
msg.messageId,
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
await user.setLock();
|
||||
const userRecord = await getUserRecord(user);
|
||||
@@ -80,4 +80,5 @@ export async function handleNoTarget(
|
||||
);
|
||||
await changeItemCount(user, userRecord, itemname, 1);
|
||||
await user.clearLock();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Cheer } from "cheers";
|
||||
import { createCheerEventRecord } from "db/dbCheerEvents";
|
||||
import { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import { createTimeoutEventCheer } from "db/CheerEvents";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { parseCheerArgs } from "lib/parseCommandArgs";
|
||||
@@ -57,8 +56,7 @@ export default new Cheer({
|
||||
sendMessage(
|
||||
`KEKPOINT KEKPOINT KEKPOINT ${target.displayName.toUpperCase()} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`,
|
||||
),
|
||||
createTimeoutRecord(user, target, "realsilverbullet"),
|
||||
createCheerEventRecord(user, "realsilverbullet"),
|
||||
createTimeoutEventCheer(user, target, "realsilverbullet"),
|
||||
playAlert({
|
||||
name: "userExecution",
|
||||
user: user.displayName,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Cheer } from "cheers";
|
||||
import { createCheerEventRecord } from "db/dbCheerEvents";
|
||||
import { createGetLootRecord } from "db/dbGetLoot";
|
||||
import { createSuperLootEvent } from "db/CheerEvents";
|
||||
import { getUserRecord, updateUserRecord } from "db/dbUser";
|
||||
import itemMap, { type inventory, type items } from "items";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
@@ -12,10 +11,10 @@ export default new Cheer({
|
||||
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 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;
|
||||
@@ -81,8 +80,7 @@ export default new Cheer({
|
||||
await Promise.all([
|
||||
updateUserRecord(user, userData),
|
||||
sendMessage(message, msg.messageId),
|
||||
createCheerEventRecord(user, "superloot"),
|
||||
createGetLootRecord(user, gainedqbucks, itemDiff, "superloot"),
|
||||
createSuperLootEvent(user, gainedqbucks, itemDiff),
|
||||
user.clearLock(),
|
||||
]);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Cheer, handleNoTarget } from "cheers";
|
||||
import { createCheerEventRecord } from "db/dbCheerEvents";
|
||||
import { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import {
|
||||
createTimeoutEventCheer,
|
||||
createCompensatedItemCheer,
|
||||
} from "db/CheerEvents";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { parseCheerArgs } from "lib/parseCommandArgs";
|
||||
@@ -17,12 +19,14 @@ export default new Cheer({
|
||||
async execute(msg, user) {
|
||||
const args = parseCheerArgs(msg.messageText);
|
||||
if (!args[0]) {
|
||||
await handleNoTarget(msg, user, ITEMNAME, false);
|
||||
const compensated = await handleNoTarget(msg, user, ITEMNAME, false);
|
||||
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
|
||||
return;
|
||||
}
|
||||
const target = await User.initUsername(args[0].toLowerCase());
|
||||
if (!target) {
|
||||
await handleNoTarget(msg, user, ITEMNAME, false);
|
||||
const compensated = await handleNoTarget(msg, user, ITEMNAME, false);
|
||||
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
|
||||
return;
|
||||
}
|
||||
await getUserRecord(target);
|
||||
@@ -37,8 +41,7 @@ export default new Cheer({
|
||||
sendMessage(
|
||||
`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`,
|
||||
),
|
||||
createTimeoutRecord(user, target, ITEMNAME),
|
||||
createCheerEventRecord(user, ITEMNAME),
|
||||
createTimeoutEventCheer(user, target, "timeout"),
|
||||
playAlert({
|
||||
name: "userBlast",
|
||||
user: user.displayName,
|
||||
@@ -46,7 +49,8 @@ export default new Cheer({
|
||||
}),
|
||||
]);
|
||||
else {
|
||||
await handleNoTarget(msg, user, ITEMNAME);
|
||||
const compensated = await handleNoTarget(msg, user, ITEMNAME);
|
||||
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
|
||||
switch (result.reason) {
|
||||
case "banned":
|
||||
await sendMessage(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Cheer, handleNoTarget } from "cheers";
|
||||
import { createCheerEventRecord } from "db/dbCheerEvents";
|
||||
import { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import {
|
||||
createTimeoutEventCheer,
|
||||
createCompensatedItemCheer,
|
||||
} from "db/CheerEvents";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { getTNTTargets } from "items/tnt";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
@@ -21,7 +23,8 @@ export default new Cheer({
|
||||
.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);
|
||||
const compensated = await handleNoTarget(msg, user, ITEMNAME, true);
|
||||
if (compensated) await createCompensatedItemCheer(user, ITEMNAME);
|
||||
return;
|
||||
}
|
||||
const targets = getTNTTargets(vulntargets);
|
||||
@@ -36,13 +39,12 @@ export default new Cheer({
|
||||
sendMessage(
|
||||
`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`,
|
||||
),
|
||||
createTimeoutRecord(user, target!, ITEMNAME),
|
||||
]);
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
createCheerEventRecord(user, ITEMNAME),
|
||||
createTimeoutEventCheer(user, targets, "tnt"),
|
||||
playAlert({
|
||||
name: "tntExplosion",
|
||||
user: user.displayName,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createGetLootRecord } from "db/dbGetLoot";
|
||||
import { getUserRecord, updateUserRecord } from "db/dbUser";
|
||||
import { createGetLootEvent } from "db/LootEvents";
|
||||
import itemMap, { type inventory, type items } from "items";
|
||||
import { Command, sendMessage } from "lib/commandUtils";
|
||||
import { buildTimeString } from "lib/dateManager";
|
||||
@@ -132,7 +132,7 @@ export default new Command({
|
||||
await Promise.all([
|
||||
updateUserRecord(user, userData),
|
||||
sendMessage(message, msg.messageId),
|
||||
createGetLootRecord(user, gainedqbucks, itemDiff, "getloot"),
|
||||
createGetLootEvent(user, gainedqbucks, itemDiff, "getloot"),
|
||||
user.clearLock(),
|
||||
]);
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export async function connectionCheck() {
|
||||
redisstatus = true;
|
||||
} catch {}
|
||||
logger.info(
|
||||
`Currently using the "${process.env.NODE_ENV ?? "production"}" database`,
|
||||
`Currently using the \x1b[3;4;1;95m"${process.env.NODE_ENV ?? "production"}"\x1b[0;97m database`,
|
||||
);
|
||||
pgstatus
|
||||
? logger.ok(`Postgresql status: good`)
|
||||
|
||||
151
src/db/CheerEvents.ts
Normal file
151
src/db/CheerEvents.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import type { cheers } from "cheers";
|
||||
import db from "db/connection";
|
||||
import { cheerEvents, events, timeouts } from "db/schema";
|
||||
import type { inventory, items } from "items";
|
||||
import type User from "user";
|
||||
import { createGetLootEvent } from "./LootEvents";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
/**
|
||||
* Use this function to create a cheer event with timeouts
|
||||
* This can only be used if the cheer succeeded
|
||||
*
|
||||
* The target can either be a single User object or an array of targetIDs
|
||||
*/
|
||||
export async function createTimeoutEventCheer(
|
||||
user: User,
|
||||
target: User | string[],
|
||||
event: cheers,
|
||||
) {
|
||||
const userInt = parseInt(user.id, 10);
|
||||
return await db.transaction(async (tx) => {
|
||||
const cheerEventRecord = await tx
|
||||
.insert(cheerEvents)
|
||||
.values({
|
||||
user: userInt,
|
||||
event,
|
||||
status: "success",
|
||||
})
|
||||
.returning();
|
||||
|
||||
if (Array.isArray(target))
|
||||
target.map(
|
||||
async (ripbozo) =>
|
||||
await tx.insert(timeouts).values({
|
||||
user: userInt,
|
||||
target: parseInt(ripbozo, 10),
|
||||
item: event,
|
||||
cheer: cheerEventRecord[0]?.id,
|
||||
}),
|
||||
);
|
||||
else
|
||||
await tx.insert(timeouts).values({
|
||||
user: userInt,
|
||||
target: parseInt(target.id, 10),
|
||||
item: event,
|
||||
cheer: cheerEventRecord[0]?.id,
|
||||
});
|
||||
|
||||
await tx.insert(events).values({
|
||||
user: userInt,
|
||||
cheer: cheerEventRecord[0]?.id,
|
||||
});
|
||||
|
||||
if (!cheerEventRecord[0]) {
|
||||
tx.rollback();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this function to create a cheer event without timeouts
|
||||
* This can only be used if the cheer succeeded
|
||||
*/
|
||||
export async function createRegularEventCheer(
|
||||
user: User,
|
||||
event: cheers | items,
|
||||
) {
|
||||
const userInt = parseInt(user.id, 10);
|
||||
return await db.transaction(async (tx) => {
|
||||
const cheerEventRecord = await tx
|
||||
.insert(cheerEvents)
|
||||
.values({
|
||||
user: userInt,
|
||||
event,
|
||||
status: "success",
|
||||
})
|
||||
.returning();
|
||||
|
||||
await tx.insert(events).values({
|
||||
user: userInt,
|
||||
cheer: cheerEventRecord[0]?.id,
|
||||
});
|
||||
|
||||
if (!cheerEventRecord[0]) {
|
||||
tx.rollback();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this function to create a cheer event where the user got an item after the cheer failed
|
||||
*/
|
||||
export async function createCompensatedItemCheer(user: User, item: items) {
|
||||
const userInt = parseInt(user.id, 10);
|
||||
return await db.transaction(async (tx) => {
|
||||
const cheerEventRecord = await tx
|
||||
.insert(cheerEvents)
|
||||
.values({ user: userInt, event: item, status: "compensated" })
|
||||
.returning();
|
||||
|
||||
await tx.insert(events).values({
|
||||
user: userInt,
|
||||
cheer: cheerEventRecord[0]?.id,
|
||||
});
|
||||
|
||||
if (!cheerEventRecord[0]) {
|
||||
tx.rollback();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Because superloot is a special case for cheers, as the event needs to link to the getLoot table and the cheerEvents table, we have this special function
|
||||
*/
|
||||
export async function createSuperLootEvent(
|
||||
user: User,
|
||||
qbucks: number,
|
||||
inventory: inventory,
|
||||
) {
|
||||
const eventRecord = await createGetLootEvent(
|
||||
user,
|
||||
qbucks,
|
||||
inventory,
|
||||
"superloot",
|
||||
);
|
||||
|
||||
if (eventRecord === false) return;
|
||||
|
||||
return await db.transaction(async (tx) => {
|
||||
const cheerEventRecord = await tx
|
||||
.insert(cheerEvents)
|
||||
.values({
|
||||
user: parseInt(user.id, 10),
|
||||
event: "superloot",
|
||||
})
|
||||
.returning();
|
||||
|
||||
await tx
|
||||
.update(events)
|
||||
.set({ cheer: cheerEventRecord[0]?.id })
|
||||
.where(eq(events.id, eventRecord.id));
|
||||
|
||||
if (!cheerEventRecord[0]) {
|
||||
tx.rollback();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
72
src/db/ItemEvents.ts
Normal file
72
src/db/ItemEvents.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import db from "db/connection";
|
||||
import { events, timeouts, usedItems } from "db/schema";
|
||||
import type { items } from "items";
|
||||
import type User from "user";
|
||||
|
||||
/**
|
||||
* Use this function for doing all item usages with timeouts
|
||||
*/
|
||||
export async function createTimeoutEventItem(
|
||||
user: User,
|
||||
target: User | string[],
|
||||
item: items,
|
||||
) {
|
||||
const userInt = parseInt(user.id, 10);
|
||||
return await db.transaction(async (tx) => {
|
||||
const usedItemRecord = await tx
|
||||
.insert(usedItems)
|
||||
.values({ user: userInt, item })
|
||||
.returning();
|
||||
|
||||
if (Array.isArray(target))
|
||||
target.map(
|
||||
async (ripbozo) =>
|
||||
await tx.insert(timeouts).values({
|
||||
user: userInt,
|
||||
target: parseInt(ripbozo, 10),
|
||||
item,
|
||||
usedItem: usedItemRecord[0]?.id,
|
||||
}),
|
||||
);
|
||||
else
|
||||
await tx.insert(timeouts).values({
|
||||
user: userInt,
|
||||
target: parseInt(target.id, 10),
|
||||
item,
|
||||
usedItem: usedItemRecord[0]?.id,
|
||||
});
|
||||
|
||||
await tx.insert(events).values({
|
||||
user: userInt,
|
||||
usedItem: usedItemRecord[0]?.id,
|
||||
});
|
||||
|
||||
if (!usedItemRecord[0]) {
|
||||
tx.rollback();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this function for doing all regular item usages (no timeouts)
|
||||
*/
|
||||
export async function createNormalEventItem(user: User, item: items) {
|
||||
const userInt = parseInt(user.id, 10);
|
||||
return await db.transaction(async (tx) => {
|
||||
const usedItemRecord = await tx
|
||||
.insert(usedItems)
|
||||
.values({ user: userInt, item })
|
||||
.returning();
|
||||
|
||||
await tx.insert(events).values({
|
||||
user: userInt,
|
||||
usedItem: usedItemRecord[0]?.id,
|
||||
});
|
||||
|
||||
if (!usedItemRecord[0]) {
|
||||
tx.rollback();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
39
src/db/LootEvents.ts
Normal file
39
src/db/LootEvents.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import db from "db/connection";
|
||||
import type { lootTriggers } from "db/schema";
|
||||
import { events, getLoots } from "db/schema";
|
||||
import type { inventory } from "items";
|
||||
import type User from "user";
|
||||
|
||||
export async function createGetLootEvent(
|
||||
user: User,
|
||||
qbucks: number,
|
||||
inventory: inventory,
|
||||
trigger: lootTriggers,
|
||||
) {
|
||||
return await db.transaction(async (tx) => {
|
||||
const glRecord = await tx
|
||||
.insert(getLoots)
|
||||
.values({
|
||||
user: parseInt(user.id, 10),
|
||||
qbucks: qbucks,
|
||||
items: inventory,
|
||||
trigger,
|
||||
})
|
||||
.returning();
|
||||
|
||||
const eventRecord = await tx
|
||||
.insert(events)
|
||||
.values({
|
||||
user: parseInt(user.id, 10),
|
||||
getLoot: glRecord[0]?.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
if (!glRecord[0]) {
|
||||
tx.rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
return eventRecord[0]!;
|
||||
});
|
||||
}
|
||||
4
src/db/UndoEvent.ts
Normal file
4
src/db/UndoEvent.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import db from "db/connection";
|
||||
import { events } from "db/schema";
|
||||
import type User from "user";
|
||||
import { desc, eq, and } from "drizzle-orm";
|
||||
@@ -1,19 +1,8 @@
|
||||
import type { cheers } from "cheers";
|
||||
import db from "db/connection";
|
||||
import { cheerEvents } from "db/schema";
|
||||
import { and, between, eq, type SQL } from "drizzle-orm";
|
||||
import type { items } from "items";
|
||||
import type User from "user";
|
||||
|
||||
export async function createCheerEventRecord(
|
||||
user: User,
|
||||
cheer: items | cheers,
|
||||
): 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,
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import db from "db/connection";
|
||||
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, 10),
|
||||
qbucks: qbucks,
|
||||
items: inventory,
|
||||
trigger,
|
||||
});
|
||||
}
|
||||
@@ -1,22 +1,8 @@
|
||||
import type { cheers } from "cheers";
|
||||
import db from "db/connection";
|
||||
import { timeouts } from "db/schema";
|
||||
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 | cheers,
|
||||
): 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,
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
import db from "db/connection";
|
||||
import { usedItems } from "db/schema";
|
||||
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, 10), item });
|
||||
}
|
||||
|
||||
export async function getItemsUsed(user: User, monthData?: string) {
|
||||
let condition: SQL<unknown> | undefined = eq(
|
||||
usedItems.user,
|
||||
|
||||
@@ -33,6 +33,7 @@ export const usersRelations = relations(users, ({ many }) => ({
|
||||
cheers: many(cheers),
|
||||
anivTimeouts: many(anivTimeouts),
|
||||
getLoots: many(getLoots),
|
||||
events: many(events),
|
||||
}));
|
||||
|
||||
export const timeouts = pgTable("timeouts", {
|
||||
@@ -45,6 +46,8 @@ export const timeouts = pgTable("timeouts", {
|
||||
.references(() => users.id),
|
||||
item: varchar().$type<items | cheertypes>().notNull(),
|
||||
created: timestamp().defaultNow().notNull(),
|
||||
cheer: uuid().references(() => cheerEvents.id),
|
||||
usedItem: uuid().references(() => usedItems.id),
|
||||
});
|
||||
|
||||
export const timeoutsRelations = relations(timeouts, ({ one }) => ({
|
||||
@@ -58,6 +61,14 @@ export const timeoutsRelations = relations(timeouts, ({ one }) => ({
|
||||
references: [users.id],
|
||||
relationName: "target",
|
||||
}),
|
||||
cheer: one(cheerEvents, {
|
||||
fields: [timeouts.cheer],
|
||||
references: [cheerEvents.id],
|
||||
}),
|
||||
usedItem: one(usedItems, {
|
||||
fields: [timeouts.usedItem],
|
||||
references: [usedItems.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const usedItems = pgTable("usedItems", {
|
||||
@@ -69,27 +80,36 @@ export const usedItems = pgTable("usedItems", {
|
||||
created: timestamp().defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const usedItemsRelations = relations(usedItems, ({ one }) => ({
|
||||
export const usedItemsRelations = relations(usedItems, ({ one, many }) => ({
|
||||
user: one(users, {
|
||||
fields: [usedItems.user],
|
||||
references: [users.id],
|
||||
}),
|
||||
timeouts: many(timeouts),
|
||||
}));
|
||||
|
||||
/**
|
||||
* "success" when everything works
|
||||
* "compensated" when the user gets an item in their inventory for a cheer
|
||||
*/
|
||||
export type cheerEventStatus = "success" | "compensated";
|
||||
|
||||
export const cheerEvents = pgTable("cheerEvents", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer()
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
event: varchar().$type<items | cheertypes>().notNull(),
|
||||
status: varchar().$type<cheerEventStatus>().default("success").notNull(),
|
||||
created: timestamp().defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const cheerEventsRelations = relations(cheerEvents, ({ one }) => ({
|
||||
export const cheerEventsRelations = relations(cheerEvents, ({ one, many }) => ({
|
||||
user: one(users, {
|
||||
fields: [cheerEvents.user],
|
||||
references: [users.id],
|
||||
}),
|
||||
timeouts: many(timeouts),
|
||||
}));
|
||||
|
||||
export const cheers = pgTable("cheers", {
|
||||
@@ -146,3 +166,33 @@ export const getLootsRelations = relations(getLoots, ({ one }) => ({
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const events = pgTable("events", {
|
||||
id: uuid().defaultRandom().primaryKey(),
|
||||
user: integer()
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
created: timestamp().defaultNow().notNull(),
|
||||
usedItem: uuid().references(() => usedItems.id),
|
||||
cheer: uuid().references(() => cheerEvents.id),
|
||||
getLoot: uuid().references(() => getLoots.id),
|
||||
});
|
||||
|
||||
export const eventsRelations = relations(events, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [events.user],
|
||||
references: [users.id],
|
||||
}),
|
||||
usedItem: one(usedItems, {
|
||||
fields: [events.usedItem],
|
||||
references: [usedItems.id],
|
||||
}),
|
||||
cheer: one(cheerEvents, {
|
||||
fields: [events.cheer],
|
||||
references: [cheerEvents.id],
|
||||
}),
|
||||
getLoot: one(getLoots, {
|
||||
fields: [events.getLoot],
|
||||
references: [getLoots.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
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)}`,
|
||||
`Successfully revoked EventSub subscription: \x1b[3;4;1;95m${event.id}`,
|
||||
);
|
||||
});
|
||||
|
||||
eventSub.onSubscriptionCreateSuccess((event) => {
|
||||
logger.ok(
|
||||
`Successfully created EventSub subscription: ${kleur.underline(event.id)}`,
|
||||
`Successfully created EventSub subscription: \x1b[3;4;1;95m${event.id}`,
|
||||
);
|
||||
});
|
||||
|
||||
eventSub.onSubscriptionCreateFailure((event) => {
|
||||
logger.err(
|
||||
`Failed to create EventSub subscription: ${kleur.underline(event.id)}`,
|
||||
`Failed to create EventSub subscription: \x1b[3;4;4;95m${event.id}`,
|
||||
);
|
||||
});
|
||||
|
||||
eventSub.onSubscriptionDeleteSuccess((event) => {
|
||||
logger.ok(
|
||||
`Successfully deleted EventSub subscription: ${kleur.underline(event.id)}`,
|
||||
`Successfully deleted EventSub subscription: \x1b[3;4;1;95m${event.id}`,
|
||||
);
|
||||
});
|
||||
|
||||
eventSub.onSubscriptionDeleteFailure((event) => {
|
||||
logger.err(
|
||||
`Failed to delete EventSub subscription: ${kleur.underline(event.id)}`,
|
||||
`Failed to delete EventSub subscription: \x1b[3;4;1;95m${event.id}`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ eventSub.onChannelSubscription(streamerId, async (msg) => {
|
||||
});
|
||||
|
||||
eventSub.onChannelSubscriptionGift(streamerId, async (msg) => {
|
||||
if (msg.isAnonymous) {
|
||||
if (msg.gifterName === null) {
|
||||
switch (msg.tier) {
|
||||
case "1000":
|
||||
await sendMessage(
|
||||
|
||||
@@ -161,7 +161,7 @@ for (const ban of banned) {
|
||||
Math.floor((ban.expiryDate.getTime() - Date.now()) / 1000) + 1,
|
||||
);
|
||||
logger.info(
|
||||
`Set the timeout of ${ban.userDisplayName} in the Redis/Valkey database.`,
|
||||
`Set the timeout of \x1b[3;4;1;95m${ban.userDisplayName}\x1b[0;97m in the Redis/Valkey database.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -170,7 +170,7 @@ 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.`,
|
||||
`Set the mod status of \x1b[3;4;1;95m${mod.userDisplayName}\x1b[0;97m in the Redis/Valkey database.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ for (const remod of bannedmods) {
|
||||
duration = Math.floor((durationdata * 1000 - Date.now()) / 1000);
|
||||
remodMod(target!, duration);
|
||||
logger.info(
|
||||
`Set the remod timer for ${target?.displayName} to ${duration} seconds.`,
|
||||
`Set the remod timer for \x1b[3;4;1;95m${target?.displayName}\x1b[0;97m to \x1b[3;4;1;95m${duration}\x1b[0;97m seconds.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import { createUsedItemRecord } from "db/dbUsedItems";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { createTimeoutEventItem } from "db/ItemEvents";
|
||||
import { changeItemCount, Item } from "items";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
@@ -57,8 +56,7 @@ export default new Item({
|
||||
`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`,
|
||||
),
|
||||
changeItemCount(user, userObj, ITEMNAME),
|
||||
createTimeoutRecord(user, target, ITEMNAME),
|
||||
createUsedItemRecord(user, ITEMNAME),
|
||||
createTimeoutEventItem(user, target, ITEMNAME),
|
||||
playAlert({
|
||||
name: "userBlast",
|
||||
user: user.displayName,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import { createUsedItemRecord } from "db/dbUsedItems";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { createTimeoutEventItem } from "db/ItemEvents";
|
||||
import { changeItemCount, Item } from "items";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { redis } from "lib/redis";
|
||||
@@ -47,8 +46,7 @@ export default new Item({
|
||||
`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`,
|
||||
),
|
||||
changeItemCount(user, userObj, ITEMNAME),
|
||||
createTimeoutRecord(user, target!, ITEMNAME),
|
||||
createUsedItemRecord(user, ITEMNAME),
|
||||
createTimeoutEventItem(user, target!, ITEMNAME),
|
||||
playAlert({
|
||||
name: "grenadeExplosion",
|
||||
user: user.displayName,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import { createUsedItemRecord } from "db/dbUsedItems";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { createTimeoutEventItem } from "db/ItemEvents";
|
||||
import { changeItemCount, Item } from "items";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import parseCommandArgs from "lib/parseCommandArgs";
|
||||
@@ -89,12 +88,11 @@ export default new Item({
|
||||
target: target.displayName,
|
||||
}),
|
||||
]);
|
||||
if (user.id !== streamerId)
|
||||
if (user.id !== streamerId || process.env.NODE_ENV === "development")
|
||||
// streamer doesn't consume bullets and doesn't count for timeouts
|
||||
await Promise.all([
|
||||
changeItemCount(user, userObj, ITEMNAME),
|
||||
createTimeoutRecord(user, target, ITEMNAME),
|
||||
createUsedItemRecord(user, ITEMNAME),
|
||||
createTimeoutEventItem(user, target, ITEMNAME),
|
||||
]);
|
||||
} else {
|
||||
switch (result.reason) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createTimeoutRecord } from "db/dbTimeouts";
|
||||
import { createUsedItemRecord } from "db/dbUsedItems";
|
||||
import { getUserRecord } from "db/dbUser";
|
||||
import { createTimeoutEventItem } from "db/ItemEvents";
|
||||
import { changeItemCount, Item } from "items";
|
||||
import { sendMessage } from "lib/commandUtils";
|
||||
import { redis } from "lib/redis";
|
||||
@@ -49,13 +48,12 @@ export default new Item({
|
||||
sendMessage(
|
||||
`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`,
|
||||
),
|
||||
createTimeoutRecord(user, target!, ITEMNAME),
|
||||
]);
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
createUsedItemRecord(user, ITEMNAME),
|
||||
createTimeoutEventItem(user, targets, ITEMNAME),
|
||||
playAlert({
|
||||
name: "tntExplosion",
|
||||
user: user.displayName,
|
||||
|
||||
@@ -1,18 +1,39 @@
|
||||
import kleur from "kleur";
|
||||
|
||||
const logger = {
|
||||
err: (arg: string) =>
|
||||
console.error(
|
||||
kleur.red().bold().italic("[ERROR] ") + kleur.red().bold(arg),
|
||||
Bun.wrapAnsi(
|
||||
`\x1b[1;91m[ERROR]: \x1b[0;97m${arg}\x1b[0m`,
|
||||
process.stdout.columns,
|
||||
),
|
||||
),
|
||||
warn: (arg: string) =>
|
||||
console.warn(
|
||||
kleur.yellow().bold().italic("[WARN] ") + kleur.yellow().bold(arg),
|
||||
Bun.wrapAnsi(
|
||||
`\x1b[1;93m[WARN]: \x1b[0;97m${arg}\x1b[0m`,
|
||||
process.stdout.columns,
|
||||
),
|
||||
),
|
||||
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`),
|
||||
console.info(
|
||||
Bun.wrapAnsi(
|
||||
`\x1b[37;1m[INFO]: \x1b[0;97m${arg}\x1b[0m`,
|
||||
process.stdout.columns,
|
||||
),
|
||||
),
|
||||
ok: (arg: string) =>
|
||||
console.info(
|
||||
Bun.wrapAnsi(
|
||||
`\x1b[1;92m[OK]: \x1b[0;97m${arg}\x1b[0m`,
|
||||
process.stdout.columns,
|
||||
),
|
||||
),
|
||||
enverr: (arg: string) =>
|
||||
logger.err(
|
||||
Bun.wrapAnsi(
|
||||
`Please provide a \x1b[4;3;93m${arg}\x1b[0;97m in the .env`,
|
||||
process.stdout.columns,
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
export default logger;
|
||||
|
||||
@@ -88,7 +88,9 @@ for (const [_, redeem] of Array.from(namedRedeems)) {
|
||||
backgroundColor: redeem.color,
|
||||
userInputRequired: redeem.input,
|
||||
});
|
||||
logger.ok(`Created custom point redeem ${redeem.title}`);
|
||||
logger.ok(
|
||||
`Created custom point redeem \x1b[3;4;1;95m${redeem.title}\x1b[0;97m`,
|
||||
);
|
||||
idMap.set(redeem.name, creation.id);
|
||||
activeRedeems.set(creation.id, redeem);
|
||||
}
|
||||
@@ -97,7 +99,7 @@ for (const [_, redeem] of Array.from(namedRedeems)) {
|
||||
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}`);
|
||||
logger.ok(`Deleted custom point redeem \x1b[3;4;1;95m${title}\x1b[0;97m`);
|
||||
});
|
||||
|
||||
logger.ok("Successfully synced all custom point redeems");
|
||||
@@ -108,7 +110,7 @@ export async function enableRedeem(redeem: PointRedeem, id: string) {
|
||||
isEnabled: true,
|
||||
});
|
||||
activeRedeems.set(id, redeem);
|
||||
logger.ok(`Enabled the ${redeem.name} point redeem`);
|
||||
logger.ok(`Enabled the \x1b[3;4;1;95m${redeem.name}\x1b[0;97m point redeem`);
|
||||
}
|
||||
|
||||
export async function disableRedeem(redeem: PointRedeem, id: string) {
|
||||
@@ -117,7 +119,7 @@ export async function disableRedeem(redeem: PointRedeem, id: string) {
|
||||
isEnabled: false,
|
||||
});
|
||||
activeRedeems.delete(id);
|
||||
logger.ok(`Disabled the ${redeem.name} point redeem`);
|
||||
logger.ok(`Disabled the \x1b[3;4;1;95m${redeem.name}\x1b[0;97m point redeem`);
|
||||
}
|
||||
|
||||
export { activeRedeems, idMap };
|
||||
|
||||
Reference in New Issue
Block a user