Skip to content

Commit 0d2e8ee

Browse files
authored
Merge pull request #154 from GalvinPython/feat/tracked
enjoy your new /tracked
2 parents 59be0bb + 977a4e3 commit 0d2e8ee

File tree

4 files changed

+252
-15
lines changed

4 files changed

+252
-15
lines changed

.env.example

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ TWITCH_CLIENT_SECRET='YOUR_TWITCH_CLIENT_SECRET'
1717
CONFIG_UPDATE_INTERVAL_YOUTUBE='10'
1818
CONFIG_UPDATE_INTERVAL_TWITCH='2'
1919
CONFIG_DISCORD_LOGS_CHANNEL='YOUR_DISCORD_LOGS_CHANNEL'
20-
CONFIG_DISCORD_WAIT_FOR_GUILD_CACHE_TIME='YOUR_TIME_IN_SECONDS'
20+
21+
## The following values are recommened to be at the default values.
22+
## If you want to change them, please make sure you know what you are doing.
23+
## Times are in seconds.
24+
CONFIG_DISCORD_WAIT_FOR_GUILD_CACHE_TIME='10'
25+
CONFIG_DISCORD_COLLECTOR_TIMEOUT='60'
26+
CONFIG_DISCORD_COMPONENTS_PAGE_SIZE='10' # The number of channels to display per page in the components (ex. https://github.com/GalvinPython/feedr/pull/154)
2127

2228
# Postgres URLs
2329
POSTGRES_URL='postgresql://user:password@server:port/database'

src/commands.ts

Lines changed: 235 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import Bun from "bun";
22
import { heapStats } from "bun:jsc";
33
import {
4+
ActionRowBuilder,
45
ApplicationCommandOptionType,
56
ApplicationCommandType,
67
AutocompleteInteraction,
8+
ButtonBuilder,
9+
ButtonStyle,
710
ChannelType,
811
ChatInputCommandInteraction,
12+
ComponentType,
13+
EmbedBuilder,
914
GuildMember,
1015
MessageFlags,
1116
type ApplicationCommandOptionData,
@@ -29,13 +34,18 @@ import {
2934
discordGetAllTrackedInGuild,
3035
discordRemoveGuildTrackingChannel,
3136
} from "./db/discord";
32-
import { Platform, YouTubeContentType } from "./types/types.d";
37+
import {
38+
Platform,
39+
YouTubeContentType,
40+
type PlatformTypes,
41+
} from "./types/types.d";
3342
import searchTwitch from "./utils/twitch/searchTwitch";
3443
import { getStreamerName } from "./utils/twitch/getStreamerName";
3544
import {
3645
addNewStreamerToTrack,
3746
checkIfStreamerIsAlreadyTracked,
3847
} from "./db/twitch";
48+
import { config } from "./config";
3949

4050
import client from ".";
4151

@@ -915,9 +925,25 @@ const commands: Record<string, Command> = {
915925
return;
916926
}
917927

918-
const trackedChannels = await getAllTrackedInGuild(guildId);
928+
const trackedChannels = await discordGetAllTrackedInGuild(guildId);
919929

920-
if (trackedChannels.length === 0) {
930+
if (!trackedChannels || !trackedChannels.success) {
931+
console.error(
932+
"An error occurred while trying to get the tracked channels in this guild!",
933+
);
934+
await interaction.reply({
935+
flags: MessageFlags.Ephemeral,
936+
content:
937+
"An error occurred while trying to get the tracked channels in this guild! Please report this error!",
938+
});
939+
940+
return;
941+
}
942+
943+
if (
944+
trackedChannels.data.youtubeSubscriptions.length === 0 &&
945+
trackedChannels.data.twitchSubscriptions.length === 0
946+
) {
921947
await interaction.reply({
922948
flags: MessageFlags.Ephemeral,
923949
content: "No channels are being tracked in this guild.",
@@ -926,24 +952,219 @@ const commands: Record<string, Command> = {
926952
return;
927953
}
928954

929-
const filteredChannels = trackedChannels.filter(
930-
(channel) => channel.guild_channel_id === channelId,
931-
);
955+
const youtubeChannels =
956+
trackedChannels.data.youtubeSubscriptions ?? [];
957+
const twitchChannels =
958+
trackedChannels.data.twitchSubscriptions ?? [];
959+
960+
const allEntries = [
961+
...youtubeChannels.map((c) => ({
962+
type: "YouTube" as const,
963+
name: c.youtubeChannel.youtubeChannelName,
964+
id: c.youtubeChannel.youtubeChannelId,
965+
notifyId: c.subscription.notificationChannelId,
966+
})),
967+
...twitchChannels.map((c) => ({
968+
type: "Twitch" as const,
969+
name: c.twitchChannel.twitchChannelName,
970+
id: c.twitchChannel.twitchChannelName,
971+
notifyId: c.subscription.notificationChannelId,
972+
})),
973+
].sort((a, b) => a.name.localeCompare(b.name));
974+
975+
type FilterType = "all" | PlatformTypes;
976+
let currentPage = 0;
977+
let currentFilter: FilterType = "all";
978+
979+
const pageSize = config.discordComponentsPageSize;
980+
981+
const filterEntries = (filter: FilterType) => {
982+
if (filter === Platform.YouTube)
983+
return allEntries.filter((e) => e.type === "YouTube");
984+
if (filter === Platform.Twitch)
985+
return allEntries.filter((e) => e.type === "Twitch");
986+
987+
return allEntries;
988+
};
989+
990+
const getEmbed = (
991+
entries: typeof allEntries,
992+
page: number,
993+
filter: FilterType,
994+
) => {
995+
const totalPages = Math.ceil(entries.length / pageSize);
996+
const pageEntries = entries.slice(
997+
page * pageSize,
998+
(page + 1) * pageSize,
999+
);
1000+
1001+
const description =
1002+
pageEntries
1003+
.map((entry) => {
1004+
const link =
1005+
entry.type === "YouTube"
1006+
? `https://www.youtube.com/channel/${entry.id}`
1007+
: `https://www.twitch.tv/${entry.id}`;
1008+
1009+
return `**[${entry.name}](${link})** • ${entry.type} • <#${entry.notifyId}>`;
1010+
})
1011+
.join("\n") || "No entries.";
1012+
1013+
return new EmbedBuilder()
1014+
.setTitle("Tracked Channels")
1015+
.setDescription(description)
1016+
.setColor(0x5865f2)
1017+
.setFooter({
1018+
text: `Page ${page + 1} of ${Math.max(totalPages, 1)} — Filter: ${filter.toUpperCase()}`,
1019+
});
1020+
};
1021+
1022+
const getButtons = (
1023+
filter: FilterType,
1024+
page: number,
1025+
entriesLength: number,
1026+
) => {
1027+
const totalPages = Math.ceil(entriesLength / pageSize);
1028+
1029+
const toggleRow =
1030+
new ActionRowBuilder<ButtonBuilder>().addComponents(
1031+
new ButtonBuilder()
1032+
.setCustomId("filter_all")
1033+
.setLabel("🌐 All")
1034+
.setStyle(
1035+
filter === "all"
1036+
? ButtonStyle.Primary
1037+
: ButtonStyle.Secondary,
1038+
),
1039+
new ButtonBuilder()
1040+
.setCustomId("filter_youtube")
1041+
.setLabel("❤️ YouTube")
1042+
.setStyle(
1043+
filter === "youtube"
1044+
? ButtonStyle.Primary
1045+
: ButtonStyle.Secondary,
1046+
),
1047+
new ButtonBuilder()
1048+
.setCustomId("filter_twitch")
1049+
.setLabel("💜 Twitch")
1050+
.setStyle(
1051+
filter === "twitch"
1052+
? ButtonStyle.Primary
1053+
: ButtonStyle.Secondary,
1054+
),
1055+
);
1056+
1057+
const navRow =
1058+
new ActionRowBuilder<ButtonBuilder>().addComponents(
1059+
new ButtonBuilder()
1060+
.setCustomId("prev_page")
1061+
.setLabel("⬅️ Previous")
1062+
.setStyle(ButtonStyle.Secondary)
1063+
.setDisabled(page === 0),
1064+
new ButtonBuilder()
1065+
.setCustomId("next_page")
1066+
.setLabel("Next ➡️")
1067+
.setStyle(ButtonStyle.Secondary)
1068+
.setDisabled(
1069+
page >= totalPages - 1 || totalPages === 0,
1070+
),
1071+
);
1072+
1073+
return [toggleRow, navRow];
1074+
};
9321075

933-
const newTrackedChannels = trackedChannels.filter(
934-
(channel) => channel.guild_channel_id !== channelId,
1076+
const entries = filterEntries(currentFilter);
1077+
const embed = getEmbed(entries, currentPage, currentFilter);
1078+
const buttons = getButtons(
1079+
currentFilter,
1080+
currentPage,
1081+
entries.length,
9351082
);
9361083

937-
// idk what is happening here anymore, but this is because eslint and prettier are fighting so i put them to rest by using only one line
9381084
await interaction.reply({
1085+
embeds: [embed],
1086+
components: buttons,
9391087
flags: MessageFlags.Ephemeral,
940-
content: `
941-
## Tracked channels in this channel (<#${channelId}>):\n${filteredChannels.length ? filteredChannels.map((channel) => `Platform: ${channel.guild_platform} | User ID: ${channel.platform_user_id}`).join("\n") : "No channels are being tracked in this channel."}
942-
943-
## Other tracked channels in this guild:\n${newTrackedChannels.map((channel) => `Platform: ${channel.guild_platform} | User ID: ${channel.platform_user_id} | Channel: <#${channel.guild_channel_id}>`).join("\n")}`,
9441088
});
9451089

946-
return;
1090+
const message = await interaction.fetchReply();
1091+
1092+
const collector = message.createMessageComponentCollector({
1093+
componentType: ComponentType.Button,
1094+
time: config.discordCollectorTimeout,
1095+
filter: (i) => i.user.id === interaction.user.id,
1096+
});
1097+
1098+
collector.on("collect", async (i) => {
1099+
let needsUpdate = false;
1100+
1101+
switch (i.customId) {
1102+
case "filter_all":
1103+
case "filter_youtube":
1104+
case "filter_twitch": {
1105+
const newFilter = i.customId.replace(
1106+
"filter_",
1107+
"",
1108+
) as FilterType;
1109+
1110+
if (currentFilter !== newFilter) {
1111+
currentFilter = newFilter;
1112+
currentPage = 0;
1113+
needsUpdate = true;
1114+
}
1115+
break;
1116+
}
1117+
case "prev_page":
1118+
if (currentPage > 0) {
1119+
currentPage--;
1120+
needsUpdate = true;
1121+
}
1122+
break;
1123+
case "next_page": {
1124+
const filteredEntries = filterEntries(currentFilter);
1125+
const totalPages = Math.ceil(
1126+
filteredEntries.length / pageSize,
1127+
);
1128+
1129+
if (currentPage < totalPages - 1) {
1130+
currentPage++;
1131+
needsUpdate = true;
1132+
}
1133+
break;
1134+
}
1135+
}
1136+
1137+
if (needsUpdate) {
1138+
const filteredEntries = filterEntries(currentFilter);
1139+
1140+
await i.update({
1141+
embeds: [
1142+
getEmbed(
1143+
filteredEntries,
1144+
currentPage,
1145+
currentFilter,
1146+
),
1147+
],
1148+
components: getButtons(
1149+
currentFilter,
1150+
currentPage,
1151+
filteredEntries.length,
1152+
),
1153+
});
1154+
} else {
1155+
await i.deferUpdate();
1156+
}
1157+
});
1158+
1159+
collector.on("end", async () => {
1160+
try {
1161+
await interaction.editReply({
1162+
components: [],
1163+
});
1164+
} catch (err) {
1165+
console.error("Failed to edit reply:", err);
1166+
}
1167+
});
9471168
},
9481169
},
9491170
};

src/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ export interface Config {
55
updateIntervalTwitch: number;
66
databaseUrl: string | undefined;
77
discordWaitForGuildCacheTime: number;
8+
discordCollectorTimeout: number;
9+
discordComponentsPageSize: number;
810
}
911

1012
export const config: Config = {
@@ -21,6 +23,12 @@ export const config: Config = {
2123
?.CONFIG_DISCORD_WAIT_FOR_GUILD_CACHE_TIME
2224
? parseInt(process.env?.CONFIG_DISCORD_WAIT_FOR_GUILD_CACHE_TIME) * 1000
2325
: 10_000,
26+
discordCollectorTimeout: process.env?.CONFIG_DISCORD_COLLECTOR_TIMEOUT
27+
? parseInt(process.env?.CONFIG_DISCORD_COLLECTOR_TIMEOUT) * 1000
28+
: 60_000,
29+
discordComponentsPageSize: process.env?.CONFIG_DISCORD_COMPONENTS_PAGE_SIZE
30+
? parseInt(process.env?.CONFIG_DISCORD_COMPONENTS_PAGE_SIZE)
31+
: 10,
2432
};
2533

2634
interface Env {

src/types/types.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ export enum Platform {
55
Twitch = "twitch",
66
}
77

8+
export type PlatformTypes = (typeof Platform)[keyof typeof Platform];
9+
810
export enum YouTubeContentType {
911
Videos = 1 << 0,
1012
Shorts = 1 << 1,

0 commit comments

Comments
 (0)