From 2fd30bd87e5bddee63817c01adf64dd8cc12a7a5 Mon Sep 17 00:00:00 2001 From: qwerinope Date: Fri, 4 Jul 2025 18:19:25 +0200 Subject: [PATCH] add pretty console formatting --- bot/auth.ts | 25 ++++++++++++++----------- bot/commands/donateqbucks.ts | 3 ++- bot/commands/giveitem.ts | 3 ++- bot/db/connection.ts | 3 ++- bot/db/dbTimeouts.ts | 5 +++-- bot/db/dbUsedItems.ts | 5 +++-- bot/db/dbUser.ts | 3 ++- bot/events/index.ts | 23 ++++++++++++----------- bot/events/message.ts | 6 +++--- bot/index.ts | 13 +++++++++++-- bot/lib/timeout.ts | 4 ++-- bun.lock | 3 +++ package.json | 1 + 13 files changed, 60 insertions(+), 37 deletions(-) diff --git a/bot/auth.ts b/bot/auth.ts index c9b859d..3a93561 100644 --- a/bot/auth.ts +++ b/bot/auth.ts @@ -1,6 +1,9 @@ import { RefreshingAuthProvider, exchangeCode, type AccessToken } from "@twurple/auth"; import { createAuthRecord, deleteAuthRecord, getAuthRecord, updateAuthRecord } from "./db/dbAuth"; +import { logger } from "."; +import kleur from "kleur"; + async function initAuth(userId: string, clientId: string, clientSecret: string, requestedIntents: string[], streamer: boolean): Promise { const port = process.env.REDIRECT_PORT ?? 3456 const redirectURL = process.env.REDIRECT_URL ?? `http://localhost:${port}`; @@ -9,9 +12,9 @@ async function initAuth(userId: string, clientId: string, clientSecret: string, 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 ${streamer ? 'the streamer' : 'the chatter'} to authenticate the bot.` - console.info(instruction); - console.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; @@ -39,20 +42,20 @@ async function initAuth(userId: string, clientId: string, clientSecret: string, const code = await codepromise; server.stop(false); - console.info(`Authentication code received.`); + logger.info(`Authentication code received.`); const tokenData = await exchangeCode(clientId, clientSecret, code, redirectURL); - console.info(`Successfully authenticated code.`); + logger.info(`Successfully authenticated code.`); await createAuthRecord(tokenData, userId); - console.info(`Successfully saved auth data in the database.`); + logger.ok(`Successfully saved auth data in the database.`); return tokenData; }; export async function createAuthProvider(user: string, intents: string[], streamer = false): Promise { const clientId = process.env.CLIENT_ID; - if (!clientId) { console.error("Please provide a CLIENT_ID in .env."); process.exit(1); }; + if (!clientId) { logger.enverr("CLIENT_ID"); process.exit(1); }; const clientSecret = process.env.CLIENT_SECRET; - if (!clientSecret) { console.error("Please provide a CLIENT_SECRET in .env."); process.exit(1); }; + if (!clientSecret) { logger.enverr("CLIENT_SECRET"); process.exit(1); }; const authRecord = await getAuthRecord(user, intents); @@ -65,18 +68,18 @@ export async function createAuthProvider(user: string, intents: string[], stream await authData.addUserForToken(token, intents); authData.onRefresh(async (user, token) => { - console.info(`Successfully refreshed auth for user ${user}`); + logger.ok(`Successfully refreshed auth for user ${user}`); await updateAuthRecord(user, token); }); authData.onRefreshFailure((user, err) => { - console.error(`ERROR: Failed to refresh auth for user ${user}: ${err.name} ${err.message}`); + logger.err(`Failed to refresh auth for user ${user}: ${err.name} ${err.message}`); }); try { await authData.refreshAccessTokenForUser(user); } catch (err) { - console.error(`Failed to refresh user ${user}. Please restart the bot and re-authenticate it. Make sure the user that auths the bot and the user that's defined in .env are the same.`); + logger.err(`Failed to refresh user ${user}. 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); }; diff --git a/bot/commands/donateqbucks.ts b/bot/commands/donateqbucks.ts index 63d4147..ead8566 100644 --- a/bot/commands/donateqbucks.ts +++ b/bot/commands/donateqbucks.ts @@ -4,6 +4,7 @@ import { getUserRecord } from "../db/dbUser"; import parseCommandArgs from "../lib/parseCommandArgs"; import { changeBalance } from "../lib/changeBalance"; import { User } from "../user"; +import { logger } from ".."; export default new Command('donate', ['donate'], 'chatter', async (msg, user) => { const args = parseCommandArgs(msg.messageText); @@ -37,7 +38,7 @@ export default new Command('donate', ['donate'], 'chatter', async (msg, user) => } else { // TODO: Rewrite this section await sendMessage(`Failed to give ${target.displayName} ${amount} qbuck${(amount === 1 ? '' : 's')}`, msg.messageId); - console.error(`WARNING: Qweribucks donation failed: target success: ${data[0] !== false}, donator success: ${data[1] !== false}`); + logger.err(`WARNING: Qweribucks donation failed: target success: ${data[0] !== false}, donator success: ${data[1] !== false}`); }; await Promise.all([ diff --git a/bot/commands/giveitem.ts b/bot/commands/giveitem.ts index a31787c..d2faf38 100644 --- a/bot/commands/giveitem.ts +++ b/bot/commands/giveitem.ts @@ -1,4 +1,5 @@ import { Command, sendMessage } from "."; +import { logger } from ".."; import type { userRecord } from "../db/connection"; import { getUserRecord } from "../db/dbUser"; import items, { changeItemCount } from "../items"; @@ -41,7 +42,7 @@ export default new Command('give', ['give'], 'chatter', async (msg, user) => { } else { // TODO: Rewrite this section await sendMessage(`Failed to give ${target.displayName} ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)}`, msg.messageId); - console.error(`WARNING: Item donation failed: target success: ${data[0] !== false}, donator success: ${data[1] !== false}`); + logger.warn(`WARNING: Item donation failed: target success: ${data[0] !== false}, donator success: ${data[1] !== false}`); }; await user.clearLock(); await target.clearLock(); diff --git a/bot/db/connection.ts b/bot/db/connection.ts index de73205..cb6949d 100644 --- a/bot/db/connection.ts +++ b/bot/db/connection.ts @@ -1,9 +1,10 @@ import type { AccessToken } from "@twurple/auth"; import PocketBase, { RecordService } from "pocketbase"; import type { inventory } from "../items"; +import { logger } from ".."; const pocketbaseurl = process.env.POCKETBASE_URL ?? "localhost:8090"; -if (pocketbaseurl === "") { console.error("Please provide a POCKETBASE_URL in .env."); process.exit(1); }; +if (pocketbaseurl === "") { logger.enverr("POCKETBASE_URL"); process.exit(1); }; export type authRecord = { id: string; diff --git a/bot/db/dbTimeouts.ts b/bot/db/dbTimeouts.ts index 3f6587d..9c11351 100644 --- a/bot/db/dbTimeouts.ts +++ b/bot/db/dbTimeouts.ts @@ -1,12 +1,13 @@ import pocketbase from "./connection"; import { User } from "../user"; +import { logger } from ".."; const pb = pocketbase.collection('timeouts'); export async function createTimeoutRecord(user: User, target: User, item: string): Promise { try { await pb.create({ user: user.id, target: target.id, item }); } catch (err) { - console.error(`Failed to create timeout record in database: user: ${user.id}, target: ${target.id}, item: ${item}`); - console.error(err); + logger.err(`Failed to create timeout record in database: user: ${user.id}, target: ${target.id}, item: ${item}`); + logger.err(err as string); }; }; diff --git a/bot/db/dbUsedItems.ts b/bot/db/dbUsedItems.ts index 152953b..0980218 100644 --- a/bot/db/dbUsedItems.ts +++ b/bot/db/dbUsedItems.ts @@ -1,12 +1,13 @@ import pocketbase from "./connection"; import { User } from "../user"; +import { logger } from ".."; const pb = pocketbase.collection('usedItems'); export async function createUsedItemRecord(user: User, item: string): Promise { try { await pb.create({ user: user.id, item }); } catch (err) { - console.error(`Failed to create usedItem record in database: user: ${user.id}, item: ${item}`); - console.error(err); + logger.err(`Failed to create usedItem record in database: user: ${user.id}, item: ${item}`); + logger.err(err as string); }; }; diff --git a/bot/db/dbUser.ts b/bot/db/dbUser.ts index d735907..40ecdbf 100644 --- a/bot/db/dbUser.ts +++ b/bot/db/dbUser.ts @@ -1,6 +1,7 @@ import pocketbase, { type userRecord } from "./connection"; import { emptyInventory, itemarray } from "../items"; import type { User } from "../user"; +import { logger } from ".."; const pb = pocketbase.collection('users'); /** Use this function to both ensure existance and to retreive data */ @@ -38,7 +39,7 @@ export async function updateUserRecord(user: User, newData: userRecord): Promise await pb.update(user.id, newData); return true; } catch (err) { - console.error(err); + logger.err(err as string); return false; }; }; diff --git a/bot/events/index.ts b/bot/events/index.ts index c00f541..e229068 100644 --- a/bot/events/index.ts +++ b/bot/events/index.ts @@ -1,24 +1,25 @@ -import { eventSub, streamerApi, streamerId } from ".."; +import kleur from "kleur"; +import { eventSub, streamerApi, streamerId, logger } from ".."; eventSub.onRevoke(event => { - console.info(`Successfully revoked EventSub subscription: ${event.id}`); + logger.ok(`Successfully revoked EventSub subscription: ${event.id}`); }); eventSub.onSubscriptionCreateSuccess(event => { - console.info(`Successfully created EventSub subscription: ${event.id}`); + logger.ok(`Successfully created EventSub subscription: ${kleur.underline(event.id)}`); deleteDuplicateSubscriptions.refresh(); }); eventSub.onSubscriptionCreateFailure(event => { - console.error(`Failed to create EventSub subscription: ${event.id}`); + logger.err(`Failed to create EventSub subscription: ${event.id}`); }); eventSub.onSubscriptionDeleteSuccess(event => { - console.info(`Successfully deleted EventSub subscription: ${event.id}`); + logger.ok(`Successfully deleted EventSub subscription: ${event.id}`); }); eventSub.onSubscriptionDeleteFailure(event => { - console.error(`Failed to delete EventSub subscription: ${event.id}`); + logger.err(`Failed to delete EventSub subscription: ${event.id}`); }); import { readdir } from 'node:fs/promises'; @@ -36,12 +37,12 @@ import { StaticAuthProvider } from "@twurple/auth"; import { ApiClient, HelixEventSubSubscription } from "@twurple/api"; const deleteDuplicateSubscriptions = setTimeout(async () => { - console.info('Deleting all double subscriptions'); + logger.info('Deleting all double subscriptions'); const tokendata = await streamerApi.getTokenInfo(); const authdata = await getAuthRecord(streamerId, []); const tempauth = new StaticAuthProvider(tokendata.clientId, authdata?.accesstoken.accessToken!); const tempapi: ApiClient = new ApiClient({ authProvider: tempauth }); - console.info('Created the temporary API client'); + logger.info('Created the temporary API client'); const subs = await tempapi.eventSub.getSubscriptionsForStatus('enabled'); const seen = new Map(); const duplicates: HelixEventSubSubscription[] = []; @@ -58,8 +59,8 @@ const deleteDuplicateSubscriptions = setTimeout(async () => { for (const sub of duplicates) { await tempapi.eventSub.deleteSubscription(sub.id); - console.info(`Deleted sub: id: ${sub.id}, type: ${sub.type}`); + logger.ok(`Deleted sub: id: ${sub.id}, type: ${sub.type}`); }; - if (duplicates.length === 0) console.info('No duplicate subscriptions found'); - console.info('Removed temporary API client'); + if (duplicates.length === 0) logger.ok('No duplicate subscriptions found'); + else logger.ok('Deleted all duplicate EventSub subscriptions'); }, 5000); diff --git a/bot/events/message.ts b/bot/events/message.ts index 84d6aa1..6b6483c 100644 --- a/bot/events/message.ts +++ b/bot/events/message.ts @@ -1,10 +1,10 @@ -import { chatterId, streamerId, eventSub, commandPrefix, singleUserMode, streamerUsers } from ".."; +import { chatterId, streamerId, eventSub, commandPrefix, singleUserMode, streamerUsers, logger } from ".."; import { User } from "../user"; import commands, { sendMessage } from "../commands"; import { redis } from "bun"; import { isAdmin } from "../lib/admins"; -console.info(`Loaded the following commands: ${commands.keys().toArray().join(', ')}`); +logger.info(`Loaded the following commands: ${commands.keys().toArray().join(', ')}`); eventSub.onChannelChatMessage(streamerId, streamerId, async msg => { // return if double user mode is on and the chatter says something, we don't need them @@ -43,7 +43,7 @@ eventSub.onChannelChatMessage(streamerId, streamerId, async msg => { try { await selected.execute(msg, user!); } catch (err) { - console.error(err); + logger.err(err as string); await sendMessage('ERROR: Something went wrong', msg.messageId); await user?.clearLock(); }; diff --git a/bot/index.ts b/bot/index.ts index e1822fd..0163d9f 100644 --- a/bot/index.ts +++ b/bot/index.ts @@ -2,15 +2,24 @@ import { createAuthProvider } from "./auth"; import { ApiClient } from "@twurple/api"; import { EventSubWsListener } from "@twurple/eventsub-ws"; import { addAdmin } from "./lib/admins"; +import kleur from "kleur"; const CHATTERINTENTS = ["user:read:chat", "user:write:chat", "user:bot"]; const STREAMERINTENTS = ["user:read:chat", "moderation:read", "channel:manage:moderators", "moderator:manage:banned_users"]; +export 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`) +}; + export const singleUserMode = process.env.CHATTER_IS_STREAMER === 'true'; export const chatterId = process.env.CHATTER_ID ?? ""; -if (chatterId === "") { console.error('Please set a CHATTER_ID in the .env'); process.exit(1); }; +if (chatterId === "") { logger.enverr('CHATTER_ID'); process.exit(1); }; export const streamerId = process.env.STREAMER_ID ?? ""; -if (streamerId === "") { console.error('Please set a STREAMER_ID in the .env'); process.exit(1); }; +if (streamerId === "") { logger.enverr('STREAMER_ID'); process.exit(1); }; export const chatterAuthProvider = await createAuthProvider(chatterId, singleUserMode ? CHATTERINTENTS.concat(STREAMERINTENTS) : CHATTERINTENTS); export const streamerAuthProvider = singleUserMode ? undefined : await createAuthProvider(streamerId, STREAMERINTENTS, true); diff --git a/bot/lib/timeout.ts b/bot/lib/timeout.ts index d2b8dde..dc60589 100644 --- a/bot/lib/timeout.ts +++ b/bot/lib/timeout.ts @@ -1,4 +1,4 @@ -import { streamerApi, streamerId, streamerUsers } from ".."; +import { logger, streamerApi, streamerId, streamerUsers } from ".."; import { User } from "../user"; type SuccessfulTimeout = { status: true }; @@ -25,7 +25,7 @@ export const timeout = async (user: User, reason: string, duration?: number): Pr try { await streamerApi.moderation.banUser(streamerId, { user: user.id, reason, duration }); } catch (err) { - console.error(err); + logger.err(err as string); return { status: false, reason: 'unknown' } }; diff --git a/bun.lock b/bun.lock index 1ac2849..8035f59 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "dependencies": { "@twurple/auth": "^7.3.0", "@twurple/eventsub-ws": "^7.3.0", + "kleur": "^4.1.5", "pocketbase": "^0.26.1", }, "devDependencies": { @@ -57,6 +58,8 @@ "bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="], + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + "klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="], "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], diff --git a/package.json b/package.json index fb8ceb6..638e6bd 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@twurple/auth": "^7.3.0", "@twurple/eventsub-ws": "^7.3.0", + "kleur": "^4.1.5", "pocketbase": "^0.26.1" } }