@rackbops/plugin-tracker 0.25.0 → 0.26.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 +26 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -494,16 +494,20 @@ eBay's saved-search alerts reach eBay watches by mail (0.25.0), while `TRACKER_M
|
|
|
494
494
|
an outage, bounces to the mailbox that forwarded it; that day's alert is not retried. The Worker
|
|
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
|
-
- **What a message does.** Gmail's forwarding confirmation
|
|
498
|
-
|
|
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, with
|
|
499
|
+
the code as `#<digits>` in the subject or after "confirmation code" in the body, or a subject in
|
|
500
|
+
any language that starts `(#<digits>)`. An eBay alert: the
|
|
499
501
|
matches above "You might also like" (never eBay's suggestions) -- title, the item's bare
|
|
500
502
|
`/itm/<number>` address, the price's lower end -- go into the inbox of each of the person's live
|
|
501
503
|
eBay watches whose words are the saved search's (case and spacing aside), exactly as listings sent
|
|
502
504
|
through the task API; the watch's next look DMs the new ones. Words match with case, spacing and
|
|
503
505
|
the marks `` ` * _ ~ | < > @ # [ ] ( ) \ : / `` ignored. An alert no watch searches for gets a
|
|
504
506
|
DM saying so, with the `/want` that would watch it, each time eBay sends it. Anything else is
|
|
505
|
-
dropped. A message is never stored, only the listings read from it, and
|
|
506
|
-
logged
|
|
507
|
+
dropped. A message is never stored, only the listings read from it, and its words are never
|
|
508
|
+
logged: one that does nothing is logged only with why (the owner is not registered, or its
|
|
509
|
+
sender's domain -- a Google no-reply sender whole -- which bodies it had, and whether its
|
|
510
|
+
subject names forwarding).
|
|
507
511
|
- **Only eBay's own domains** (ebay.com, .co.uk, .de, .ca, .com.au and the rest of eBay's sites;
|
|
508
512
|
never a lookalike such as `ebay.com.de`) count as eBay, for the sender and for every item link.
|
|
509
513
|
The sender is its `From` address, not a checked signature: the private address is the guard, and
|
package/dist/plugin.js
CHANGED
|
@@ -13598,7 +13598,6 @@ var MAIL_DOMAIN_KEY = "TRACKER_MAIL_DOMAIN";
|
|
|
13598
13598
|
var MAIL_KEY_KEY = "TRACKER_MAIL_KEY";
|
|
13599
13599
|
var MAIL_DOMAIN_FORMAT = "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$";
|
|
13600
13600
|
var MIN_MAIL_KEY = 32;
|
|
13601
|
-
var GMAIL_FORWARDING = "forwarding-noreply@google.com";
|
|
13602
13601
|
var LOCAL = /^m-[0-9a-f]{24}$/;
|
|
13603
13602
|
function parseMailConfig(env) {
|
|
13604
13603
|
const domain = (env[MAIL_DOMAIN_KEY] ?? "").trim();
|
|
@@ -13644,13 +13643,29 @@ class MailAddresses {
|
|
|
13644
13643
|
return row?.user_id ?? null;
|
|
13645
13644
|
}
|
|
13646
13645
|
}
|
|
13646
|
+
function fromGoogle(address) {
|
|
13647
|
+
const host = address?.slice(address.lastIndexOf("@") + 1) ?? "";
|
|
13648
|
+
return host === "google.com" || host.endsWith(".google.com");
|
|
13649
|
+
}
|
|
13647
13650
|
function forwardingCode(mail) {
|
|
13648
|
-
if (mail.from
|
|
13651
|
+
if (!fromGoogle(mail.from))
|
|
13652
|
+
return null;
|
|
13653
|
+
const leading = /^\(#(\d{6,12})\)/.exec(mail.subject)?.[1];
|
|
13654
|
+
if (leading)
|
|
13655
|
+
return leading;
|
|
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))
|
|
13649
13658
|
return null;
|
|
13650
|
-
const inSubject =
|
|
13659
|
+
const inSubject = /#\s?(\d{6,12})\b/.exec(mail.subject)?.[1];
|
|
13651
13660
|
if (inSubject)
|
|
13652
13661
|
return inSubject;
|
|
13653
|
-
return /confirmation code
|
|
13662
|
+
return /confirmation code\W{0,20}(\d{6,12})\b/i.exec(body)?.[1] ?? null;
|
|
13663
|
+
}
|
|
13664
|
+
function ignoredWhy(mail) {
|
|
13665
|
+
const system = mail.from !== null && fromGoogle(mail.from) && /^[a-z-]*no-?reply$/.test(mail.from.slice(0, mail.from.lastIndexOf("@")));
|
|
13666
|
+
const domain = mail.from ? system ? mail.from : mail.from.slice(mail.from.lastIndexOf("@") + 1) : "none";
|
|
13667
|
+
const parts = [mail.html !== null ? "html" : "", mail.text !== null ? "text" : ""].filter(Boolean).join("+") || "no body";
|
|
13668
|
+
return `from ${domain.replace(/[^a-z0-9.@-]/g, "?").slice(0, 100)}, ${parts}, subject ${/forwarding/i.test(mail.subject) ? "names" : "does not name"} forwarding`;
|
|
13654
13669
|
}
|
|
13655
13670
|
function quoted(words) {
|
|
13656
13671
|
return words.replace(SEARCH_MARKS, " ").replace(/\s+/g, " ").trim().slice(0, 100);
|
|
@@ -13678,7 +13693,7 @@ async function tell(d, userId, text) {
|
|
|
13678
13693
|
async function deliverMail(d, userId, mail) {
|
|
13679
13694
|
const user = await d.store.getUser(userId);
|
|
13680
13695
|
if (!user || !d.admissions.isRegistered(user.id) || !d.inbox)
|
|
13681
|
-
return { kind: "ignored" };
|
|
13696
|
+
return { kind: "ignored", why: "the owner is not registered" };
|
|
13682
13697
|
const code = forwardingCode(mail);
|
|
13683
13698
|
if (code) {
|
|
13684
13699
|
await tell(d, user.id, `Gmail asks to confirm forwarding to your alert address. The confirmation code is ${code}: enter it in Gmail's Settings, under Forwarding and POP/IMAP. If you did not ask for this, ignore it.`);
|
|
@@ -13686,7 +13701,7 @@ async function deliverMail(d, userId, mail) {
|
|
|
13686
13701
|
}
|
|
13687
13702
|
const alert = readEbayAlert(mail);
|
|
13688
13703
|
if (!alert)
|
|
13689
|
-
return { kind: "ignored" };
|
|
13704
|
+
return { kind: "ignored", why: ignoredWhy(mail) };
|
|
13690
13705
|
const listings = [];
|
|
13691
13706
|
for (const raw of alert.listings) {
|
|
13692
13707
|
const l = acceptListing(raw);
|
|
@@ -13758,7 +13773,7 @@ function params(value) {
|
|
|
13758
13773
|
return { value: first.trim().toLowerCase(), params: out };
|
|
13759
13774
|
}
|
|
13760
13775
|
function quotedPrintable(body) {
|
|
13761
|
-
const s = body.replace(
|
|
13776
|
+
const s = body.replace(/=[ \t]*\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_, h) => String.fromCharCode(Number.parseInt(h, 16)));
|
|
13762
13777
|
return bytesOf(s);
|
|
13763
13778
|
}
|
|
13764
13779
|
function base64(body) {
|
|
@@ -13884,8 +13899,12 @@ async function handleMailInbound(m, request) {
|
|
|
13884
13899
|
const outcome = await deliverMail(d, userId, parseMail(raw));
|
|
13885
13900
|
if (outcome.kind === "stored")
|
|
13886
13901
|
d.log.info(`mail: an eBay alert gave ${outcome.listings} listing(s) to ${outcome.watches} watch(es)`);
|
|
13902
|
+
else if (outcome.kind === "ignored")
|
|
13903
|
+
d.log.info(`mail: one message ignored (${outcome.why})`);
|
|
13887
13904
|
else
|
|
13888
13905
|
d.log.info(`mail: one message ${outcome.kind}`);
|
|
13906
|
+
if (outcome.kind === "ignored")
|
|
13907
|
+
return answer(200, { result: "ignored" });
|
|
13889
13908
|
const { kind, ...rest } = outcome;
|
|
13890
13909
|
return answer(200, { result: kind, ...rest });
|
|
13891
13910
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rackbops/plugin-tracker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.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",
|