116 lines
4.2 KiB
JavaScript
116 lines
4.2 KiB
JavaScript
const { ConfidentialClientApplication } = require('@azure/msal-node');
|
|
const { Client } = require('@microsoft/microsoft-graph-client');
|
|
const { readFileSync } = require('node:fs');
|
|
const { resolve } = require('node:path');
|
|
|
|
function loadEnv() {
|
|
const envPath = resolve(__dirname, '..', '.env');
|
|
const content = readFileSync(envPath, 'utf-8');
|
|
const vars = {};
|
|
for (const line of content.split('\n')) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
const [key, ...rest] = trimmed.split('=');
|
|
vars[key.trim()] = rest.join('=').trim();
|
|
}
|
|
return vars;
|
|
}
|
|
|
|
async function buildGraphClient(env = loadEnv()) {
|
|
const cca = new ConfidentialClientApplication({
|
|
auth: {
|
|
clientId: env.AZURE_CLIENT_ID,
|
|
clientSecret: env.AZURE_CLIENT_SECRET,
|
|
authority: `https://login.microsoftonline.com/${env.AZURE_TENANT_ID}`
|
|
}
|
|
});
|
|
const tokenResponse = await cca.acquireTokenByClientCredential({
|
|
scopes: ['https://graph.microsoft.com/.default']
|
|
});
|
|
return Client.init({
|
|
authProvider: (done) => done(null, tokenResponse.accessToken),
|
|
defaultVersion: 'v1.0'
|
|
});
|
|
}
|
|
|
|
async function safeGet(client, path, options = {}) {
|
|
try {
|
|
let req = client.api(path);
|
|
if (options.version && typeof req.version === 'function') req = req.version(options.version);
|
|
if (options.query && typeof req.query === 'function') req = req.query(options.query);
|
|
return await req.get();
|
|
} catch (err) {
|
|
if (err.statusCode === 404 || err.statusCode === 403) return null;
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async function resolveOnlineMeeting(client, userId, joinWebUrl) {
|
|
const path = `/users/${userId}/onlineMeetings`;
|
|
const resp = await safeGet(client, path, {
|
|
query: { $filter: `JoinWebUrl eq '${joinWebUrl}'` }
|
|
});
|
|
const list = resp?.value || [];
|
|
return list[0] || null;
|
|
}
|
|
|
|
async function fetchTranscriptVtt(client, userId, meetingId) {
|
|
const list = await safeGet(client, `/users/${userId}/onlineMeetings/${meetingId}/transcripts`);
|
|
const items = list?.value || [];
|
|
if (items.length === 0) return null;
|
|
const latest = [...items].sort((a, b) =>
|
|
new Date(b.createdDateTime) - new Date(a.createdDateTime)
|
|
)[0];
|
|
const contentPath = `/users/${userId}/onlineMeetings/${meetingId}/transcripts/${latest.id}/content`;
|
|
// Transcript content endpoint returns text/vtt; default Accept */* is rejected.
|
|
try {
|
|
let req = client.api(contentPath).query({ $format: 'text/vtt' });
|
|
if (typeof req.header === 'function') req = req.header('Accept', 'text/vtt');
|
|
if (typeof req.responseType === 'function') req = req.responseType('text');
|
|
const resp = await req.get();
|
|
if (typeof resp === 'string') return resp;
|
|
if (resp && typeof resp.text === 'function') return await resp.text();
|
|
return resp;
|
|
} catch (err) {
|
|
if (err.statusCode === 404 || err.statusCode === 403) return null;
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async function fetchAiInsights(client, userId, meetingId) {
|
|
// Copilot recap moved under /copilot/... (beta) ca. 2025-2026.
|
|
// List call returns metadata only; detail call required for meetingNotes/actionItems.
|
|
const listPath = `/copilot/users/${userId}/onlineMeetings/${meetingId}/aiInsights`;
|
|
const list = await safeGet(client, listPath, { version: 'beta' });
|
|
const items = list?.value || [];
|
|
if (items.length === 0) return null;
|
|
const details = [];
|
|
for (const item of items) {
|
|
const detail = await safeGet(client, `${listPath}/${item.id}`, { version: 'beta' });
|
|
if (detail) details.push(detail);
|
|
}
|
|
if (details.length === 0) return null;
|
|
// Return single object if one insight, otherwise wrap.
|
|
if (details.length === 1) return details[0];
|
|
return { id: details[0].id, insights: details };
|
|
}
|
|
|
|
async function fetchRecordingUrl(client, userId, meetingId) {
|
|
const list = await safeGet(client, `/users/${userId}/onlineMeetings/${meetingId}/recordings`);
|
|
const items = list?.value || [];
|
|
if (items.length === 0) return null;
|
|
const latest = [...items].sort((a, b) =>
|
|
new Date(b.createdDateTime) - new Date(a.createdDateTime)
|
|
)[0];
|
|
return latest.recordingContentUrl || latest.contentCorrelationId || null;
|
|
}
|
|
|
|
module.exports = {
|
|
loadEnv,
|
|
buildGraphClient,
|
|
resolveOnlineMeeting,
|
|
fetchTranscriptVtt,
|
|
fetchAiInsights,
|
|
fetchRecordingUrl
|
|
};
|