Files
broccolini-bot/scripts/map-batch-to-transcript.js
2026-02-17 21:49:58 -06:00

130 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 <channelId> -- 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 <channelId>');
} finally {
client.destroy();
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});