import { Client, ChannelType } from "discord.js"; import { CENTRAL_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 => { 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 => { 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; if (additionalContent.length <= maxLength) { await (channel as any).send(additionalContent); } else { const chunks = []; let remaining = additionalContent; while (remaining.length > 0) { if (remaining.length <= maxLength) { chunks.push(remaining); break; } let breakPoint = maxLength; const lastNewline = remaining.lastIndexOf("\n", maxLength); const lastSpace = remaining.lastIndexOf(" ", maxLength); if (lastNewline > maxLength * 0.8) { breakPoint = lastNewline; } else if (lastSpace > maxLength * 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}\n\n` : ""; await (channel as any).send(`${header}${chunk}`); } } } } } 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, 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, 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, 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> => { const userIds = new Set(); 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(CENTRAL_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 sendPersonaLog = async ( client: Client, type: "persona" | "conversation" | "reaction", personaName: string, messageLink?: string, isPrimer: boolean = false, sentMessageLink?: string, ): Promise => { try { const logChannelId = "1426269876569509968"; const channel = client.channels.cache.get(logChannelId); if (!channel || !("send" in channel)) return; const { EmbedBuilder } = await import("discord.js"); const embed = new EmbedBuilder() .setTitle( type === "persona" ? "๐ŸŽญ Persona Message" : type === "conversation" ? "๐Ÿ’ฌ Conversation Starter" : "๐Ÿ‘€ Random Reaction", ) .setColor( type === "persona" ? "#ff6b6b" : type === "conversation" ? "#4ecdc4" : "#ffd93d", ) .addFields( { name: "Persona", value: personaName, inline: true, }, { name: "Type", value: isPrimer ? "Primer Message" : "Regular Message", inline: true, }, ) .setTimestamp(); if (isPrimer && sentMessageLink) { embed.addFields({ name: "Sent Message", value: `[Click to view](${sentMessageLink})`, inline: false, }); } else if (type === "reaction" && messageLink) { embed.addFields({ name: "Trigger Message", value: `[Click to view](${messageLink})`, inline: false, }); } else if (sentMessageLink) { embed.addFields({ name: "Sent Message", value: `[Click to view](${sentMessageLink})`, inline: false, }); } await (channel as any).send({ embeds: [embed] }); } catch (error) { console.error("Failed to send persona log:", error); } }; export const executeBulkRoleAssignment = async ( client: Client, roleId: string, customUpdateChannelId: string, channelId?: string, categoryId?: string, ): Promise => { try { const guild = client.guilds.cache.get(CENTRAL_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(); 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; } 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, ); } };