fix(scripts): resolve UPN to OID, fix transcript headers, move aiInsights to /copilot path
- OnlineMeetings API + Application Access Policy match users by OID;
UPN routing returns 404. Resolve UPN -> OID before all Graph calls.
- Transcript content endpoint rejects default Accept '*/*';
add ?$format=text/vtt + Accept header + responseType('text').
- aiInsights moved to /copilot/users/{oid}/onlineMeetings/{mid}/aiInsights (Beta).
List call returns metadata only; fetch detail per insight for
meetingNotes/actionItems/mentions.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
18e7737db5
commit
69c7f86fac
|
|
@ -99,8 +99,12 @@ async function main() {
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
const env = loadEnv();
|
const env = loadEnv();
|
||||||
const userId = opts.user || env.AZURE_USER_EMAIL;
|
const upn = opts.user || env.AZURE_USER_EMAIL;
|
||||||
const client = await buildGraphClient(env);
|
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 result = await runFetch(client, userId, opts);
|
||||||
const json = JSON.stringify(result, null, 2);
|
const json = JSON.stringify(result, null, 2);
|
||||||
if (opts.out) writeFileSync(opts.out, json, 'utf-8');
|
if (opts.out) writeFileSync(opts.out, json, 'utf-8');
|
||||||
|
|
|
||||||
|
|
@ -62,14 +62,37 @@ async function fetchTranscriptVtt(client, userId, meetingId) {
|
||||||
new Date(b.createdDateTime) - new Date(a.createdDateTime)
|
new Date(b.createdDateTime) - new Date(a.createdDateTime)
|
||||||
)[0];
|
)[0];
|
||||||
const contentPath = `/users/${userId}/onlineMeetings/${meetingId}/transcripts/${latest.id}/content`;
|
const contentPath = `/users/${userId}/onlineMeetings/${meetingId}/transcripts/${latest.id}/content`;
|
||||||
return await safeGet(client, contentPath);
|
// 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) {
|
async function fetchAiInsights(client, userId, meetingId) {
|
||||||
const path = `/users/${userId}/onlineMeetings/${meetingId}/aiInsights`;
|
// Copilot recap moved under /copilot/... (beta) ca. 2025-2026.
|
||||||
const resp = await safeGet(client, path, { version: 'beta' });
|
// List call returns metadata only; detail call required for meetingNotes/actionItems.
|
||||||
const items = resp?.value || [];
|
const listPath = `/copilot/users/${userId}/onlineMeetings/${meetingId}/aiInsights`;
|
||||||
return items[0] || null;
|
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) {
|
async function fetchRecordingUrl(client, userId, meetingId) {
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,11 @@ describe('fetchTranscriptVtt', () => {
|
||||||
describe('fetchAiInsights', () => {
|
describe('fetchAiInsights', () => {
|
||||||
it('returns insights when available', async () => {
|
it('returns insights when available', async () => {
|
||||||
const client = fakeClient({
|
const client = fakeClient({
|
||||||
"/users/u/onlineMeetings/M1/aiInsights": {
|
"/copilot/users/u/onlineMeetings/M1/aiInsights": {
|
||||||
value: [{ id: 'I1', actionItems: [], meetingNotes: [], mentions: [] }]
|
value: [{ id: 'I1' }]
|
||||||
|
},
|
||||||
|
"/copilot/users/u/onlineMeetings/M1/aiInsights/I1": {
|
||||||
|
id: 'I1', actionItems: [], meetingNotes: [], mentions: []
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const out = await fetchAiInsights(client, 'u', 'M1');
|
const out = await fetchAiInsights(client, 'u', 'M1');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue