aboutsummaryrefslogtreecommitdiffstats
path: root/nwcpush/server.js
diff options
context:
space:
mode:
Diffstat (limited to 'nwcpush/server.js')
-rw-r--r--nwcpush/server.js21
1 files changed, 18 insertions, 3 deletions
diff --git a/nwcpush/server.js b/nwcpush/server.js
index 85fc7b5..d4eb6a5 100644
--- a/nwcpush/server.js
+++ b/nwcpush/server.js
@@ -138,7 +138,8 @@ function resubscribeNotify() {
if (!ptags.length && !authors.length) return;
const filters = [];
// DMs / direct invites / zap receipts land p-tagged at the user
- if (ptags.length) filters.push({ kinds: [1059, 9735, 9737], '#p': ptags, since: Math.floor(Date.now() / 1000) });
+ // ...as do replies and mentions (kind 1 naming the user)
+ if (ptags.length) filters.push({ kinds: [1059, 9735, 9737, 1], '#p': ptags, since: Math.floor(Date.now() / 1000) });
// community chat: wraps authored by a channel's derived stream key
if (authors.length) filters.push({ kinds: [1059], authors, since: Math.floor(Date.now() / 1000) });
// one sub per filter — the pool API takes a single filter object
@@ -151,7 +152,7 @@ function resubscribeNotify() {
// DMs are throttled barely at all now: the device drops the ones it decides
// are noise (a stranger, or our own sent-copy), and a server-side cooldown
// would let a discarded push swallow the friend's message that followed it.
-const COOLDOWN = { payment: 10_000, dm: 1_000, chat: 90_000 };
+const COOLDOWN = { payment: 10_000, dm: 1_000, chat: 90_000, mention: 5_000 };
const lastPush = new Map(); // endpointId:reason -> ts
const seenNotifyEvents = new Map(); // event id -> ts
function onNotifyEvent(ev) {
@@ -162,6 +163,20 @@ function onNotifyEvent(ev) {
for (const [k, t] of seenNotifyEvents) if (t < cut) seenNotifyEvents.delete(k);
}
const p = ev.tags?.find((t) => t[0] === 'p')?.[1];
+ // A public reply or mention: the note is readable here, so the device
+ // gets what it needs to say who and what — an excerpt, and whether it
+ // answers a post (e tag) or just names the person.
+ if (ev.kind === 1) {
+ const ps = (ev.tags || []).filter((t) => t[0] === 'p' && t[1]).map((t) => t[1]);
+ const reply = (ev.tags || []).some((t) => t[0] === 'e' && t[1]);
+ const extra = { from: ev.pubkey, reply, text: String(ev.content || '').replace(/\s+/g, ' ').slice(0, 140) };
+ for (const [id, r] of Object.entries(notifyRegs)) {
+ const mine = r.ptags || [];
+ if (mine.includes(ev.pubkey)) continue; // their own post, not news
+ if (ps.some((x) => mine.includes(x))) pushNotify(id, r, 'mention', extra).catch(() => {});
+ }
+ return;
+ }
const reason = ev.kind === 9735 || ev.kind === 9737 ? 'payment'
: p && notifyPtags().includes(p) ? 'dm' : 'chat';
// A wrapped DM hides its sender from us by design, so we can't decide here
@@ -423,7 +438,7 @@ const server = Bun.serve({
const id = Bun.hash(sub_.endpoint).toString(36);
// per-category opt-outs; absent means everything on (older clients)
const reasons = {};
- for (const k of ['payment', 'dm', 'chat']) {
+ for (const k of ['payment', 'dm', 'chat', 'mention']) {
if (typeof body.notify.reasons?.[k] === 'boolean') reasons[k] = body.notify.reasons[k];
}
notifyRegs[id] = {