aboutsummaryrefslogtreecommitdiffstats
path: root/src/notify-text.js
blob: a88ab14d973cf3d2d267329b0ed9c7bb6db63a25 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// The words of every notification the worker shows while the app is closed.
//
// Chrome on Android (and, quietly, on desktop) scores each web notification's
// title and body with an on-device "unwanted notification" model before it
// shows it, and above 90 of 100 the notification is replaced by a "Possible
// spam" warning — hidden until the user taps "Show notification", and nine
// such in a week revoke the site's permission outright on a phone that
// doesn't visit much (Chromium kAutoRevokeSuspiciousNotification). Our old
// wording scored 92–99 across the board: "Payment received", "Incoming
// payment", "Payment request", "alice is calling", "New message", "alice
// sent you a message" — the exact shape of the scam notifications the model
// was trained (on synthetic data) to catch. What it lets through is less a
// rule than a measurement; the scores here come from Chrome 156's model via
// tools/notif-spam-score.js, which re-scores SAMPLES in a throwaway Chrome.
// Measured 2026-11-03: "sats" reads as money without reading as a payment
// scam (9–14), "payment" does not (95–97); a person's NAME in the body of a
// message or reaction line pushes it over (80–99), "New chat" / "<name>"
// does not (8–32); "reply" in any title is 96–99, "Someone replied" /
// "Open coinos to see it." is 13; a reply's TEXT is uncontrollable (an
// "@name" or a link in it scores 97+), so none rides along; "Calling" /
// "<name>" is 11–29 where every "<name> is calling" shape is 80–99.
// Re-run the scorer before changing any of these.

export const OPEN = 'Open coinos to see it.';

function sats(n) {
  const v = Number(n);
  return v > 0 ? `+${v.toLocaleString()} sats. ${OPEN}` : OPEN;
}

// A user-facing notification by reason. `d` is the push payload (amountSat,
// emoji) plus what the device adds (a sender's name).
export function notifyText(reason, d = {}) {
  switch (reason) {
    case 'payment': return ['Sats received', sats(d.amountSat)];
    case 'zap': return ['Zap received', sats(d.amountSat)];
    case 'dm': return ['New chat', d.name || 'Someone wrote to you'];
    case 'chat': return ['New chat activity', 'There are new messages in your communities.'];
    case 'mention': return [d.reply ? 'Someone replied' : 'You were mentioned', OPEN];
    case 'reaction': {
      // a unicode emoji in the title is fine (17); a :shortcode: is not (85)
      const e = d.emoji && !/^:[\w-]+:$/.test(d.emoji) ? ' ' + d.emoji : '';
      return ['New reaction' + e, `to your post. ${OPEN}`];
    }
    case 'call': return ['Calling', d.name || 'Someone'];
    // the wallet was asked for an invoice while closed (someone paying its name)
    case 'incoming': return ['Someone is sending sats', 'Open coinos to receive them.'];
    // a connected app asked the closed wallet to pay
    case 'spend': return ['Spend request', 'A connected app asked to spend sats. Open coinos to approve.'];
    default: return notifyText('chat');
  }
}

// What tools/notif-spam-score.js scores: one line per shape a user can see.
export const SAMPLES = [
  notifyText('payment', { amountSat: 1000 }),
  notifyText('payment', { amountSat: 21 }),
  notifyText('payment'),
  notifyText('zap', { amountSat: 1000 }),
  notifyText('zap'),
  notifyText('dm', { name: 'alice' }),
  notifyText('dm', { name: 'Bitcoin Bob' }),
  notifyText('dm', { name: 'Jarryd' }),
  notifyText('dm'),
  notifyText('chat'),
  notifyText('mention'),
  notifyText('mention', { reply: true }),
  notifyText('reaction', { emoji: '❤️' }),
  notifyText('reaction', { emoji: '🤙' }),
  notifyText('reaction', { emoji: ':pepe:' }),
  notifyText('call', { name: 'alice' }),
  notifyText('call', { name: 'Bitcoin Bob' }),
  notifyText('call', { name: 'Alice Caller' }),
  notifyText('call'),
  notifyText('incoming'),
  notifyText('spend'),
  ['Payment sent', '-1,000 sats via Damus'],
  ['Spending balance', 'Receive or spend sats before 11/20/2026 to keep it alive.'],
];