89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
import { ImapFlow } from 'imapflow';
|
|
import { getUserByEmail, getUserIntegration, getServerIntegration } from 'officerdb';
|
|
import { openEmailDb, setSyncMeta } from '../src/servers/api/email/email-db';
|
|
|
|
const userEmail = process.argv[2];
|
|
if (!userEmail) {
|
|
console.error('Usage: bun run scripts/seed-imap-uids.ts <email>');
|
|
process.exit(1);
|
|
}
|
|
|
|
// ── Load credentials ──
|
|
|
|
const dbUser = await getUserByEmail(userEmail);
|
|
if (!dbUser) throw new Error('User not found');
|
|
|
|
const userGoogle = await getUserIntegration(dbUser.id, 'google');
|
|
const config = userGoogle?.config as Record<string, unknown> | undefined;
|
|
if (!config?.accessToken) throw new Error('No OAuth tokens found');
|
|
|
|
// Refresh token if needed
|
|
let accessToken = config.accessToken as string;
|
|
const expiresAt = config.expiresAt as number | undefined;
|
|
if (!expiresAt || expiresAt < Date.now() + 60_000) {
|
|
console.log('Refreshing expired token...');
|
|
const serverGoogle = await getServerIntegration('google');
|
|
const serverConfig = serverGoogle?.config as Record<string, unknown>;
|
|
const res = await fetch('https://oauth2.googleapis.com/token', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: new URLSearchParams({
|
|
client_id: serverConfig.clientId as string,
|
|
client_secret: serverConfig.clientSecret as string,
|
|
refresh_token: config.refreshToken as string,
|
|
grant_type: 'refresh_token',
|
|
}),
|
|
});
|
|
if (!res.ok) throw new Error(`Token refresh failed: ${await res.text()}`);
|
|
const data = (await res.json()) as { access_token: string };
|
|
accessToken = data.access_token;
|
|
}
|
|
|
|
// ── Connect IMAP ──
|
|
|
|
const client = new ImapFlow({
|
|
host: 'imap.gmail.com',
|
|
port: 993,
|
|
secure: true,
|
|
auth: { user: config.email as string, accessToken },
|
|
logger: false,
|
|
});
|
|
|
|
await client.connect();
|
|
console.log('Connected to IMAP');
|
|
|
|
const GMAIL_PREFIX_RE = /^\[(?:Gmail|Google Mail)\]\//;
|
|
const SKIP_SUFFIXES = new Set(['All Mail', 'Trash', 'Spam', 'Bin']);
|
|
|
|
const folders = await client.list();
|
|
const db = openEmailDb(userEmail);
|
|
|
|
let seeded = 0;
|
|
|
|
for (const folder of folders) {
|
|
const suffix = folder.path.replace(GMAIL_PREFIX_RE, '');
|
|
const isGmailFolder = suffix !== folder.path;
|
|
if (isGmailFolder && SKIP_SUFFIXES.has(suffix)) continue;
|
|
if (folder.specialUse && ['\\Trash', '\\Junk', '\\All'].includes(folder.specialUse)) continue;
|
|
|
|
try {
|
|
const status = await client.status(folder.path, { uidNext: true, uidValidity: true });
|
|
const lastUid = (status.uidNext ?? 1) - 1;
|
|
const uidValidity = String(status.uidValidity);
|
|
|
|
setSyncMeta(db, `imap_lastuid:${folder.path}`, String(lastUid));
|
|
setSyncMeta(db, `imap_uidvalidity:${folder.path}`, uidValidity);
|
|
|
|
console.log(` ${folder.path}: lastUid=${lastUid}, uidValidity=${uidValidity}`);
|
|
seeded++;
|
|
} catch (err) {
|
|
console.log(` ${folder.path}: skipped (${err instanceof Error ? err.message : err})`);
|
|
}
|
|
}
|
|
|
|
db.close();
|
|
await client.logout();
|
|
|
|
console.log(`\nSeeded ${seeded} folders. Next sync will only fetch new messages.`);
|
|
process.exit(0);
|