132 lines
3.7 KiB
JavaScript
132 lines
3.7 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;
|
|
}
|
|
|
|
function stripHtml(html) {
|
|
return html
|
|
.replace(/<br\s*\/?>/gi, '\n')
|
|
.replace(/<\/p>/gi, '\n')
|
|
.replace(/<[^>]+>/g, '')
|
|
.replace(/ /g, ' ')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/\n{3,}/g, '\n\n')
|
|
.trim();
|
|
}
|
|
|
|
function isRecurring(event) {
|
|
return Boolean(event.seriesMasterId);
|
|
}
|
|
|
|
function parseEventToMeeting(event) {
|
|
const startDate = event.start.dateTime.split('T');
|
|
const endDate = event.end.dateTime.split('T');
|
|
|
|
return {
|
|
id: event.id,
|
|
title: event.subject,
|
|
date: startDate[0],
|
|
start: startDate[1].substring(0, 5),
|
|
end: endDate[1].substring(0, 5),
|
|
bodyText: event.body?.contentType === 'html'
|
|
? stripHtml(event.body.content)
|
|
: (event.body?.content || '').trim(),
|
|
attendees: (event.attendees || []).map(a => ({
|
|
name: a.emailAddress.name,
|
|
email: a.emailAddress.address.toLowerCase()
|
|
})),
|
|
isRecurring: isRecurring(event)
|
|
};
|
|
}
|
|
|
|
function formatEventChoice(meeting) {
|
|
return `📅 ${meeting.date} ${meeting.start}-${meeting.end} ${meeting.title}`;
|
|
}
|
|
|
|
async function buildAuthenticatedClient() {
|
|
const 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)
|
|
});
|
|
}
|
|
|
|
async function getCalendarEvents(daysAhead = 7) {
|
|
const env = loadEnv();
|
|
const client = await buildAuthenticatedClient();
|
|
|
|
const now = new Date();
|
|
const future = new Date(now);
|
|
future.setDate(future.getDate() + daysAhead);
|
|
|
|
const response = await client
|
|
.api(`/users/${env.AZURE_USER_EMAIL}/calendarView`)
|
|
.query({
|
|
startDateTime: now.toISOString(),
|
|
endDateTime: future.toISOString()
|
|
})
|
|
.select('id,subject,start,end,body,attendees,seriesMasterId')
|
|
.orderby('start/dateTime')
|
|
.top(500)
|
|
.get();
|
|
|
|
return (response.value || []).map(parseEventToMeeting);
|
|
}
|
|
|
|
function extractJoinUrlFromBody(body) {
|
|
if (!body) return null;
|
|
const m = body.match(/https:\/\/teams\.microsoft\.com\/l\/meetup-join\/[^\s"'<>]+/);
|
|
return m ? m[0] : null;
|
|
}
|
|
|
|
async function getEventById(eventId) {
|
|
if (typeof eventId !== 'string' || !eventId.trim()) {
|
|
throw new TypeError('eventId must be a non-empty string');
|
|
}
|
|
|
|
try {
|
|
const env = loadEnv();
|
|
const client = await buildAuthenticatedClient();
|
|
|
|
const event = await client
|
|
.api(`/users/${env.AZURE_USER_EMAIL}/events/${eventId}`)
|
|
.select('id,subject,start,end,body,attendees,seriesMasterId,onlineMeeting')
|
|
.get();
|
|
return event;
|
|
} catch (cause) {
|
|
throw new Error(`Failed to fetch event ${eventId}: ${cause.message}`, { cause });
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
isRecurring, parseEventToMeeting, formatEventChoice,
|
|
getCalendarEvents, getEventById, extractJoinUrlFromBody
|
|
};
|