const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); const { Octokit } = require('@octokit/rest'); const { createEmbed } = require('../utils/embeds'); const { Colors } = require('../config'); /** * Scans a channel for old drop embeds with broken GitHub Link buttons * and replaces them with new interactive download buttons (dl_). * * Usage: fix downloads */ module.exports = { async execute(client, message, args) { const channelId = args[0]?.replace(/[<#>]/g, ''); if (!channelId) { return message.reply({ content: '❌ Usage: `fix downloads `' }); } const guild = client.guilds.cache.first(); const channel = await guild.channels.fetch(channelId).catch(() => null); if (!channel) { return message.reply({ content: '❌ Channel not found.' }); } const statusMsg = await message.reply({ embeds: [createEmbed({ title: '🔧 Fixing Downloads', description: `Scanning <#${channelId}> for broken GitHub links...`, color: Colors.INFO })] }); try { const octokit = new Octokit({ auth: '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' }); const [owner, repo] = 'APRK01/WSB-Storage'.split('/'); // 1. Build a lookup map of all release assets: { "tag/filename" -> assetId } const assetMap = new Map(); let page = 1; let hasMore = true; while (hasMore) { const { data: releases } = await octokit.rest.repos.listReleases({ owner, repo, per_page: 100, page }); if (releases.length === 0) { hasMore = false; break; } for (const release of releases) { for (const asset of release.assets) { // Map by tag_name/filename for lookup assetMap.set(`${release.tag_name}/${asset.name}`, asset.id); // Also map by just the browser_download_url for direct matching assetMap.set(asset.browser_download_url, asset.id); } } page++; } await statusMsg.edit({ embeds: [createEmbed({ title: '🔧 Fixing Downloads', description: `Found **${assetMap.size}** GitHub assets. Now scanning messages in <#${channelId}>...`, color: Colors.INFO })] }); // 2. Fetch all messages in the channel and find ones with GitHub Link buttons let fixedCount = 0; let skippedCount = 0; let lastId = null; let scannedCount = 0; while (true) { const options = { limit: 100 }; if (lastId) options.before = lastId; const messages = await channel.messages.fetch(options); if (messages.size === 0) break; for (const [, msg] of messages) { scannedCount++; // Only process bot messages with components if (msg.author.id !== client.user.id) continue; if (!msg.components || msg.components.length === 0) continue; // Check if any component row has a Link button pointing to WSB-Storage let needsFix = false; const newRows = []; for (const row of msg.components) { const newComponents = []; for (const component of row.components) { // Check if it's a Link button pointing to our GitHub repo if (component.type === 2 && component.style === ButtonStyle.Link && component.url && component.url.includes('APRK01/WSB-Storage')) { // Try to find the asset ID from the URL const assetId = assetMap.get(component.url); if (assetId) { // Replace with interactive button newComponents.push( new ButtonBuilder() .setCustomId(`dl_${assetId}`) .setLabel(component.label || '📥 Download Drop') .setStyle(ButtonStyle.Success) ); needsFix = true; } else { // Can't find asset ID — try parsing from URL // URL format: https://github.com/APRK01/WSB-Storage/releases/download/TAG/FILENAME const urlMatch = component.url.match(/\/releases\/download\/([^/]+)\/(.+)$/); if (urlMatch) { const lookupKey = `${decodeURIComponent(urlMatch[1])}/${decodeURIComponent(urlMatch[2])}`; const foundId = assetMap.get(lookupKey); if (foundId) { newComponents.push( new ButtonBuilder() .setCustomId(`dl_${foundId}`) .setLabel(component.label || '📥 Download Drop') .setStyle(ButtonStyle.Success) ); needsFix = true; } else { // Keep original if we can't resolve newComponents.push(ButtonBuilder.from(component)); skippedCount++; } } else { newComponents.push(ButtonBuilder.from(component)); skippedCount++; } } } else if (component.type === 2 && component.style !== ButtonStyle.Link) { // Already an interactive button (already fixed or dl_ button) newComponents.push(ButtonBuilder.from(component)); } else { newComponents.push(ButtonBuilder.from(component)); } } newRows.push(new ActionRowBuilder().addComponents(newComponents)); } if (needsFix) { try { await msg.edit({ components: newRows }); fixedCount++; } catch (editErr) { console.error(`[Fix Downloads] Failed to edit message ${msg.id}:`, editErr.message); skippedCount++; } } } lastId = messages.last().id; // Update status periodically if (scannedCount % 200 === 0) { await statusMsg.edit({ embeds: [createEmbed({ title: '🔧 Fixing Downloads', description: `Scanned **${scannedCount}** messages... Fixed **${fixedCount}** so far.`, color: Colors.INFO })] }).catch(() => { }); } } await statusMsg.edit({ embeds: [createEmbed({ title: '✅ Downloads Fixed!', description: [ `**Channel:** <#${channelId}>`, `**Messages scanned:** ${scannedCount}`, `**Buttons fixed:** ${fixedCount}`, skippedCount > 0 ? `**Skipped (no match):** ${skippedCount}` : '', '', fixedCount > 0 ? '> All download buttons now use the private ephemeral system. 🔒' : '> No broken buttons found — everything looks good!', ].filter(Boolean).join('\n'), color: Colors.SUCCESS })] }); } catch (err) { console.error('[Fix Downloads Error]', err); await statusMsg.edit({ embeds: [createEmbed({ title: '❌ Fix Failed', description: `Error: ${err.message}`, color: Colors.ACCENT })] }); } } };