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 '); 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 | 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; 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);