prism/server/modules/protonmail.ts

126 lines
3.5 KiB
TypeScript

import chalk from 'chalk';
import Imap from 'imap';
import {
BRIDGE_IMAP_PASSWORD,
BRIDGE_IMAP_USERNAME,
ENABLE_PROTON_ANDROID,
LAUNCH_ENDPOINT_PREFIX,
PROTON_BRIDGE_HOST,
PROTON_BRIDGE_PORT,
SUP_TOPIC,
VERBOSE,
} from '../constants/config';
import { createGroup, sendGroupMessage } from './signal';
import { getGroupId, register } from './store';
const log = (...args: unknown[]) => VERBOSE && console.log(...args);
export async function startProtonMonitor() {
if (!BRIDGE_IMAP_USERNAME || !BRIDGE_IMAP_PASSWORD) {
console.error(
chalk.red('Missing required env vars: BRIDGE_IMAP_USERNAME and BRIDGE_IMAP_PASSWORD'),
);
console.error(chalk.yellow('Run: docker compose run --rm protonmail-bridge init'));
console.error(chalk.yellow('Then use `login` and `info` commands to get IMAP credentials'));
return;
}
console.log(
chalk.blue(`🔗 Connecting to Proton Bridge at ${PROTON_BRIDGE_HOST}:${PROTON_BRIDGE_PORT}`),
);
console.log(chalk.blue(`📨 Monitoring mailbox: ${BRIDGE_IMAP_USERNAME}`));
const imap = new Imap({
user: BRIDGE_IMAP_USERNAME,
password: BRIDGE_IMAP_PASSWORD,
host: PROTON_BRIDGE_HOST,
port: PROTON_BRIDGE_PORT,
tls: true,
tlsOptions: { rejectUnauthorized: false },
keepalive: true,
});
async function sendNotification(title: string, message: string) {
try {
const topicKey = `proton-${SUP_TOPIC}`;
const groupId = getGroupId(topicKey) ?? (await createGroup(SUP_TOPIC));
if (!getGroupId(topicKey)) {
register(topicKey, groupId, SUP_TOPIC);
}
const prefix = ENABLE_PROTON_ANDROID
? `${LAUNCH_ENDPOINT_PREFIX}ch.protonmail.android]\n`
: '';
await sendGroupMessage(groupId, `${prefix}**${title}**\n${message}`);
console.log(chalk.green(`✅ Notification sent: ${title}`));
} catch (error) {
console.error(chalk.red('❌ Failed to send notification:'), error);
}
}
function openInbox() {
imap.openBox('INBOX', false, (err, box) => {
if (err) {
console.error(chalk.red('Failed to open inbox:'), err);
return;
}
log(`✅ Connected to inbox (${box.messages.total} messages)`);
imap.on('mail', async (numNewMsgs: number) => {
log(`📬 ${numNewMsgs} new message(s) received`);
const fetch = imap.seq.fetch(`${box.messages.total}:*`, {
bodies: 'HEADER.FIELDS (FROM SUBJECT)',
struct: true,
});
fetch.on('message', (msg) => {
msg.on('body', (stream) => {
let buffer = '';
stream.on('data', (chunk) => {
buffer += chunk.toString('utf8');
});
stream.once('end', () => {
const header = Imap.parseHeader(buffer);
const from = header.from?.[0] || 'Unknown sender';
const subject = header.subject?.[0] || 'No subject';
sendNotification(`New Mail from ${from}`, subject);
});
});
});
});
imap.on('update', () => {
log('📊 Mailbox updated');
});
});
}
imap.once('ready', () => {
log('✅ IMAP connection ready');
openInbox();
});
imap.once('error', (err: Error) => {
console.error(chalk.red('❌ IMAP error:'), err);
});
imap.once('end', () => {
log('⚠️ IMAP connection ended, reconnecting...');
setTimeout(() => imap.connect(), 5000);
});
imap.connect();
process.on('SIGTERM', () => {
imap.end();
});
process.on('SIGINT', () => {
imap.end();
});
}