feat: add gmail-hook monorepo with watcher, api, and web
Ship the receipt pipeline (Livin/Grab/BRI), PocketBase spendings, PM2 ecosystem, and a proper .gitignore that excludes secrets, SQLite data, and media dumps. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
function decodeBase64Url(data) {
|
||||
if (!data) return "";
|
||||
const pad = data.length % 4 === 0 ? "" : "=".repeat(4 - (data.length % 4));
|
||||
return Buffer.from(
|
||||
data.replace(/-/g, "+").replace(/_/g, "/") + pad,
|
||||
"base64",
|
||||
).toString("utf8");
|
||||
}
|
||||
|
||||
function stripHtml(html) {
|
||||
return html
|
||||
.replace(/<style[\s\S]*?<\/style>/gi, "")
|
||||
.replace(/<script[\s\S]*?<\/script>/gi, "")
|
||||
.replace(/<br\s*\/?>/gi, "\n")
|
||||
.replace(/<\/p>/gi, "\n\n")
|
||||
.replace(/<\/div>/gi, "\n")
|
||||
.replace(/<\/tr>/gi, "\n")
|
||||
.replace(/<[^>]+>/g, "")
|
||||
.replace(/ /g, " ")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/'/gi, "'")
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function walkParts(payload, acc = { text: "", html: "" }) {
|
||||
if (!payload) return acc;
|
||||
const mime = payload.mimeType || "";
|
||||
const data = payload.body?.data;
|
||||
if (data) {
|
||||
if (mime === "text/plain") acc.text += decodeBase64Url(data);
|
||||
else if (mime === "text/html") acc.html += decodeBase64Url(data);
|
||||
}
|
||||
for (const part of payload.parts || []) {
|
||||
walkParts(part, acc);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
export function extractBody(payload) {
|
||||
const { text, html } = walkParts(payload);
|
||||
if (text.trim()) return text.trim();
|
||||
if (html.trim()) return stripHtml(html);
|
||||
return "";
|
||||
}
|
||||
|
||||
export function header(payload, name) {
|
||||
const match = payload?.headers?.find(
|
||||
(h) => h.name?.toLowerCase() === name.toLowerCase(),
|
||||
);
|
||||
return match?.value || "";
|
||||
}
|
||||
|
||||
export function summarizeMessage(message) {
|
||||
const payload = message.payload || {};
|
||||
return {
|
||||
id: message.id,
|
||||
threadId: message.threadId,
|
||||
from: header(payload, "From"),
|
||||
to: header(payload, "To"),
|
||||
subject: header(payload, "Subject") || "(no subject)",
|
||||
date: header(payload, "Date"),
|
||||
snippet: message.snippet || "",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user