brain/scripts/fetch-meeting-artifacts.js

119 lines
3.7 KiB
JavaScript

// scripts/fetch-meeting-artifacts.js
const { writeFileSync } = require('node:fs');
const {
loadEnv,
buildGraphClient,
resolveOnlineMeeting,
fetchTranscriptVtt,
fetchAiInsights,
fetchRecordingUrl
} = require('./lib/graph-meetings.js');
const { extractJoinUrlFromBody } = require('./lib/o365-calendar.js');
const { parseVtt, formatTranscript } = require('./lib/vtt-parser.js');
function parseArgs(argv) {
const out = {};
for (let i = 2; i < argv.length; i++) {
const a = argv[i];
if (a === '--o365-id') out.o365Id = argv[++i];
else if (a === '--join-url') out.joinUrl = argv[++i];
else if (a === '--out') out.out = argv[++i];
else if (a === '--user') out.user = argv[++i];
}
return out;
}
async function runFetch(client, userId, opts) {
const warnings = [];
let event = null;
let joinUrl = opts.joinUrl || null;
let seriesMasterId = null;
if (opts.o365Id) {
event = await client.api(`/users/${userId}/events/${opts.o365Id}`)
.select('id,subject,start,end,body,attendees,seriesMasterId,onlineMeeting')
.get();
seriesMasterId = event.seriesMasterId || null;
if (!joinUrl) {
joinUrl = event.onlineMeeting?.joinUrl
|| extractJoinUrlFromBody(event.body?.content || '');
}
}
if (!joinUrl) {
warnings.push('no teams join url on event');
return {
meeting: event ? eventSummary(event) : null,
transcript: null, recap: null, recordingUrl: null, warnings
};
}
const om = await resolveOnlineMeeting(client, userId, joinUrl);
if (!om) {
warnings.push(`onlineMeeting not found for joinUrl=${joinUrl}`);
return {
meeting: event ? eventSummary(event) : null,
transcript: null, recap: null, recordingUrl: null, warnings
};
}
const [vtt, recap, recordingUrl] = await Promise.all([
fetchTranscriptVtt(client, userId, om.id).catch(e => { warnings.push(`transcript: ${e.message}`); return null; }),
fetchAiInsights(client, userId, om.id).catch(e => { warnings.push(`aiInsights: ${e.message}`); return null; }),
fetchRecordingUrl(client, userId, om.id).catch(e => { warnings.push(`recording: ${e.message}`); return null; })
]);
let transcript = null;
if (vtt) {
const cues = parseVtt(vtt, { mergeConsecutive: true });
transcript = formatTranscript(cues);
}
return {
meeting: event ? { ...eventSummary(event), onlineMeetingId: om.id, seriesMasterId } : { onlineMeetingId: om.id },
transcript,
recap,
recordingUrl,
warnings
};
}
function eventSummary(event) {
return {
id: event.id,
subject: event.subject,
start: event.start?.dateTime,
end: event.end?.dateTime,
seriesMasterId: event.seriesMasterId || null,
attendees: (event.attendees || []).map(a => ({
name: a.emailAddress?.name,
email: (a.emailAddress?.address || '').toLowerCase()
}))
};
}
async function main() {
const opts = parseArgs(process.argv);
if (!opts.o365Id && !opts.joinUrl) {
console.error('Usage: node fetch-meeting-artifacts.js --o365-id <id> [--out file.json]');
process.exit(2);
}
const env = loadEnv();
const upn = opts.user || env.AZURE_USER_EMAIL;
const client = await buildGraphClient(env);
// Resolve UPN -> Object ID. OnlineMeetings API + Application Access Policy
// match users by OID; UPN routing returns 404 even with correct policy.
const me = await client.api('/users/' + upn).select('id').get();
const userId = me.id;
const result = await runFetch(client, userId, opts);
const json = JSON.stringify(result, null, 2);
if (opts.out) writeFileSync(opts.out, json, 'utf-8');
else process.stdout.write(json + '\n');
}
if (require.main === module) {
main().catch(err => { console.error(err.stack || err.message); process.exit(1); });
}
module.exports = { runFetch, parseArgs };