@oxidezap/baileyrs 0.1.0 → 0.1.2
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/README.md +70 -0
- package/lib/Compatibility/legacy-store/namespaces.d.ts +20 -0
- package/lib/Compatibility/legacy-store/namespaces.js +27 -0
- package/lib/Compatibility/legacy-store/routing.js +13 -8
- package/lib/Compatibility/newsletter-results.d.ts +15 -0
- package/lib/Compatibility/newsletter-results.js +38 -0
- package/lib/Socket/business.d.ts +29 -0
- package/lib/Socket/business.js +104 -0
- package/lib/Socket/chat-actions.d.ts +20 -11
- package/lib/Socket/chat-actions.js +171 -83
- package/lib/Socket/events.js +5 -5
- package/lib/Socket/index.d.ts +85 -14
- package/lib/Socket/index.js +36 -26
- package/lib/Socket/internals.d.ts +88 -0
- package/lib/Socket/internals.js +145 -0
- package/lib/Socket/messages.d.ts +1 -15
- package/lib/Socket/messages.js +3 -22
- package/lib/Socket/newsletter.d.ts +61 -6
- package/lib/Socket/newsletter.js +125 -7
- package/lib/Socket/privacy.d.ts +25 -0
- package/lib/Socket/privacy.js +54 -0
- package/lib/Socket/server-queries.d.ts +38 -0
- package/lib/Socket/server-queries.js +121 -0
- package/lib/Socket/types.d.ts +6 -0
- package/lib/Types/Product.d.ts +9 -0
- package/lib/Utils/index.d.ts +1 -0
- package/lib/Utils/index.js +3 -0
- package/lib/Utils/link-preview.d.ts +60 -0
- package/lib/Utils/link-preview.js +357 -0
- package/lib/Utils/messages.d.ts +20 -7
- package/lib/Utils/messages.js +14 -3
- package/lib/Utils/wrap-legacy-store.d.ts +1 -0
- package/lib/Utils/wrap-legacy-store.js +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { BinaryNode, MessageUpsertType, WAMessage, WAPatchCreate } from '../Types/index.js';
|
|
2
|
+
import type { ILogger } from '../Utils/logger.js';
|
|
3
|
+
import type { SocketContext } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* The one place an unexpected failure is reported, shared by the socket's
|
|
6
|
+
* `onUnexpectedError` property and by the internal paths that raise them.
|
|
7
|
+
*
|
|
8
|
+
* `report` cannot throw. Some of its callers are bridge callbacks that borrow
|
|
9
|
+
* memory and are contractually forbidden from raising, so a consumer whose
|
|
10
|
+
* handler throws would cost the session its borrowed batches. A handler that
|
|
11
|
+
* fails is reported through the logger and the caller carries on.
|
|
12
|
+
*
|
|
13
|
+
* The return type says `void`, which an `async` handler also satisfies, so a
|
|
14
|
+
* handler that rejects rather than throws is contained the same way. Left
|
|
15
|
+
* loose it would surface as an unhandled rejection, which ends the process
|
|
16
|
+
* under `--unhandled-rejections=strict`.
|
|
17
|
+
*/
|
|
18
|
+
export declare const makeUnexpectedErrorReporter: (logger: ILogger) => {
|
|
19
|
+
report: (err: unknown, msg: string) => void;
|
|
20
|
+
handler: (err: unknown, msg: string) => void;
|
|
21
|
+
};
|
|
22
|
+
export declare const makeInternalMethods: (ctx: SocketContext) => {
|
|
23
|
+
/**
|
|
24
|
+
* `waitForSocket`, not `waitForConnected`: upstream waits for the socket
|
|
25
|
+
* to open, which is not the same as being logged in, and the bridge
|
|
26
|
+
* separates the two.
|
|
27
|
+
*
|
|
28
|
+
* A rejection means the socket did not open within one connect attempt,
|
|
29
|
+
* not that it never will: the engine reconnects on its own, and a caller
|
|
30
|
+
* that wants the next attempt waits again.
|
|
31
|
+
*/
|
|
32
|
+
waitForSocketOpen: () => Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Publishes a message onto the event bus, which is this layer's own job.
|
|
35
|
+
* Buffered as upstream buffers it, so a replay of many messages
|
|
36
|
+
* consolidates into the batches a listener expects rather than one
|
|
37
|
+
* event per call.
|
|
38
|
+
*
|
|
39
|
+
* The push-name half of upstream's version is not reproduced: contact
|
|
40
|
+
* state belongs to the core, and writing it from here would make two
|
|
41
|
+
* writers for one value.
|
|
42
|
+
*/
|
|
43
|
+
upsertMessage: (msg: WAMessage, type: MessageUpsertType) => Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* The same reporter the socket's own failure paths use, rather than a
|
|
46
|
+
* second one that only a consumer could reach: a dispatcher that threw
|
|
47
|
+
* or a wire batch that would not decode arrives here. The socket
|
|
48
|
+
* redefines this as an accessor, so assigning to it replaces the
|
|
49
|
+
* handler those paths report through.
|
|
50
|
+
*/
|
|
51
|
+
onUnexpectedError: (err: Error, msg: string) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Resolves without forcing anything, and says so once.
|
|
54
|
+
*
|
|
55
|
+
* The engine syncs app state on connect and re-syncs every collection
|
|
56
|
+
* when the server raises the `syncd_app_state` dirty bit, so the state a
|
|
57
|
+
* caller wants current already is. There is no on-demand resync to
|
|
58
|
+
* delegate to, and rejecting would break a call whose intent is met.
|
|
59
|
+
*/
|
|
60
|
+
resyncAppState: (collections?: readonly ('critical_block' | 'critical_unblock_low' | 'regular_high' | 'regular_low' | 'regular')[], isInitialSync?: boolean) => Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Refused: the offset is protocol state the core keeps from the stanzas
|
|
63
|
+
* it already reads. A second clock here would drift from the one that
|
|
64
|
+
* actually timestamps outgoing messages.
|
|
65
|
+
*/
|
|
66
|
+
updateServerTimeOffset: (node: BinaryNode) => never;
|
|
67
|
+
/**
|
|
68
|
+
* Refused one layer down. The core's session instance is `pub(crate)`,
|
|
69
|
+
* and what exists returns a half-built stanza plus a sequence number, so
|
|
70
|
+
* exposing it would hand JavaScript the job of finishing and sequencing
|
|
71
|
+
* a lifecycle stanza.
|
|
72
|
+
*/
|
|
73
|
+
sendUnifiedSession: () => Promise<never>;
|
|
74
|
+
/**
|
|
75
|
+
* Refused: the bridge exposes app-state actions typed per action and
|
|
76
|
+
* deliberately no generic. Accepting a raw patch here would mean
|
|
77
|
+
* building mutation indices and schema versions in TypeScript, beside
|
|
78
|
+
* the copy the core already maintains.
|
|
79
|
+
*/
|
|
80
|
+
appPatch: (patchCreate: WAPatchCreate) => Promise<never>;
|
|
81
|
+
/**
|
|
82
|
+
* Null rather than absent, and rather than a shadow. Retry is the
|
|
83
|
+
* engine's, so there is no manager here to hand out, and saying so in
|
|
84
|
+
* the type is more useful than leaving the field missing.
|
|
85
|
+
*/
|
|
86
|
+
messageRetryManager: null;
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=internals.d.ts.map
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Boom } from '../Utils/boom.js';
|
|
2
|
+
/**
|
|
3
|
+
* The one place an unexpected failure is reported, shared by the socket's
|
|
4
|
+
* `onUnexpectedError` property and by the internal paths that raise them.
|
|
5
|
+
*
|
|
6
|
+
* `report` cannot throw. Some of its callers are bridge callbacks that borrow
|
|
7
|
+
* memory and are contractually forbidden from raising, so a consumer whose
|
|
8
|
+
* handler throws would cost the session its borrowed batches. A handler that
|
|
9
|
+
* fails is reported through the logger and the caller carries on.
|
|
10
|
+
*
|
|
11
|
+
* The return type says `void`, which an `async` handler also satisfies, so a
|
|
12
|
+
* handler that rejects rather than throws is contained the same way. Left
|
|
13
|
+
* loose it would surface as an unhandled rejection, which ends the process
|
|
14
|
+
* under `--unhandled-rejections=strict`.
|
|
15
|
+
*/
|
|
16
|
+
export const makeUnexpectedErrorReporter = (logger) => {
|
|
17
|
+
const logUnexpected = (err, msg) => logger.error({ err }, `unexpected error in '${msg}'`);
|
|
18
|
+
const logHandlerFailure = (err, msg, reportingError) => {
|
|
19
|
+
logger.error({ err: reportingError }, 'the onUnexpectedError handler threw');
|
|
20
|
+
logUnexpected(err, msg);
|
|
21
|
+
};
|
|
22
|
+
let handler = logUnexpected;
|
|
23
|
+
return {
|
|
24
|
+
report: (err, msg) => {
|
|
25
|
+
try {
|
|
26
|
+
const settled = handler(err, msg);
|
|
27
|
+
if (settled instanceof Promise) {
|
|
28
|
+
settled.catch(reportingError => logHandlerFailure(err, msg, reportingError));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (reportingError) {
|
|
32
|
+
logHandlerFailure(err, msg, reportingError);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
get handler() {
|
|
36
|
+
return handler;
|
|
37
|
+
},
|
|
38
|
+
set handler(next) {
|
|
39
|
+
handler = next;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Upstream has no timeout on `waitForSocketOpen`; the bridge requires one, so
|
|
45
|
+
* one has to be chosen.
|
|
46
|
+
*
|
|
47
|
+
* It is the core's own transport connect ceiling, not `connectTimeoutMs`. That
|
|
48
|
+
* config reaches neither the transport nor the core, so a value below the
|
|
49
|
+
* ceiling would reject a wait while the attempt behind it was still running and
|
|
50
|
+
* about to succeed. A value above it would sit past the point the attempt was
|
|
51
|
+
* already abandoned.
|
|
52
|
+
*/
|
|
53
|
+
const TRANSPORT_CONNECT_TIMEOUT_MS = 20000;
|
|
54
|
+
export const makeInternalMethods = (ctx) => {
|
|
55
|
+
/** Warned once per socket rather than per call, as with the other no-ops. */
|
|
56
|
+
let warnedAboutResync = false;
|
|
57
|
+
return {
|
|
58
|
+
/**
|
|
59
|
+
* `waitForSocket`, not `waitForConnected`: upstream waits for the socket
|
|
60
|
+
* to open, which is not the same as being logged in, and the bridge
|
|
61
|
+
* separates the two.
|
|
62
|
+
*
|
|
63
|
+
* A rejection means the socket did not open within one connect attempt,
|
|
64
|
+
* not that it never will: the engine reconnects on its own, and a caller
|
|
65
|
+
* that wants the next attempt waits again.
|
|
66
|
+
*/
|
|
67
|
+
waitForSocketOpen: async () => {
|
|
68
|
+
await (await ctx.getClient()).waitForSocket(TRANSPORT_CONNECT_TIMEOUT_MS);
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* Publishes a message onto the event bus, which is this layer's own job.
|
|
72
|
+
* Buffered as upstream buffers it, so a replay of many messages
|
|
73
|
+
* consolidates into the batches a listener expects rather than one
|
|
74
|
+
* event per call.
|
|
75
|
+
*
|
|
76
|
+
* The push-name half of upstream's version is not reproduced: contact
|
|
77
|
+
* state belongs to the core, and writing it from here would make two
|
|
78
|
+
* writers for one value.
|
|
79
|
+
*/
|
|
80
|
+
upsertMessage: ctx.ev.createBufferedFunction(async (msg, type) => {
|
|
81
|
+
ctx.ev.emit('messages.upsert', { messages: [msg], type });
|
|
82
|
+
}),
|
|
83
|
+
/**
|
|
84
|
+
* The same reporter the socket's own failure paths use, rather than a
|
|
85
|
+
* second one that only a consumer could reach: a dispatcher that threw
|
|
86
|
+
* or a wire batch that would not decode arrives here. The socket
|
|
87
|
+
* redefines this as an accessor, so assigning to it replaces the
|
|
88
|
+
* handler those paths report through.
|
|
89
|
+
*/
|
|
90
|
+
onUnexpectedError: (err, msg) => {
|
|
91
|
+
ctx.reportUnexpectedError(err, msg);
|
|
92
|
+
},
|
|
93
|
+
/**
|
|
94
|
+
* Resolves without forcing anything, and says so once.
|
|
95
|
+
*
|
|
96
|
+
* The engine syncs app state on connect and re-syncs every collection
|
|
97
|
+
* when the server raises the `syncd_app_state` dirty bit, so the state a
|
|
98
|
+
* caller wants current already is. There is no on-demand resync to
|
|
99
|
+
* delegate to, and rejecting would break a call whose intent is met.
|
|
100
|
+
*/
|
|
101
|
+
resyncAppState: async (collections, isInitialSync) => {
|
|
102
|
+
void collections;
|
|
103
|
+
void isInitialSync;
|
|
104
|
+
if (!warnedAboutResync) {
|
|
105
|
+
warnedAboutResync = true;
|
|
106
|
+
ctx.logger.warn('resyncAppState is a no-op: the engine syncs app state on connect and again whenever the server marks it dirty');
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
/**
|
|
110
|
+
* Refused: the offset is protocol state the core keeps from the stanzas
|
|
111
|
+
* it already reads. A second clock here would drift from the one that
|
|
112
|
+
* actually timestamps outgoing messages.
|
|
113
|
+
*/
|
|
114
|
+
updateServerTimeOffset: (node) => {
|
|
115
|
+
void node;
|
|
116
|
+
throw new Boom('updateServerTimeOffset is not supported: the engine tracks the server clock offset itself, and a second one here would diverge from the one it stamps messages with', { statusCode: 501 });
|
|
117
|
+
},
|
|
118
|
+
/**
|
|
119
|
+
* Refused one layer down. The core's session instance is `pub(crate)`,
|
|
120
|
+
* and what exists returns a half-built stanza plus a sequence number, so
|
|
121
|
+
* exposing it would hand JavaScript the job of finishing and sequencing
|
|
122
|
+
* a lifecycle stanza.
|
|
123
|
+
*/
|
|
124
|
+
sendUnifiedSession: async () => {
|
|
125
|
+
throw new Boom('sendUnifiedSession is not supported: it is connection lifecycle machinery the engine owns, not a consumer operation', { statusCode: 501 });
|
|
126
|
+
},
|
|
127
|
+
/**
|
|
128
|
+
* Refused: the bridge exposes app-state actions typed per action and
|
|
129
|
+
* deliberately no generic. Accepting a raw patch here would mean
|
|
130
|
+
* building mutation indices and schema versions in TypeScript, beside
|
|
131
|
+
* the copy the core already maintains.
|
|
132
|
+
*/
|
|
133
|
+
appPatch: async (patchCreate) => {
|
|
134
|
+
void patchCreate;
|
|
135
|
+
throw new Boom('appPatch is not supported: use the typed chatModify variants, which is the only app-state surface this package can offer without a second implementation of the patch format', { statusCode: 501 });
|
|
136
|
+
},
|
|
137
|
+
/**
|
|
138
|
+
* Null rather than absent, and rather than a shadow. Retry is the
|
|
139
|
+
* engine's, so there is no manager here to hand out, and saying so in
|
|
140
|
+
* the type is more useful than leaving the field missing.
|
|
141
|
+
*/
|
|
142
|
+
messageRetryManager: null
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=internals.js.map
|
package/lib/Socket/messages.d.ts
CHANGED
|
@@ -1,20 +1,6 @@
|
|
|
1
|
-
import type { AnyMessageContent, MessageGenerationOptions, MessageReceiptType, MessageRelayOptions, WAMessage,
|
|
1
|
+
import type { AnyMessageContent, MessageGenerationOptions, MessageReceiptType, MessageRelayOptions, WAMessage, WAMessageKey } from '../Types/index.js';
|
|
2
2
|
import { WAProto } from '../Types/index.js';
|
|
3
3
|
import type { SocketContext } from './types.js';
|
|
4
|
-
/**
|
|
5
|
-
* Drop `messageContextInfo` before handing the proto to the Rust bridge so the
|
|
6
|
-
* bridge can fill in its own `messageSecret` / `reportingTokenVersion`.
|
|
7
|
-
*
|
|
8
|
-
* Exception: pin messages need `messageAddOnDurationInSecs` (86400 / 604800 /
|
|
9
|
-
* 2592000 to pin, 0 to unpin). The bridge does not set this field, so we save
|
|
10
|
-
* it across the delete and restore it on a fresh contextInfo.
|
|
11
|
-
*
|
|
12
|
-
* Mutates `msg` in place.
|
|
13
|
-
*
|
|
14
|
-
* `contentType` is a parameter so the send path can hand over the type it has
|
|
15
|
-
* already resolved rather than have it scanned out of `msg` a second time.
|
|
16
|
-
*/
|
|
17
|
-
export declare function stripContextInfoForBridge(msg: WAMessageContent, contentType?: keyof WAProto.IMessage | undefined): void;
|
|
18
4
|
export declare const makeMessageMethods: (ctx: SocketContext) => {
|
|
19
5
|
sendMessage: (jid: string, content: AnyMessageContent, options?: Omit<MessageGenerationOptions, 'waClient' | 'logger' | 'userJid' | 'mediaInNote'>) => Promise<WAMessage>;
|
|
20
6
|
updateMediaMessage: (message: WAMessage) => Promise<WAMessage>;
|
package/lib/Socket/messages.js
CHANGED
|
@@ -13,26 +13,6 @@ function getMediaContent(content) {
|
|
|
13
13
|
content?.documentMessage ||
|
|
14
14
|
content?.stickerMessage);
|
|
15
15
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Drop `messageContextInfo` before handing the proto to the Rust bridge so the
|
|
18
|
-
* bridge can fill in its own `messageSecret` / `reportingTokenVersion`.
|
|
19
|
-
*
|
|
20
|
-
* Exception: pin messages need `messageAddOnDurationInSecs` (86400 / 604800 /
|
|
21
|
-
* 2592000 to pin, 0 to unpin). The bridge does not set this field, so we save
|
|
22
|
-
* it across the delete and restore it on a fresh contextInfo.
|
|
23
|
-
*
|
|
24
|
-
* Mutates `msg` in place.
|
|
25
|
-
*
|
|
26
|
-
* `contentType` is a parameter so the send path can hand over the type it has
|
|
27
|
-
* already resolved rather than have it scanned out of `msg` a second time.
|
|
28
|
-
*/
|
|
29
|
-
export function stripContextInfoForBridge(msg, contentType = getContentType(msg)) {
|
|
30
|
-
const pinAddOnDuration = contentType === 'pinInChatMessage' ? msg.messageContextInfo?.messageAddOnDurationInSecs : undefined;
|
|
31
|
-
delete msg.messageContextInfo;
|
|
32
|
-
if (pinAddOnDuration !== undefined && pinAddOnDuration !== null) {
|
|
33
|
-
msg.messageContextInfo = { messageAddOnDurationInSecs: pinAddOnDuration };
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
16
|
const normalizedUserJids = new WeakMap();
|
|
37
17
|
const getNormalizedUserJid = (ctx) => {
|
|
38
18
|
const userId = ctx.getUser()?.id;
|
|
@@ -87,7 +67,6 @@ export const makeMessageMethods = (ctx) => ({
|
|
|
87
67
|
return fullMsg;
|
|
88
68
|
}
|
|
89
69
|
}
|
|
90
|
-
stripContextInfoForBridge(msg, contentType);
|
|
91
70
|
let msgId;
|
|
92
71
|
const msgBytes = encodeProto('Message', msg);
|
|
93
72
|
if (jid === 'status@broadcast' && options?.statusJidList?.length) {
|
|
@@ -155,7 +134,9 @@ export const makeMessageMethods = (ctx) => ({
|
|
|
155
134
|
messageId: plan.messageId,
|
|
156
135
|
extraNodes: plan.kind === 'retransmission' ? 0 : plan.nodes.length
|
|
157
136
|
}, 'relayMessage compatibility plan');
|
|
158
|
-
|
|
137
|
+
// The message goes to the bridge as the caller built it: the core settles
|
|
138
|
+
// messageSecret / reportingTokenVersion itself, reusing a caller-set secret
|
|
139
|
+
// rather than replacing it, so nothing here has to be dropped.
|
|
159
140
|
const bytes = encodeProto('Message', message);
|
|
160
141
|
if (plan.kind === 'retransmission') {
|
|
161
142
|
await client.retransmitMessageBytes(jid, bytes, plan.input);
|
|
@@ -1,14 +1,69 @@
|
|
|
1
|
+
import type { NewsletterMetadataResult } from '@oxidezap/whatsapp-rust-bridge';
|
|
2
|
+
import type { NewsletterMetadata, NewsletterUpdate } from '../Types/Newsletter.js';
|
|
3
|
+
import type { WAMediaUpload } from '../Types/index.js';
|
|
1
4
|
import type { SocketContext } from './types.js';
|
|
2
5
|
export declare const makeNewsletterMethods: (ctx: SocketContext) => {
|
|
3
|
-
newsletterCreate: (name: string, description?: string) => Promise<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
newsletterCreate: (name: string, description?: string) => Promise<NewsletterMetadata>;
|
|
7
|
+
/**
|
|
8
|
+
* `type` selects which lookup runs: the bridge has a method per key kind
|
|
9
|
+
* rather than one that inspects the key. Resolves to null when the
|
|
10
|
+
* newsletter does not exist, matching upstream.
|
|
11
|
+
*/
|
|
12
|
+
newsletterMetadata: (type: 'invite' | 'jid', key: string) => Promise<NewsletterMetadata | null>;
|
|
13
|
+
/**
|
|
14
|
+
* `picture` is base64 of an already-generated image, and the empty string
|
|
15
|
+
* means remove, as upstream builds it. The core splits those into two
|
|
16
|
+
* methods, so the field is dispatched rather than forwarded.
|
|
17
|
+
*/
|
|
18
|
+
newsletterUpdate: (jid: string, updates: NewsletterUpdate) => Promise<NewsletterMetadata>;
|
|
19
|
+
newsletterUpdateName: (jid: string, name: string) => Promise<NewsletterMetadata>;
|
|
20
|
+
newsletterUpdateDescription: (jid: string, description: string) => Promise<NewsletterMetadata>;
|
|
21
|
+
newsletterUpdatePicture: (jid: string, content: WAMediaUpload) => Promise<NewsletterMetadata>;
|
|
22
|
+
newsletterRemovePicture: (jid: string) => Promise<NewsletterMetadata>;
|
|
23
|
+
newsletterFollow: (jid: string) => Promise<NewsletterMetadata>;
|
|
24
|
+
newsletterUnfollow: (jid: string) => Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* The names this package used before it grew upstream's. Kept so existing
|
|
27
|
+
* callers do not break on a rename that buys them nothing, and returning
|
|
28
|
+
* the bridge result unmapped for the same reason: a caller reading `jid`
|
|
29
|
+
* or `subscriberCount` off it would find them renamed otherwise.
|
|
30
|
+
* `newsletterFollow` is the one that speaks upstream's shape.
|
|
31
|
+
*/
|
|
32
|
+
newsletterSubscribe: (jid: string) => Promise<NewsletterMetadataResult>;
|
|
6
33
|
newsletterUnsubscribe: (jid: string) => Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* The follower-activity mute, which is the one a subscriber toggles. The
|
|
36
|
+
* core's other newsletter mute is for admin activity and is a different
|
|
37
|
+
* control, so the ambiguous alias is avoided here.
|
|
38
|
+
*/
|
|
39
|
+
newsletterMute: (jid: string) => Promise<void>;
|
|
40
|
+
newsletterUnmute: (jid: string) => Promise<void>;
|
|
41
|
+
newsletterSubscribers: (jid: string) => Promise<{
|
|
42
|
+
subscribers: number;
|
|
43
|
+
}>;
|
|
7
44
|
newsletterReactMessage: (jid: string, serverId: string, reaction?: string) => Promise<void>;
|
|
8
45
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
46
|
+
* `since` and `after` have no equivalent: the core's query pages backward
|
|
47
|
+
* from a `before` cursor and carries no time filter. Mapping `after` onto
|
|
48
|
+
* `before` would page the opposite direction and return a plausible wrong
|
|
49
|
+
* answer, so a caller asking for either is told instead.
|
|
50
|
+
*
|
|
51
|
+
* Zero is not asking. `since: 0` is the epoch and `after: 0` is no cursor,
|
|
52
|
+
* which is what the unfiltered query already does, so the common
|
|
53
|
+
* `(jid, count, 0, 0)` call runs rather than being refused for nothing.
|
|
54
|
+
*/
|
|
55
|
+
newsletterFetchMessages: (jid: string, count: number, since?: number, after?: number) => Promise<import("@oxidezap/whatsapp-rust-bridge").NewsletterMessageResult[]>;
|
|
56
|
+
subscribeNewsletterUpdates: (jid: string) => Promise<{
|
|
57
|
+
duration: string;
|
|
58
|
+
}>;
|
|
59
|
+
/**
|
|
60
|
+
* The count rides on the admin-info result and the server omits it for an
|
|
61
|
+
* account that may not see it. Absent is reported as absent: `0` here would
|
|
62
|
+
* read as "no admins", which no newsletter can be.
|
|
11
63
|
*/
|
|
12
|
-
|
|
64
|
+
newsletterAdminCount: (jid: string) => Promise<number>;
|
|
65
|
+
newsletterChangeOwner: (jid: string, newOwnerJid: string) => Promise<void>;
|
|
66
|
+
newsletterDemote: (jid: string, userJid: string) => Promise<void>;
|
|
67
|
+
newsletterDelete: (jid: string) => Promise<void>;
|
|
13
68
|
};
|
|
14
69
|
//# sourceMappingURL=newsletter.d.ts.map
|
package/lib/Socket/newsletter.js
CHANGED
|
@@ -1,25 +1,143 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { bridgeNewsletterMetadataToBaileys } from '../Compatibility/newsletter-results.js';
|
|
3
|
+
import { Boom } from '../Utils/boom.js';
|
|
4
|
+
import { generateProfilePicture } from '../Utils/messages-media.js';
|
|
1
5
|
export const makeNewsletterMethods = (ctx) => ({
|
|
2
6
|
newsletterCreate: async (name, description) => {
|
|
3
|
-
return await (await ctx.getClient()).newsletterCreate(name, description);
|
|
7
|
+
return bridgeNewsletterMetadataToBaileys(await (await ctx.getClient()).newsletterCreate(name, description ?? null));
|
|
4
8
|
},
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
/**
|
|
10
|
+
* `type` selects which lookup runs: the bridge has a method per key kind
|
|
11
|
+
* rather than one that inspects the key. Resolves to null when the
|
|
12
|
+
* newsletter does not exist, matching upstream.
|
|
13
|
+
*/
|
|
14
|
+
newsletterMetadata: async (type, key) => {
|
|
15
|
+
if (type !== 'invite' && type !== 'jid') {
|
|
16
|
+
throw new Boom(`newsletterMetadata: unknown key type '${type}'`, { statusCode: 400 });
|
|
17
|
+
}
|
|
18
|
+
const client = await ctx.getClient();
|
|
19
|
+
const result = type === 'invite' ? await client.newsletterMetadataByInvite(key) : await client.newsletterMetadata(key);
|
|
20
|
+
return result ? bridgeNewsletterMetadataToBaileys(result) : null;
|
|
21
|
+
},
|
|
22
|
+
/**
|
|
23
|
+
* `picture` is base64 of an already-generated image, and the empty string
|
|
24
|
+
* means remove, as upstream builds it. The core splits those into two
|
|
25
|
+
* methods, so the field is dispatched rather than forwarded.
|
|
26
|
+
*/
|
|
27
|
+
newsletterUpdate: async (jid, updates) => {
|
|
28
|
+
const client = await ctx.getClient();
|
|
29
|
+
let result;
|
|
30
|
+
if (updates.name !== undefined || updates.description !== undefined) {
|
|
31
|
+
result = await client.newsletterUpdate(jid, updates.name ?? null, updates.description ?? null);
|
|
32
|
+
}
|
|
33
|
+
if (updates.picture === '') {
|
|
34
|
+
result = await client.newsletterRemovePicture(jid);
|
|
35
|
+
}
|
|
36
|
+
else if (updates.picture !== undefined) {
|
|
37
|
+
result = await client.newsletterSetPicture(jid, new Uint8Array(Buffer.from(updates.picture, 'base64')));
|
|
38
|
+
}
|
|
39
|
+
// Only when the delta asked for nothing: every write above already
|
|
40
|
+
// answers with the refreshed metadata, so reading it would be a round
|
|
41
|
+
// trip whose result is thrown away.
|
|
42
|
+
return bridgeNewsletterMetadataToBaileys(result ?? (await client.newsletterMetadata(jid)));
|
|
43
|
+
},
|
|
44
|
+
newsletterUpdateName: async (jid, name) => {
|
|
45
|
+
return bridgeNewsletterMetadataToBaileys(await (await ctx.getClient()).newsletterUpdate(jid, name, null));
|
|
46
|
+
},
|
|
47
|
+
newsletterUpdateDescription: async (jid, description) => {
|
|
48
|
+
return bridgeNewsletterMetadataToBaileys(await (await ctx.getClient()).newsletterUpdate(jid, null, description));
|
|
7
49
|
},
|
|
50
|
+
newsletterUpdatePicture: async (jid, content) => {
|
|
51
|
+
const { img } = await generateProfilePicture(content);
|
|
52
|
+
return bridgeNewsletterMetadataToBaileys(await (await ctx.getClient()).newsletterSetPicture(jid, img));
|
|
53
|
+
},
|
|
54
|
+
newsletterRemovePicture: async (jid) => {
|
|
55
|
+
return bridgeNewsletterMetadataToBaileys(await (await ctx.getClient()).newsletterRemovePicture(jid));
|
|
56
|
+
},
|
|
57
|
+
newsletterFollow: async (jid) => {
|
|
58
|
+
return bridgeNewsletterMetadataToBaileys(await (await ctx.getClient()).newsletterSubscribe(jid));
|
|
59
|
+
},
|
|
60
|
+
newsletterUnfollow: async (jid) => {
|
|
61
|
+
await (await ctx.getClient()).newsletterUnsubscribe(jid);
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* The names this package used before it grew upstream's. Kept so existing
|
|
65
|
+
* callers do not break on a rename that buys them nothing, and returning
|
|
66
|
+
* the bridge result unmapped for the same reason: a caller reading `jid`
|
|
67
|
+
* or `subscriberCount` off it would find them renamed otherwise.
|
|
68
|
+
* `newsletterFollow` is the one that speaks upstream's shape.
|
|
69
|
+
*/
|
|
8
70
|
newsletterSubscribe: async (jid) => {
|
|
9
71
|
return await (await ctx.getClient()).newsletterSubscribe(jid);
|
|
10
72
|
},
|
|
11
73
|
newsletterUnsubscribe: async (jid) => {
|
|
12
74
|
await (await ctx.getClient()).newsletterUnsubscribe(jid);
|
|
13
75
|
},
|
|
76
|
+
/**
|
|
77
|
+
* The follower-activity mute, which is the one a subscriber toggles. The
|
|
78
|
+
* core's other newsletter mute is for admin activity and is a different
|
|
79
|
+
* control, so the ambiguous alias is avoided here.
|
|
80
|
+
*/
|
|
81
|
+
newsletterMute: async (jid) => {
|
|
82
|
+
await (await ctx.getClient()).newsletterFollowerMute(jid, true);
|
|
83
|
+
},
|
|
84
|
+
newsletterUnmute: async (jid) => {
|
|
85
|
+
await (await ctx.getClient()).newsletterFollowerMute(jid, false);
|
|
86
|
+
},
|
|
87
|
+
newsletterSubscribers: async (jid) => {
|
|
88
|
+
const metadata = await (await ctx.getClient()).newsletterMetadata(jid);
|
|
89
|
+
return { subscribers: metadata.subscriberCount };
|
|
90
|
+
},
|
|
14
91
|
newsletterReactMessage: async (jid, serverId, reaction) => {
|
|
15
92
|
await (await ctx.getClient()).newsletterReactMessage(jid, serverId, reaction ?? null);
|
|
16
93
|
},
|
|
17
94
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
95
|
+
* `since` and `after` have no equivalent: the core's query pages backward
|
|
96
|
+
* from a `before` cursor and carries no time filter. Mapping `after` onto
|
|
97
|
+
* `before` would page the opposite direction and return a plausible wrong
|
|
98
|
+
* answer, so a caller asking for either is told instead.
|
|
99
|
+
*
|
|
100
|
+
* Zero is not asking. `since: 0` is the epoch and `after: 0` is no cursor,
|
|
101
|
+
* which is what the unfiltered query already does, so the common
|
|
102
|
+
* `(jid, count, 0, 0)` call runs rather than being refused for nothing.
|
|
20
103
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
104
|
+
newsletterFetchMessages: async (jid, count, since, after) => {
|
|
105
|
+
if (since) {
|
|
106
|
+
throw new Boom('newsletterFetchMessages: `since` is not supported, the message query carries no time filter', {
|
|
107
|
+
statusCode: 400
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (after) {
|
|
111
|
+
throw new Boom('newsletterFetchMessages: `after` is not supported, the message query pages backward from a `before` cursor', { statusCode: 400 });
|
|
112
|
+
}
|
|
113
|
+
return await (await ctx.getClient()).newsletterMessages(jid, count, null);
|
|
114
|
+
},
|
|
115
|
+
subscribeNewsletterUpdates: async (jid) => {
|
|
116
|
+
const duration = await (await ctx.getClient()).newsletterSubscribeLiveUpdates(jid);
|
|
117
|
+
return { duration: String(duration) };
|
|
118
|
+
},
|
|
119
|
+
/**
|
|
120
|
+
* The count rides on the admin-info result and the server omits it for an
|
|
121
|
+
* account that may not see it. Absent is reported as absent: `0` here would
|
|
122
|
+
* read as "no admins", which no newsletter can be.
|
|
123
|
+
*/
|
|
124
|
+
newsletterAdminCount: async (jid) => {
|
|
125
|
+
const info = await (await ctx.getClient()).newsletterAdminInfo(jid);
|
|
126
|
+
if (info.adminCount === undefined) {
|
|
127
|
+
throw new Boom('newsletterAdminCount: the server did not return an admin count for this newsletter', {
|
|
128
|
+
statusCode: 404
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return info.adminCount;
|
|
132
|
+
},
|
|
133
|
+
newsletterChangeOwner: async (jid, newOwnerJid) => {
|
|
134
|
+
await (await ctx.getClient()).newsletterChangeOwner(jid, newOwnerJid);
|
|
135
|
+
},
|
|
136
|
+
newsletterDemote: async (jid, userJid) => {
|
|
137
|
+
await (await ctx.getClient()).newsletterDemoteAdmin(jid, userJid);
|
|
138
|
+
},
|
|
139
|
+
newsletterDelete: async (jid) => {
|
|
140
|
+
await (await ctx.getClient()).newsletterDelete(jid);
|
|
23
141
|
}
|
|
24
142
|
});
|
|
25
143
|
//# sourceMappingURL=newsletter.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WAPrivacyCallValue, WAPrivacyGroupAddValue, WAPrivacyMessagesValue, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '../Types/index.js';
|
|
2
|
+
import type { SocketContext } from './types.js';
|
|
3
|
+
export declare const makePrivacyMethods: (ctx: SocketContext) => {
|
|
4
|
+
fetchPrivacySettings: (force?: boolean) => Promise<any>;
|
|
5
|
+
updatePrivacySetting: (category: string, value: string) => Promise<void>;
|
|
6
|
+
updateLastSeenPrivacy: (value: WAPrivacyValue) => Promise<void>;
|
|
7
|
+
updateOnlinePrivacy: (value: WAPrivacyOnlineValue) => Promise<void>;
|
|
8
|
+
updateProfilePicturePrivacy: (value: WAPrivacyValue) => Promise<void>;
|
|
9
|
+
updateStatusPrivacy: (value: WAPrivacyValue) => Promise<void>;
|
|
10
|
+
updateReadReceiptsPrivacy: (value: WAReadReceiptsValue) => Promise<void>;
|
|
11
|
+
updateGroupsAddPrivacy: (value: WAPrivacyGroupAddValue) => Promise<void>;
|
|
12
|
+
updateCallPrivacy: (value: WAPrivacyCallValue) => Promise<void>;
|
|
13
|
+
updateMessagesPrivacy: (value: WAPrivacyMessagesValue) => Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Resolves without issuing anything. The engine already issues these
|
|
16
|
+
* tokens on every 1:1 send, rate limited by a sender bucket, so the
|
|
17
|
+
* caller's intent is met before they ask; a second manual issue would land
|
|
18
|
+
* outside that bucket and move the timestamp the limiter reads.
|
|
19
|
+
*
|
|
20
|
+
* Warned once rather than thrown: upstream callers await this inside a send
|
|
21
|
+
* workflow, and rejecting would abort a workflow that was going to succeed.
|
|
22
|
+
*/
|
|
23
|
+
issuePrivacyTokens: (jids: string[], timestamp?: number) => Promise<void>;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=privacy.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const makePrivacyMethods = (ctx) => {
|
|
2
|
+
/** Per socket, so a send loop calling this does not flood the log. */
|
|
3
|
+
let warnedAboutPrivacyTokens = false;
|
|
4
|
+
return {
|
|
5
|
+
fetchPrivacySettings: async (force) => {
|
|
6
|
+
void force;
|
|
7
|
+
return (await ctx.getClient()).fetchPrivacySettings();
|
|
8
|
+
},
|
|
9
|
+
updatePrivacySetting: async (category, value) => {
|
|
10
|
+
await (await ctx.getClient()).updatePrivacySetting(category, value);
|
|
11
|
+
},
|
|
12
|
+
updateLastSeenPrivacy: async (value) => {
|
|
13
|
+
await (await ctx.getClient()).updatePrivacySetting('last', value);
|
|
14
|
+
},
|
|
15
|
+
updateOnlinePrivacy: async (value) => {
|
|
16
|
+
await (await ctx.getClient()).updatePrivacySetting('online', value);
|
|
17
|
+
},
|
|
18
|
+
updateProfilePicturePrivacy: async (value) => {
|
|
19
|
+
await (await ctx.getClient()).updatePrivacySetting('profile', value);
|
|
20
|
+
},
|
|
21
|
+
updateStatusPrivacy: async (value) => {
|
|
22
|
+
await (await ctx.getClient()).updatePrivacySetting('status', value);
|
|
23
|
+
},
|
|
24
|
+
updateReadReceiptsPrivacy: async (value) => {
|
|
25
|
+
await (await ctx.getClient()).updatePrivacySetting('readreceipts', value);
|
|
26
|
+
},
|
|
27
|
+
updateGroupsAddPrivacy: async (value) => {
|
|
28
|
+
await (await ctx.getClient()).updatePrivacySetting('groupadd', value);
|
|
29
|
+
},
|
|
30
|
+
updateCallPrivacy: async (value) => {
|
|
31
|
+
await (await ctx.getClient()).updatePrivacySetting('calladd', value);
|
|
32
|
+
},
|
|
33
|
+
updateMessagesPrivacy: async (value) => {
|
|
34
|
+
await (await ctx.getClient()).updatePrivacySetting('messages', value);
|
|
35
|
+
},
|
|
36
|
+
/**
|
|
37
|
+
* Resolves without issuing anything. The engine already issues these
|
|
38
|
+
* tokens on every 1:1 send, rate limited by a sender bucket, so the
|
|
39
|
+
* caller's intent is met before they ask; a second manual issue would land
|
|
40
|
+
* outside that bucket and move the timestamp the limiter reads.
|
|
41
|
+
*
|
|
42
|
+
* Warned once rather than thrown: upstream callers await this inside a send
|
|
43
|
+
* workflow, and rejecting would abort a workflow that was going to succeed.
|
|
44
|
+
*/
|
|
45
|
+
issuePrivacyTokens: async (jids, timestamp) => {
|
|
46
|
+
void timestamp;
|
|
47
|
+
if (!warnedAboutPrivacyTokens) {
|
|
48
|
+
warnedAboutPrivacyTokens = true;
|
|
49
|
+
ctx.logger.warn({ count: jids.length }, 'issuePrivacyTokens is a no-op: the engine issues privacy tokens on every 1:1 send, so this call can be removed');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=privacy.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { BotListInfo } from '../Types/Chat.js';
|
|
2
|
+
import type { NewChatMessageCapInfo } from '../Types/State.js';
|
|
3
|
+
import type { MediaConnInfo } from '../Types/Message.js';
|
|
4
|
+
import type { SocketContext } from './types.js';
|
|
5
|
+
export declare const makeServerQueryMethods: (ctx: SocketContext) => {
|
|
6
|
+
/**
|
|
7
|
+
* `maxContentLengthBytes` is absent by design: the core's hosts carry
|
|
8
|
+
* nothing but a hostname, so the field upstream declares has no source
|
|
9
|
+
* and is not invented here.
|
|
10
|
+
*/
|
|
11
|
+
refreshMediaConn: (forceGet?: boolean) => Promise<Omit<MediaConnInfo, 'hosts'> & {
|
|
12
|
+
hosts: {
|
|
13
|
+
hostname: string;
|
|
14
|
+
}[];
|
|
15
|
+
}>;
|
|
16
|
+
/** Synchronous, as upstream has it, so it reads what the last refresh saw. */
|
|
17
|
+
getMediaHost: () => string;
|
|
18
|
+
getBotListV2: () => Promise<BotListInfo[]>;
|
|
19
|
+
fetchNewChatMessageCap: () => Promise<NewChatMessageCapInfo & {
|
|
20
|
+
remaining_quota?: number;
|
|
21
|
+
}>;
|
|
22
|
+
cleanDirtyBits: (type: 'account_sync' | 'groups', fromTimestamp?: number | string) => Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Refused rather than wired up. The core already fires a peer data
|
|
25
|
+
* request itself when a message fails to decrypt, with its own age
|
|
26
|
+
* policy, so a second one here would duplicate it. The request a
|
|
27
|
+
* consumer actually drives, asking for history, is `fetchMessageHistory`.
|
|
28
|
+
*/
|
|
29
|
+
sendPeerDataOperationMessage: (_pdoMessage: unknown) => Promise<never>;
|
|
30
|
+
/**
|
|
31
|
+
* Refused one layer down, as a build decision. `create_call_link` exists
|
|
32
|
+
* in the core behind its voip feature, and the bridge pins the core with
|
|
33
|
+
* default features off, so it is not compiled into the wasm artifact at
|
|
34
|
+
* all. Reaching it would pull the webrtc stack into the bundle.
|
|
35
|
+
*/
|
|
36
|
+
createCallLink: (_type: 'audio' | 'video', _event?: unknown, _timeoutMs?: number) => Promise<never>;
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=server-queries.d.ts.map
|