summaryrefslogtreecommitdiff
path: root/packages/gateway/src/commands/utilities.ts
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-09-24 19:04:14 -0700
committerFuwn <[email protected]>2025-09-24 19:15:34 -0700
commit3b08854f33c9944761367597e6850fe6e27e3af3 (patch)
tree8b6169d08cc85b0a6a74c761615c2a8e560ac105 /packages/gateway/src/commands/utilities.ts
parentrefactor: Move interactions client to packages directory (diff)
downloadumabotdiscord-3b08854f33c9944761367597e6850fe6e27e3af3.tar.xz
umabotdiscord-3b08854f33c9944761367597e6850fe6e27e3af3.zip
feat: Integrate gateway client
Diffstat (limited to 'packages/gateway/src/commands/utilities.ts')
-rw-r--r--packages/gateway/src/commands/utilities.ts615
1 files changed, 615 insertions, 0 deletions
diff --git a/packages/gateway/src/commands/utilities.ts b/packages/gateway/src/commands/utilities.ts
new file mode 100644
index 0000000..38d7bc2
--- /dev/null
+++ b/packages/gateway/src/commands/utilities.ts
@@ -0,0 +1,615 @@
+import { Client, ChannelType } from "discord.js";
+import { GUILD_ID } from "../constants";
+
+export const AUDIT_LOG_GUILD_ID = "1419211292396224575";
+export const AUDIT_LOG_CHANNEL_ID = "1419211778793144411";
+
+export const sendProgressUpdate = async (
+ client: Client,
+ message: string,
+ channelId: string,
+): Promise<void> => {
+ try {
+ const channel = client.channels.cache.get(channelId);
+
+ if (channel && "send" in channel) await (channel as any).send(message);
+ } catch (error) {
+ console.error("Failed to send progress update:", error);
+ }
+};
+
+export const sendAuditLog = async (
+ client: Client,
+ embed: any,
+ additionalContent?: string,
+ customChannelId?: string,
+): Promise<void> => {
+ try {
+ const channelId = customChannelId || AUDIT_LOG_CHANNEL_ID;
+ const channel = client.channels.cache.get(channelId);
+
+ if (channel && "send" in channel) {
+ await (channel as any).send({ embeds: [embed] });
+
+ if (additionalContent) {
+ const maxLength = 1900;
+ const codeBlockStart = "```\n";
+ const codeBlockEnd = "\n```";
+ const availableLength =
+ maxLength - codeBlockStart.length - codeBlockEnd.length;
+
+ if (additionalContent.length <= availableLength) {
+ await (channel as any).send(
+ `${codeBlockStart}${additionalContent}${codeBlockEnd}`,
+ );
+ } else {
+ const chunks = [];
+ let remaining = additionalContent;
+
+ while (remaining.length > 0) {
+ if (remaining.length <= availableLength) {
+ chunks.push(remaining);
+
+ break;
+ }
+
+ let breakPoint = availableLength;
+ const lastNewline = remaining.lastIndexOf("\n", availableLength);
+ const lastSpace = remaining.lastIndexOf(" ", availableLength);
+
+ if (lastNewline > availableLength * 0.8) {
+ breakPoint = lastNewline;
+ } else if (lastSpace > availableLength * 0.8) {
+ breakPoint = lastSpace;
+ }
+
+ chunks.push(remaining.substring(0, breakPoint));
+
+ remaining = remaining.substring(breakPoint).trim();
+ }
+
+ for (let i = 0; i < chunks.length; i++) {
+ const chunk = chunks[i];
+ const header =
+ chunks.length > 1 ? `Part ${i + 1}/${chunks.length}:\n` : "";
+
+ await (channel as any).send(
+ `${codeBlockStart}${header}${chunk}${codeBlockEnd}`,
+ );
+ }
+ }
+ }
+ }
+ } catch (error) {
+ console.error("Failed to send audit log:", error);
+ }
+};
+
+export const getChannelName = (channel: any): string => {
+ return channel.name || channel.id || "Unknown Channel";
+};
+
+export const fetchMessagesFromChannel = async (
+ client: Client,
+ channel: any,
+ userIds: Set<string>,
+ updateChannelId: string,
+): Promise<{ messageCount: number; batchCount: number }> => {
+ let lastMessageId: string | undefined;
+ let hasMoreMessages = true;
+ let messageCount = 0;
+ let batchCount = 0;
+
+ while (hasMoreMessages) {
+ try {
+ const messages = await channel.messages.fetch({
+ limit: 100,
+ before: lastMessageId,
+ });
+
+ if (messages.size === 0) {
+ hasMoreMessages = false;
+
+ break;
+ }
+
+ for (const message of messages.values())
+ if (message.author && message.author.id) userIds.add(message.author.id);
+
+ messageCount += messages.size;
+ batchCount += 1;
+
+ if (batchCount % 10 === 0)
+ await sendProgressUpdate(
+ client,
+ `๐Ÿ“Š Progress for ${getChannelName(channel)}: ${messageCount} messages processed, ${userIds.size} unique users found`,
+ updateChannelId,
+ );
+
+ lastMessageId = messages.last()?.id;
+
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ } catch (error) {
+ console.error(
+ `Error fetching messages from channel ${channel.id}:`,
+ error,
+ );
+
+ hasMoreMessages = false;
+ }
+ }
+
+ return { messageCount, batchCount };
+};
+
+export const fetchForumPosts = async (
+ client: Client,
+ forumChannel: any,
+ userIds: Set<string>,
+ updateChannelId: string,
+): Promise<{ messageCount: number; batchCount: number }> => {
+ let totalMessageCount = 0;
+ let totalBatchCount = 0;
+
+ try {
+ const threads = await forumChannel.threads.fetchActive();
+ const archivedThreads = await forumChannel.threads.fetchArchived();
+
+ const allThreads = [
+ ...threads.threads.values(),
+ ...archivedThreads.threads.values(),
+ ];
+
+ await sendProgressUpdate(
+ client,
+ `๐Ÿ” Found ${allThreads.length} forum posts in ${getChannelName(forumChannel)}`,
+ updateChannelId,
+ );
+
+ for (const thread of allThreads) {
+ try {
+ const { messageCount, batchCount } = await fetchMessagesFromChannel(
+ client,
+ thread,
+ userIds,
+ updateChannelId,
+ );
+
+ totalMessageCount += messageCount;
+ totalBatchCount += batchCount;
+
+ await sendProgressUpdate(
+ client,
+ `๐Ÿ“ Processed forum post "${getChannelName(thread)}": ${messageCount} messages`,
+ updateChannelId,
+ );
+ } catch (error) {
+ console.error(`Error processing forum post ${thread.id}:`, error);
+ }
+ }
+ } catch (error) {
+ console.error(`Error fetching forum posts from ${forumChannel.id}:`, error);
+ }
+
+ return { messageCount: totalMessageCount, batchCount: totalBatchCount };
+};
+
+export const fetchChannelThreads = async (
+ client: Client,
+ textChannel: any,
+ userIds: Set<string>,
+ updateChannelId: string,
+): Promise<{ messageCount: number; batchCount: number }> => {
+ let totalMessageCount = 0;
+ let totalBatchCount = 0;
+
+ try {
+ const threads = await textChannel.threads.fetchActive();
+ const archivedThreads = await textChannel.threads.fetchArchived();
+ const allThreads = [
+ ...threads.threads.values(),
+ ...archivedThreads.threads.values(),
+ ];
+
+ if (allThreads.length > 0) {
+ await sendProgressUpdate(
+ client,
+ `๐Ÿ” Found ${allThreads.length} threads in ${getChannelName(textChannel)}`,
+ updateChannelId,
+ );
+
+ for (const thread of allThreads) {
+ try {
+ const { messageCount, batchCount } = await fetchMessagesFromChannel(
+ client,
+ thread,
+ userIds,
+ updateChannelId,
+ );
+
+ totalMessageCount += messageCount;
+ totalBatchCount += batchCount;
+
+ await sendProgressUpdate(
+ client,
+ `๐Ÿงต Processed thread "${getChannelName(thread)}": ${messageCount} messages`,
+ updateChannelId,
+ );
+ } catch (error) {
+ console.error(`Error processing thread ${thread.id}:`, error);
+ }
+ }
+ }
+ } catch (error) {
+ console.error(`Error fetching threads from ${textChannel.id}:`, error);
+ }
+
+ return { messageCount: totalMessageCount, batchCount: totalBatchCount };
+};
+
+export const fetchMessageAuthors = async (
+ client: Client,
+ channelId: string,
+ channelName: string | undefined,
+ updateChannelId: string,
+): Promise<Set<string>> => {
+ const userIds = new Set<string>();
+ let totalMessageCount = 0;
+
+ await sendProgressUpdate(
+ client,
+ `๐Ÿ” Starting comprehensive analysis of ${channelName || `channel ${channelId}`} ...`,
+ updateChannelId,
+ );
+
+ const channel = client.channels.cache.get(channelId);
+
+ if (!channel) {
+ await sendProgressUpdate(
+ client,
+ `โŒ Channel ${channelId} not found`,
+ updateChannelId,
+ );
+
+ return userIds;
+ }
+
+ const channelDisplayName = channelName || getChannelName(channel);
+
+ switch (channel.type) {
+ case ChannelType.GuildText: {
+ await sendProgressUpdate(
+ client,
+ `๐Ÿ“ Processing text channel: ${channelDisplayName}`,
+ updateChannelId,
+ );
+
+ const { messageCount: channelMessages } = await fetchMessagesFromChannel(
+ client,
+ channel,
+ userIds,
+ updateChannelId,
+ );
+
+ totalMessageCount += channelMessages;
+
+ const { messageCount: threadMessages } = await fetchChannelThreads(
+ client,
+ channel,
+ userIds,
+ updateChannelId,
+ );
+
+ totalMessageCount += threadMessages;
+
+ break;
+ }
+
+ case ChannelType.GuildForum: {
+ await sendProgressUpdate(
+ client,
+ `๐Ÿ“‹ Processing forum channel: ${channelDisplayName}`,
+ updateChannelId,
+ );
+
+ const { messageCount: forumMessages } = await fetchForumPosts(
+ client,
+ channel,
+ userIds,
+ updateChannelId,
+ );
+
+ totalMessageCount += forumMessages;
+
+ break;
+ }
+
+ case ChannelType.PublicThread:
+ case ChannelType.PrivateThread:
+ case ChannelType.AnnouncementThread: {
+ await sendProgressUpdate(
+ client,
+ `๐Ÿงต Processing thread: ${channelDisplayName}`,
+ updateChannelId,
+ );
+
+ const { messageCount: threadMessageCount } =
+ await fetchMessagesFromChannel(
+ client,
+ channel,
+ userIds,
+ updateChannelId,
+ );
+
+ totalMessageCount += threadMessageCount;
+
+ break;
+ }
+
+ case ChannelType.GuildAnnouncement: {
+ await sendProgressUpdate(
+ client,
+ `๐Ÿ“ข Processing announcement channel: ${channelDisplayName}`,
+ updateChannelId,
+ );
+
+ const { messageCount: announcementMessages } =
+ await fetchMessagesFromChannel(
+ client,
+ channel,
+ userIds,
+ updateChannelId,
+ );
+
+ totalMessageCount += announcementMessages;
+
+ const { messageCount: announcementThreadMessages } =
+ await fetchChannelThreads(client, channel, userIds, updateChannelId);
+
+ totalMessageCount += announcementThreadMessages;
+
+ break;
+ }
+
+ case ChannelType.GuildVoice:
+ case ChannelType.GuildStageVoice: {
+ await sendProgressUpdate(
+ client,
+ `๐Ÿ”‡ Voice channel ${channelDisplayName} has no text messages`,
+ updateChannelId,
+ );
+
+ break;
+ }
+
+ default:
+ await sendProgressUpdate(
+ client,
+ `โ“ Unknown channel type for ${channelDisplayName}, attempting to fetch messages ...`,
+ updateChannelId,
+ );
+
+ if (channel.isTextBased()) {
+ const { messageCount: unknownMessages } =
+ await fetchMessagesFromChannel(
+ client,
+ channel,
+ userIds,
+ updateChannelId,
+ );
+
+ totalMessageCount += unknownMessages;
+ }
+
+ break;
+ }
+
+ await sendProgressUpdate(
+ client,
+ `โœ… Completed comprehensive analysis of ${channelDisplayName}: ${totalMessageCount} messages processed, ${userIds.size} unique users found`,
+ updateChannelId,
+ );
+
+ return userIds;
+};
+
+export const getAllChannelsInCategory = async (
+ client: Client,
+ categoryId: string,
+): Promise<{ channels: string[]; channelNames: { [key: string]: string } }> => {
+ const guild = client.guilds.cache.get(GUILD_ID);
+
+ if (!guild) return { channels: [], channelNames: {} };
+
+ const channels: string[] = [];
+ const channelNames: { [key: string]: string } = {};
+
+ const categoryChannels = guild.channels.cache.filter(
+ (channel) => channel.parentId === categoryId,
+ );
+
+ for (const channel of categoryChannels.values()) {
+ channels.push(channel.id);
+
+ channelNames[channel.id] = `#${getChannelName(channel)}`;
+
+ if (
+ channel.type === ChannelType.GuildText ||
+ channel.type === ChannelType.GuildAnnouncement
+ ) {
+ try {
+ const threads = await channel.threads.fetchActive();
+ const archivedThreads = await channel.threads.fetchArchived();
+ const allThreads = [
+ ...threads.threads.values(),
+ ...archivedThreads.threads.values(),
+ ];
+
+ for (const thread of allThreads) {
+ channels.push(thread.id);
+
+ channelNames[thread.id] = `๐Ÿงต ${getChannelName(thread)}`;
+ }
+ } catch (error) {
+ console.error(
+ `Error fetching threads for channel ${channel.id}:`,
+ error,
+ );
+ }
+ }
+
+ if (channel.type === ChannelType.GuildForum) {
+ try {
+ const threads = await channel.threads.fetchActive();
+ const archivedThreads = await channel.threads.fetchArchived();
+ const allThreads = [
+ ...threads.threads.values(),
+ ...archivedThreads.threads.values(),
+ ];
+
+ for (const thread of allThreads) {
+ channels.push(thread.id);
+
+ channelNames[thread.id] = `๐Ÿ“ ${getChannelName(thread)}`;
+ }
+ } catch (error) {
+ console.error(
+ `Error fetching forum posts for channel ${channel.id}:`,
+ error,
+ );
+ }
+ }
+ }
+
+ return { channels, channelNames };
+};
+
+export const executeBulkRoleAssignment = async (
+ client: Client,
+ roleId: string,
+ customUpdateChannelId: string,
+ channelId?: string,
+ categoryId?: string,
+): Promise<void> => {
+ try {
+ const guild = client.guilds.cache.get(GUILD_ID);
+
+ if (!guild) {
+ await sendProgressUpdate(
+ client,
+ "โŒ Guild not found",
+ customUpdateChannelId,
+ );
+
+ return;
+ }
+
+ const role = guild.roles.cache.get(roleId);
+
+ if (!role) {
+ await sendProgressUpdate(
+ client,
+ "โŒ Role not found",
+ customUpdateChannelId,
+ );
+
+ return;
+ }
+
+ let channelsToCheck: string[] = [];
+ let channelNames: { [key: string]: string } = {};
+
+ if (channelId) {
+ channelsToCheck = [channelId];
+
+ const channel = guild.channels.cache.get(channelId);
+
+ if (channel) channelNames[channelId] = `#${getChannelName(channel)}`;
+ } else if (categoryId) {
+ const result = await getAllChannelsInCategory(client, categoryId);
+
+ channelsToCheck = result.channels;
+ channelNames = result.channelNames;
+ }
+
+ await sendProgressUpdate(
+ client,
+ `๐Ÿš€ Starting comprehensive bulk role operation: ${channelsToCheck.length} channel(s)/thread(s) to process`,
+ customUpdateChannelId,
+ );
+
+ const userIds = new Set<string>();
+
+ for (const channelId of channelsToCheck) {
+ const channelUserIds = await fetchMessageAuthors(
+ client,
+ channelId,
+ channelNames[channelId],
+ customUpdateChannelId,
+ );
+
+ channelUserIds.forEach((userId) => userIds.add(userId));
+ }
+
+ const uniqueUserIds = Array.from(userIds);
+
+ await sendProgressUpdate(
+ client,
+ `๐ŸŽฏ Starting role assignment phase: ${uniqueUserIds.length} users to process\n๐Ÿ“ Note: Some users may have left the guild since sending messages`,
+ customUpdateChannelId,
+ );
+
+ let successCount = 0;
+ let errorCount = 0;
+ let notInGuildCount = 0;
+
+ for (let i = 0; i < uniqueUserIds.length; i++) {
+ const userId = uniqueUserIds[i];
+
+ try {
+ const member = await guild.members.fetch(userId);
+ const currentRoles = member.roles.cache.map((role) => role.id);
+
+ if (!currentRoles.includes(roleId)) {
+ await member.roles.add(role);
+
+ successCount += 1;
+ } else {
+ successCount += 1;
+ }
+
+ if ((i + 1) % 25 === 0)
+ await sendProgressUpdate(
+ client,
+ `๐Ÿ“ˆ Role assignment progress: ${i + 1}/${uniqueUserIds.length} users processed (${successCount} successful, ${errorCount} errors, ${notInGuildCount} not in guild)`,
+ customUpdateChannelId,
+ );
+
+ await new Promise((resolve) => setTimeout(resolve, 200));
+ } catch (error: any) {
+ console.error(`Error processing user ${userId}:`, error);
+
+ if (error.code === 10007 || error.message?.includes("Unknown Member")) {
+ notInGuildCount += 1;
+
+ console.log(`User ${userId} is no longer in the guild, skipping ...`);
+ } else {
+ errorCount += 1;
+ }
+ }
+ }
+
+ await sendProgressUpdate(
+ client,
+ `๐Ÿ Bulk role operation completed!\n\n๐Ÿ“Š **Final Results:**\n- โœ… **${successCount}** users processed successfully\n- โŒ **${errorCount}** errors occurred\n- ๐Ÿšช **${notInGuildCount}** users no longer in guild\n\n๐Ÿ’ก **Note:** The "successful" count includes users who already had the role. Users who left the guild are automatically skipped.`,
+ customUpdateChannelId,
+ );
+ } catch (error) {
+ console.error("Error in bulk role assignment:", error);
+ await sendProgressUpdate(
+ client,
+ "โŒ An error occurred during bulk role assignment",
+ customUpdateChannelId,
+ );
+ }
+};