@bobfrankston/rmfmail 1.2.305 → 1.2.307

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.
@@ -3445,6 +3445,58 @@ export class MailxDB {
3445
3445
  this.notifyContactsChanged();
3446
3446
  }
3447
3447
 
3448
+ /** Apply one email entry of a Google People contact.
3449
+ * (Claude Code 2026-09-06, at Bob's direction)
3450
+ *
3451
+ * The `google` row for this address is the one Google owns: its name and
3452
+ * card follow Google on every sync, so a contact renamed in Google
3453
+ * Contacts is renamed here. Discovered rows for the same address are left
3454
+ * alone — they hold the name the correspondent's own mail carried, and
3455
+ * searchContacts folds the two into one line with the google row on top.
3456
+ *
3457
+ * Replaces the pre-2026-09-06 sequence recordSentAddress + a bare
3458
+ * `UPDATE ... WHERE email = ?` in the People sync, which had two defects,
3459
+ * both visible on one contact in Bob's DB: (1) it never wrote `name`, so
3460
+ * a google row kept whatever name it was first created with — Tyler's
3461
+ * Gmail address stayed "T Nichols" after the contact became "tyler K
3462
+ * Nichols" in Google, and typing "Tyler Nichols" in compose found only
3463
+ * his protonmail row; (2) the WHERE-by-email update also tried to turn
3464
+ * the discovered duplicate into a second google row with the same
3465
+ * (source, email, name) and hit the UNIQUE constraint — 4,505
3466
+ * "[contacts] field update failed" lines in one day's log, and none of
3467
+ * those contacts ever received a card update either.
3468
+ *
3469
+ * Returns true when the google row was created. */
3470
+ upsertGoogleContact(googleId: string, name: string, email: string, card: Partial<ContactFields>): boolean {
3471
+ const lower = (email || "").toLowerCase();
3472
+ if (!lower || !/^[^\s<>@]+@[^\s<>@]+\.[^\s<>@]+$/.test(lower)) return false;
3473
+ name = cleanContactName(name);
3474
+ const now = Date.now();
3475
+ const rows = this.db.prepare(
3476
+ "SELECT id FROM contacts WHERE source = 'google' AND lower(email) = ? ORDER BY use_count DESC, id ASC"
3477
+ ).all(lower) as { id: number }[];
3478
+ // Older syncs could leave two google rows for one address (the
3479
+ // WHERE-by-email update converted discovered rows). Keep the
3480
+ // most-used one; the rest would collide with the rename below.
3481
+ for (const extra of rows.slice(1)) this.db.prepare("DELETE FROM contacts WHERE id = ?").run(extra.id);
3482
+ const cardVals = CONTACT_FIELD_COLUMNS.map(c => card?.[c] ?? "");
3483
+ if (rows.length) {
3484
+ this.db.prepare(
3485
+ `UPDATE contacts SET name = CASE WHEN ? != '' THEN ? ELSE name END, google_id = ?,
3486
+ ${CONTACT_FIELD_COLUMNS.map(c => `${c} = ?`).join(", ")}, updated_at = ?
3487
+ WHERE id = ?`
3488
+ ).run(name, name, googleId, ...cardVals, now, rows[0].id);
3489
+ this.notifyContactsChanged();
3490
+ return false;
3491
+ }
3492
+ this.db.prepare(
3493
+ `INSERT INTO contacts (source, google_id, name, email, ${CONTACT_FIELD_COLUMNS.join(", ")}, last_used, use_count, updated_at)
3494
+ VALUES ('google', ?, ?, ?, ${CONTACT_FIELD_COLUMNS.map(() => "?").join(", ")}, 0, 0, ?)`
3495
+ ).run(googleId, name, lower, ...cardVals, now);
3496
+ this.notifyContactsChanged();
3497
+ return true;
3498
+ }
3499
+
3448
3500
  /** True if `email` (lowercased) appears in the active denylist. Cached
3449
3501
  * in-memory; refreshed on contacts.jsonc reload via setContactsDenylist. */
3450
3502
  private _denylist: Set<string> = new Set();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-store",
3
- "version": "0.1.112",
3
+ "version": "0.1.114",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -1,116 +0,0 @@
1
- {
2
- "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.12",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "../../../../projects/oauth/oauthsupport": {
8
- "name": "@bobfrankston/oauthsupport",
9
- "version": "1.0.25",
10
- "license": "MIT",
11
- "devDependencies": {
12
- "@types/node": "^25.2.1"
13
- },
14
- "engines": {
15
- "node": ">=18.0.0"
16
- }
17
- },
18
- "../../../MailApps/iflow-direct": {
19
- "name": "@bobfrankston/iflow-direct",
20
- "version": "0.1.27",
21
- "license": "ISC",
22
- "dependencies": {
23
- "@bobfrankston/tcp-transport": "file:../tcp-transport"
24
- },
25
- "devDependencies": {
26
- "@types/node": "^25.6.0"
27
- }
28
- },
29
- "../../../MailApps/mailx-sync": {
30
- "name": "@bobfrankston/mailx-sync",
31
- "version": "0.1.10",
32
- "license": "ISC",
33
- "dependencies": {
34
- "@bobfrankston/iflow-direct": "file:../iflow-direct",
35
- "@bobfrankston/tcp-transport": "file:../tcp-transport"
36
- },
37
- "devDependencies": {
38
- "@types/node": "^25.6.0"
39
- }
40
- },
41
- "../../../MailApps/smtp-direct": {
42
- "name": "@bobfrankston/smtp-direct",
43
- "version": "0.1.5",
44
- "license": "ISC",
45
- "dependencies": {
46
- "@bobfrankston/tcp-transport": "file:../tcp-transport"
47
- },
48
- "devDependencies": {
49
- "@types/node": "^25.6.0"
50
- }
51
- },
52
- "../../../MailApps/tcp-transport": {
53
- "name": "@bobfrankston/tcp-transport",
54
- "version": "0.1.5",
55
- "license": "ISC",
56
- "devDependencies": {
57
- "@types/node": "^25.6.0"
58
- }
59
- },
60
- "../../app/packages/mailx-settings": {
61
- "name": "@bobfrankston/mailx-settings",
62
- "version": "0.1.6",
63
- "license": "ISC",
64
- "dependencies": {
65
- "@bobfrankston/mailx-types": "file:../mailx-types",
66
- "jsonc-parser": "^3.3.1"
67
- }
68
- },
69
- "../../app/packages/mailx-store": {
70
- "name": "@bobfrankston/mailx-store",
71
- "version": "0.1.5",
72
- "license": "ISC",
73
- "dependencies": {
74
- "@bobfrankston/mailx-settings": "file:../mailx-settings",
75
- "@bobfrankston/mailx-types": "file:../mailx-types"
76
- }
77
- },
78
- "../../app/packages/mailx-types": {
79
- "name": "@bobfrankston/mailx-types",
80
- "version": "0.1.5",
81
- "license": "ISC"
82
- },
83
- "node_modules/@bobfrankston/iflow-direct": {
84
- "resolved": "../../../MailApps/iflow-direct",
85
- "link": true
86
- },
87
- "node_modules/@bobfrankston/mailx-settings": {
88
- "resolved": "../../app/packages/mailx-settings",
89
- "link": true
90
- },
91
- "node_modules/@bobfrankston/mailx-store": {
92
- "resolved": "../../app/packages/mailx-store",
93
- "link": true
94
- },
95
- "node_modules/@bobfrankston/mailx-sync": {
96
- "resolved": "../../../MailApps/mailx-sync",
97
- "link": true
98
- },
99
- "node_modules/@bobfrankston/mailx-types": {
100
- "resolved": "../../app/packages/mailx-types",
101
- "link": true
102
- },
103
- "node_modules/@bobfrankston/oauthsupport": {
104
- "resolved": "../../../../projects/oauth/oauthsupport",
105
- "link": true
106
- },
107
- "node_modules/@bobfrankston/smtp-direct": {
108
- "resolved": "../../../MailApps/smtp-direct",
109
- "link": true
110
- },
111
- "node_modules/@bobfrankston/tcp-transport": {
112
- "resolved": "../../../MailApps/tcp-transport",
113
- "link": true
114
- }
115
- }
116
- }