APRK01
feat: add 'fix downloads' command to migrate old Link buttons to private download system
3d599a0 | 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_<assetId>). | |
| * | |
| * Usage: fix downloads <channel_id> | |
| */ | |
| module.exports = { | |
| async execute(client, message, args) { | |
| const channelId = args[0]?.replace(/[<#>]/g, ''); | |
| if (!channelId) { | |
| return message.reply({ content: 'β Usage: `fix downloads <channel_id>`' }); | |
| } | |
| 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 | |
| })] | |
| }); | |
| } | |
| } | |
| }; | |