From 04171092ad382fbe5e9e0060bef8df3d73850f04 Mon Sep 17 00:00:00 2001 From: GalvinPython Date: Sun, 20 Jul 2025 22:39:04 +0100 Subject: [PATCH 1/6] feat(bot): add autocomplete to /untrack --- src/commands.ts | 59 +++++++++++++++++++++++++++++++++-------------- src/db/discord.ts | 58 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 94 insertions(+), 23 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index c80b5df..923b100 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -26,6 +26,7 @@ import search from "./utils/youtube/search"; import { checkIfGuildIsTrackingUserAlready, discordAddGuildTrackingUser, + discordGetAllTrackedInGuild, } from "./db/discord"; import { Platform, YouTubeContentType } from "./types/types.d"; import searchTwitch from "./utils/twitch/searchTwitch"; @@ -756,28 +757,14 @@ const commands: Record = { untrack: { data: { options: [ - { - name: "platform", - description: "Select a supported platform to track", - type: 3, - required: true, - choices: [ - { - name: "Twitch", - value: "twitch", - }, - { - name: "YouTube", - value: "youtube", - }, - ], - }, { name: "user_id", + // TODO: Searching description: - "Enter the YouTube/Twitch channel ID to stop tracking", + "Select the channel or streamer to stop tracking. Searching is not supported, use the above options!", type: 3, required: true, + autocomplete: true, }, ], name: "untrack", @@ -924,6 +911,44 @@ const commands: Record = { return; } }, + autoComplete: async (interaction: AutocompleteInteraction) => { + const trackedChannels = await discordGetAllTrackedInGuild( + interaction.guildId as string, + ); + + console.dir( + { message: "Tracked channels:", data: trackedChannels }, + { depth: null }, + ); + + if (!trackedChannels || !trackedChannels.success) { + console.error( + "An error occurred while trying to get the tracked channels in this guild!", + ); + await interaction.respond([]); + + return; + } + + const trackedYouTubeChannels = + trackedChannels.data.youtubeSubscriptions; + const trackedTwitchChannels = + trackedChannels.data.twitchSubscriptions; + + return await interaction.respond( + trackedYouTubeChannels + .map((channel) => ({ + name: `YouTube: ${channel.youtubeChannel.youtubeChannelName} (${channel.youtubeChannel.youtubeChannelId}) | <#${channel.subscription.notificationChannelId}>`, + value: String(channel.subscription.id), + })) + .concat( + trackedTwitchChannels.map((channel) => ({ + name: `Twitch: ${channel.twitchChannel.twitchChannelName} (${channel.twitchChannel.twitchChannelId}) | <#${channel.subscription.notificationChannelId}>`, + value: String(channel.subscription.id), + })), + ), + ); + }, }, tracked: { data: { diff --git a/src/db/discord.ts b/src/db/discord.ts index 52be8e5..0e76b10 100644 --- a/src/db/discord.ts +++ b/src/db/discord.ts @@ -7,6 +7,8 @@ import { dbGuildYouTubeSubscriptionsTable, dbGuildTwitchSubscriptionsTable, dbDiscordTable, + dbYouTubeTable, + dbTwitchTable, } from "./schema"; export async function checkIfGuildIsTrackingUserAlready( @@ -199,22 +201,66 @@ export async function discordGetAllTrackedInGuild(guildId: string): Promise< | { success: true; data: { - youtubeSubscriptions: (typeof dbGuildYouTubeSubscriptionsTable.$inferSelect)[]; - twitchSubscriptions: (typeof dbGuildTwitchSubscriptionsTable.$inferSelect)[]; + youtubeSubscriptions: { + subscription: typeof dbGuildYouTubeSubscriptionsTable.$inferSelect; + youtubeChannel: typeof dbYouTubeTable.$inferSelect; + discord: typeof dbDiscordTable.$inferSelect; + }[]; + twitchSubscriptions: { + subscription: typeof dbGuildTwitchSubscriptionsTable.$inferSelect; + twitchChannel: typeof dbTwitchTable.$inferSelect; + discord: typeof dbDiscordTable.$inferSelect; + }[]; }; } | { success: false; data: null } > { try { const youtubeSubscriptions = await db - .select() + .select({ + subscription: dbGuildYouTubeSubscriptionsTable, + youtubeChannel: dbYouTubeTable, + discord: dbDiscordTable, + }) .from(dbGuildYouTubeSubscriptionsTable) - .where(eq(dbGuildYouTubeSubscriptionsTable.guildId, guildId)); + .where(eq(dbGuildYouTubeSubscriptionsTable.guildId, guildId)) + .innerJoin( + dbYouTubeTable, + eq( + dbGuildYouTubeSubscriptionsTable.youtubeChannelId, + dbYouTubeTable.youtubeChannelId, + ), + ) + .innerJoin( + dbDiscordTable, + eq( + dbGuildYouTubeSubscriptionsTable.guildId, + dbDiscordTable.guildId, + ), + ); const twitchSubscriptions = await db - .select() + .select({ + subscription: dbGuildTwitchSubscriptionsTable, + twitchChannel: dbTwitchTable, + discord: dbDiscordTable, + }) .from(dbGuildTwitchSubscriptionsTable) - .where(eq(dbGuildTwitchSubscriptionsTable.guildId, guildId)); + .where(eq(dbGuildTwitchSubscriptionsTable.guildId, guildId)) + .innerJoin( + dbTwitchTable, + eq( + dbGuildTwitchSubscriptionsTable.twitchChannelId, + dbTwitchTable.twitchChannelId, + ), + ) + .innerJoin( + dbDiscordTable, + eq( + dbGuildTwitchSubscriptionsTable.guildId, + dbDiscordTable.guildId, + ), + ); return { success: true, From f2462f491184f13a20e46947c664718e7bf7ed17 Mon Sep 17 00:00:00 2001 From: GalvinPython Date: Sun, 20 Jul 2025 23:07:41 +0100 Subject: [PATCH 2/6] fix(twitch): add streamerName to database in /track --- src/commands.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands.ts b/src/commands.ts index 923b100..fae51a5 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -625,6 +625,7 @@ const commands: Record = { const channelAdded = await addNewStreamerToTrack( platformUserId, isLive, + streamerName, ); if (!channelAdded.success) { From bcd5c6b42096de945c7d6993ad4576f877fd8a11 Mon Sep 17 00:00:00 2001 From: GalvinPython Date: Mon, 21 Jul 2025 13:42:41 +0100 Subject: [PATCH 3/6] feat(bot): complete /untrack command improvements --- src/commands.ts | 161 +++++++++++++++------------------------------- src/db/discord.ts | 29 ++++----- 2 files changed, 62 insertions(+), 128 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index fae51a5..e357df8 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -27,6 +27,7 @@ import { checkIfGuildIsTrackingUserAlready, discordAddGuildTrackingUser, discordGetAllTrackedInGuild, + discordRemoveGuildTrackingChannel, } from "./db/discord"; import { Platform, YouTubeContentType } from "./types/types.d"; import searchTwitch from "./utils/twitch/searchTwitch"; @@ -477,10 +478,25 @@ const commands: Record = { trackedChannels.data ) { // If the channel is already being tracked in the guild, we can just return - await interaction.reply({ - flags: MessageFlags.Ephemeral, - content: `This channel is already being tracked in ${trackedChannels.data.map((channel, index) => `${index > 0 && index === trackedChannels.data.length - 1 ? "and " : ""}<#${channel.guild_channel_id}>`).join(", ")}!`, - }); + if (Array.isArray(trackedChannels.data)) { + const channelList = trackedChannels.data + .map( + (channel, index, arr) => + `${index > 0 && index === arr.length - 1 ? "and " : ""}<#${channel.notificationChannelId}>`, + ) + .join(", "); + + await interaction.reply({ + flags: MessageFlags.Ephemeral, + content: `This channel is already being tracked in ${channelList}!`, + }); + } else { + await interaction.reply({ + flags: MessageFlags.Ephemeral, + content: + "This channel is already being tracked, but the data format is invalid.", + }); + } return; } @@ -590,10 +606,25 @@ const commands: Record = { trackedChannels.data ) { // If the channel is already being tracked in the guild, we can just return - await interaction.reply({ - flags: MessageFlags.Ephemeral, - content: `This channel is already being tracked in ${trackedChannels.data.map((channel, index) => `${index > 0 && index === trackedChannels.data.length - 1 ? "and " : ""}<#${channel.guild_channel_id}>`).join(", ")}!`, - }); + if (Array.isArray(trackedChannels.data)) { + const channelList = trackedChannels.data + .map( + (channel, index, arr) => + `${index > 0 && index === arr.length - 1 ? "and " : ""}<#${channel.notificationChannelId}>`, + ) + .join(", "); + + await interaction.reply({ + flags: MessageFlags.Ephemeral, + content: `This channel is already being tracked in ${channelList}!`, + }); + } else { + await interaction.reply({ + flags: MessageFlags.Ephemeral, + content: + "This channel is already being tracked, but the data format is invalid.", + }); + } return; } @@ -775,9 +806,7 @@ const commands: Record = { }, execute: async (interaction: CommandInteraction) => { // Get the YouTube Channel ID - const youtubeChannelId = interaction.options.get("user_id") - ?.value as string; - const platform = interaction.options.get("platform") + const platformUserId = interaction.options.get("user_id") ?.value as string; const guildId = interaction.guildId; @@ -807,110 +836,22 @@ const commands: Record = { return; } - // Platform check (to shut up TS) - if (platform != "youtube" && platform != "twitch") { + // Remove the guild from the database + const trackingDeleteSuccess = + await discordRemoveGuildTrackingChannel(platformUserId); + + if (!trackingDeleteSuccess || !trackingDeleteSuccess.success) { await interaction.reply({ flags: MessageFlags.Ephemeral, - content: - "Platform not supported! Please select a platform to track!", + content: "Failed to stop tracking the channel.", }); return; } - // Remove the guild from the database - switch (platform) { - case "youtube": - // Check if the channel is not being tracked in the guild - if ( - !(await checkIfGuildIsTrackingUserAlready( - youtubeChannelId, - guildId, - )) - ) { - await interaction.reply({ - flags: MessageFlags.Ephemeral, - content: - "This channel is not being tracked in this guild!", - }); - - return; - } - if ( - await stopGuildTrackingChannel( - guildId, - youtubeChannelId, - ) - ) { - await interaction.reply({ - flags: MessageFlags.Ephemeral, - content: - "Successfully stopped tracking the channel!", - }); - } else { - await interaction.reply({ - flags: MessageFlags.Ephemeral, - content: - "An error occurred while trying to stop tracking the channel! Please report this error!", - }); - } - - return; - case "twitch": { - // get the twitch id for the streamer - const platformUserId = - await getplatformUserId(youtubeChannelId); - - if (!platformUserId) { - await interaction.reply({ - flags: MessageFlags.Ephemeral, - content: - "An error occurred while trying to get the streamer ID! Please report this error!", - }); - - return; - } - - // check if the channel is not being tracked in the guild - if ( - !(await checkIfGuildIsTrackingUserAlready( - platformUserId, - guildId, - )) - ) { - await interaction.reply({ - flags: MessageFlags.Ephemeral, - content: - "This streamer is not being tracked in this guild!", - }); - - return; - } - - if ( - await twitchStopGuildTrackingChannel( - guildId, - platformUserId, - ) - ) { - await interaction.reply({ - flags: MessageFlags.Ephemeral, - content: - "Successfully stopped tracking the streamer!", - }); - } else { - await interaction.reply({ - flags: MessageFlags.Ephemeral, - content: - "An error occurred while trying to stop tracking the streamer! Please report this error!", - }); - } - - return; - } - default: - return; - } + await interaction.reply({ + content: "Successfully stopped tracking the channel.", + }); }, autoComplete: async (interaction: AutocompleteInteraction) => { const trackedChannels = await discordGetAllTrackedInGuild( @@ -940,12 +881,12 @@ const commands: Record = { trackedYouTubeChannels .map((channel) => ({ name: `YouTube: ${channel.youtubeChannel.youtubeChannelName} (${channel.youtubeChannel.youtubeChannelId}) | <#${channel.subscription.notificationChannelId}>`, - value: String(channel.subscription.id), + value: `youtube.${String(channel.subscription.id)}`, })) .concat( trackedTwitchChannels.map((channel) => ({ name: `Twitch: ${channel.twitchChannel.twitchChannelName} (${channel.twitchChannel.twitchChannelId}) | <#${channel.subscription.notificationChannelId}>`, - value: String(channel.subscription.id), + value: `twitch.${String(channel.subscription.id)}`, })), ), ); diff --git a/src/db/discord.ts b/src/db/discord.ts index 0e76b10..a4eb7e9 100644 --- a/src/db/discord.ts +++ b/src/db/discord.ts @@ -280,38 +280,31 @@ export async function discordGetAllTrackedInGuild(guildId: string): Promise< } // Remove tracking for a specific channel in a guild +// TODO: Make it so that if the channel is no longer tracked by any guilds, it is removed from the db entirely export async function discordRemoveGuildTrackingChannel( - guildId: string, - platform: Platform, - platformUserId: string, + trackingId: string, ): Promise<{ success: boolean; data: [] }> { - console.log( - `Removing guild ${guildId} tracking for user ${platformUserId} on platform ${platform}`, - ); + console.log(`Removing tracking for ID: ${trackingId}`); + + const [platform, platformTrackingId] = trackingId.split("."); try { if (platform === Platform.YouTube) { await db .delete(dbGuildYouTubeSubscriptionsTable) .where( - and( - eq(dbGuildYouTubeSubscriptionsTable.guildId, guildId), - eq( - dbGuildYouTubeSubscriptionsTable.youtubeChannelId, - platformUserId, - ), + eq( + dbGuildYouTubeSubscriptionsTable.youtubeChannelId, + platformTrackingId, ), ); } else if (platform === Platform.Twitch) { await db .delete(dbGuildTwitchSubscriptionsTable) .where( - and( - eq(dbGuildTwitchSubscriptionsTable.guildId, guildId), - eq( - dbGuildTwitchSubscriptionsTable.twitchChannelId, - platformUserId, - ), + eq( + dbGuildTwitchSubscriptionsTable.twitchChannelId, + platformTrackingId, ), ); } else { From 55eb203e942707ecf72f90881cdd5c420185d057 Mon Sep 17 00:00:00 2001 From: GalvinPython Date: Mon, 21 Jul 2025 13:46:42 +0100 Subject: [PATCH 4/6] fix(bot): nvm i lied, /untracked is now done --- src/db/discord.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/db/discord.ts b/src/db/discord.ts index a4eb7e9..b402f5a 100644 --- a/src/db/discord.ts +++ b/src/db/discord.ts @@ -294,8 +294,8 @@ export async function discordRemoveGuildTrackingChannel( .delete(dbGuildYouTubeSubscriptionsTable) .where( eq( - dbGuildYouTubeSubscriptionsTable.youtubeChannelId, - platformTrackingId, + dbGuildYouTubeSubscriptionsTable.id, + Number(platformTrackingId), ), ); } else if (platform === Platform.Twitch) { @@ -303,8 +303,8 @@ export async function discordRemoveGuildTrackingChannel( .delete(dbGuildTwitchSubscriptionsTable) .where( eq( - dbGuildTwitchSubscriptionsTable.twitchChannelId, - platformTrackingId, + dbGuildTwitchSubscriptionsTable.id, + Number(platformTrackingId), ), ); } else { From cc46591e42855a231d3d93c1aa27565a6c2b97c7 Mon Sep 17 00:00:00 2001 From: Galvin Date: Mon, 21 Jul 2025 13:59:34 +0100 Subject: [PATCH 5/6] Update src/db/discord.ts ok copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/db/discord.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/db/discord.ts b/src/db/discord.ts index b402f5a..01501ee 100644 --- a/src/db/discord.ts +++ b/src/db/discord.ts @@ -286,8 +286,13 @@ export async function discordRemoveGuildTrackingChannel( ): Promise<{ success: boolean; data: [] }> { console.log(`Removing tracking for ID: ${trackingId}`); - const [platform, platformTrackingId] = trackingId.split("."); + const parts = trackingId.split("."); + if (parts.length !== 2) { + console.error("Invalid trackingId format. Expected format: 'platform.id'"); + return { success: false, data: [] }; + } + const [platform, platformTrackingId] = parts; try { if (platform === Platform.YouTube) { await db From 7a768f8a50c20c97dd48bb639cbafc2c7e36e9b5 Mon Sep 17 00:00:00 2001 From: GalvinPython Date: Mon, 21 Jul 2025 14:02:15 +0100 Subject: [PATCH 6/6] style: run lint --- src/db/discord.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/db/discord.ts b/src/db/discord.ts index 01501ee..02b25fe 100644 --- a/src/db/discord.ts +++ b/src/db/discord.ts @@ -287,12 +287,17 @@ export async function discordRemoveGuildTrackingChannel( console.log(`Removing tracking for ID: ${trackingId}`); const parts = trackingId.split("."); + if (parts.length !== 2) { - console.error("Invalid trackingId format. Expected format: 'platform.id'"); + console.error( + "Invalid trackingId format. Expected format: 'platform.id'", + ); + return { success: false, data: [] }; } const [platform, platformTrackingId] = parts; + try { if (platform === Platform.YouTube) { await db