add events to database, remove kleur dependency/slightly nicer logging, update twurple

This commit is contained in:
2026-01-29 21:06:01 +01:00
parent f3c6f6a6b3
commit b88a93a6cf
31 changed files with 478 additions and 210 deletions

View File

@@ -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);

View File

@@ -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(

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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(),
]);
},

View File

@@ -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(

View File

@@ -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,

View File

@@ -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(),
]);

View File

@@ -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
View 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
View 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
View 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
View 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";

View File

@@ -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,

View File

@@ -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,
});
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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],
}),
}));

View File

@@ -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}`,
);
});

View File

@@ -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(

View File

@@ -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.`,
);
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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;

View File

@@ -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 };