diff options
| author | Fuwn <[email protected]> | 2025-09-24 19:04:14 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2025-09-24 19:15:34 -0700 |
| commit | 3b08854f33c9944761367597e6850fe6e27e3af3 (patch) | |
| tree | 8b6169d08cc85b0a6a74c761615c2a8e560ac105 /packages/gateway/src/commands/utilities.ts | |
| parent | refactor: Move interactions client to packages directory (diff) | |
| download | umabotdiscord-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.ts | 615 |
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, + ); + } +}; |