#!/usr/bin/env node /** * Map batch tickets (TICKET: guild_channelId_suffix) to transcript channel messages. * * Connection: * - Batch line: TICKET: 798321161082896395_1423340928588054621_indiffe → channelId = 1423340928588054621. * - Transcript channel (🖥️│transcripts): each message is an embed with "Ticket Name: indifferentketchup🍅-claimed-7235". * - Embed does NOT include channel ID, so we match by (1) ticket name (when known) or (2) time: transcript posted when ticket closes. * * Usage: * node scripts/map-batch-to-transcript.js list [limit] -- fetch transcript messages, output CSV (messageId, created, ticket_name) * node scripts/map-batch-to-transcript.js find -- find transcript message(s) likely for this ticket (by time window) * * Known mapping (from embed): 1423340928588054621 ↔ message 1423400708769579120 (Ticket: indifferentketchup🍅-claimed-7235). */ const path = require('path'); const fs = require('fs'); const { Client, GatewayIntentBits } = require('discord.js'); require('dotenv').config({ path: path.join(__dirname, '../.env') }); require('dotenv').config({ path: path.join(__dirname, '../../.env') }); const TOKEN = process.env.MEMBER_BOT_TOKEN || process.env.DISCORD_BOT_TOKEN; const TRANSCRIPT_CHANNEL_ID = '1335424071227281520'; const METRICS_CSV = path.join(__dirname, '../../Discord Ticket Transcripts/transcript_metrics_per_ticket.csv'); function getTicketNameFromEmbed(emb) { const f = emb.fields?.find((x) => x.name && x.name.toLowerCase().includes('ticket name')); return f ? f.value.trim() : null; } async function fetchTranscriptMessages(client, limit = 100) { const channel = await client.channels.fetch(TRANSCRIPT_CHANNEL_ID).catch(() => null); if (!channel) return []; const cap = Math.min(limit, 100); // Discord API max 100 per request const messages = await channel.messages.fetch({ limit: cap }); const out = []; for (const [, m] of messages) { const emb = m.embeds?.[0]; const ticketName = emb ? getTicketNameFromEmbed(emb) : null; out.push({ messageId: m.id, created: m.createdAt ? m.createdAt.toISOString() : m.createdTimestamp, createdTs: m.createdTimestamp, ticketName: ticketName || '', }); } out.sort((a, b) => b.createdTs - a.createdTs); return out; } function loadMetricsCsv() { if (!fs.existsSync(METRICS_CSV)) return []; const text = fs.readFileSync(METRICS_CSV, 'utf8'); const lines = text.split(/\r?\n/).filter((l) => l.trim()); const header = lines[0].split(','); const ticketIdIdx = header.indexOf('ticket_id'); const lastTsIdx = header.indexOf('last_message_ts'); if (ticketIdIdx === -1 || lastTsIdx === -1) return []; const rows = []; for (let i = 1; i < lines.length; i++) { const parts = lines[i].split(','); const ticketId = parts[ticketIdIdx]; const lastTs = parseInt(parts[lastTsIdx], 10); if (!ticketId || !ticketId.includes('_')) continue; const channelId = ticketId.split('_')[1]; if (channelId && !isNaN(lastTs)) rows.push({ ticketId, channelId, last_message_ts: lastTs }); } return rows; } async function main() { const cmd = process.argv[2]; const arg = process.argv[3]; if (!TOKEN) { console.error('No bot token'); process.exit(1); } const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages], }); await new Promise((resolve, reject) => { client.once('ready', resolve); client.login(TOKEN).catch(reject); }); try { if (cmd === 'list') { const limit = Math.min(parseInt(arg, 10) || 100, 100); const list = await fetchTranscriptMessages(client, limit); console.log('transcript_message_id,created_iso,ticket_name'); list.forEach((r) => console.log([r.messageId, r.created, r.ticketName].map((c) => `"${String(c).replace(/"/g, '""')}"`).join(','))); return; } if (cmd === 'find' && arg) { const channelId = arg.trim(); const metrics = loadMetricsCsv(); const row = metrics.find((r) => r.channelId === channelId); const closeTs = row ? row.last_message_ts : null; const list = await fetchTranscriptMessages(client, 100); const windowMs = 2 * 60 * 60 * 1000; // ±2 hours const candidates = closeTs ? list.filter((r) => Math.abs(r.createdTs - closeTs) <= windowMs) : list.slice(0, 20); console.log('Batch ticket channelId:', channelId); if (row) console.log('Ticket close time (last_message_ts):', closeTs, new Date(closeTs).toISOString()); console.log('Transcript channel messages (candidates by time or recent):'); candidates.forEach((r) => { const delta = closeTs != null ? (r.createdTs - closeTs) / 60000 : null; console.log(' ', r.messageId, r.created, r.ticketName || '(no name)', delta != null ? `delta ${delta.toFixed(0)} min` : ''); }); return; } console.log('Usage: node scripts/map-batch-to-transcript.js list [limit]'); console.log(' node scripts/map-batch-to-transcript.js find '); } finally { client.destroy(); } } main().catch((e) => { console.error(e); process.exit(1); });