From 3cb988cad9afab4e64c104e09e93412d7b1ec64d Mon Sep 17 00:00:00 2001 From: GalvinPython Date: Fri, 18 Jul 2025 14:57:11 +0100 Subject: [PATCH 1/4] feat(bot): add guild events --- src/db/discord.ts | 44 ++++++++++++ src/db/schema.ts | 1 + src/events/guildCreate.ts | 17 +++++ src/events/guildDelete.ts | 17 +++++ src/index.ts | 4 ++ src/utils/discord/updateGuildsOnStartup.ts | 78 ++++++++++++++++++++++ 6 files changed, 161 insertions(+) create mode 100644 src/events/guildCreate.ts create mode 100644 src/events/guildDelete.ts create mode 100644 src/utils/discord/updateGuildsOnStartup.ts diff --git a/src/db/discord.ts b/src/db/discord.ts index f954df9..f06d1b7 100644 --- a/src/db/discord.ts +++ b/src/db/discord.ts @@ -6,6 +6,7 @@ import { db } from "./db"; import { dbGuildYouTubeSubscriptionsTable, dbGuildTwitchSubscriptionsTable, + dbDiscordTable, } from "./schema"; export async function checkIfGuildIsTrackingUserAlready( @@ -280,3 +281,46 @@ export async function discordRemoveGuildTrackingChannel( return { success: false, data: [] }; } } + +// Add a new guild to track +export async function discordAddNewGuild( + guildId: string, +): Promise<{ success: boolean; data: [] }> { + console.log(`Adding new guild to track: ${guildId}`); + + try { + await db.insert(dbDiscordTable).values({ + guildId: guildId, + allowedPublicSharing: false, + isInServer: true, + memberCount: 0, + }); + + return { success: true, data: [] }; + } catch (error) { + console.error("Error adding new guild to track:", error); + + return { success: false, data: [] }; + } +} + +// "Remove" a guild from tracking +// Basically just set isInServer to false for archival purposes +export async function discordRemoveGuildFromTracking( + guildId: string, +): Promise<{ success: boolean; data: [] }> { + console.log(`Removing guild from tracking: ${guildId}`); + + try { + await db + .update(dbDiscordTable) + .set({ isInServer: false }) + .where(eq(dbDiscordTable.guildId, guildId)); + + return { success: true, data: [] }; + } catch (error) { + console.error("Error removing guild from tracking:", error); + + return { success: false, data: [] }; + } +} diff --git a/src/db/schema.ts b/src/db/schema.ts index 9cde7bf..1617f8a 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -8,6 +8,7 @@ export const dbDiscordTable = pgTable("discord", { allowedPublicSharing: boolean("allowed_public_sharing").notNull().default(false), feedrUpdatesChannelId: text("feedr_updates_channel_id"), isInServer: boolean("is_in_server").notNull().default(true), + memberCount: integer("member_count").notNull().default(0), }); export const dbBlueskyTable = pgTable("bluesky", { diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts new file mode 100644 index 0000000..1a3697b --- /dev/null +++ b/src/events/guildCreate.ts @@ -0,0 +1,17 @@ +import { Events } from "discord.js"; + +import client from ".."; +import { discordAddNewGuild } from "../db/discord"; + +client.on(Events.GuildCreate, async (guild) => { + console.log(`Joined new guild: ${guild.name} (ID: ${guild.id})`); + + // Add the new guild to tracking + const result = await discordAddNewGuild(guild.id); + + if (result.success) { + console.log(`Successfully added guild ${guild.id} to tracking.`); + } else { + console.error(`Failed to add guild ${guild.id} to tracking.`); + } +}); diff --git a/src/events/guildDelete.ts b/src/events/guildDelete.ts new file mode 100644 index 0000000..02d878b --- /dev/null +++ b/src/events/guildDelete.ts @@ -0,0 +1,17 @@ +import { Events } from "discord.js"; + +import client from ".."; +import { discordRemoveGuildFromTracking } from "../db/discord"; + +client.on(Events.GuildDelete, async (guild) => { + console.log(`Left guild: ${guild.name} (ID: ${guild.id})`); + + // Remove the guild from tracking + const result = await discordRemoveGuildFromTracking(guild.id); + + if (result.success) { + console.log(`Successfully removed guild ${guild.id} from tracking.`); + } else { + console.error(`Failed to remove guild ${guild.id} from tracking.`); + } +}); diff --git a/src/index.ts b/src/index.ts index 5c9d22e..1f21bb1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,7 @@ import { import { env } from "./config.ts"; import commandsMap from "./commands.ts"; import { getTwitchToken } from "./utils/twitch/auth.ts"; +import updateGuildsOnStartup from "./utils/discord/updateGuildsOnStartup.ts"; if (!env.discordToken || env.discordToken === "YOUR_DISCORD_TOKEN") { throw new Error("You MUST provide a discord token in .env!"); @@ -75,6 +76,9 @@ await Promise.all( }), ); +// Update the guilds on startup +await updateGuildsOnStartup(); + // Attempt the garbage collection every hour setInterval( () => { diff --git a/src/utils/discord/updateGuildsOnStartup.ts b/src/utils/discord/updateGuildsOnStartup.ts new file mode 100644 index 0000000..6c3b970 --- /dev/null +++ b/src/utils/discord/updateGuildsOnStartup.ts @@ -0,0 +1,78 @@ +// Checks for any guilds that may have been added/removed while the bot was offline +import { eq } from "drizzle-orm"; + +import { dbDiscordTable } from "../../db/schema"; +import client from "../.."; +import { db } from "../../db/db"; + +export default async function () { + console.log("Checking for guilds to update on startup..."); + + let currentGuilds: string[] = []; + + // Keep checking every 10 seconds until currentGuilds is not empty + while (currentGuilds.length === 0) { + console.log("Waiting for guilds to load..."); + currentGuilds = client.guilds.cache.map((guild) => guild.id); + if (currentGuilds.length === 0) { + await new Promise((resolve) => setTimeout(resolve, 10000)); + } + } + + // Get all the guilds from the database + const data = await db.select().from(dbDiscordTable); + + console.log( + `Currently in ${currentGuilds.length} guilds, checking against ${data.length} in the database.`, + ); + + // Find any guilds that are in the database but not in the current guilds + const missingGuilds = data.filter( + (guild) => !currentGuilds.includes(guild.guildId), + ); + + // Find any guilds that are in the current guilds but not in the database + const newGuilds = currentGuilds.filter( + (id) => !data.some((guild) => guild.guildId === id), + ); + + // Update the database for missing guilds + missingGuilds.forEach(async (guild) => { + console.log(`Removing guild from tracking: ${guild.guildId}`); + const result = await db + .update(dbDiscordTable) + .set({ isInServer: false }) + .where(eq(dbDiscordTable.guildId, guild.guildId)) + .returning(); + + if (result) { + console.log( + `Successfully removed guild ${guild.guildId} from tracking.`, + ); + } else { + console.error( + `Failed to remove guild ${guild.guildId} from tracking.`, + ); + } + }); + + newGuilds.forEach(async (guildId) => { + console.log(`Adding new guild to tracking: ${guildId}`); + const result = await db + .insert(dbDiscordTable) + .values({ + guildId, + allowedPublicSharing: false, + feedrUpdatesChannelId: null, + isInServer: true, + memberCount: 0, + }) + .returning(); + + if (result) { + console.log(`Successfully added guild ${guildId} to tracking.`); + } else { + console.error(`Failed to add guild ${guildId} to tracking.`); + } + }); +} From fb6819e88fd71c5f2c4c8f5f8094504339277ab1 Mon Sep 17 00:00:00 2001 From: Galvin Date: Fri, 18 Jul 2025 15:46:03 +0100 Subject: [PATCH 2/4] Update src/utils/discord/updateGuildsOnStartup.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/utils/discord/updateGuildsOnStartup.ts | 82 +++++++++++++--------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/src/utils/discord/updateGuildsOnStartup.ts b/src/utils/discord/updateGuildsOnStartup.ts index 6c3b970..b92ec1d 100644 --- a/src/utils/discord/updateGuildsOnStartup.ts +++ b/src/utils/discord/updateGuildsOnStartup.ts @@ -37,42 +37,54 @@ export default async function () { ); // Update the database for missing guilds - missingGuilds.forEach(async (guild) => { - console.log(`Removing guild from tracking: ${guild.guildId}`); - const result = await db - .update(dbDiscordTable) - .set({ isInServer: false }) - .where(eq(dbDiscordTable.guildId, guild.guildId)) - .returning(); + try { + await Promise.all( + missingGuilds.map(async (guild) => { + console.log(`Removing guild from tracking: ${guild.guildId}`); + const result = await db + .update(dbDiscordTable) + .set({ isInServer: false }) + .where(eq(dbDiscordTable.guildId, guild.guildId)) + .returning(); - if (result) { - console.log( - `Successfully removed guild ${guild.guildId} from tracking.`, - ); - } else { - console.error( - `Failed to remove guild ${guild.guildId} from tracking.`, - ); - } - }); + if (result) { + console.log( + `Successfully removed guild ${guild.guildId} from tracking.`, + ); + } else { + console.error( + `Failed to remove guild ${guild.guildId} from tracking.`, + ); + } + }), + ); + } catch (error) { + console.error("Error while removing missing guilds:", error); + } - newGuilds.forEach(async (guildId) => { - console.log(`Adding new guild to tracking: ${guildId}`); - const result = await db - .insert(dbDiscordTable) - .values({ - guildId, - allowedPublicSharing: false, - feedrUpdatesChannelId: null, - isInServer: true, - memberCount: 0, - }) - .returning(); + try { + await Promise.all( + newGuilds.map(async (guildId) => { + console.log(`Adding new guild to tracking: ${guildId}`); + const result = await db + .insert(dbDiscordTable) + .values({ + guildId, + allowedPublicSharing: false, + feedrUpdatesChannelId: null, + isInServer: true, + memberCount: 0, + }) + .returning(); - if (result) { - console.log(`Successfully added guild ${guildId} to tracking.`); - } else { - console.error(`Failed to add guild ${guildId} to tracking.`); - } - }); + if (result) { + console.log(`Successfully added guild ${guildId} to tracking.`); + } else { + console.error(`Failed to add guild ${guildId} to tracking.`); + } + }), + ); + } catch (error) { + console.error("Error while adding new guilds:", error); + } } From c40d5a732c4575e81f64924a6001012b6e4c5c5a Mon Sep 17 00:00:00 2001 From: Galvin Date: Fri, 18 Jul 2025 15:46:30 +0100 Subject: [PATCH 3/4] Update src/db/discord.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/db/discord.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/discord.ts b/src/db/discord.ts index f06d1b7..52be8e5 100644 --- a/src/db/discord.ts +++ b/src/db/discord.ts @@ -290,7 +290,7 @@ export async function discordAddNewGuild( try { await db.insert(dbDiscordTable).values({ - guildId: guildId, + guildId, allowedPublicSharing: false, isInServer: true, memberCount: 0, From 67d0c1866438f8b43fcaed9d698c8b70de5044dd Mon Sep 17 00:00:00 2001 From: GalvinPython Date: Fri, 18 Jul 2025 16:24:23 +0100 Subject: [PATCH 4/4] style: update file based on suggestions --- .env.example | 1 + src/config.ts | 5 +++++ src/utils/discord/updateGuildsOnStartup.ts | 17 ++++++++++++----- src/utils/youtube/fetchLatestUploads.ts | 3 +-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index b203af7..cd48a5d 100644 --- a/.env.example +++ b/.env.example @@ -17,6 +17,7 @@ TWITCH_CLIENT_SECRET='YOUR_TWITCH_CLIENT_SECRET' CONFIG_UPDATE_INTERVAL_YOUTUBE='10' CONFIG_UPDATE_INTERVAL_TWITCH='2' CONFIG_DISCORD_LOGS_CHANNEL='YOUR_DISCORD_LOGS_CHANNEL' +CONFIG_DISCORD_WAIT_FOR_GUILD_CACHE_TIME='YOUR_TIME_IN_SECONDS' # Postgres URLs POSTGRES_URL='postgresql://user:password@server:port/database' diff --git a/src/config.ts b/src/config.ts index 40dd781..4e827fd 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,6 +4,7 @@ export interface Config { updateIntervalYouTube: number; updateIntervalTwitch: number; databaseUrl: string | undefined; + discordWaitForGuildCacheTime: number; } export const config: Config = { @@ -16,6 +17,10 @@ export const config: Config = { databaseUrl: runningInDevMode ? process.env?.POSTGRES_DEV_URL : process.env?.POSTGRES_URL, + discordWaitForGuildCacheTime: process.env + ?.CONFIG_DISCORD_WAIT_FOR_GUILD_CACHE_TIME + ? parseInt(process.env?.CONFIG_DISCORD_WAIT_FOR_GUILD_CACHE_TIME) * 1000 + : 10_000, }; interface Env { diff --git a/src/utils/discord/updateGuildsOnStartup.ts b/src/utils/discord/updateGuildsOnStartup.ts index b92ec1d..ba0c37a 100644 --- a/src/utils/discord/updateGuildsOnStartup.ts +++ b/src/utils/discord/updateGuildsOnStartup.ts @@ -4,6 +4,7 @@ import { eq } from "drizzle-orm"; import { dbDiscordTable } from "../../db/schema"; import client from "../.."; import { db } from "../../db/db"; +import { config } from "../../config"; export default async function () { console.log("Checking for guilds to update on startup..."); @@ -15,7 +16,9 @@ export default async function () { console.log("Waiting for guilds to load..."); currentGuilds = client.guilds.cache.map((guild) => guild.id); if (currentGuilds.length === 0) { - await new Promise((resolve) => setTimeout(resolve, 10000)); + await new Promise((resolve) => + setTimeout(resolve, config.discordWaitForGuildCacheTime), + ); } } @@ -47,7 +50,7 @@ export default async function () { .where(eq(dbDiscordTable.guildId, guild.guildId)) .returning(); - if (result) { + if (result.length > 0) { console.log( `Successfully removed guild ${guild.guildId} from tracking.`, ); @@ -77,10 +80,14 @@ export default async function () { }) .returning(); - if (result) { - console.log(`Successfully added guild ${guildId} to tracking.`); + if (result.length > 0) { + console.log( + `Successfully added guild ${guildId} to tracking.`, + ); } else { - console.error(`Failed to add guild ${guildId} to tracking.`); + console.error( + `Failed to add guild ${guildId} to tracking.`, + ); } }), ); diff --git a/src/utils/youtube/fetchLatestUploads.ts b/src/utils/youtube/fetchLatestUploads.ts index b684cea..d1f32e4 100644 --- a/src/utils/youtube/fetchLatestUploads.ts +++ b/src/utils/youtube/fetchLatestUploads.ts @@ -1,5 +1,4 @@ -import type { Platform } from "../../types/types.d.ts"; - +import { Platform } from "../../types/types.d"; import { dbGuildYouTubeSubscriptionsTable, dbYouTubeTable,