130 lines
5.1 KiB
JavaScript
130 lines
5.1 KiB
JavaScript
#!/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);
|
||
});
|