@rackbops/plugin-tracker 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/plugin.js +52 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -495,9 +495,11 @@ eBay's saved-search alerts reach eBay watches by mail (0.25.0), while `TRACKER_M
|
|
|
495
495
|
is deployed by pasting `mail-worker/worker.js` into the Cloudflare dashboard's Worker editor; it is
|
|
496
496
|
not in the plugin's npm bundle.
|
|
497
497
|
- **What a message does.** Gmail's forwarding confirmation: its code is DMed, to enter in Gmail's
|
|
498
|
-
Forwarding and POP/IMAP settings. Since 0.26.0 it is any sender at Google naming forwarding
|
|
499
|
-
the code
|
|
500
|
-
|
|
498
|
+
Forwarding and POP/IMAP settings. Since 0.26.0 it is any sender at Google naming forwarding. Since
|
|
499
|
+
0.27.0 the code is a run of digits standing alone in the subject or shortly after the word
|
|
500
|
+
"code" in the body, and Gmail's verify link (a `/mail/vf-` address on `mail.google.com`,
|
|
501
|
+
`mail-settings.google.com` or a subdomain of either, never its cancel link) is DMed too; digits inside
|
|
502
|
+
an address are never the code. An eBay alert: the
|
|
501
503
|
matches above "You might also like" (never eBay's suggestions) -- title, the item's bare
|
|
502
504
|
`/itm/<number>` address, the price's lower end -- go into the inbox of each of the person's live
|
|
503
505
|
eBay watches whose words are the saved search's (case and spacing aside), exactly as listings sent
|
|
@@ -507,7 +509,9 @@ eBay's saved-search alerts reach eBay watches by mail (0.25.0), while `TRACKER_M
|
|
|
507
509
|
dropped. A message is never stored, only the listings read from it, and its words are never
|
|
508
510
|
logged: one that does nothing is logged only with why (the owner is not registered, or its
|
|
509
511
|
sender's domain -- a Google no-reply sender whole -- which bodies it had, and whether its
|
|
510
|
-
subject names forwarding
|
|
512
|
+
subject names forwarding; for Google's no-reply senders also its subject's shape, letters `a`,
|
|
513
|
+
digits `9` and each address `<addr>`, and counts from its body as read, plain text else the
|
|
514
|
+
HTML's text).
|
|
511
515
|
- **Only eBay's own domains** (ebay.com, .co.uk, .de, .ca, .com.au and the rest of eBay's sites;
|
|
512
516
|
never a lookalike such as `ebay.com.de`) count as eBay, for the sender and for every item link.
|
|
513
517
|
The sender is its `From` address, not a checked signature: the private address is the guard, and
|
package/dist/plugin.js
CHANGED
|
@@ -13647,25 +13647,59 @@ function fromGoogle(address) {
|
|
|
13647
13647
|
const host = address?.slice(address.lastIndexOf("@") + 1) ?? "";
|
|
13648
13648
|
return host === "google.com" || host.endsWith(".google.com");
|
|
13649
13649
|
}
|
|
13650
|
-
|
|
13650
|
+
var GMAIL_LINK = /https:\/\/(?:[a-z0-9-]{1,63}\.){0,4}(?:mail|mail-settings)\.google\.com\/[A-Za-z0-9\-._~%/?#&=+:@!$,;]{1,1000}/gi;
|
|
13651
|
+
function decodedHtmlLinks(html) {
|
|
13652
|
+
return html.slice(0, MAX_HTML_CHARS).replace(/&(?:amp|#0*38|#x0*26);/gi, "&");
|
|
13653
|
+
}
|
|
13654
|
+
function trimClosingMarks(s) {
|
|
13655
|
+
let end = s.length;
|
|
13656
|
+
while (end > 0 && ".,;:!?".includes(s.charAt(end - 1)))
|
|
13657
|
+
end--;
|
|
13658
|
+
return s.slice(0, end);
|
|
13659
|
+
}
|
|
13660
|
+
function gmailLink(s) {
|
|
13661
|
+
for (const l of s.match(GMAIL_LINK) ?? []) {
|
|
13662
|
+
const link = trimClosingMarks(l);
|
|
13663
|
+
if (/^https:\/\/[^/]+\/mail\/vf-/i.test(link))
|
|
13664
|
+
return link;
|
|
13665
|
+
}
|
|
13666
|
+
return null;
|
|
13667
|
+
}
|
|
13668
|
+
function withoutAddresses(s) {
|
|
13669
|
+
return s.split(/\s+/).filter((w) => !w.includes("@")).join(" ");
|
|
13670
|
+
}
|
|
13671
|
+
function bodyText(mail) {
|
|
13672
|
+
return mail.text || (mail.html !== null ? htmlText(mail.html.slice(0, MAX_HTML_CHARS)) : "");
|
|
13673
|
+
}
|
|
13674
|
+
function forwardingConfirmation(mail) {
|
|
13651
13675
|
if (!fromGoogle(mail.from))
|
|
13652
13676
|
return null;
|
|
13653
|
-
const leading = /^\(#(\d{6,12})\)/.exec(mail.subject)?.[1];
|
|
13654
|
-
|
|
13655
|
-
|
|
13656
|
-
const body = mail.text || (mail.html !== null ? htmlText(mail.html.slice(0, MAX_HTML_CHARS)) : "");
|
|
13657
|
-
if (!/forwarding/i.test(mail.subject) && !/forward/i.test(body))
|
|
13677
|
+
const leading = /^\(#(\d{6,12})\)/.exec(mail.subject)?.[1] ?? null;
|
|
13678
|
+
const body = bodyText(mail);
|
|
13679
|
+
if (!leading && !/forwarding/i.test(mail.subject) && !/forward/i.test(body))
|
|
13658
13680
|
return null;
|
|
13659
|
-
const
|
|
13660
|
-
|
|
13661
|
-
|
|
13662
|
-
return
|
|
13681
|
+
const subject = withoutAddresses(mail.subject.slice(0, 500));
|
|
13682
|
+
const code = leading ?? /(?:^|[\s#(])(\d{6,12})(?=$|[\s).,:;])/.exec(subject)?.[1] ?? /\bcode\b\D{0,40}?(?<![\w.@/=%?&#:-])(\d{6,12})(?!\d)/i.exec(withoutAddresses(body))?.[1] ?? null;
|
|
13683
|
+
const link = gmailLink(mail.text ?? "") ?? (mail.html !== null ? gmailLink(decodedHtmlLinks(mail.html)) : null);
|
|
13684
|
+
return code || link ? { code, link } : null;
|
|
13685
|
+
}
|
|
13686
|
+
function shape(s) {
|
|
13687
|
+
return s.replace(/\p{L}/gu, "a").replace(/\p{N}/gu, "9").replace(/[^\x20-\x7e]/g, "?").slice(0, 160);
|
|
13688
|
+
}
|
|
13689
|
+
function subjectShape(subject) {
|
|
13690
|
+
return subject.slice(0, 500).split(/\s+/).map((w) => w.includes("@") ? "<addr>" : shape(w)).join(" ").slice(0, 160);
|
|
13663
13691
|
}
|
|
13664
13692
|
function ignoredWhy(mail) {
|
|
13665
13693
|
const system = mail.from !== null && fromGoogle(mail.from) && /^[a-z-]*no-?reply$/.test(mail.from.slice(0, mail.from.lastIndexOf("@")));
|
|
13666
13694
|
const domain = mail.from ? system ? mail.from : mail.from.slice(mail.from.lastIndexOf("@") + 1) : "none";
|
|
13667
13695
|
const parts = [mail.html !== null ? "html" : "", mail.text !== null ? "text" : ""].filter(Boolean).join("+") || "no body";
|
|
13668
|
-
|
|
13696
|
+
const base = `from ${domain.replace(/[^a-z0-9.@-]/g, "?").slice(0, 100)}, ${parts}, subject ${/forwarding/i.test(mail.subject) ? "names" : "does not name"} forwarding`;
|
|
13697
|
+
if (!system)
|
|
13698
|
+
return base;
|
|
13699
|
+
const body = bodyText(mail);
|
|
13700
|
+
const runs = (body.match(/\d{6,12}/g) ?? []).length;
|
|
13701
|
+
const links = (body.match(/https:\/\/[a-z0-9.-]{1,253}/gi) ?? []).length;
|
|
13702
|
+
return `${base}, subject shape "${subjectShape(mail.subject)}", body ${body.length} chars, ${runs} digit run(s), ${links} https link(s), says code: ${/code/i.test(body) ? "yes" : "no"}`;
|
|
13669
13703
|
}
|
|
13670
13704
|
function quoted(words) {
|
|
13671
13705
|
return words.replace(SEARCH_MARKS, " ").replace(/\s+/g, " ").trim().slice(0, 100);
|
|
@@ -13694,9 +13728,13 @@ async function deliverMail(d, userId, mail) {
|
|
|
13694
13728
|
const user = await d.store.getUser(userId);
|
|
13695
13729
|
if (!user || !d.admissions.isRegistered(user.id) || !d.inbox)
|
|
13696
13730
|
return { kind: "ignored", why: "the owner is not registered" };
|
|
13697
|
-
const
|
|
13698
|
-
if (
|
|
13699
|
-
|
|
13731
|
+
const confirm = forwardingConfirmation(mail);
|
|
13732
|
+
if (confirm) {
|
|
13733
|
+
const how = [
|
|
13734
|
+
confirm.code ? `The confirmation code is ${confirm.code}: enter it in Gmail's Settings, under Forwarding and POP/IMAP.` : "",
|
|
13735
|
+
confirm.link ? `${confirm.code ? "Or open" : "Open"} Gmail's confirmation link: <${confirm.link}>` : ""
|
|
13736
|
+
].filter(Boolean).join(" ");
|
|
13737
|
+
await tell(d, user.id, `Gmail asks to confirm forwarding to your alert address. ${how} If you did not ask for this, ignore it.`);
|
|
13700
13738
|
return { kind: "confirmation" };
|
|
13701
13739
|
}
|
|
13702
13740
|
const alert = readEbayAlert(mail);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rackbops/plugin-tracker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "The task tracker for Rackbops Clerk: reminders, renewals, price drops, one-off research and an interest scout (through city-hall, with their findings), a want-list watcher (listing pages you paste, BoardGameGeek through its API once its token is set, and eBay and BoardGameGeek listings you send in through the task API or that eBay's saved-search alert emails forwarded to your alert address carry, which the watcher never opens; with the model checking each new listing and its seller first, through city-hall) by slash command, delivered by DM with buttons, and a daily DM at each person's hour of what is due today and overdue, on docket's SQLite store (/allow, /register, /remind, /renewal, /price, /research, /scout, /want, /tasks, /task, /settings, /web), with a web area for your tasks, history and settings, a web editor to make, change, pause and delete your own tasks (research requests too, made but not changed, and scouts, made and changed), an admin view (with the DMs that did not arrive, raising a person's daily model ceiling, and daily model usage per person), an operator switch that turns the daily budgets off for the alpha, forget-me, and a JSON task API with personal API tokens",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/plugin.js",
|