imsg-mcp 1.7.0 → 1.9.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/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and follows [Semantic Versioning](https://semver.org/).
5
5
 
6
+ # [1.9.0](https://github.com/george43g/imsg-mcp/compare/v1.8.0...v1.9.0) (2026-07-21)
7
+
8
+
9
+ ### Features
10
+
11
+ * **db:** extract Apple-native voice-note transcripts, Genmoji & reply kinds ([6b9d715](https://github.com/george43g/imsg-mcp/commit/6b9d715dd6c356a337b8bb0b3ef96ff40419a73a))
12
+
13
+ # [1.8.0](https://github.com/george43g/imsg-mcp/compare/v1.7.0...v1.8.0) (2026-07-20)
14
+
15
+
16
+ ### Features
17
+
18
+ * **tui:** per-thread info / attachment drawer (press `i`) ([cbcf809](https://github.com/george43g/imsg-mcp/commit/cbcf80960207a081f5813a4982f94d927a2d7756))
19
+
6
20
  # [1.7.0](https://github.com/george43g/imsg-mcp/compare/v1.6.2...v1.7.0) (2026-07-20)
7
21
 
8
22
 
package/README.md CHANGED
@@ -108,7 +108,7 @@ Full reference (CLI subcommands + MCP tools + every flag): [**docs/TOOLS.md**](d
108
108
  imsg tui
109
109
  ```
110
110
 
111
- Vim-style: `j/k` move, `gg/G` jump, `Enter` drawer, `o` Quick Look an attachment, `:` jump to date, `V` visual select, `e` export, `S` send via other app, `y` copy slug, `c` compose in current thread, `N` compose to new recipient (phone / email / contact name), `q` quit.
111
+ Vim-style: `j/k` move, `gg/G` jump, `Enter` drawer, `i` per-thread info + attachment browser, `o` Quick Look an attachment, `:` jump to date, `V` visual select, `e` export, `S` send via other app, `y` copy slug, `c` compose in current thread, `N` compose to new recipient (phone / email / contact name), `q` quit.
112
112
 
113
113
  Themes (`safe` / `powerline`) and a single accent color drive the whole palette. See [docs/TOOLS.md#tui-configuration](docs/TOOLS.md#tui-configuration).
114
114
 
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { createInterface } from "node:readline";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { Command } from "commander";
6
6
  import { c as checkLocalAccess, f as formatAccessReport, J as IMPLEMENTED_TYPES, K as ANALYTIC_INFO, w as installShutdownHandlers, x as registerCleanup, L as looksLikeThreadSlug } from "./shutdown-CQ9wzrxA.js";
7
- import { A as APP_VERSION, t as toYaml } from "./meta-Lpz_PHJ5.js";
7
+ import { A as APP_VERSION, t as toYaml } from "./meta-BrYyl7Ls.js";
8
8
  import { spawn } from "node:child_process";
9
9
  import { join, dirname } from "node:path";
10
10
  function distRoot() {
@@ -447,7 +447,7 @@ async function runExportCommand(target, opts) {
447
447
  const { homedir } = await import("node:os");
448
448
  const { dirname: dirname2, join: join2, isAbsolute, resolve } = await import("node:path");
449
449
  const { getContactsDbPaths, getImsgDbPath, getSlugsDbPath } = await import("./shutdown-CQ9wzrxA.js").then((n) => n.a0);
450
- const { IMessageDB } = await import("./imessage-db-Cid38QGT.js").then((n) => n.i);
450
+ const { IMessageDB } = await import("./imessage-db-vrzopRGT.js").then((n) => n.i);
451
451
  const { streamExport } = await import("./exportStream-DgPVRZTZ.js");
452
452
  const { parseUserDate } = await import("./date-parse-DJXMfq3a.js");
453
453
  const format = normalizeFormat(opts.format ?? "md");
@@ -767,6 +767,47 @@ function scoreCandidate(text) {
767
767
  return score;
768
768
  }
769
769
  const STRUCTURED_BOOST = 500;
770
+ const AUDIO_TRANSCRIPTION_MARKER = Buffer.from("IMAudioTranscription", "ascii");
771
+ const AUDIO_VALUE_FRAMING = Buffer.from([134, 146, 132, 150, 150]);
772
+ function extractAudioTranscription(blob) {
773
+ if (!blob) return void 0;
774
+ const markerAt = blob.indexOf(AUDIO_TRANSCRIPTION_MARKER);
775
+ if (markerAt === -1) return void 0;
776
+ let pos = markerAt + AUDIO_TRANSCRIPTION_MARKER.length;
777
+ if (blob.subarray(pos, pos + AUDIO_VALUE_FRAMING.length).equals(AUDIO_VALUE_FRAMING)) {
778
+ pos += AUDIO_VALUE_FRAMING.length;
779
+ } else {
780
+ const scanEnd = Math.min(pos + 8, blob.length);
781
+ let found = -1;
782
+ for (let i = pos; i < scanEnd; i++) {
783
+ const b = blob[i];
784
+ if (b === 129 || b === 130 || b <= 128) {
785
+ found = i;
786
+ break;
787
+ }
788
+ }
789
+ if (found === -1) return void 0;
790
+ pos = found;
791
+ }
792
+ if (pos >= blob.length) return void 0;
793
+ const lenByte = blob[pos++];
794
+ let length;
795
+ if (lenByte === 129) {
796
+ if (pos + 2 > blob.length) return void 0;
797
+ length = blob.readUInt16LE(pos);
798
+ pos += 2;
799
+ } else if (lenByte === 130) {
800
+ if (pos + 4 > blob.length) return void 0;
801
+ length = blob.readUInt32LE(pos);
802
+ pos += 4;
803
+ } else {
804
+ length = lenByte;
805
+ }
806
+ if (length <= 0 || pos + length > blob.length) return void 0;
807
+ const text = blob.toString("utf8", pos, pos + length).trim();
808
+ if (!text || !/[\p{L}\p{N}]/u.test(text)) return void 0;
809
+ return text;
810
+ }
770
811
  function extractAttributedBodyText(blob) {
771
812
  if (!blob) return void 0;
772
813
  const native = getNativeParser();
@@ -819,6 +860,63 @@ function extractAttributedBodyText(blob) {
819
860
  const best = [...candidates.entries()].sort((a, b) => b[1] - a[1])[0];
820
861
  return best?.[0];
821
862
  }
863
+ function mergeDuplicateConversations(prepared) {
864
+ const merged = [];
865
+ const indexByKey = /* @__PURE__ */ new Map();
866
+ for (const entry of prepared) {
867
+ const existingIndex = indexByKey.get(entry.mergeKey);
868
+ if (existingIndex === void 0) {
869
+ indexByKey.set(entry.mergeKey, merged.length);
870
+ merged.push(entry);
871
+ continue;
872
+ }
873
+ merged[existingIndex] = mergeConversationEntries(merged[existingIndex], entry);
874
+ }
875
+ return merged;
876
+ }
877
+ function mergeConversationEntries(left, right) {
878
+ const preferred = pickPreferredConversationEntry(left, right);
879
+ const other = preferred === left ? right : left;
880
+ const sameIdentifier = preferred.conversation.chatIdentifier === other.conversation.chatIdentifier;
881
+ return {
882
+ mergeKey: preferred.mergeKey,
883
+ last: preferred.last ?? other.last,
884
+ conversation: {
885
+ ...preferred.conversation,
886
+ displayName: preferred.conversation.displayName ?? other.conversation.displayName,
887
+ participants: [
888
+ .../* @__PURE__ */ new Set([...preferred.conversation.participants, ...other.conversation.participants])
889
+ ],
890
+ unreadCount: sameIdentifier ? Math.max(preferred.conversation.unreadCount, other.conversation.unreadCount) : preferred.conversation.unreadCount + other.conversation.unreadCount
891
+ }
892
+ };
893
+ }
894
+ function pickPreferredConversationEntry(left, right) {
895
+ const leftTime = left.conversation.lastMessageDate?.getTime() ?? 0;
896
+ const rightTime = right.conversation.lastMessageDate?.getTime() ?? 0;
897
+ if (leftTime !== rightTime) {
898
+ return leftTime > rightTime ? left : right;
899
+ }
900
+ const preferredService = left.last?.lastService ?? right.last?.lastService ?? null;
901
+ if (preferredService) {
902
+ const preferredType = preferredService.toLowerCase().includes("sms") ? "SMS" : "iMessage";
903
+ if (left.conversation.serviceType === preferredType && right.conversation.serviceType !== preferredType) {
904
+ return left;
905
+ }
906
+ if (right.conversation.serviceType === preferredType && left.conversation.serviceType !== preferredType) {
907
+ return right;
908
+ }
909
+ }
910
+ if (left.conversation.displayName && !right.conversation.displayName) return left;
911
+ if (right.conversation.displayName && !left.conversation.displayName) return right;
912
+ if (left.conversation.serviceType === "iMessage" && right.conversation.serviceType === "SMS") {
913
+ return left;
914
+ }
915
+ if (right.conversation.serviceType === "iMessage" && left.conversation.serviceType === "SMS") {
916
+ return right;
917
+ }
918
+ return left;
919
+ }
822
920
  function normalizeSnippetText(text) {
823
921
  if (!text) return null;
824
922
  const attachmentMarker = new RegExp(
@@ -1162,6 +1260,8 @@ class IMessageDB {
1162
1260
  * other clear site. Add one if you introduce a read path that bypasses it.
1163
1261
  */
1164
1262
  cachedReactionsByChat = /* @__PURE__ */ new Map();
1263
+ /** Per-table column-name sets, for optional-column guards (schema drift / synthetic fixtures). */
1264
+ columnCache = /* @__PURE__ */ new Map();
1165
1265
  backgroundSyncNeeded = true;
1166
1266
  backgroundRefreshScheduled = false;
1167
1267
  /** Set on close() so background chunked work stops touching a closed DB. */
@@ -1771,12 +1871,12 @@ class IMessageDB {
1771
1871
  const CHUNK = Math.max(limit, 200);
1772
1872
  const prepared = [];
1773
1873
  let cursor = 0;
1774
- let deduped = this.mergeDuplicateConversations(prepared);
1874
+ let deduped = mergeDuplicateConversations(prepared);
1775
1875
  while (deduped.length < limit && cursor < sortEntries.length) {
1776
1876
  const chunk = sortEntries.slice(cursor, cursor + CHUNK);
1777
1877
  cursor += chunk.length;
1778
1878
  for (const entry of chunk) prepared.push(enrich(entry));
1779
- deduped = this.mergeDuplicateConversations(prepared);
1879
+ deduped = mergeDuplicateConversations(prepared);
1780
1880
  }
1781
1881
  const selected = deduped.slice(0, limit);
1782
1882
  const result = selected.map(({ conversation, last }) => ({
@@ -2091,63 +2191,6 @@ class IMessageDB {
2091
2191
  }
2092
2192
  return null;
2093
2193
  }
2094
- mergeDuplicateConversations(prepared) {
2095
- const merged = [];
2096
- const indexByKey = /* @__PURE__ */ new Map();
2097
- for (const entry of prepared) {
2098
- const existingIndex = indexByKey.get(entry.mergeKey);
2099
- if (existingIndex === void 0) {
2100
- indexByKey.set(entry.mergeKey, merged.length);
2101
- merged.push(entry);
2102
- continue;
2103
- }
2104
- merged[existingIndex] = this.mergeConversationEntries(merged[existingIndex], entry);
2105
- }
2106
- return merged;
2107
- }
2108
- mergeConversationEntries(left, right) {
2109
- const preferred = this.pickPreferredConversationEntry(left, right);
2110
- const other = preferred === left ? right : left;
2111
- const sameIdentifier = preferred.conversation.chatIdentifier === other.conversation.chatIdentifier;
2112
- return {
2113
- mergeKey: preferred.mergeKey,
2114
- last: preferred.last ?? other.last,
2115
- conversation: {
2116
- ...preferred.conversation,
2117
- displayName: preferred.conversation.displayName ?? other.conversation.displayName,
2118
- participants: [
2119
- .../* @__PURE__ */ new Set([...preferred.conversation.participants, ...other.conversation.participants])
2120
- ],
2121
- unreadCount: sameIdentifier ? Math.max(preferred.conversation.unreadCount, other.conversation.unreadCount) : preferred.conversation.unreadCount + other.conversation.unreadCount
2122
- }
2123
- };
2124
- }
2125
- pickPreferredConversationEntry(left, right) {
2126
- const leftTime = left.conversation.lastMessageDate?.getTime() ?? 0;
2127
- const rightTime = right.conversation.lastMessageDate?.getTime() ?? 0;
2128
- if (leftTime !== rightTime) {
2129
- return leftTime > rightTime ? left : right;
2130
- }
2131
- const preferredService = left.last?.lastService ?? right.last?.lastService ?? null;
2132
- if (preferredService) {
2133
- const preferredType = preferredService.toLowerCase().includes("sms") ? "SMS" : "iMessage";
2134
- if (left.conversation.serviceType === preferredType && right.conversation.serviceType !== preferredType) {
2135
- return left;
2136
- }
2137
- if (right.conversation.serviceType === preferredType && left.conversation.serviceType !== preferredType) {
2138
- return right;
2139
- }
2140
- }
2141
- if (left.conversation.displayName && !right.conversation.displayName) return left;
2142
- if (right.conversation.displayName && !left.conversation.displayName) return right;
2143
- if (left.conversation.serviceType === "iMessage" && right.conversation.serviceType === "SMS") {
2144
- return left;
2145
- }
2146
- if (right.conversation.serviceType === "iMessage" && left.conversation.serviceType === "SMS") {
2147
- return right;
2148
- }
2149
- return left;
2150
- }
2151
2194
  getConversationMergeKey(chatIdentifier, chatGuid, isGroup) {
2152
2195
  const cacheKey = `${chatIdentifier}::${chatGuid}::${isGroup}`;
2153
2196
  const cached = this.cachedMergeKeys.get(cacheKey);
@@ -2486,11 +2529,7 @@ class IMessageDB {
2486
2529
  const isReply = Boolean(ext.thread_originator_guid);
2487
2530
  let replyTo;
2488
2531
  if (isReply && ext.thread_originator_guid) {
2489
- const originalText = this.getMessageTextByGuid(ext.thread_originator_guid);
2490
- replyTo = {
2491
- replyToGuid: ext.thread_originator_guid,
2492
- replyToText: originalText
2493
- };
2532
+ replyTo = this.getReplyContextByGuid(ext.thread_originator_guid);
2494
2533
  }
2495
2534
  const richContentType = getRichContentType(ext.balloon_bundle_id || null);
2496
2535
  const hasAttachments = Boolean(ext.cache_has_attachments);
@@ -2520,6 +2559,7 @@ class IMessageDB {
2520
2559
  associatedMessageType: associatedType,
2521
2560
  hasAttachments
2522
2561
  });
2562
+ const appleAudioTranscript = extractAudioTranscription(raw.attributedBody ?? null);
2523
2563
  return {
2524
2564
  id: raw.ROWID,
2525
2565
  guid: raw.guid,
@@ -2547,6 +2587,7 @@ class IMessageDB {
2547
2587
  richContentSummary,
2548
2588
  isEdited: !isUnsent && Boolean(ext.date_edited && ext.date_edited > 0),
2549
2589
  isRetracted: isUnsent || Boolean(ext.date_retracted && ext.date_retracted > 0),
2590
+ appleAudioTranscript,
2550
2591
  hasAttachments,
2551
2592
  attachments
2552
2593
  };
@@ -2674,6 +2715,24 @@ class IMessageDB {
2674
2715
  }
2675
2716
  return result;
2676
2717
  }
2718
+ /**
2719
+ * Whether `table` has `column` in the current chat.db schema (cached per table).
2720
+ * Guards columns that are absent on older macOS DBs or synthetic fixtures
2721
+ * (e.g. `message.is_audio_message`, `attachment.emoji_image_short_description`).
2722
+ */
2723
+ hasColumn(table, column) {
2724
+ let cols = this.columnCache.get(table);
2725
+ if (!cols) {
2726
+ try {
2727
+ const rows = this.raw.prepare(`PRAGMA table_info(${table})`).all();
2728
+ cols = new Set(rows.map((r) => r.name));
2729
+ } catch {
2730
+ cols = /* @__PURE__ */ new Set();
2731
+ }
2732
+ this.columnCache.set(table, cols);
2733
+ }
2734
+ return cols.has(column);
2735
+ }
2677
2736
  /** Max ROWID currently in the message table — used as a cache key. */
2678
2737
  getMaxMessageRowId() {
2679
2738
  const row = this.raw.prepare(`SELECT COALESCE(MAX(ROWID), 0) AS m FROM ${Tables.MESSAGE}`).get();
@@ -2804,6 +2863,51 @@ class IMessageDB {
2804
2863
  span.end({ count: out.length });
2805
2864
  return out;
2806
2865
  }
2866
+ /**
2867
+ * All attachments across EVERY merged leg of one conversation (newest first)
2868
+ * for the TUI per-thread info drawer. Unlike searchAttachments (single
2869
+ * chat_identifier), this resolves the conversation to all its chat ROWIDs
2870
+ * (resolveChatsForConversation) so the SMS and iMessage legs of a merged
2871
+ * identity are both covered. Excludes stickers and Apple plugin payloads.
2872
+ */
2873
+ listConversationAttachments(chatIdentifier, limit = 500) {
2874
+ const span = perf("listConversationAttachments");
2875
+ const chats = this.resolveChatsForConversation(chatIdentifier);
2876
+ if (chats.length === 0) {
2877
+ span.end({ returned: 0 });
2878
+ return [];
2879
+ }
2880
+ const placeholders = chats.map(() => "?").join(",");
2881
+ const lim = limit > 0 ? limit : 500;
2882
+ const sql = `
2883
+ SELECT DISTINCT
2884
+ a.ROWID as rowId,
2885
+ a.filename,
2886
+ a.mime_type,
2887
+ a.transfer_name,
2888
+ a.total_bytes,
2889
+ a.created_date
2890
+ FROM ${Tables.ATTACHMENT} a
2891
+ JOIN ${Tables.MESSAGE_ATTACHMENT_JOIN} maj ON a.ROWID = maj.attachment_id
2892
+ JOIN ${Tables.CHAT_MESSAGE_JOIN} cmj ON maj.message_id = cmj.message_id
2893
+ WHERE cmj.chat_id IN (${placeholders})
2894
+ AND a.is_sticker = 0
2895
+ AND (a.uti IS NULL OR a.uti NOT LIKE 'com.apple.messages.plugin%')
2896
+ ORDER BY a.created_date DESC
2897
+ LIMIT ?
2898
+ `;
2899
+ const rows = this.raw.prepare(sql).all(...chats.map((c) => c.ROWID), lim);
2900
+ const out = rows.map((r) => ({
2901
+ rowId: Number(r.rowId),
2902
+ filename: r.filename || "",
2903
+ mimeType: r.mime_type ?? null,
2904
+ transferName: r.transfer_name ?? null,
2905
+ totalBytes: Number(r.total_bytes) || 0,
2906
+ createdDate: macAutoTimestampToDate(Number(r.created_date)) ?? /* @__PURE__ */ new Date(0)
2907
+ }));
2908
+ span.end({ returned: out.length });
2909
+ return out;
2910
+ }
2807
2911
  /** Fetch a single attachment record by ROWID. */
2808
2912
  getAttachmentByRowId(rowId) {
2809
2913
  const row = this.raw.prepare(
@@ -2824,13 +2928,14 @@ class IMessageDB {
2824
2928
  * Fetch attachments for a message
2825
2929
  */
2826
2930
  fetchAttachments(messageRowId) {
2931
+ const emojiCol = this.hasColumn("attachment", "emoji_image_short_description") ? ", a.emoji_image_short_description as emoji_desc" : "";
2827
2932
  const stmt = this.raw.prepare(`
2828
- SELECT
2933
+ SELECT
2829
2934
  a.ROWID as row_id,
2830
2935
  a.filename,
2831
2936
  a.mime_type,
2832
2937
  a.transfer_name,
2833
- a.total_bytes
2938
+ a.total_bytes${emojiCol}
2834
2939
  FROM ${Tables.ATTACHMENT} a
2835
2940
  JOIN ${Tables.MESSAGE_ATTACHMENT_JOIN} maj ON a.ROWID = maj.attachment_id
2836
2941
  WHERE maj.message_id = ?
@@ -2841,7 +2946,8 @@ class IMessageDB {
2841
2946
  filename: r.filename || "",
2842
2947
  mimeType: r.mime_type,
2843
2948
  transferName: r.transfer_name,
2844
- totalBytes: r.total_bytes || 0
2949
+ totalBytes: r.total_bytes || 0,
2950
+ emojiDescription: r.emoji_desc ?? null
2845
2951
  }));
2846
2952
  }
2847
2953
  /**
@@ -2953,9 +3059,53 @@ class IMessageDB {
2953
3059
  const row = this.raw.prepare(`SELECT ROWID, text, attributedBody FROM ${Tables.MESSAGE} WHERE ROWID = ? LIMIT 1`).get(rowId);
2954
3060
  return this.extractMessageText(row);
2955
3061
  }
2956
- getMessageTextByGuid(guid) {
2957
- const row = this.raw.prepare(`SELECT ROWID, text, attributedBody FROM ${Tables.MESSAGE} WHERE guid = ? LIMIT 1`).get(guid);
2958
- return this.extractMessageText(row);
3062
+ /**
3063
+ * Build reply context for the message being replied to, by GUID. Beyond the
3064
+ * plain text, this resolves what KIND of message it was when it carries no text
3065
+ * of its own — a voice note (surfacing Apple's transcript), image, video, or
3066
+ * file — so the UI renders "↩ voice note: '…'" instead of a bare "(unknown)".
3067
+ */
3068
+ getReplyContextByGuid(guid) {
3069
+ const audioCol = this.hasColumn("message", "is_audio_message") ? "is_audio_message" : "0 AS is_audio_message";
3070
+ const row = this.raw.prepare(
3071
+ `SELECT ROWID, text, attributedBody, ${audioCol}, cache_has_attachments
3072
+ FROM ${Tables.MESSAGE} WHERE guid = ? LIMIT 1`
3073
+ ).get(guid);
3074
+ const ctx = { replyToGuid: guid };
3075
+ if (!row) return ctx;
3076
+ if (row.is_audio_message) {
3077
+ ctx.replyToKind = "voice-note";
3078
+ ctx.replyToText = extractAudioTranscription(row.attributedBody) ?? null;
3079
+ return ctx;
3080
+ }
3081
+ const text = this.extractMessageText(row);
3082
+ if (text) {
3083
+ ctx.replyToText = text;
3084
+ return ctx;
3085
+ }
3086
+ if (row.cache_has_attachments) {
3087
+ ctx.replyToKind = this.deriveAttachmentReplyKind(row.ROWID);
3088
+ ctx.replyToText = null;
3089
+ return ctx;
3090
+ }
3091
+ ctx.replyToText = null;
3092
+ return ctx;
3093
+ }
3094
+ /** Map a message's first (non-sticker) attachment mime/uti to a reply-kind label. */
3095
+ deriveAttachmentReplyKind(messageRowId) {
3096
+ const utiCol = this.hasColumn("attachment", "uti") ? "a.uti" : "NULL AS uti";
3097
+ const row = this.raw.prepare(
3098
+ `SELECT a.mime_type, ${utiCol}
3099
+ FROM ${Tables.ATTACHMENT} a
3100
+ JOIN ${Tables.MESSAGE_ATTACHMENT_JOIN} maj ON a.ROWID = maj.attachment_id
3101
+ WHERE maj.message_id = ? AND a.is_sticker = 0
3102
+ ORDER BY a.ROWID LIMIT 1`
3103
+ ).get(messageRowId);
3104
+ const mime = (row?.mime_type ?? "").toLowerCase();
3105
+ const uti = (row?.uti ?? "").toLowerCase();
3106
+ if (mime.startsWith("image/") || uti.includes("image")) return "image";
3107
+ if (mime.startsWith("video/") || uti.includes("movie") || uti.includes("video")) return "video";
3108
+ return "file";
2959
3109
  }
2960
3110
  extractMessageText(row) {
2961
3111
  if (!row) return null;
@@ -2974,4 +3124,4 @@ export {
2974
3124
  normalizedPhoneVariants as n,
2975
3125
  rankFuzzy as r
2976
3126
  };
2977
- //# sourceMappingURL=imessage-db-Cid38QGT.js.map
3127
+ //# sourceMappingURL=imessage-db-vrzopRGT.js.map