Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/db/discord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { db } from "./db";
import {
dbGuildYouTubeSubscriptionsTable,
dbGuildTwitchSubscriptionsTable,
dbDiscordTable,
} from "./schema";

export async function checkIfGuildIsTrackingUserAlready(
Expand Down Expand Up @@ -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: [] };
}
}
1 change: 1 addition & 0 deletions src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", {
Expand Down
17 changes: 17 additions & 0 deletions src/events/guildCreate.ts
Original file line number Diff line number Diff line change
@@ -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.`);
}
});
17 changes: 17 additions & 0 deletions src/events/guildDelete.ts
Original file line number Diff line number Diff line change
@@ -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.`);
}
});
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!");
Expand Down Expand Up @@ -75,6 +76,9 @@ await Promise.all(
}),
);

// Update the guilds on startup
await updateGuildsOnStartup();

// Attempt the garbage collection every hour
setInterval(
() => {
Expand Down
78 changes: 78 additions & 0 deletions src/utils/discord/updateGuildsOnStartup.ts
Original file line number Diff line number Diff line change
@@ -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));
Copy link

Copilot AI Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The 10-second timeout is a magic number. Consider extracting this to a named constant like GUILD_LOAD_RETRY_INTERVAL for better maintainability.

Suggested change
await new Promise((resolve) => setTimeout(resolve, 10000));
await new Promise((resolve) => setTimeout(resolve, GUILD_LOAD_RETRY_INTERVAL));

Copilot uses AI. Check for mistakes.
}
}

// 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) {
Copy link

Copilot AI Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition if (result) is incorrect. Database update operations return an array, which is always truthy even when empty. Check result.length > 0 instead.

Suggested change
if (result) {
if (result.length > 0) {

Copilot uses AI. Check for mistakes.
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) {
Copy link

Copilot AI Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition if (result) is incorrect. Database insert operations return an array, which is always truthy even when empty. Check result.length > 0 instead.

Suggested change
if (result) {
if (result.length > 0) {

Copilot uses AI. Check for mistakes.
console.log(`Successfully added guild ${guildId} to tracking.`);
} else {
console.error(`Failed to add guild ${guildId} to tracking.`);
}
});
Copy link

Copilot AI Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using forEach with async callbacks can lead to unhandled promise rejections and doesn't properly handle errors. Use Promise.all with map or a for...of loop instead.

Suggested change
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.`);
}
});
for (const guild of missingGuilds) {
try {
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.`,
);
}
} catch (error) {
console.error(
`Error while removing guild ${guild.guildId} from tracking:`,
error,
);
}
}
for (const guildId of newGuilds) {
try {
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.`);
}
} catch (error) {
console.error(
`Error while adding guild ${guildId} to tracking:`,
error,
);
}
}

Copilot uses AI. Check for mistakes.
}