add whispering messages, add db connection check, add commands alias

This commit is contained in:
2025-09-12 21:07:17 +02:00
parent aba024b49f
commit 902d6cc6bc
7 changed files with 159 additions and 55 deletions

View File

@@ -4,7 +4,7 @@ import parseCommandArgs from "lib/parseCommandArgs";
export default new Command({
name: 'getcommands',
aliases: ['getcommands', 'getc'],
aliases: ['getcommands', 'getc', 'commands'],
usertype: 'chatter',
disableable: false,
execution: async msg => {

24
src/connectionCheck.ts Normal file
View File

@@ -0,0 +1,24 @@
import pocketbase from "db/connection";
import { RedisClient } from "bun";
import logger from "lib/logger";
export async function connectionCheck() {
let pbstatus = false;
try {
await pocketbase.health.check().then(a => a.code === 200);
pbstatus = true;
} catch { };
const tempclient = new RedisClient(undefined, {
connectionTimeout: 100,
maxRetries: 1,
});
let redisstatus = false;
try {
await tempclient.connect();
redisstatus = true;
} catch { };
logger.info(`Currently using the "${process.env.NODE_ENV ?? "production"}" database`);
pbstatus ? logger.ok(`Pocketbase status: good`) : logger.err(`Pocketbase status: bad`);
redisstatus ? logger.ok(`Redis/Valkey status: good`) : logger.err(`Redis/Valkey status: bad`);
if (!pbstatus || !redisstatus) process.exit(1);
};

View File

@@ -0,0 +1,98 @@
import { eventSub, chatterEventSub, streamerApi, streamerId, chatterApi, chatterId } from "main";
import { HelixEventSubSubscription } from "@twurple/api";
import kleur from "kleur";
import logger from "lib/logger";
// This file is such a fucking disaster lmaooooo
eventSub.onRevoke(event => {
logger.ok(`Successfully revoked streamer EventSub subscription: ${kleur.underline(event.id)}`);
});
eventSub.onSubscriptionCreateSuccess(event => {
logger.ok(`Successfully created streamer EventSub subscription: ${kleur.underline(event.id)}`);
deleteDuplicateStreamerSubscriptions.refresh();
});
eventSub.onSubscriptionCreateFailure(event => {
logger.err(`Failed to create streamer EventSub subscription: ${kleur.underline(event.id)}`);
});
eventSub.onSubscriptionDeleteSuccess(event => {
logger.ok(`Successfully deleted streamer EventSub subscription: ${kleur.underline(event.id)}`);
});
eventSub.onSubscriptionDeleteFailure(event => {
logger.err(`Failed to delete streamer EventSub subscription: ${kleur.underline(event.id)}`);
});
chatterEventSub.onRevoke(event => {
logger.ok(`Successfully revoked chatter EventSub subscription: ${kleur.underline(event.id)}`);
});
chatterEventSub.onSubscriptionCreateSuccess(event => {
logger.ok(`Successfully created chatter EventSub subscription: ${kleur.underline(event.id)}`);
deleteDuplicateChatterSubscriptions.refresh();
});
chatterEventSub.onSubscriptionCreateFailure(event => {
logger.err(`Failed to create chatter EventSub subscription: ${kleur.underline(event.id)}`);
});
chatterEventSub.onSubscriptionDeleteSuccess(event => {
logger.ok(`Successfully deleted chatter EventSub subscription: ${kleur.underline(event.id)}`);
});
chatterEventSub.onSubscriptionDeleteFailure(event => {
logger.err(`Failed to delete chatter EventSub subscription: ${kleur.underline(event.id)}`);
});
const deleteDuplicateStreamerSubscriptions = setTimeout(async () => {
logger.info('Deleting all double streamer subscriptions');
await streamerApi.asUser(streamerId, async tempapi => {
const subs = await tempapi.eventSub.getSubscriptionsForStatus("enabled");
const seen = new Map();
const duplicates: HelixEventSubSubscription[] = [];
for (const sub of subs.data) {
if (seen.has(sub.type)) {
duplicates.push(sub);
} else {
seen.set(sub.type, sub);
};
};
for (const sub of duplicates) {
await tempapi.eventSub.deleteSubscription(sub.id);
logger.ok(`Deleted streamer sub: id: ${sub.id}, type: ${sub.type}`);
};
if (duplicates.length === 0) logger.ok('No duplicate streamer subscriptions found');
else logger.ok('Deleted all duplicate streamer EventSub subscriptions');
});
}, 5000);
const deleteDuplicateChatterSubscriptions = setTimeout(async () => {
logger.info('Deleting all double chatter subscriptions');
await chatterApi.asUser(chatterId, async tempapi => {
const subs = await tempapi.eventSub.getSubscriptionsForStatus("enabled");
const seen = new Map();
const duplicates: HelixEventSubSubscription[] = [];
for (const sub of subs.data) {
if (seen.has(sub.type)) {
duplicates.push(sub);
} else {
seen.set(sub.type, sub);
};
};
for (const sub of duplicates) {
await tempapi.eventSub.deleteSubscription(sub.id);
logger.ok(`Deleted chatter sub: id: ${sub.id}, type: ${sub.type}`);
};
if (duplicates.length === 0) logger.ok('No duplicate chatter subscriptions found');
else logger.ok('Deleted all duplicate chatter EventSub subscriptions');
});
}, 10000);

View File

@@ -1,27 +1,4 @@
import kleur from "kleur";
import { eventSub, streamerApi, streamerId } from "main";
import logger from "lib/logger";
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)}`);
deleteDuplicateSubscriptions.refresh();
});
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.onSubscriptionDeleteFailure(event => {
logger.err(`Failed to delete EventSub subscription: ${kleur.underline(event.id)}`);
});
import { eventSub, chatterEventSub } from "main";
import { readdir } from 'node:fs/promises';
const files = await readdir(import.meta.dir);
@@ -32,30 +9,4 @@ for (const file of files) {
};
eventSub.start();
import { HelixEventSubSubscription } from "@twurple/api";
const deleteDuplicateSubscriptions = setTimeout(async () => {
logger.info('Deleting all double subscriptions');
await streamerApi.asUser(streamerId, async tempapi => {
const subs = await tempapi.eventSub.getSubscriptionsForStatus("enabled");
const seen = new Map();
const duplicates: HelixEventSubSubscription[] = [];
for (const sub of subs.data) {
if (seen.has(sub.type)) {
duplicates.push(sub);
} else {
seen.set(sub.type, sub);
};
};
for (const sub of duplicates) {
await tempapi.eventSub.deleteSubscription(sub.id);
logger.ok(`Deleted sub: id: ${sub.id}, type: ${sub.type}`);
};
if (duplicates.length === 0) logger.ok('No duplicate subscriptions found');
else logger.ok('Deleted all duplicate EventSub subscriptions');
});
}, 5000);
chatterEventSub.start();

19
src/events/whisper.ts Normal file
View File

@@ -0,0 +1,19 @@
import { redis } from "bun";
import { sendMessage } from "commands";
import { buildTimeString } from "lib/dateManager";
import { chatterEventSub, chatterApi, chatterId } from "main";
const WHISPERCOOLDOWN = 60 * 10; // 10 minutes
chatterEventSub.onUserWhisperMessage(chatterId, async msg => {
if (await redis.ttl(`user:${msg.senderUserId}:timeout`) < 0) return;
const cooldown = await redis.expiretime(`user:${msg.senderUserId}:whispercooldown`);
if (cooldown < 0) {
await redis.set(`user:${msg.senderUserId}:whispercooldown`, '1');
await redis.expire(`user:${msg.senderUserId}:whispercooldown`, WHISPERCOOLDOWN);
await sendMessage(`${msg.senderUserDisplayName} whispered: ${msg.messageText}`);
await chatterApi.whispers.sendWhisper(chatterId, msg.senderUserId, "Message sent. Please wait 10 minutes until you can send another message.");
} else {
await chatterApi.whispers.sendWhisper(chatterId, msg.senderUserId, `Wait another ${buildTimeString(cooldown * 1000, Date.now())} before sending another message.`);
};
});

View File

@@ -7,9 +7,11 @@ import { addInvuln } from "lib/invuln";
import { redis } from "bun";
import { remodMod, timeoutDuration } from "lib/timeout";
import User from "user";
import { buildTimeString } from "lib/dateManager";
import { connectionCheck } from "connectionCheck";
const CHATTERINTENTS = ["user:read:chat", "user:write:chat", "user:bot"];
await connectionCheck()
const CHATTERINTENTS = ["user:read:chat", "user:write:chat", "user:bot", "user:manage:whispers"];
const STREAMERINTENTS = ["channel:bot", "user:read:chat", "moderation:read", "channel:manage:moderators", "moderator:manage:chat_messages", "moderator:manage:banned_users", "bits:read", "channel:moderate", "moderator:manage:shoutouts"];
export const singleUserMode = process.env.CHATTER_IS_STREAMER === 'true';
@@ -30,6 +32,8 @@ export const streamerApi = streamerAuthProvider ? new ApiClient({ authProvider:
/** As the streamerApi has either the streamer or the chatter if the chatter IS the streamer this has streamer permissions */
export const eventSub = new EventSubWsListener({ apiClient: streamerApi });
export const chatterEventSub = singleUserMode ? eventSub : new EventSubWsListener({ apiClient: chatterApi });
export const commandPrefix = process.env.COMMAND_PREFIX ?? "!";
export const streamerUsers = [chatterId, streamerId];