@oxidezap/baileyrs 0.0.18 → 0.0.21
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/lib/Bridge/__tests__/adapt.test.js +41 -9
- package/lib/Bridge/__tests__/adapt.test.js.map +1 -1
- package/lib/Bridge/adapt.d.ts +10 -16
- package/lib/Bridge/adapt.d.ts.map +1 -1
- package/lib/Bridge/adapt.js +12 -515
- package/lib/Bridge/adapt.js.map +1 -1
- package/lib/Bridge/schema.d.ts +36 -0
- package/lib/Bridge/schema.d.ts.map +1 -0
- package/lib/Bridge/schema.js +773 -0
- package/lib/Bridge/schema.js.map +1 -0
- package/lib/Bridge/types.d.ts +257 -3
- package/lib/Bridge/types.d.ts.map +1 -1
- package/lib/Socket/events.d.ts +18 -9
- package/lib/Socket/events.d.ts.map +1 -1
- package/lib/Socket/events.js +520 -204
- package/lib/Socket/events.js.map +1 -1
- package/lib/Socket/group-notifications.d.ts +6 -0
- package/lib/Socket/group-notifications.d.ts.map +1 -1
- package/lib/Socket/group-notifications.js +123 -16
- package/lib/Socket/group-notifications.js.map +1 -1
- package/lib/Socket/groups.d.ts +10 -2
- package/lib/Socket/groups.d.ts.map +1 -1
- package/lib/Socket/groups.js +24 -30
- package/lib/Socket/groups.js.map +1 -1
- package/lib/Socket/index.d.ts +31 -8
- package/lib/Socket/index.d.ts.map +1 -1
- package/lib/Socket/index.js +178 -13
- package/lib/Socket/index.js.map +1 -1
- package/lib/Socket/messages.d.ts +13 -2
- package/lib/Socket/messages.d.ts.map +1 -1
- package/lib/Socket/messages.js +27 -6
- package/lib/Socket/messages.js.map +1 -1
- package/lib/Types/Call.d.ts +20 -0
- package/lib/Types/Call.d.ts.map +1 -1
- package/lib/Types/Contact.d.ts +3 -1
- package/lib/Types/Contact.d.ts.map +1 -1
- package/lib/Types/Events.d.ts +2 -0
- package/lib/Types/Events.d.ts.map +1 -1
- package/lib/Utils/generics.d.ts +17 -0
- package/lib/Utils/generics.d.ts.map +1 -1
- package/lib/Utils/generics.js +31 -0
- package/lib/Utils/generics.js.map +1 -1
- package/lib/Utils/group-stub-params.d.ts +29 -0
- package/lib/Utils/group-stub-params.d.ts.map +1 -0
- package/lib/Utils/group-stub-params.js +45 -0
- package/lib/Utils/group-stub-params.js.map +1 -0
- package/lib/Utils/messages-media.d.ts +1 -1
- package/lib/Utils/messages-media.d.ts.map +1 -1
- package/lib/Utils/messages-media.js +1 -1
- package/lib/Utils/messages-media.js.map +1 -1
- package/lib/Utils/messages.d.ts.map +1 -1
- package/lib/Utils/messages.js +4 -6
- package/lib/Utils/messages.js.map +1 -1
- package/lib/Utils/process-history-message.d.ts +30 -0
- package/lib/Utils/process-history-message.d.ts.map +1 -0
- package/lib/Utils/process-history-message.js +148 -0
- package/lib/Utils/process-history-message.js.map +1 -0
- package/lib/Utils/use-bridge-store.d.ts.map +1 -1
- package/lib/Utils/use-bridge-store.js +50 -6
- package/lib/Utils/use-bridge-store.js.map +1 -1
- package/lib/Utils/use-multi-file-auth-state.d.ts +7 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts.map +1 -1
- package/lib/Utils/use-multi-file-auth-state.js +11 -1
- package/lib/Utils/use-multi-file-auth-state.js.map +1 -1
- package/lib/Utils/wrap-legacy-store.d.ts.map +1 -1
- package/lib/Utils/wrap-legacy-store.js +110 -90
- package/lib/Utils/wrap-legacy-store.js.map +1 -1
- package/lib/WABinary/generic-utils.d.ts +17 -0
- package/lib/WABinary/generic-utils.d.ts.map +1 -0
- package/lib/WABinary/generic-utils.js +44 -0
- package/lib/WABinary/generic-utils.js.map +1 -0
- package/lib/WABinary/index.d.ts +1 -0
- package/lib/WABinary/index.d.ts.map +1 -1
- package/lib/WABinary/index.js +1 -0
- package/lib/WABinary/index.js.map +1 -1
- package/lib/WABinary/jid-utils.d.ts +4 -0
- package/lib/WABinary/jid-utils.d.ts.map +1 -1
- package/lib/WABinary/jid-utils.js +4 -0
- package/lib/WABinary/jid-utils.js.map +1 -1
- package/lib/__tests__/expect.d.ts +14 -0
- package/lib/__tests__/expect.d.ts.map +1 -1
- package/lib/__tests__/expect.js +42 -2
- package/lib/__tests__/expect.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-driven bridge → canonical adapter table.
|
|
3
|
+
*
|
|
4
|
+
* The single source of truth for "every bridge event variant we accept and
|
|
5
|
+
* how it maps to a CanonicalEvent". Each entry receives the bridge's typed
|
|
6
|
+
* data slot (`Extract<WhatsAppEvent, { type: T }>['data']`) and returns
|
|
7
|
+
* either a CanonicalEvent or `null` (drop on unrecoverable shape).
|
|
8
|
+
*
|
|
9
|
+
* Type safety properties:
|
|
10
|
+
* 1. The table is `satisfies AdapterMap` where `AdapterMap` is keyed by
|
|
11
|
+
* every `WhatsAppEvent['type']`. Adding a new variant to the bridge
|
|
12
|
+
* `.d.ts` and forgetting an entry here is a compile error.
|
|
13
|
+
* 2. Each entry's `data` parameter is the bridge's narrow type — fields
|
|
14
|
+
* come back as the right TS type without manual `unknown` checks.
|
|
15
|
+
* 3. Each entry's return type is `CanonicalEvent | null`. Returning the
|
|
16
|
+
* wrong canonical shape is a compile error.
|
|
17
|
+
*
|
|
18
|
+
* Where the bridge `.d.ts` references a sub-type that isn't separately
|
|
19
|
+
* exported (PinAction, MuteAction, ArchiveChatAction, etc. — the action
|
|
20
|
+
* field of every sync-action event), TS resolves the type to `any`. We
|
|
21
|
+
* apply narrowing parsers from `./primitives` (`isObject`, `asBoolOr`,
|
|
22
|
+
* `asNumber`, `asString`) at those leak points only — not boilerplate
|
|
23
|
+
* everywhere.
|
|
24
|
+
*/
|
|
25
|
+
import { processHistoryMessage } from "../Utils/process-history-message.js";
|
|
26
|
+
import { asBoolOr, asJidString, asNumber, asString, bridgeJidToString, isBridgeJid, isObject, normalizeDiscriminator, toUnixSeconds } from "./primitives.js";
|
|
27
|
+
/**
|
|
28
|
+
* Bridge sync-action events (`pin_update`, `mute_update`, etc.) carry the
|
|
29
|
+
* proto action under `data.action`, but the bridge `.d.ts` types it as
|
|
30
|
+
* the unexported `PinAction` / `MuteAction` (resolves to `any`). Narrow
|
|
31
|
+
* once at the call site.
|
|
32
|
+
*/
|
|
33
|
+
const extractAction = (data) => isObject(data.action) ? data.action : undefined;
|
|
34
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
// Per-event adapters — the bulk of the file. One entry per WhatsAppEvent variant.
|
|
36
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
|
+
const ADAPTERS = {
|
|
38
|
+
// ── Connection lifecycle ──
|
|
39
|
+
connected: () => ({ type: 'connected' }),
|
|
40
|
+
disconnected: () => ({ type: 'disconnected' }),
|
|
41
|
+
stream_replaced: () => ({ type: 'streamReplaced' }),
|
|
42
|
+
client_outdated: () => ({ type: 'clientOutdated' }),
|
|
43
|
+
temporary_ban: data => ({
|
|
44
|
+
type: 'temporaryBan',
|
|
45
|
+
code: asNumber(data?.code),
|
|
46
|
+
expire: asNumber(data?.expire)
|
|
47
|
+
}),
|
|
48
|
+
qr_scanned_without_multidevice: () => ({ type: 'qrScannedWithoutMultidevice' }),
|
|
49
|
+
logged_out: data => ({
|
|
50
|
+
type: 'loggedOut',
|
|
51
|
+
onConnect: asBoolOr(data?.on_connect, false),
|
|
52
|
+
reason: asString(data?.reason)
|
|
53
|
+
}),
|
|
54
|
+
qr: data => (data.code ? { type: 'qr', code: data.code } : null),
|
|
55
|
+
pairing_code: data => (data.code ? { type: 'qr', code: data.code } : null),
|
|
56
|
+
pair_success: data => {
|
|
57
|
+
// `id` and `lid` come typed as `Jid` in the bridge .d.ts, but the
|
|
58
|
+
// bridge actually serializes pair_success.{id,lid} as strings (see
|
|
59
|
+
// the wire log) — accept both shapes.
|
|
60
|
+
const id = typeof data.id === 'string' ? data.id : asJidString(data.id);
|
|
61
|
+
if (!id)
|
|
62
|
+
return null;
|
|
63
|
+
return {
|
|
64
|
+
type: 'pairSuccess',
|
|
65
|
+
id,
|
|
66
|
+
lid: typeof data.lid === 'string' ? data.lid : asJidString(data.lid),
|
|
67
|
+
platform: asString(data.platform),
|
|
68
|
+
businessName: asString(data.business_name)
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
pair_error: data => ({
|
|
72
|
+
type: 'pairError',
|
|
73
|
+
error: asString(data.error) ?? 'Unknown pairing error',
|
|
74
|
+
id: typeof data.id === 'string' ? data.id : asJidString(data.id),
|
|
75
|
+
lid: typeof data.lid === 'string' ? data.lid : asJidString(data.lid),
|
|
76
|
+
businessName: asString(data.business_name),
|
|
77
|
+
platform: asString(data.platform)
|
|
78
|
+
}),
|
|
79
|
+
connect_failure: data => isObject(data)
|
|
80
|
+
? { type: 'connectFailure', message: asString(data.message), reason: asNumber(data.reason) }
|
|
81
|
+
: { type: 'connectFailure' },
|
|
82
|
+
stream_error: data => ({ type: 'streamError', code: asString(data.code) ?? 'unknown' }),
|
|
83
|
+
// ── Messages ──
|
|
84
|
+
message: (data, logger) => adaptMessage(data, logger),
|
|
85
|
+
receipt: (data, logger) => adaptReceipt(data, logger),
|
|
86
|
+
undecryptable_message: data => {
|
|
87
|
+
// Bridge ships `{ info: MessageInfo, is_unavailable, unavailable_type,
|
|
88
|
+
// decrypt_fail_mode }` — same MessageInfo shape as the regular
|
|
89
|
+
// `message` event. Extract the chat / sender / id so the dispatcher
|
|
90
|
+
// can synthesize a CIPHERTEXT stub matching upstream
|
|
91
|
+
// `messages-recv.ts:1352`.
|
|
92
|
+
if (!isObject(data))
|
|
93
|
+
return null;
|
|
94
|
+
const info = isObject(data.info) ? data.info : undefined;
|
|
95
|
+
if (!info)
|
|
96
|
+
return null;
|
|
97
|
+
const src = isObject(info.source) ? info.source : undefined;
|
|
98
|
+
const chat = src && asJidString(src.chat);
|
|
99
|
+
const id = asString(info.id);
|
|
100
|
+
if (!src || !chat || !id)
|
|
101
|
+
return null;
|
|
102
|
+
const isGroup = asBoolOr(src.is_group, false);
|
|
103
|
+
const participantAlt = isGroup && isBridgeJid(src.sender_alt) ? bridgeJidToString(src.sender_alt) : undefined;
|
|
104
|
+
const remoteJidAlt = !isGroup && isBridgeJid(src.recipient_alt) ? bridgeJidToString(src.recipient_alt) : undefined;
|
|
105
|
+
return {
|
|
106
|
+
type: 'undecryptableMessage',
|
|
107
|
+
chatJid: chat,
|
|
108
|
+
senderJid: isGroup ? asJidString(src.sender) : undefined,
|
|
109
|
+
id,
|
|
110
|
+
timestamp: toUnixSeconds(info.timestamp),
|
|
111
|
+
isFromMe: asBoolOr(src.is_from_me, false),
|
|
112
|
+
isGroup,
|
|
113
|
+
pushName: asString(info.push_name),
|
|
114
|
+
participantAlt,
|
|
115
|
+
remoteJidAlt,
|
|
116
|
+
isUnavailable: asBoolOr(data.is_unavailable, false),
|
|
117
|
+
unavailableType: asString(data.unavailable_type),
|
|
118
|
+
decryptFailMode: asString(data.decrypt_fail_mode),
|
|
119
|
+
raw: data
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
// ── Contacts ──
|
|
123
|
+
push_name_update: data => {
|
|
124
|
+
const jid = asJidString(data.jid);
|
|
125
|
+
return jid ? { type: 'pushNameUpdate', jid, newPushName: asString(data.new_push_name) } : null;
|
|
126
|
+
},
|
|
127
|
+
contact_update: data => adaptContactUpdate(data),
|
|
128
|
+
contact_updated: data => adaptContactUpdate(data),
|
|
129
|
+
picture_update: data => {
|
|
130
|
+
const jid = asJidString(data.jid);
|
|
131
|
+
if (!jid)
|
|
132
|
+
return null;
|
|
133
|
+
return {
|
|
134
|
+
type: 'pictureUpdate',
|
|
135
|
+
jid,
|
|
136
|
+
removed: asBoolOr(data.removed, false),
|
|
137
|
+
author: asJidString(data.author),
|
|
138
|
+
pictureId: asString(data.picture_id)
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
// ── Presence ──
|
|
142
|
+
presence: data => {
|
|
143
|
+
const from = asJidString(data.from);
|
|
144
|
+
if (!from)
|
|
145
|
+
return null;
|
|
146
|
+
return {
|
|
147
|
+
type: 'presence',
|
|
148
|
+
from,
|
|
149
|
+
unavailable: asBoolOr(data.unavailable, false),
|
|
150
|
+
lastSeen: asNumber(data.last_seen)
|
|
151
|
+
};
|
|
152
|
+
},
|
|
153
|
+
chat_presence: data => {
|
|
154
|
+
const src = isObject(data.source) ? data.source : undefined;
|
|
155
|
+
if (!src)
|
|
156
|
+
return null;
|
|
157
|
+
const chat = asJidString(src.chat);
|
|
158
|
+
const sender = asJidString(src.sender);
|
|
159
|
+
if (!chat || !sender)
|
|
160
|
+
return null;
|
|
161
|
+
// Bridge sends `media: ''` for "no media" — normalize to undefined
|
|
162
|
+
// so consumers can rely on field omission as the absence signal.
|
|
163
|
+
const media = asString(data.media);
|
|
164
|
+
// Bridge `state` MUST be one of the two canonical values; defaulting
|
|
165
|
+
// to 'composing' on an unknown/missing value would synthesize a
|
|
166
|
+
// false typing indicator. Drop the event instead.
|
|
167
|
+
const rawState = asString(data.state);
|
|
168
|
+
if (rawState !== 'composing' && rawState !== 'paused')
|
|
169
|
+
return null;
|
|
170
|
+
return {
|
|
171
|
+
type: 'chatPresence',
|
|
172
|
+
chatJid: chat,
|
|
173
|
+
senderJid: sender,
|
|
174
|
+
state: rawState,
|
|
175
|
+
media: media === 'audio' ? 'audio' : undefined
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
// ── Groups ──
|
|
179
|
+
group_update: (data, logger) => adaptGroupUpdate(data, logger),
|
|
180
|
+
// ── Chat state ──
|
|
181
|
+
archive_update: data => {
|
|
182
|
+
const jid = asJidString(data.jid);
|
|
183
|
+
if (!jid)
|
|
184
|
+
return null;
|
|
185
|
+
return { type: 'archiveUpdate', jid, archived: asBoolOr(extractAction(data)?.archived, true) };
|
|
186
|
+
},
|
|
187
|
+
pin_update: data => {
|
|
188
|
+
const jid = asJidString(data.jid);
|
|
189
|
+
if (!jid)
|
|
190
|
+
return null;
|
|
191
|
+
return {
|
|
192
|
+
type: 'pinUpdate',
|
|
193
|
+
jid,
|
|
194
|
+
timestamp: asNumber(data.timestamp),
|
|
195
|
+
pinned: asBoolOr(extractAction(data)?.pinned, true)
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
mute_update: data => {
|
|
199
|
+
const jid = asJidString(data.jid);
|
|
200
|
+
if (!jid)
|
|
201
|
+
return null;
|
|
202
|
+
const action = extractAction(data);
|
|
203
|
+
return {
|
|
204
|
+
type: 'muteUpdate',
|
|
205
|
+
jid,
|
|
206
|
+
timestamp: asNumber(data.timestamp),
|
|
207
|
+
muted: asBoolOr(action?.muted, true),
|
|
208
|
+
muteEndTimestamp: asNumber(action?.muteEndTimestamp) ?? asNumber(action?.mute_end_timestamp)
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
star_update: data => adaptStarUpdate(data),
|
|
212
|
+
mark_chat_as_read_update: data => {
|
|
213
|
+
const jid = asJidString(data.jid);
|
|
214
|
+
if (!jid)
|
|
215
|
+
return null;
|
|
216
|
+
return { type: 'markChatAsReadUpdate', jid, read: asBoolOr(extractAction(data)?.read, true) };
|
|
217
|
+
},
|
|
218
|
+
// ── Calls ──
|
|
219
|
+
incoming_call: (data, logger) => adaptIncomingCall(data, logger),
|
|
220
|
+
// ── History sync — fully decoded by the bridge, normalized 1:1 with upstream ──
|
|
221
|
+
history_sync: data => {
|
|
222
|
+
// `data` is `proto.IHistorySync & { syncType, chunkOrder?, progress? }` —
|
|
223
|
+
// the bridge serialized the whole proto via `to_js_value_camel`. Walk
|
|
224
|
+
// it through the upstream-aligned `processHistoryMessage` to bucket
|
|
225
|
+
// chats / contacts / messages / lidPnMappings, then forward the
|
|
226
|
+
// metadata fields (the dispatcher folds in `isLatest` /
|
|
227
|
+
// `peerDataRequestSessionId`).
|
|
228
|
+
if (!isObject(data))
|
|
229
|
+
return null;
|
|
230
|
+
const processed = processHistoryMessage(data);
|
|
231
|
+
// Top-level metadata overlay wins over the proto's own `syncType` /
|
|
232
|
+
// `progress` fields when both present — the bridge maintains the
|
|
233
|
+
// authoritative copy across multi-chunk arrivals.
|
|
234
|
+
const overlay = data;
|
|
235
|
+
const metaSyncType = asNumber(overlay.syncType);
|
|
236
|
+
const metaChunkOrder = asNumber(overlay.chunkOrder);
|
|
237
|
+
const metaProgress = asNumber(overlay.progress);
|
|
238
|
+
const peerDataRequestSessionId = asString(overlay.peerDataRequestSessionId);
|
|
239
|
+
return {
|
|
240
|
+
type: 'historySync',
|
|
241
|
+
chats: processed.chats,
|
|
242
|
+
contacts: processed.contacts,
|
|
243
|
+
messages: processed.messages,
|
|
244
|
+
lidPnMappings: processed.lidPnMappings,
|
|
245
|
+
syncType: metaSyncType ?? processed.syncType,
|
|
246
|
+
progress: metaProgress ?? processed.progress,
|
|
247
|
+
chunkOrder: metaChunkOrder,
|
|
248
|
+
peerDataRequestSessionId
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
// ── Acknowledged but no Baileys equivalent (noop) ──
|
|
252
|
+
self_push_name_updated: () => ({ type: 'noop', bridgeType: 'self_push_name_updated' }),
|
|
253
|
+
offline_sync_completed: () => ({ type: 'noop', bridgeType: 'offline_sync_completed' }),
|
|
254
|
+
offline_sync_preview: () => ({ type: 'noop', bridgeType: 'offline_sync_preview' }),
|
|
255
|
+
device_list_update: () => ({ type: 'noop', bridgeType: 'device_list_update' }),
|
|
256
|
+
disappearing_mode_changed: data => {
|
|
257
|
+
const jid = asJidString(data.from);
|
|
258
|
+
const duration = asNumber(data.duration);
|
|
259
|
+
if (!jid || duration == null)
|
|
260
|
+
return { type: 'noop', bridgeType: 'disappearing_mode_changed' };
|
|
261
|
+
return {
|
|
262
|
+
type: 'disappearingModeChanged',
|
|
263
|
+
jid,
|
|
264
|
+
duration,
|
|
265
|
+
settingTimestamp: asNumber(data.setting_timestamp)
|
|
266
|
+
};
|
|
267
|
+
},
|
|
268
|
+
business_status_update: () => ({ type: 'noop', bridgeType: 'business_status_update' }),
|
|
269
|
+
newsletter_live_update: data => {
|
|
270
|
+
const newsletterJid = asJidString(data.newsletter_jid);
|
|
271
|
+
if (!newsletterJid)
|
|
272
|
+
return { type: 'noop', bridgeType: 'newsletter_live_update' };
|
|
273
|
+
const rawMessages = Array.isArray(data.messages) ? data.messages : [];
|
|
274
|
+
const messages = rawMessages
|
|
275
|
+
.map(m => {
|
|
276
|
+
if (!isObject(m))
|
|
277
|
+
return null;
|
|
278
|
+
// Prefer the original string form — `server_id` is 64-bit and
|
|
279
|
+
// `Number()` rounds at 2^53, so going through `asNumber` first
|
|
280
|
+
// would silently corrupt newsletter ids past that boundary.
|
|
281
|
+
const serverId = asString(m.server_id) ?? asNumber(m.server_id)?.toString();
|
|
282
|
+
if (!serverId)
|
|
283
|
+
return null;
|
|
284
|
+
const rawReactions = Array.isArray(m.reactions) ? m.reactions : [];
|
|
285
|
+
const reactions = rawReactions
|
|
286
|
+
.map(r => {
|
|
287
|
+
if (!isObject(r))
|
|
288
|
+
return null;
|
|
289
|
+
const code = asString(r.code);
|
|
290
|
+
const count = asNumber(r.count);
|
|
291
|
+
if (!code || count == null)
|
|
292
|
+
return null;
|
|
293
|
+
return { code, count };
|
|
294
|
+
})
|
|
295
|
+
.filter((r) => r !== null);
|
|
296
|
+
return { serverId, reactions };
|
|
297
|
+
})
|
|
298
|
+
.filter((m) => m !== null);
|
|
299
|
+
return { type: 'newsletterLiveUpdate', newsletterJid, messages };
|
|
300
|
+
},
|
|
301
|
+
contact_number_changed: data => {
|
|
302
|
+
// Bridge `ContactNumberChanged` carries up to two LID↔PN pairs:
|
|
303
|
+
// (old_lid, old_jid) and (new_lid, new_jid). We learn whatever's
|
|
304
|
+
// present and let the dispatcher fan out one upstream event per
|
|
305
|
+
// pair. Mirrors upstream `messages-recv.ts:287`.
|
|
306
|
+
const oldJid = asJidString(data.old_jid);
|
|
307
|
+
const newJid = asJidString(data.new_jid);
|
|
308
|
+
const oldLid = asJidString(data.old_lid);
|
|
309
|
+
const newLid = asJidString(data.new_lid);
|
|
310
|
+
const mappings = [];
|
|
311
|
+
if (oldLid && oldJid)
|
|
312
|
+
mappings.push({ lid: oldLid, pn: oldJid });
|
|
313
|
+
if (newLid && newJid)
|
|
314
|
+
mappings.push({ lid: newLid, pn: newJid });
|
|
315
|
+
if (mappings.length === 0)
|
|
316
|
+
return { type: 'noop', bridgeType: 'contact_number_changed' };
|
|
317
|
+
return { type: 'lidMappingUpdate', mappings };
|
|
318
|
+
},
|
|
319
|
+
contact_sync_requested: () => ({ type: 'noop', bridgeType: 'contact_sync_requested' }),
|
|
320
|
+
user_about_update: () => ({ type: 'noop', bridgeType: 'user_about_update' }),
|
|
321
|
+
delete_chat_update: data => {
|
|
322
|
+
const jid = asJidString(data.jid);
|
|
323
|
+
return jid ? { type: 'chatDelete', jid } : { type: 'noop', bridgeType: 'delete_chat_update' };
|
|
324
|
+
},
|
|
325
|
+
delete_message_for_me_update: data => {
|
|
326
|
+
const chatJid = asJidString(data.chat_jid);
|
|
327
|
+
const messageId = asString(data.message_id);
|
|
328
|
+
if (!chatJid || !messageId)
|
|
329
|
+
return { type: 'noop', bridgeType: 'delete_message_for_me_update' };
|
|
330
|
+
return {
|
|
331
|
+
type: 'messageDelete',
|
|
332
|
+
chatJid,
|
|
333
|
+
messageId,
|
|
334
|
+
fromMe: asBoolOr(data.from_me, false),
|
|
335
|
+
participantJid: asJidString(data.participant_jid)
|
|
336
|
+
};
|
|
337
|
+
},
|
|
338
|
+
// ── Generic notification / raw node ──
|
|
339
|
+
notification: data => {
|
|
340
|
+
if (!isObject(data))
|
|
341
|
+
return { type: 'noop', bridgeType: 'notification' };
|
|
342
|
+
// Validate attrs values are actually strings — bridge serde might
|
|
343
|
+
// emit numbers / nested objects on rare attrs (e.g. server bugs),
|
|
344
|
+
// and the previous unchecked cast would smuggle non-strings into
|
|
345
|
+
// CanonicalNotification.attrs (typed `Record<string, string>`).
|
|
346
|
+
// Drop non-string values, coerce the rest.
|
|
347
|
+
const attrs = {};
|
|
348
|
+
if (isObject(data.attrs)) {
|
|
349
|
+
for (const [k, v] of Object.entries(data.attrs)) {
|
|
350
|
+
if (typeof v === 'string')
|
|
351
|
+
attrs[k] = v;
|
|
352
|
+
else if (typeof v === 'number' || typeof v === 'boolean')
|
|
353
|
+
attrs[k] = String(v);
|
|
354
|
+
// Drop nested objects / null silently — they wouldn't make
|
|
355
|
+
// sense on a flat attrs map anyway.
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return { type: 'notification', tag: asString(data.tag) ?? 'notification', attrs };
|
|
359
|
+
},
|
|
360
|
+
raw_node: data => {
|
|
361
|
+
// `BinaryNode` is shaped exactly like the bridge payload
|
|
362
|
+
// (`{ tag, attrs, content }`). Minimal sanity check on `tag`.
|
|
363
|
+
if (!isObject(data) || typeof data.tag !== 'string')
|
|
364
|
+
return null;
|
|
365
|
+
return { type: 'rawNode', node: data };
|
|
366
|
+
},
|
|
367
|
+
mex_notification: data => {
|
|
368
|
+
const opName = asString(data.op_name);
|
|
369
|
+
if (!opName)
|
|
370
|
+
return null;
|
|
371
|
+
const payload = isObject(data.payload) ? data.payload : {};
|
|
372
|
+
return {
|
|
373
|
+
type: 'mexNotification',
|
|
374
|
+
opName,
|
|
375
|
+
from: asJidString(data.from),
|
|
376
|
+
stanzaId: asString(data.stanza_id),
|
|
377
|
+
offline: asBoolOr(data.offline, false),
|
|
378
|
+
payload
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
/** Set of bridge event types we explicitly handle — derived from the table. */
|
|
383
|
+
export const KNOWN_BRIDGE_EVENT_TYPES = new Set(Object.keys(ADAPTERS));
|
|
384
|
+
/**
|
|
385
|
+
* Public entry point — looks up the adapter by event type and runs it.
|
|
386
|
+
* Mirrors the previous `adaptBridgeEvent` API exactly.
|
|
387
|
+
*/
|
|
388
|
+
export const adaptBridgeEventViaSchema = (event, logger) => {
|
|
389
|
+
const typed = event;
|
|
390
|
+
const adapter = ADAPTERS[typed.type];
|
|
391
|
+
if (!adapter) {
|
|
392
|
+
logger?.debug({ eventType: typed.type }, 'unknown bridge event (no canonical mapping)');
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
// `data` cast here is the only `as` in the public path — the bridge
|
|
396
|
+
// runtime is the source of truth that the type matches the discriminator.
|
|
397
|
+
return adapter(typed.data, logger);
|
|
398
|
+
};
|
|
399
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
400
|
+
// Heavy adapters — kept as named functions because the inline form would
|
|
401
|
+
// blow up the table. Each takes the bridge's typed data slot.
|
|
402
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
403
|
+
const adaptMessage = (data, logger) => {
|
|
404
|
+
if (!isObject(data))
|
|
405
|
+
return null;
|
|
406
|
+
const info = isObject(data.info) ? data.info : undefined;
|
|
407
|
+
const messageProto = isObject(data.message) ? data.message : undefined;
|
|
408
|
+
if (!info || !messageProto)
|
|
409
|
+
return null;
|
|
410
|
+
const src = isObject(info.source) ? info.source : undefined;
|
|
411
|
+
const chat = src && asJidString(src.chat);
|
|
412
|
+
const id = asString(info.id);
|
|
413
|
+
if (!src || !chat || !id) {
|
|
414
|
+
logger?.debug({ info }, 'message adapter: missing chat/id');
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
const isGroup = asBoolOr(src.is_group, false);
|
|
418
|
+
const senderRaw = src.sender;
|
|
419
|
+
const senderJid = isGroup ? asJidString(senderRaw) : undefined;
|
|
420
|
+
const participantAlt = isGroup && isBridgeJid(src.sender_alt) ? bridgeJidToString(src.sender_alt) : undefined;
|
|
421
|
+
const remoteJidAlt = !isGroup && isBridgeJid(src.recipient_alt) ? bridgeJidToString(src.recipient_alt) : undefined;
|
|
422
|
+
// Bridge `EditAttribute` is one of "1"|"2"|"3"|"7"|"8" or "" (none) — narrow
|
|
423
|
+
// to the literal set so consumers can switch exhaustively without
|
|
424
|
+
// re-parsing.
|
|
425
|
+
const editAttributeRaw = asString(info.edit);
|
|
426
|
+
const editAttribute = editAttributeRaw === '1' ||
|
|
427
|
+
editAttributeRaw === '2' ||
|
|
428
|
+
editAttributeRaw === '3' ||
|
|
429
|
+
editAttributeRaw === '7' ||
|
|
430
|
+
editAttributeRaw === '8'
|
|
431
|
+
? editAttributeRaw
|
|
432
|
+
: undefined;
|
|
433
|
+
return {
|
|
434
|
+
type: 'message',
|
|
435
|
+
chatJid: chat,
|
|
436
|
+
senderJid,
|
|
437
|
+
isGroup,
|
|
438
|
+
isFromMe: asBoolOr(src.is_from_me, false),
|
|
439
|
+
id,
|
|
440
|
+
timestamp: toUnixSeconds(info.timestamp),
|
|
441
|
+
pushName: asString(info.push_name),
|
|
442
|
+
participantAlt,
|
|
443
|
+
remoteJidAlt,
|
|
444
|
+
isViewOnce: info.is_view_once === true ? true : undefined,
|
|
445
|
+
isOffline: asBoolOr(info.is_offline, false) ? true : undefined,
|
|
446
|
+
unavailableRequestId: asString(info.unavailable_request_id),
|
|
447
|
+
editAttribute,
|
|
448
|
+
messageProto: messageProto
|
|
449
|
+
};
|
|
450
|
+
};
|
|
451
|
+
const adaptReceipt = (data, logger) => {
|
|
452
|
+
if (!isObject(data))
|
|
453
|
+
return null;
|
|
454
|
+
const src = isObject(data.source) ? data.source : undefined;
|
|
455
|
+
if (!src)
|
|
456
|
+
return null;
|
|
457
|
+
const chat = asJidString(src.chat);
|
|
458
|
+
const ids = Array.isArray(data.message_ids) ? data.message_ids.filter((x) => typeof x === 'string') : [];
|
|
459
|
+
if (!chat || ids.length === 0) {
|
|
460
|
+
logger?.debug({ data }, 'receipt adapter: missing chat or message_ids');
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
const isGroup = asBoolOr(src.is_group, false);
|
|
464
|
+
return {
|
|
465
|
+
type: 'receipt',
|
|
466
|
+
chatJid: chat,
|
|
467
|
+
senderJid: isGroup ? asJidString(src.sender) : undefined,
|
|
468
|
+
isGroup,
|
|
469
|
+
isFromMe: asBoolOr(src.is_from_me, false),
|
|
470
|
+
messageIds: ids,
|
|
471
|
+
timestamp: toUnixSeconds(data.timestamp),
|
|
472
|
+
receiptType: parseReceiptType(data.type)
|
|
473
|
+
};
|
|
474
|
+
};
|
|
475
|
+
const adaptContactUpdate = (data) => {
|
|
476
|
+
if (!isObject(data))
|
|
477
|
+
return null;
|
|
478
|
+
const jid = asJidString(data.jid);
|
|
479
|
+
if (!jid)
|
|
480
|
+
return null;
|
|
481
|
+
const action = isObject(data.action) ? data.action : undefined;
|
|
482
|
+
return {
|
|
483
|
+
type: 'contactUpdate',
|
|
484
|
+
jid,
|
|
485
|
+
fullName: asString(action?.fullName) ?? asString(action?.full_name),
|
|
486
|
+
firstName: asString(action?.firstName) ?? asString(action?.first_name),
|
|
487
|
+
lidJid: asString(action?.lidJid) ?? asString(action?.lid_jid),
|
|
488
|
+
pnJid: asString(action?.pnJid) ?? asString(action?.pn_jid),
|
|
489
|
+
username: asString(action?.username)
|
|
490
|
+
};
|
|
491
|
+
};
|
|
492
|
+
/**
|
|
493
|
+
* Bridge wire `ReceiptType` → canonical kebab-cased variant.
|
|
494
|
+
*
|
|
495
|
+
* The bridge's generated `.d.ts` advertises `ReceiptType` as
|
|
496
|
+
* `{type: "delivered"} | …`, but `#[serde(from = "String")]` on the rust
|
|
497
|
+
* enum disables the matching `Serialize` rename: the wire form is the
|
|
498
|
+
* bare PascalCase variant name (`"Delivered"`, `"Read"`, `"PeerMsg"`).
|
|
499
|
+
* Keep both spellings here so a future bridge bump that re-introduces
|
|
500
|
+
* the snake_case wire form keeps working.
|
|
501
|
+
*/
|
|
502
|
+
const RECEIPT_TYPE_MAP = {
|
|
503
|
+
Delivered: 'delivered',
|
|
504
|
+
Sender: 'sender',
|
|
505
|
+
Retry: 'retry',
|
|
506
|
+
EncRekeyRetry: 'enc-rekey-retry',
|
|
507
|
+
Read: 'read',
|
|
508
|
+
ReadSelf: 'read-self',
|
|
509
|
+
Played: 'played',
|
|
510
|
+
PlayedSelf: 'played-self',
|
|
511
|
+
Inactive: 'inactive',
|
|
512
|
+
PeerMsg: 'peer-msg',
|
|
513
|
+
HistorySync: 'history-sync',
|
|
514
|
+
ServerError: 'server-error',
|
|
515
|
+
delivered: 'delivered',
|
|
516
|
+
sender: 'sender',
|
|
517
|
+
retry: 'retry',
|
|
518
|
+
enc_rekey_retry: 'enc-rekey-retry',
|
|
519
|
+
read: 'read',
|
|
520
|
+
read_self: 'read-self',
|
|
521
|
+
played: 'played',
|
|
522
|
+
played_self: 'played-self',
|
|
523
|
+
inactive: 'inactive',
|
|
524
|
+
peer_msg: 'peer-msg',
|
|
525
|
+
history_sync: 'history-sync',
|
|
526
|
+
server_error: 'server-error'
|
|
527
|
+
};
|
|
528
|
+
const parseReceiptType = (raw) => {
|
|
529
|
+
const norm = typeof raw === 'string' ? raw : isObject(raw) && typeof raw.type === 'string' ? raw.type : undefined;
|
|
530
|
+
if (norm == null)
|
|
531
|
+
return undefined;
|
|
532
|
+
return RECEIPT_TYPE_MAP[norm] ?? 'other';
|
|
533
|
+
};
|
|
534
|
+
const adaptStarUpdate = (data) => {
|
|
535
|
+
if (!isObject(data))
|
|
536
|
+
return null;
|
|
537
|
+
const chatJid = asJidString(data.chat_jid);
|
|
538
|
+
const messageId = asString(data.message_id);
|
|
539
|
+
if (!chatJid || !messageId)
|
|
540
|
+
return null;
|
|
541
|
+
const action = isObject(data.action) ? data.action : undefined;
|
|
542
|
+
return {
|
|
543
|
+
type: 'starUpdate',
|
|
544
|
+
chatJid,
|
|
545
|
+
messageId,
|
|
546
|
+
fromMe: asBoolOr(data.from_me, false),
|
|
547
|
+
participantJid: asJidString(data.participant_jid),
|
|
548
|
+
starred: asBoolOr(action?.starred, false)
|
|
549
|
+
};
|
|
550
|
+
};
|
|
551
|
+
const adaptIncomingCall = (data, logger) => {
|
|
552
|
+
if (!isObject(data))
|
|
553
|
+
return null;
|
|
554
|
+
const from = asJidString(data.from);
|
|
555
|
+
if (!from)
|
|
556
|
+
return null;
|
|
557
|
+
const action = isObject(data.action) ? data.action : undefined;
|
|
558
|
+
if (!action)
|
|
559
|
+
return null;
|
|
560
|
+
const actionType = parseCallActionType(action.type);
|
|
561
|
+
const callId = asString(action.call_id);
|
|
562
|
+
if (!actionType || !callId) {
|
|
563
|
+
logger?.debug({ data }, 'incoming_call adapter: missing action.type/call_id');
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
const canonicalAction = { type: actionType, callId };
|
|
567
|
+
// The bridge `CallAction` union narrows variant-specific fields only
|
|
568
|
+
// on the matching variant. We've already validated `action.type` via
|
|
569
|
+
// `parseCallActionType`, so a runtime cast through the action's own
|
|
570
|
+
// shape is safe — reading fields off `unknown` is the cheapest way
|
|
571
|
+
// around TS's inability to follow our string-normalized discriminator
|
|
572
|
+
// back into the bridge union.
|
|
573
|
+
const fields = action;
|
|
574
|
+
if (actionType === 'offer') {
|
|
575
|
+
canonicalAction.callerPn = asJidString(fields.caller_pn);
|
|
576
|
+
canonicalAction.callerCountryCode = asString(fields.caller_country_code);
|
|
577
|
+
canonicalAction.deviceClass = asString(fields.device_class);
|
|
578
|
+
canonicalAction.joinable = asBoolOr(fields.joinable, false);
|
|
579
|
+
canonicalAction.isVideo = asBoolOr(fields.is_video, false);
|
|
580
|
+
if (Array.isArray(fields.audio)) {
|
|
581
|
+
canonicalAction.audio = fields.audio.filter((x) => typeof x === 'string');
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
else if (actionType === 'terminate') {
|
|
585
|
+
canonicalAction.duration = asNumber(fields.duration);
|
|
586
|
+
canonicalAction.audioDuration = asNumber(fields.audio_duration);
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
type: 'incomingCall',
|
|
590
|
+
from,
|
|
591
|
+
timestamp: toUnixSeconds(data.timestamp),
|
|
592
|
+
offline: asBoolOr(data.offline, false),
|
|
593
|
+
stanzaId: asString(data.stanza_id),
|
|
594
|
+
notify: asString(data.notify),
|
|
595
|
+
platform: asString(data.platform),
|
|
596
|
+
version: asString(data.version),
|
|
597
|
+
action: canonicalAction
|
|
598
|
+
};
|
|
599
|
+
};
|
|
600
|
+
const parseCallActionType = (raw) => {
|
|
601
|
+
const norm = normalizeDiscriminator(raw);
|
|
602
|
+
switch (norm) {
|
|
603
|
+
case 'offer':
|
|
604
|
+
return 'offer';
|
|
605
|
+
case 'pre_accept':
|
|
606
|
+
case 'preaccept':
|
|
607
|
+
return 'preAccept';
|
|
608
|
+
case 'accept':
|
|
609
|
+
return 'accept';
|
|
610
|
+
case 'reject':
|
|
611
|
+
return 'reject';
|
|
612
|
+
case 'terminate':
|
|
613
|
+
return 'terminate';
|
|
614
|
+
default:
|
|
615
|
+
return undefined;
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
// ── Group action adapter ──
|
|
619
|
+
const adaptGroupParticipant = (raw) => {
|
|
620
|
+
if (!isObject(raw))
|
|
621
|
+
return null;
|
|
622
|
+
const jid = asJidString(raw.jid);
|
|
623
|
+
if (!jid)
|
|
624
|
+
return null;
|
|
625
|
+
return { jid, phoneNumber: asJidString(raw.phone_number) };
|
|
626
|
+
};
|
|
627
|
+
const adaptGroupParticipants = (raw) => {
|
|
628
|
+
if (!Array.isArray(raw))
|
|
629
|
+
return [];
|
|
630
|
+
return raw.map(adaptGroupParticipant).filter((p) => p !== null);
|
|
631
|
+
};
|
|
632
|
+
const adaptGroupAction = (raw) => {
|
|
633
|
+
if (!isObject(raw))
|
|
634
|
+
return null;
|
|
635
|
+
const norm = normalizeDiscriminator(raw.type);
|
|
636
|
+
const rawType = asString(raw.type) ?? 'unknown';
|
|
637
|
+
if (!norm)
|
|
638
|
+
return { type: 'unknown', rawType };
|
|
639
|
+
switch (norm) {
|
|
640
|
+
case 'add':
|
|
641
|
+
return { type: 'add', participants: adaptGroupParticipants(raw.participants), reason: asString(raw.reason) };
|
|
642
|
+
case 'remove':
|
|
643
|
+
return { type: 'remove', participants: adaptGroupParticipants(raw.participants), reason: asString(raw.reason) };
|
|
644
|
+
case 'promote':
|
|
645
|
+
return { type: 'promote', participants: adaptGroupParticipants(raw.participants) };
|
|
646
|
+
case 'demote':
|
|
647
|
+
return { type: 'demote', participants: adaptGroupParticipants(raw.participants) };
|
|
648
|
+
case 'modify':
|
|
649
|
+
return { type: 'modify', participants: adaptGroupParticipants(raw.participants) };
|
|
650
|
+
case 'subject':
|
|
651
|
+
return {
|
|
652
|
+
type: 'subject',
|
|
653
|
+
subject: asString(raw.subject) ?? '',
|
|
654
|
+
subjectOwner: asJidString(raw.subject_owner),
|
|
655
|
+
subjectTime: asNumber(raw.subject_time)
|
|
656
|
+
};
|
|
657
|
+
case 'description':
|
|
658
|
+
return {
|
|
659
|
+
type: 'description',
|
|
660
|
+
id: asString(raw.id) ?? '',
|
|
661
|
+
description: asString(raw.description)
|
|
662
|
+
};
|
|
663
|
+
case 'locked':
|
|
664
|
+
return { type: 'locked' };
|
|
665
|
+
case 'unlocked':
|
|
666
|
+
return { type: 'unlocked' };
|
|
667
|
+
case 'announce':
|
|
668
|
+
case 'announcement':
|
|
669
|
+
return { type: 'announce' };
|
|
670
|
+
case 'not_announce':
|
|
671
|
+
case 'not_announcement':
|
|
672
|
+
case 'notannounce':
|
|
673
|
+
return { type: 'notAnnounce' };
|
|
674
|
+
case 'ephemeral': {
|
|
675
|
+
const expiration = asNumber(raw.expiration);
|
|
676
|
+
if (expiration == null)
|
|
677
|
+
return null;
|
|
678
|
+
return { type: 'ephemeral', expiration, trigger: asNumber(raw.trigger) };
|
|
679
|
+
}
|
|
680
|
+
case 'membership_approval_mode':
|
|
681
|
+
case 'membershipapprovalmode':
|
|
682
|
+
return { type: 'membershipApprovalMode', enabled: asBoolOr(raw.enabled, false) };
|
|
683
|
+
case 'member_add_mode':
|
|
684
|
+
case 'memberaddmode':
|
|
685
|
+
return { type: 'memberAddMode', mode: asString(raw.mode) ?? '' };
|
|
686
|
+
case 'no_frequently_forwarded':
|
|
687
|
+
case 'nofrequentlyforwarded':
|
|
688
|
+
return { type: 'noFrequentlyForwarded' };
|
|
689
|
+
case 'frequently_forwarded_ok':
|
|
690
|
+
case 'frequentlyforwardedok':
|
|
691
|
+
return { type: 'frequentlyForwardedOk' };
|
|
692
|
+
case 'invite':
|
|
693
|
+
return { type: 'invite', code: asString(raw.code) ?? '' };
|
|
694
|
+
case 'revoke':
|
|
695
|
+
case 'revoke_invite':
|
|
696
|
+
case 'revokeinvite':
|
|
697
|
+
return { type: 'revokeInvite' };
|
|
698
|
+
case 'create':
|
|
699
|
+
return { type: 'create' };
|
|
700
|
+
case 'delete':
|
|
701
|
+
return { type: 'delete', reason: asString(raw.reason) };
|
|
702
|
+
case 'link':
|
|
703
|
+
return { type: 'link', linkType: asString(raw.link_type) ?? '' };
|
|
704
|
+
case 'unlink':
|
|
705
|
+
return {
|
|
706
|
+
type: 'unlink',
|
|
707
|
+
unlinkType: asString(raw.unlink_type) ?? '',
|
|
708
|
+
unlinkReason: asString(raw.unlink_reason)
|
|
709
|
+
};
|
|
710
|
+
case 'growth_locked':
|
|
711
|
+
case 'growthlocked': {
|
|
712
|
+
const expiration = asNumber(raw.expiration);
|
|
713
|
+
if (expiration == null)
|
|
714
|
+
return null;
|
|
715
|
+
return { type: 'growthLocked', expiration, lockType: asString(raw.lock_type) ?? '' };
|
|
716
|
+
}
|
|
717
|
+
case 'growth_unlocked':
|
|
718
|
+
case 'growthunlocked':
|
|
719
|
+
return { type: 'growthUnlocked' };
|
|
720
|
+
case 'membership_approval_request':
|
|
721
|
+
return {
|
|
722
|
+
type: 'membershipApprovalRequest',
|
|
723
|
+
requestMethod: asString(raw.request_method),
|
|
724
|
+
parentGroupJid: asJidString(raw.parent_group_jid)
|
|
725
|
+
};
|
|
726
|
+
case 'created_membership_requests':
|
|
727
|
+
return {
|
|
728
|
+
type: 'createdMembershipRequests',
|
|
729
|
+
requestMethod: asString(raw.request_method),
|
|
730
|
+
parentGroupJid: asJidString(raw.parent_group_jid),
|
|
731
|
+
requests: adaptGroupParticipants(raw.requests)
|
|
732
|
+
};
|
|
733
|
+
case 'revoked_membership_requests':
|
|
734
|
+
return {
|
|
735
|
+
type: 'revokedMembershipRequests',
|
|
736
|
+
// `revoked_membership_requests` ships bare Jids (not full
|
|
737
|
+
// participant infos), so wrap each into the canonical
|
|
738
|
+
// participant shape with no `phoneNumber`.
|
|
739
|
+
participants: Array.isArray(raw.participants)
|
|
740
|
+
? raw.participants
|
|
741
|
+
.map(j => {
|
|
742
|
+
const jid = asJidString(j);
|
|
743
|
+
return jid ? { jid } : null;
|
|
744
|
+
})
|
|
745
|
+
.filter((p) => p !== null)
|
|
746
|
+
: []
|
|
747
|
+
};
|
|
748
|
+
default:
|
|
749
|
+
return { type: 'unknown', rawType };
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
const adaptGroupUpdate = (data, logger) => {
|
|
753
|
+
if (!isObject(data))
|
|
754
|
+
return null;
|
|
755
|
+
const groupJid = asJidString(data.group_jid);
|
|
756
|
+
if (!groupJid)
|
|
757
|
+
return null;
|
|
758
|
+
const action = adaptGroupAction(data.action);
|
|
759
|
+
if (!action) {
|
|
760
|
+
logger?.warn({ data }, 'group_update adapter: action shape rejected');
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
type: 'groupUpdate',
|
|
765
|
+
groupJid,
|
|
766
|
+
author: asJidString(data.participant),
|
|
767
|
+
authorPn: asJidString(data.participant_pn),
|
|
768
|
+
timestamp: toUnixSeconds(data.timestamp),
|
|
769
|
+
isLidAddressingMode: asBoolOr(data.is_lid_addressing_mode, false),
|
|
770
|
+
action
|
|
771
|
+
};
|
|
772
|
+
};
|
|
773
|
+
//# sourceMappingURL=schema.js.map
|