Files
jarvis/api/endpoints/email.php
T
myron 8d2c1948bb feat: Gmail IMAP email integration with voice intents
- email.php: IMAP reader for Gmail/Outlook/iCloud with 5-min cache
- api.php: add /api/email route
- chat.php: email voice intents — check count, read recent, filter by sender
- config.php: Gmail credentials (gitignored)
2026-05-31 06:20:15 +00:00

140 lines
5.2 KiB
PHP

<?php
// JARVIS Email endpoint — IMAP reader for Gmail, Outlook, iCloud.
// GET ?action=email → recent + unread summary (cached 5 min)
// GET ?action=email&account=gmail → Gmail only
// GET ?action=email&force=1 → bypass cache, fetch live
// GET ?action=email&search=from:john → search emails
function imapFetch(string $host, string $user, string $pass, int $maxMsgs = 10): array {
if (!$user || !$pass) return ['error' => 'not_configured'];
$mbox = @imap_open($host, $user, $pass, 0, 1, ['DISABLE_AUTHENTICATOR' => 'GSSAPI']);
if (!$mbox) return ['error' => imap_last_error() ?: 'connection_failed'];
$total = imap_num_msg($mbox);
$unseen = imap_search($mbox, 'UNSEEN') ?: [];
$unread = count($unseen);
// Fetch most recent messages (newest first)
$start = max(1, $total - $maxMsgs + 1);
$msgs = [];
for ($i = $total; $i >= $start; $i--) {
$hdr = @imap_headerinfo($mbox, $i);
if (!$hdr) continue;
$from = $hdr->from[0] ?? null;
$fromName = $from ? (isset($from->personal) ? imap_utf8($from->personal) : '') : '';
$fromEmail = $from ? ($from->mailbox . '@' . ($from->host ?? '')) : '';
$subject = isset($hdr->subject) ? imap_utf8($hdr->subject) : '(no subject)';
$date = isset($hdr->date) ? date('M j g:ia', strtotime($hdr->date)) : '';
$isUnread = in_array($i, $unseen);
// Fetch plain text preview (first 300 chars)
$preview = '';
$struct = @imap_fetchstructure($mbox, $i);
if ($struct) {
if ($struct->type === 0) {
// Single-part message
$raw = @imap_fetchbody($mbox, $i, '1');
$enc = $struct->encoding ?? 0;
if ($enc === 3) $raw = base64_decode($raw);
elseif ($enc === 4) $raw = quoted_printable_decode($raw);
$preview = mb_substr(strip_tags($raw), 0, 300);
} else {
// Multi-part — find first text/plain part
foreach (($struct->parts ?? []) as $idx => $part) {
if ($part->type === 0) { // text
$raw = @imap_fetchbody($mbox, $i, (string)($idx + 1));
$enc = $part->encoding ?? 0;
if ($enc === 3) $raw = base64_decode($raw);
elseif ($enc === 4) $raw = quoted_printable_decode($raw);
$preview = mb_substr(strip_tags($raw), 0, 300);
break;
}
}
}
}
$preview = trim(preg_replace('/\s+/', ' ', $preview));
$msgs[] = [
'id' => $i,
'from_name' => $fromName ?: $fromEmail,
'from_email'=> $fromEmail,
'subject' => $subject,
'date' => $date,
'unread' => $isUnread,
'preview' => $preview,
];
}
imap_close($mbox);
return ['total' => $total, 'unread' => $unread, 'messages' => $msgs];
}
$account = $data['account'] ?? $_GET['account'] ?? 'all';
$force = !empty($data['force']) || !empty($_GET['force']);
$search = trim($data['search'] ?? $_GET['search'] ?? '');
$cacheKey = 'email_' . $account;
$cacheTtl = 300; // 5 minutes
if (!$force) {
$cached = JarvisDB::single(
"SELECT data, UNIX_TIMESTAMP(updated_at) ts FROM api_cache WHERE cache_key=?",
[$cacheKey]
);
if ($cached && (time() - (int)$cached['ts']) < $cacheTtl) {
$out = json_decode($cached['data'], true);
$out['cache_age_s'] = time() - (int)$cached['ts'];
echo json_encode($out);
exit;
}
}
$result = ['accounts' => [], 'summary' => []];
if (in_array($account, ['all', 'gmail']) && defined('GMAIL_USER') && GMAIL_USER) {
$r = imapFetch('{imap.gmail.com:993/imap/ssl}INBOX', GMAIL_USER, GMAIL_PASS);
$result['accounts']['gmail'] = $r;
}
if (in_array($account, ['all', 'outlook']) && defined('OUTLOOK_USER') && OUTLOOK_USER) {
$r = imapFetch('{outlook.office365.com:993/imap/ssl}INBOX', OUTLOOK_USER, OUTLOOK_PASS);
$result['accounts']['outlook'] = $r;
}
if (in_array($account, ['all', 'icloud']) && defined('ICLOUD_USER') && ICLOUD_USER) {
$r = imapFetch('{imap.mail.me.com:993/imap/ssl}INBOX', ICLOUD_USER, ICLOUD_PASS);
$result['accounts']['icloud'] = $r;
}
// Build summary across all accounts
$totalUnread = 0;
$allMessages = [];
foreach ($result['accounts'] as $acct => $r) {
if (isset($r['unread'])) $totalUnread += (int)$r['unread'];
if (!empty($r['messages'])) {
foreach ($r['messages'] as $m) {
$m['account'] = $acct;
$allMessages[] = $m;
}
}
}
// Sort by date descending (approximate — already newest first per account)
$result['summary'] = [
'total_unread' => $totalUnread,
'recent' => array_slice($allMessages, 0, 10),
'fetched_at' => date('c'),
];
// Cache result
JarvisDB::execute(
"INSERT INTO api_cache (cache_key, data, updated_at) VALUES (?,?,NOW())
ON DUPLICATE KEY UPDATE data=VALUES(data), updated_at=NOW()",
[$cacheKey, json_encode($result)]
);
$result['cache_age_s'] = 0;
echo json_encode($result);