- Reset master to upstream/main (16,697 commits) - Overlay 2,271 local-only files (skills, tools, workspace, configs, apps) - Restore IDENTITY.md and USER.md templates - Build verified, gateway running, Discord working Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
100 lines
3.4 KiB
JavaScript
100 lines
3.4 KiB
JavaScript
#!/usr/bin/env node
|
|
// dba-inbox-check.cjs — tjekker Outlook for nye DBA-beskeder
|
|
// Output: "NEW: <besked>" eller "NO_NEW"
|
|
|
|
const { join } = require('path');
|
|
const { homedir } = require('os');
|
|
const fs = require('fs');
|
|
|
|
const STATE_FILE = '/tmp/dba-inbox-state.json';
|
|
const CREDS_PATH = join(homedir(), '.openclaw/secrets/outlook-oauth.json');
|
|
|
|
async function refreshToken(creds) {
|
|
const res = await fetch('https://login.microsoftonline.com/consumers/oauth2/v2.0/token', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: new URLSearchParams({
|
|
client_id: creds.client_id,
|
|
refresh_token: creds.refresh_token,
|
|
grant_type: 'refresh_token',
|
|
scope: 'https://graph.microsoft.com/Mail.Read offline_access'
|
|
})
|
|
});
|
|
if (!res.ok) throw new Error(`Token refresh fejlede: ${res.status}`);
|
|
const data = await res.json();
|
|
// Save updated tokens
|
|
const updated = { ...creds, access_token: data.access_token, refresh_token: data.refresh_token || creds.refresh_token };
|
|
fs.writeFileSync(CREDS_PATH, JSON.stringify(updated, null, 2));
|
|
return data.access_token;
|
|
}
|
|
|
|
async function main() {
|
|
const creds = JSON.parse(fs.readFileSync(CREDS_PATH, 'utf8'));
|
|
let token = creds.access_token;
|
|
|
|
// Hent ulæste emails
|
|
let res = await fetch(
|
|
'https://graph.microsoft.com/v1.0/me/messages?$top=20&$filter=isRead eq false&$orderby=receivedDateTime desc&$select=id,subject,from,receivedDateTime,bodyPreview',
|
|
{ headers: { Authorization: `Bearer ${token}` } }
|
|
);
|
|
|
|
// Refresh token hvis udløbet
|
|
if (res.status === 401) {
|
|
token = await refreshToken(creds);
|
|
res = await fetch(
|
|
'https://graph.microsoft.com/v1.0/me/messages?$top=20&$filter=isRead eq false&$orderby=receivedDateTime desc&$select=id,subject,from,receivedDateTime,bodyPreview',
|
|
{ headers: { Authorization: `Bearer ${token}` } }
|
|
);
|
|
}
|
|
|
|
if (!res.ok) {
|
|
process.stderr.write(`Graph API fejl: ${res.status}\n`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const { value: messages = [] } = await res.json();
|
|
|
|
// Filter: kun DBA-relaterede beskeder
|
|
const dbaEmails = messages.filter(m => {
|
|
const from = (m.from?.emailAddress?.address || '').toLowerCase();
|
|
const subject = (m.subject || '').toLowerCase();
|
|
const preview = (m.bodyPreview || '').toLowerCase();
|
|
return (
|
|
from.includes('dba.dk') || from.includes('vend.dk') || from.includes('finn.no') ||
|
|
subject.includes('ny besked') || subject.includes('har svaret') ||
|
|
preview.includes('har sendt dig en besked') || preview.includes('new message')
|
|
);
|
|
});
|
|
|
|
// Load previous state
|
|
let prev = { seenIds: [] };
|
|
try { prev = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')); } catch {}
|
|
const seenIds = new Set(prev.seenIds || []);
|
|
|
|
// Find nye beskeder
|
|
const newReplies = dbaEmails.filter(m => !seenIds.has(m.id));
|
|
|
|
// Gem state
|
|
fs.writeFileSync(STATE_FILE, JSON.stringify({
|
|
seenIds: [...new Set([...seenIds, ...dbaEmails.map(m => m.id)])].slice(-100),
|
|
checkedAt: new Date().toISOString()
|
|
}, null, 2));
|
|
|
|
if (newReplies.length === 0) {
|
|
console.log('NO_NEW');
|
|
return;
|
|
}
|
|
|
|
// Formater output
|
|
for (const m of newReplies) {
|
|
const subject = m.subject || '(intet emne)';
|
|
const preview = (m.bodyPreview || '').slice(0, 200).trim();
|
|
console.log(`NEW:📬 **Nyt DBA-svar!**\n**Emne:** ${subject}\n${preview}`);
|
|
}
|
|
}
|
|
|
|
main().catch(e => {
|
|
process.stderr.write(`Fejl: ${e.message}\n`);
|
|
process.exit(1);
|
|
});
|