diff --git a/server/src/email_extract.rs b/server/src/email_extract.rs index 30ccfaf..19d68a1 100644 --- a/server/src/email_extract.rs +++ b/server/src/email_extract.rs @@ -149,7 +149,7 @@ pub fn extract_calendar_metadata_from_mail( // Fallback extraction: if iCal did not provide metadata, extract from subject/body before generating fallback HTML if body_html.is_none() { - // Try to extract summary from subject (e.g., "New event: @ ...") + // Try to extract summary from subject (e.g., "New event: @ ..." or "Updated invitation: @ ...") if summary.is_none() { if let Some(subject) = m.headers.get_first_value("Subject") { if let Some(caps) = regex::Regex::new(r"New event: ([^@]+) @") @@ -162,14 +162,19 @@ pub fn extract_calendar_metadata_from_mail( .and_then(|re| re.captures(&subject)) { summary = Some(caps[1].trim().to_string()); + } else if let Some(caps) = regex::Regex::new(r"Updated invitation: ([^@]+) @") + .ok() + .and_then(|re| re.captures(&subject)) + { + summary = Some(caps[1].trim().to_string()); } } } // Try to extract start/end dates from subject if start_date.is_none() || end_date.is_none() { if let Some(subject) = m.headers.get_first_value("Subject") { - // Pattern: New event: Dentist appt @ Tue Sep 23, 2025 3pm - 4pm (PDT) (tconvertino@gmail.com) - if let Some(caps) = regex::Regex::new(r"New event: [^@]+@ ([A-Za-z]{3}) ([A-Za-z]{3}) (\d{1,2}), (\d{4}) (\d{1,2})(?::(\d{2}))? ?([ap]m) ?- ?(\d{1,2})(?::(\d{2}))? ?([ap]m)").ok().and_then(|re| re.captures(&subject)) { + // Pattern: @ Tue Sep 23, 2025 3pm - 4pm (works for New event, Invitation, Updated invitation, etc.) + if let Some(caps) = regex::Regex::new(r"@ ([A-Za-z]{3}) ([A-Za-z]{3}) (\d{1,2}), (\d{4}) (\d{1,2})(?::(\d{2}))? ?([ap]m) ?- ?(\d{1,2})(?::(\d{2}))? ?([ap]m)").ok().and_then(|re| re.captures(&subject)) { let month = &caps[2]; let day = &caps[3]; let year = &caps[4]; @@ -2606,6 +2611,29 @@ mod tests { assert!(html.contains(r#"data-event-day="2026-01-18""#), "Jan 18 should be highlighted"); } + #[test] + fn google_calendar_email_5_updated_invitation() { + use mailparse::parse_mail; + let raw_email = include_str!("../../server/testdata/google-calendar-example-5.eml"); + let parsed = parse_mail(raw_email.as_bytes()).expect("parse_mail"); + let mut part_addr = vec![]; + let body = extract_body(&parsed, &mut part_addr).expect("extract_body"); + let meta = extract_calendar_metadata_from_mail(&parsed, &body); + // Assert detection as Google Calendar + assert!(meta.is_google_calendar_event); + // Assert metadata extraction for updated invitation + assert_eq!(meta.summary, Some("painting class".to_string())); + assert_eq!(meta.organizer, Some("tconvertino@gmail.com".to_string())); + // Dates: Thursday Feb 12, 2026 7pm - 9pm (same day event with time) + assert_eq!(meta.start_date, Some("20260212".to_string())); + assert_eq!(meta.end_date, Some("20260212".to_string())); + // Assert ical summary is rendered and shows Feb 12 highlighted + let html = meta.body_html.expect("body_html"); + println!("Rendered HTML: {}", html); + assert!(html.contains("ical-flex"), "Calendar widget should be rendered"); + assert!(html.contains(r#"data-event-day="2026-02-12""#), "Feb 12 should be highlighted"); + } + #[test] fn recurring_event_rrule_metadata_and_highlight() { use super::render_ical_summary; diff --git a/server/testdata/google-calendar-example-5.eml b/server/testdata/google-calendar-example-5.eml new file mode 100644 index 0000000..188fde8 --- /dev/null +++ b/server/testdata/google-calendar-example-5.eml @@ -0,0 +1,733 @@ +Return-Path: +Delivered-To: bill@xinu.tv +Received: from phx.xinu.tv [74.207.253.222] + by nixos-01.h.xinu.tv with IMAP (fetchmail-6.5.6) + for (single-drop); Sat, 17 Jan 2026 07:15:51 -0800 (PST) +Received: from phx.xinu.tv + by phx.xinu.tv with LMTP + id EBhFKaana2lD2BMAJR8clQ + (envelope-from ) + for ; Sat, 17 Jan 2026 07:15:50 -0800 +X-Original-To: gmail@xinu.tv +Received: from mail-lf1-f47.google.com (mail-lf1-f47.google.com [209.85.167.47]) + by phx.xinu.tv (Postfix) with ESMTPS id 9ED9E80023 + for ; Sat, 17 Jan 2026 07:15:49 -0800 (PST) +Received: by mail-lf1-f47.google.com with SMTP id 2adb3069b0e04-59b72a1e2f0so3298670e87.0 + for ; Sat, 17 Jan 2026 07:15:49 -0800 (PST) +ARC-Seal: i=2; a=rsa-sha256; t=1768662948; cv=pass; + d=google.com; s=arc-20240605; + b=P8ukIhKVppSoOtIG2772uR9xtbgbpQjTdNpTjI3WHjWj7WKqgmV4ndotO/y7GwqvOX + OwYJbwY9cIR/PPOGf0/UfUSfPTp6iWwfapN3B5YDoSrOk3ef8e0HXRcSMeIG9D0JWfKB + YTFLsg6dqxdrPLsyDhFeJQQFK4+qb4gndM7SEw9UZtvvEbzGmGro9QFcO+uXsPZ6jQsb + UNn7VJhOqi3sc+mNAmPSNN1KSjeZ5wQoJDKHAhc39P7HlCI6Upm3UgyejjToTNa5pFUH + YfqgrPEU0NsJWZNUfnItYhChHXZyHE9iUcK8eDO7qRyvVAwkEdxmc34EC4hOJsHxdLli + ik4A== +ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; + h=to:from:subject:date:message-id:auto-submitted:sender:reply-to + :mime-version:dkim-signature:dkim-signature:delivered-to; + bh=DSYDWRwqF5i+Yosvco75LpPB3hH2JqH2JZElsHalDcM=; + fh=opiyoKXgWpm8kcAa3Nw4m5S9DBRIdfMLcfsQwVK5zKs=; + b=hyfgpNzlhW6aPYNzOd5CkaqQ2qYr/Sb25Qti1ez7y+n0SxJM/QbpyMCafAFv1s6vSR + jdbmeaofRjmDkL0uDdoQb0E5TnycNKP3r4FWEKc2KZkfnKEtWWOivFDIvQDPF4NpYNn3 + 9SFZkfmVa2TSfrnH4wRG37gCQgc6gzDpl6a2oZRmM4T44xbIXCIseFSalR+oUTFCJU3M + XSBCNjV7w/V8S2hiH72Ace8enjUG+DRbeE8aYiNwu0XkECojPxGgBuM9OJPSmYzNt3LW + 7M+weqkx0Byoi6UDaI2NQqXH4wkSrjmIFcX+84jglfl2zOsPGwgehJYdo/QcPMXtQohY + sMZA==; + darn=xinu.tv +ARC-Authentication-Results: i=2; mx.google.com; + dkim=pass header.i=@google.com header.s=20230601 header.b=UV12BjMR; + dkim=pass header.i=@gmail.com header.s=20230601 header.b=HTQ+XWtf; + spf=pass (google.com: domain of tconvertino@gmail.com designates 209.85.220.73 as permitted sender) smtp.mailfrom=tconvertino@gmail.com; + dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com; + dara=pass header.i=@gmail.com +X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=1e100.net; s=20230601; t=1768662948; x=1769267748; + h=to:from:subject:date:message-id:auto-submitted:sender:reply-to + :mime-version:dkim-signature:dkim-signature:delivered-to + :x-forwarded-for:x-forwarded-to:x-gm-message-state:from:to:cc + :subject:date:message-id:reply-to; + bh=DSYDWRwqF5i+Yosvco75LpPB3hH2JqH2JZElsHalDcM=; + b=wCE8iOXwi+1PwnSvMIbzsg5Pdo1jXyrqQjqZN3UN9bXyLJmoKV+a5l4cE7La2TkWIe + zdw/qcgetdMWPrJncmnLYptNMOMhHf8OmgDbtKbZLo1WVnY6OcnTTCWljhm9l3p1sNCJ + NNvrE79CNsi4mB30/gh5GUSFldNs4CKXLoX5U1JyaR0URBNSdgTtWHQEk9Zip6lzSJ/W + 3myHdqXjlFIBn8JdN2F+Uvpe4SOcpUYivDzRzGgKBVBybgL+7/B2FVxu1DSqImvvdnew + G45//ZKZLk1NZTwB9b3fVu1ZRrtVwXykyTsLWQs/L+h2RatzY5cbRknOUEg+Sh12glbJ + Mvzg== +X-Forwarded-Encrypted: i=2; AJvYcCWTlZh7K3d3elcvXPqK9OnJxLWgTqppYJqLWqVOMFJDYDcmSN8i4AmT2kLKruApNvLk5mchcQ==@xinu.tv +X-Gm-Message-State: AOJu0YzRCKajwo80HG2ZJYkIh92JNKbnNkbFqaG5Dye25AAdsFY1p/Sa + HUpoeDQQxTSXezOEygAE9wR8sFTjlB4uRgVqtHJ5s0ml5G9sQVG9Ir31aJU5vBs4OpeeAa01KHq + OqoyqsCDybPJBW43d/qWOxpcpIORkUKMmPkNGNzU1+hFlox6fAt4TasNy8lbiLw== +X-Received: by 2002:a05:6512:2507:b0:59b:b55a:a293 with SMTP id 2adb3069b0e04-59bb55aa3b5mr1328824e87.34.1768662947779; + Sat, 17 Jan 2026 07:15:47 -0800 (PST) +X-Forwarded-To: gmail@xinu.tv +X-Forwarded-For: couchmoney@gmail.com gmail@xinu.tv +Delivered-To: couchmoney@gmail.com +Received: by 2002:a05:6504:2382:b0:2d3:710a:2457 with SMTP id h2csp2302771lty; + Sat, 17 Jan 2026 07:15:46 -0800 (PST) +X-Received: by 2002:a05:6808:221e:b0:45a:6ef9:79 with SMTP id 5614622812f47-45c9d86c3acmr2292013b6e.52.1768662946398; + Sat, 17 Jan 2026 07:15:46 -0800 (PST) +ARC-Seal: i=1; a=rsa-sha256; t=1768662946; cv=none; + d=google.com; s=arc-20240605; + b=KdiLKIN7OQmPF5O3bozzB9t0JL/To2npnCMuMVg4Iawwtni3NbkM+Y4nXB3Wtm5kAj + Sa6HsrnbgNz3D8lp3nJB8bXexvVTc73ZQFDKtKnDRj7sv/8eK0qMpCkRb3Rhgu2cQEfa + lYv7E0pEj0qcMxDM5osGhdhrwsV8BnEboHL38bCfhDxSmcmGsmPgiC5IWiyhcWq8G2W6 + XwwPDdcHXk4wLwy4AQ1/NR5q130ELNP1f2e1Xq+xxLhfuV7GzhgOCTxYBIzrzJdM0kC/ + Sxd1ejz+WLbowjVP2s4rStRtCvHU/g1LlPGmdnmyN9wQ+Tkya+Q7LjRWWB99e04qENOE + 3E9g== +ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; + h=to:from:subject:date:message-id:auto-submitted:sender:reply-to + :mime-version:dkim-signature:dkim-signature; + bh=DSYDWRwqF5i+Yosvco75LpPB3hH2JqH2JZElsHalDcM=; + fh=mbzrMIWIgWMC0ni1xEx+ViW4J0RLAdLdPT2cX81nTlk=; + b=R24D+rdvNBJO+lAlMBX/5KkpnryHlsj/t5x1rJvWsKG/yrKJe81Epa9aDOWh5pljV4 + Cg7IH6KrYLzYYMNQ83stCE7zIBs7sjtBl81bd/OpWCoXgJEAnE2bYs958fk3z8jINzBv + huNTL3ijCNiZeI/y5Ye/S8avkRfWf1tNn7nNc7oVf6Xc+ujGIk28KL3EGerj29au519E + MqL6BVYlSAx9VcBXY2vRZft2xDJo+JiKIw2n+XJT7+Ax0LubBX5BCfcRinBFT/XsVFTD + zxNaxxq9DbbAtr78NgacXMOxmo8bJYi/MD0wo0BVZBxd3kXhKF367iEKBreXu7tYPp5i + d7tg==; + dara=google.com +ARC-Authentication-Results: i=1; mx.google.com; + dkim=pass header.i=@google.com header.s=20230601 header.b=UV12BjMR; + dkim=pass header.i=@gmail.com header.s=20230601 header.b=HTQ+XWtf; + spf=pass (google.com: domain of tconvertino@gmail.com designates 209.85.220.73 as permitted sender) smtp.mailfrom=tconvertino@gmail.com; + dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com; + dara=pass header.i=@gmail.com +Received: from mail-sor-f73.google.com (mail-sor-f73.google.com. [209.85.220.73]) + by mx.google.com with SMTPS id 5614622812f47-45ca0e0d20dsor1067583b6e.18.2026.01.17.07.15.46 + for + (Google Transport Security); + Sat, 17 Jan 2026 07:15:46 -0800 (PST) +Received-SPF: pass (google.com: domain of tconvertino@gmail.com designates 209.85.220.73 as permitted sender) client-ip=209.85.220.73; +Authentication-Results: mx.google.com; + dkim=pass header.i=@google.com header.s=20230601 header.b=UV12BjMR; + dkim=pass header.i=@gmail.com header.s=20230601 header.b=HTQ+XWtf; + spf=pass (google.com: domain of tconvertino@gmail.com designates 209.85.220.73 as permitted sender) smtp.mailfrom=tconvertino@gmail.com; + dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com; + dara=pass header.i=@gmail.com +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=google.com; s=20230601; t=1768662946; x=1769267746; dara=google.com; + h=to:from:subject:date:message-id:auto-submitted:sender:reply-to + :mime-version:from:to:cc:subject:date:message-id:reply-to; + bh=DSYDWRwqF5i+Yosvco75LpPB3hH2JqH2JZElsHalDcM=; + b=UV12BjMRpTIHG34HTOnEB4ApBu4DnXlft6fU+aIV2TzL1HFmpDf9rz1riEoHyaWSGU + P7ydXwhdNhl1b9SVRu+jhtsd0Wt7vLGTb/ru1OZEnxC8P2A9jCdqjT3J9GwtpZxcv4nh + ipdJF6LYZ6yt1H2AftMJf59/9L1HbiwMs3MceHqj+R0AlU4KNtuevB/ImTump5XiSH/8 + iDgvVfqyvm2kYwp9yV4cMXWUIQG6bWHB/No01kLG2PtgOy45chmfp/P22/ZyY3wEFd/U + 044fReZdGmqwqGfhM81y2+WVwuzQltjv8dZ2yf1SeSVOLmrc8Uc5lYFvzQJK7NiaVsyv + sYWQ== +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=gmail.com; s=20230601; t=1768662946; x=1769267746; dara=google.com; + h=to:from:subject:date:message-id:auto-submitted:sender:reply-to + :mime-version:from:to:cc:subject:date:message-id:reply-to; + bh=DSYDWRwqF5i+Yosvco75LpPB3hH2JqH2JZElsHalDcM=; + b=HTQ+XWtfciYuHpwOHCqsF6ctCQc5eRWblgRxVdEfjGFYy2TmgSb/jA5+HldQ7hxk5S + mfppkAhF82R1zQM96e2QMnoamqslIpum/+pvdIfxbvXiAytCNOmQZoc1xhlTz5CCkpni + g23H0O3kiXSdzhG4uLJgsGMaNR/rAkslbMW7ARNTluvuWM60d9At9ZCazZCVLq2C0nhS + 0HvaBa5uxi19A6l/55NmEbhPvAHXSw7V9SEUF8TTBuGLfZEkrjUF+RxfAgN/+NuZR5o7 + yOAlNI/iEi+F8T7Is+ZmjbHYysZu01/nzldZnZEQamYuUQBeKpbNHSEGbVrpD3+AuT6f + /t8w== +MIME-Version: 1.0 +X-Received: by 2002:a05:6820:1ca1:b0:65f:7009:d6e5 with SMTP id + 006d021491bc7-661188b9d4bmr1995718eaf.22.1768662945924; Sat, 17 Jan 2026 + 07:15:45 -0800 (PST) +Reply-To: tconvertino@gmail.com +Sender: Google Calendar +Auto-Submitted: auto-generated +Message-ID: +Date: Sat, 17 Jan 2026 15:15:45 +0000 +Subject: Updated invitation: painting class @ Thu Feb 12, 2026 7pm - 9pm (PST) (tconvertino@gmail.com) +From: tconvertino@gmail.com +To: couchmoney@gmail.com +Content-Type: multipart/alternative; boundary="0000000000009e8d3e064896eeed" +X-Rspamd-Queue-Id: 9ED9E80023 +X-Rspamd-Server: phx +X-Spamd-Result: default: False [-0.70 / 15.00]; + URI_COUNT_ODD(1.00)[1]; + ARC_ALLOW(-1.00)[google.com:s=arc-20240605:i=2]; + DMARC_POLICY_ALLOW(-0.50)[gmail.com,none]; + MANY_INVISIBLE_PARTS(0.20)[3]; + R_DKIM_ALLOW(-0.20)[google.com:s=20230601,gmail.com:s=20230601]; + R_SPF_ALLOW(-0.20)[+ip4:209.85.128.0/17:c]; + MIME_GOOD(-0.10)[multipart/alternative,text/plain]; + MIME_BASE64_TEXT(0.10)[]; + FREEMAIL_ENVFROM(0.00)[gmail.com]; + RCVD_TLS_LAST(0.00)[]; + RCVD_COUNT_THREE(0.00)[3]; + FREEMAIL_TO(0.00)[gmail.com]; + TAGGED_FROM(0.00)[caf_=gmail=xinutv]; + FREEMAIL_FROM(0.00)[gmail.com]; + FREEMAIL_REPLYTO(0.00)[gmail.com]; + MIME_TRACE(0.00)[0:+,1:+,2:~]; + RCPT_COUNT_ONE(0.00)[1]; + FORGED_SENDER(0.00)[tconvertino@gmail.com,couchmoney@gmail.com]; + RCVD_IN_DNSWL_NONE(0.00)[209.85.220.73:received]; + MISSING_XM_UA(0.00)[]; + HAS_REPLYTO(0.00)[tconvertino@gmail.com]; + TO_DN_NONE(0.00)[]; + DNSWL_BLOCKED(0.00)[209.85.167.47:from]; + FORGED_SENDER_FORWARDING(0.00)[]; + FROM_NEQ_ENVFROM(0.00)[tconvertino@gmail.com,couchmoney@gmail.com]; + DWL_DNSWL_NONE(0.00)[google.com:dkim]; + DKIM_TRACE(0.00)[google.com:+,gmail.com:+]; + DWL_DNSWL_BLOCKED(0.00)[gmail.com:dkim]; + TO_DOM_EQ_FROM_DOM(0.00)[]; + FROM_NO_DN(0.00)[]; + FWD_GOOGLE(0.00)[couchmoney@gmail.com]; + ASN(0.00)[asn:15169, ipnet:209.85.128.0/17, country:US]; + RWL_MAILSPIKE_POSSIBLE(0.00)[209.85.167.47:from]; + REPLYTO_EQ_FROM(0.00)[] +X-Rspamd-Action: no action +X-TUID: SJoEMVYLjYPK + +--0000000000009e8d3e064896eeed +Content-Type: text/plain; charset="UTF-8"; format=flowed; delsp=yes +Content-Transfer-Encoding: base64 + +VGhpcyBldmVudCBoYXMgYmVlbiB1cGRhdGVkDQpDaGFuZ2VkOiB0aW1lDQoNCg0KcGFpbnRpbmcg +Y2xhc3MNClRodXJzZGF5IEZlYiAxMiwgMjAyNiDii4UgN3BtIOKAkyA5cG0NClBhY2lmaWMgVGlt +ZSAtIExvcyBBbmdlbGVzDQoNCg0KDQpPcmdhbml6ZXINCnRjb252ZXJ0aW5vQGdtYWlsLmNvbQ0K +dGNvbnZlcnRpbm9AZ21haWwuY29tDQoNCn5+Ly9+fg0KSW52aXRhdGlvbiBmcm9tIEdvb2dsZSBD +YWxlbmRhcjogaHR0cHM6Ly9jYWxlbmRhci5nb29nbGUuY29tL2NhbGVuZGFyLw0KDQpZb3UgYXJl +IHJlY2VpdmluZyB0aGlzIGVtYWlsIGJlY2F1c2UgeW91IGFyZSBzdWJzY3JpYmVkIHRvIGNhbGVu +ZGFyICANCm5vdGlmaWNhdGlvbnMuIFRvIHN0b3AgcmVjZWl2aW5nIHRoZXNlIGVtYWlscywgZ28g +dG8gIA0KaHR0cHM6Ly9jYWxlbmRhci5nb29nbGUuY29tL2NhbGVuZGFyL3Ivc2V0dGluZ3MsIHNl +bGVjdCB0aGlzIGNhbGVuZGFyLCBhbmQgIA0KY2hhbmdlICJPdGhlciBub3RpZmljYXRpb25zIi4N +Cg0KRm9yd2FyZGluZyB0aGlzIGludml0YXRpb24gY291bGQgYWxsb3cgYW55IHJlY2lwaWVudCB0 +byBzZW5kIGEgcmVzcG9uc2UgdG8gIA0KdGhlIG9yZ2FuaXplciwgYmUgYWRkZWQgdG8gdGhlIGd1 +ZXN0IGxpc3QsIGludml0ZSBvdGhlcnMgcmVnYXJkbGVzcyBvZiAgDQp0aGVpciBvd24gaW52aXRh +dGlvbiBzdGF0dXMsIG9yIG1vZGlmeSB5b3VyIFJTVlAuDQoNCkxlYXJuIG1vcmUgaHR0cHM6Ly9z +dXBwb3J0Lmdvb2dsZS5jb20vY2FsZW5kYXIvYW5zd2VyLzM3MTM1I2ZvcndhcmRpbmcNCg== +--0000000000009e8d3e064896eeed +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + + + + + + + + + + + + + =20 + + =20 + + =20 + + =20 + + + + + + + + + painting class= +
You have been invited by tconvertino@gmail.com to attend an event na= +med painting class on Thursday Feb 12, 2026 =E2=8B=85 7pm =E2=80=93 9pm (Pa= +cific Time - Los Angeles).
 
This event has been updated
Chan= +ged: time
 
= += +

When

CHANGED
Thursday Feb 12, 2026 =E2=8B=85 7pm =E2=80=93 9pm (Pacific Time - Los A= +ngeles)
Old: Thursday Jan 22, 2026 =E2=8B=85 7pm = +=E2=80=93 9pm (Pacific Time - Los Angeles)
= +

Organizer

tconvertino@gmail.com<= +span itemprop=3D"organizer" itemscope itemtype=3D"http://schema.org/Person"= +>

Invitation from Google Calendar

You are receiving this email because you are s= +ubscribed to calendar notifications. To stop receiving these emails, go to = +Calendar settings, select thi= +s calendar, and change "Other notifications".

Forwarding this invitat= +ion could allow any recipient to send a response to the organizer, be added= + to the guest list, invite others regardless of their own invitation status= +, or modify your RSVP. Learn more

<= +/body> +--0000000000009e8d3e064896eeed--