215 lines
5.9 KiB
JavaScript
215 lines
5.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Check anthonymau@gmail.com for new AI-related emails
|
|
* Fetches full email content for summarization
|
|
*/
|
|
|
|
const Imap = require('imap');
|
|
|
|
const config = {
|
|
user: 'anthonymau@gmail.com',
|
|
password: 'wfux qjhw eqjo jswm',
|
|
host: 'imap.gmail.com',
|
|
port: 993,
|
|
tls: true,
|
|
tlsOptions: { rejectUnauthorized: false }
|
|
};
|
|
|
|
// AI newsletter patterns to match
|
|
const AI_PATTERNS = [
|
|
'ai valley',
|
|
'the rundown',
|
|
'ai secret',
|
|
'byte-sized',
|
|
'bytesized',
|
|
'myclaw',
|
|
'openai global',
|
|
'deep view',
|
|
'khoj',
|
|
'synthetic',
|
|
'openhands',
|
|
'artificial intelligence',
|
|
'ai newsletter',
|
|
'benevolent',
|
|
'tldr',
|
|
'benedict'
|
|
];
|
|
|
|
function isAINewsletter(from, subject) {
|
|
const text = (from + ' ' + subject).toLowerCase();
|
|
return AI_PATTERNS.some(pattern => text.includes(pattern.toLowerCase()));
|
|
}
|
|
|
|
const imap = new Imap(config);
|
|
|
|
imap.once('ready', () => {
|
|
imap.openBox('INBOX', false, (err, box) => {
|
|
if (err) {
|
|
console.log('STATUS:error');
|
|
console.log('ERROR:open_box:' + err.message);
|
|
imap.end();
|
|
process.exit(1);
|
|
}
|
|
|
|
imap.search(['ALL'], (err, results) => {
|
|
if (err) {
|
|
console.log('STATUS:error');
|
|
console.log('ERROR:search:' + err.message);
|
|
imap.end();
|
|
process.exit(1);
|
|
}
|
|
|
|
const total = results.length;
|
|
const recent = results.slice(-200); // Get last 200 emails (covers ~2 days for most inboxes)
|
|
const lastUid = recent.length > 0 ? recent[recent.length - 1] : 0;
|
|
|
|
console.log('STATUS:connected');
|
|
console.log('TOTAL:' + total);
|
|
console.log('LAST_UID:' + lastUid);
|
|
console.log('RECENT:' + recent.join(','));
|
|
|
|
if (recent.length > 0) {
|
|
// Fetch headers with UID
|
|
const fetch = imap.fetch(recent, {
|
|
bodies: 'HEADER.FIELDS (FROM SUBJECT DATE)',
|
|
struct: false,
|
|
uid: true
|
|
});
|
|
|
|
const aiEmails = [];
|
|
|
|
fetch.on('message', (msg) => {
|
|
let headers = '';
|
|
let uid = null;
|
|
|
|
msg.on('body', (stream, info) => {
|
|
stream.on('data', (chunk) => {
|
|
headers += chunk.toString('utf8');
|
|
});
|
|
});
|
|
|
|
msg.once('attributes', (attrs) => {
|
|
uid = attrs.uid;
|
|
});
|
|
|
|
msg.once('end', () => {
|
|
const fromMatch = headers.match(/From: (.+)/i);
|
|
const subjectMatch = headers.match(/Subject: (.+)/i);
|
|
const dateMatch = headers.match(/Date: (.+)/i);
|
|
const from = fromMatch ? fromMatch[1].trim() : '';
|
|
const subject = subjectMatch ? subjectMatch[1].trim() : '';
|
|
const dateStr = dateMatch ? dateMatch[1].trim() : '';
|
|
|
|
// Only include emails from the last 36 hours
|
|
let isRecent = true;
|
|
if (dateStr) {
|
|
try {
|
|
const emailDate = new Date(dateStr);
|
|
const cutoff = new Date(Date.now() - 36 * 60 * 60 * 1000);
|
|
isRecent = emailDate > cutoff;
|
|
} catch(e) {}
|
|
}
|
|
|
|
if (isRecent && isAINewsletter(from, subject)) {
|
|
aiEmails.push({ uid, from, subject });
|
|
}
|
|
});
|
|
});
|
|
|
|
fetch.once('end', () => {
|
|
console.log('AI_COUNT:' + aiEmails.length);
|
|
|
|
if (aiEmails.length === 0) {
|
|
imap.end();
|
|
process.exit(0);
|
|
}
|
|
|
|
// Get UIDs for body fetch
|
|
const aiUids = aiEmails.map(e => e.uid).filter(u => u);
|
|
|
|
if (aiUids.length === 0) {
|
|
imap.end();
|
|
process.exit(0);
|
|
}
|
|
|
|
// Fetch full body for AI emails
|
|
const bodyFetch = imap.fetch(aiUids, {
|
|
bodies: ['1'],
|
|
struct: false,
|
|
uid: true
|
|
});
|
|
|
|
let processed = 0;
|
|
|
|
bodyFetch.on('message', (msg, seqno) => {
|
|
let body = '';
|
|
let uid = null;
|
|
|
|
msg.on('body', (stream) => {
|
|
stream.on('data', (chunk) => {
|
|
body += chunk.toString('utf8');
|
|
});
|
|
});
|
|
|
|
msg.once('attributes', (attrs) => {
|
|
uid = attrs.uid;
|
|
});
|
|
|
|
msg.once('end', () => {
|
|
processed++;
|
|
|
|
// Find matching email metadata
|
|
const meta = aiEmails.find(e => e.uid === uid) || aiEmails[processed - 1];
|
|
|
|
// Clean content
|
|
let content = body;
|
|
try {
|
|
// Decode quoted-printable soft line breaks FIRST
|
|
content = content.replace(/=\r?\n/g, '');
|
|
// Decode quoted-printable hex codes
|
|
content = content.replace(/=([0-9A-F]{2})/gi, (m, p) =>
|
|
String.fromCharCode(parseInt(p, 16)));
|
|
// Strip HTML
|
|
content = content.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
|
} catch (e) {}
|
|
|
|
const truncated = content.substring(0, 3000).trim();
|
|
|
|
console.log(`AI_EMAIL:${meta.from} | ${meta.subject}`);
|
|
console.log(`AI_CONTENT:${truncated}`);
|
|
|
|
if (processed === aiUids.length) {
|
|
imap.end();
|
|
process.exit(0);
|
|
}
|
|
});
|
|
});
|
|
|
|
bodyFetch.once('error', (err) => {
|
|
console.log('ERROR:body_fetch:' + err.message);
|
|
imap.end();
|
|
process.exit(1);
|
|
});
|
|
});
|
|
|
|
} else {
|
|
console.log('AI_COUNT:0');
|
|
imap.end();
|
|
process.exit(0);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
imap.once('error', (err) => {
|
|
console.log('STATUS:error');
|
|
console.log('ERROR:connect:' + err.message);
|
|
process.exit(1);
|
|
});
|
|
|
|
imap.once('end', () => {
|
|
process.exit(0);
|
|
});
|
|
|
|
imap.connect();
|