@oxidezap/baileyrs 0.1.0 → 0.1.1
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/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
|
@@ -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
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Boom } from '../Utils/boom.js';
|
|
2
|
+
/**
|
|
3
|
+
* Every section, flattened and deduplicated, rather than only the section the
|
|
4
|
+
* server types `all`.
|
|
5
|
+
*
|
|
6
|
+
* Upstream reads that one section, and the core deliberately refused to: the
|
|
7
|
+
* real client walks every section and uses the type only for layout, so a bot
|
|
8
|
+
* that appears solely under a category is dropped by the narrower reading. A
|
|
9
|
+
* caller iterating this list handles the extra entries; one that silently lost
|
|
10
|
+
* a bot has no way to notice.
|
|
11
|
+
*/
|
|
12
|
+
const flattenBotList = (list) => {
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
const bots = [];
|
|
15
|
+
for (const section of list.sections) {
|
|
16
|
+
for (const bot of section.bots) {
|
|
17
|
+
if (seen.has(bot.jid))
|
|
18
|
+
continue;
|
|
19
|
+
seen.add(bot.jid);
|
|
20
|
+
bots.push({ jid: bot.jid, personaId: bot.personaId });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return bots;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Upstream names these in snake case and types the three timestamps as
|
|
27
|
+
* strings. Every field is optional because the server omits whatever does not
|
|
28
|
+
* apply to the account's tier, and an absent quota stays absent: `0` here means
|
|
29
|
+
* the quota is spent.
|
|
30
|
+
*/
|
|
31
|
+
const toCapInfo = (result) => ({
|
|
32
|
+
...(result.totalQuota !== undefined ? { total_quota: result.totalQuota } : {}),
|
|
33
|
+
...(result.usedQuota !== undefined ? { used_quota: result.usedQuota } : {}),
|
|
34
|
+
...(result.remainingQuota !== undefined ? { remaining_quota: result.remainingQuota } : {}),
|
|
35
|
+
...(result.cycleStartTimestamp !== undefined ? { cycle_start_timestamp: String(result.cycleStartTimestamp) } : {}),
|
|
36
|
+
...(result.cycleEndTimestamp !== undefined ? { cycle_end_timestamp: String(result.cycleEndTimestamp) } : {}),
|
|
37
|
+
...(result.serverSentTimestamp !== undefined ? { server_sent_timestamp: String(result.serverSentTimestamp) } : {}),
|
|
38
|
+
...(result.oteStatus !== undefined ? { ote_status: result.oteStatus } : {}),
|
|
39
|
+
...(result.mvStatus !== undefined ? { mv_status: result.mvStatus } : {}),
|
|
40
|
+
...(result.cappingStatus !== undefined
|
|
41
|
+
? { capping_status: result.cappingStatus }
|
|
42
|
+
: {})
|
|
43
|
+
});
|
|
44
|
+
export const makeServerQueryMethods = (ctx) => {
|
|
45
|
+
/** Last host seen, so the synchronous accessor upstream exposes can answer. */
|
|
46
|
+
let mediaHost = '';
|
|
47
|
+
/**
|
|
48
|
+
* When the credentials were obtained, not when they were last handed out.
|
|
49
|
+
* The engine serves a live connection from its cache and gives no signal
|
|
50
|
+
* for which calls were actual fetches, so the stamp is kept only while the
|
|
51
|
+
* connection it describes is still live: past its own ttl, on a forced
|
|
52
|
+
* call, or on rotated credentials, whatever comes back is a fetch.
|
|
53
|
+
*
|
|
54
|
+
* A caller renewing on `fetchDate + ttl` needs both halves of that. Always
|
|
55
|
+
* restamping would push its deadline forward forever; never restamping
|
|
56
|
+
* would leave it renewing against a moment that has already passed.
|
|
57
|
+
*/
|
|
58
|
+
let fetched;
|
|
59
|
+
const isLive = (held) => Date.now() - held.at.getTime() < held.ttl * 1000;
|
|
60
|
+
return {
|
|
61
|
+
/**
|
|
62
|
+
* `maxContentLengthBytes` is absent by design: the core's hosts carry
|
|
63
|
+
* nothing but a hostname, so the field upstream declares has no source
|
|
64
|
+
* and is not invented here.
|
|
65
|
+
*/
|
|
66
|
+
refreshMediaConn: async (forceGet = false) => {
|
|
67
|
+
// Forced on the first call, as upstream's first call is: the engine may
|
|
68
|
+
// already hold a connection acquired by an upload, and stamping that
|
|
69
|
+
// one as fetched now would report it fresher than it is.
|
|
70
|
+
const held = fetched;
|
|
71
|
+
const conn = await (await ctx.getClient()).getMediaConn(forceGet || !held);
|
|
72
|
+
mediaHost = conn.hosts[0]?.hostname ?? mediaHost;
|
|
73
|
+
const isFetch = forceGet || !held || held.auth !== conn.auth || !isLive(held);
|
|
74
|
+
fetched = isFetch ? { auth: conn.auth, ttl: conn.ttl, at: new Date() } : held;
|
|
75
|
+
// A copy: the stored instant decides when the next call restamps, and a
|
|
76
|
+
// consumer holding the same Date could move it.
|
|
77
|
+
return { auth: conn.auth, ttl: conn.ttl, hosts: conn.hosts, fetchDate: new Date(fetched.at) };
|
|
78
|
+
},
|
|
79
|
+
/** Synchronous, as upstream has it, so it reads what the last refresh saw. */
|
|
80
|
+
getMediaHost: () => mediaHost,
|
|
81
|
+
getBotListV2: async () => {
|
|
82
|
+
return flattenBotList(await (await ctx.getClient()).getBotList());
|
|
83
|
+
},
|
|
84
|
+
fetchNewChatMessageCap: async () => {
|
|
85
|
+
return toCapInfo(await (await ctx.getClient()).fetchNewChatMessageCappingInfo());
|
|
86
|
+
},
|
|
87
|
+
cleanDirtyBits: async (type, fromTimestamp) => {
|
|
88
|
+
let timestamp = null;
|
|
89
|
+
if (fromTimestamp !== undefined) {
|
|
90
|
+
// A blank string is not a timestamp, and `Number('')` is the epoch,
|
|
91
|
+
// so without this a caller meaning "no timestamp" would ask the
|
|
92
|
+
// server to clean from the beginning of time.
|
|
93
|
+
const blank = typeof fromTimestamp === 'string' && fromTimestamp.trim() === '';
|
|
94
|
+
timestamp = typeof fromTimestamp === 'string' ? Number(fromTimestamp) : fromTimestamp;
|
|
95
|
+
if (blank || !Number.isFinite(timestamp)) {
|
|
96
|
+
throw new Boom(`cleanDirtyBits: fromTimestamp '${fromTimestamp}' is not a number`, { statusCode: 400 });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
await (await ctx.getClient()).cleanDirtyBits(type, timestamp);
|
|
100
|
+
},
|
|
101
|
+
/**
|
|
102
|
+
* Refused rather than wired up. The core already fires a peer data
|
|
103
|
+
* request itself when a message fails to decrypt, with its own age
|
|
104
|
+
* policy, so a second one here would duplicate it. The request a
|
|
105
|
+
* consumer actually drives, asking for history, is `fetchMessageHistory`.
|
|
106
|
+
*/
|
|
107
|
+
sendPeerDataOperationMessage: async (_pdoMessage) => {
|
|
108
|
+
throw new Boom('sendPeerDataOperationMessage is not supported: use fetchMessageHistory to request history, and note the engine issues its own peer data request when a message fails to decrypt', { statusCode: 501 });
|
|
109
|
+
},
|
|
110
|
+
/**
|
|
111
|
+
* Refused one layer down, as a build decision. `create_call_link` exists
|
|
112
|
+
* in the core behind its voip feature, and the bridge pins the core with
|
|
113
|
+
* default features off, so it is not compiled into the wasm artifact at
|
|
114
|
+
* all. Reaching it would pull the webrtc stack into the bundle.
|
|
115
|
+
*/
|
|
116
|
+
createCallLink: async (_type, _event, _timeoutMs) => {
|
|
117
|
+
throw new Boom('createCallLink is not available: the call-link operation sits behind the core voip feature, which is not compiled into the wasm bridge', { statusCode: 501 });
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=server-queries.js.map
|
package/lib/Socket/types.d.ts
CHANGED
|
@@ -23,6 +23,12 @@ export interface SocketContext {
|
|
|
23
23
|
getClientSync: () => WasmWhatsAppClient;
|
|
24
24
|
/** Raw stanza EventEmitter for CB: pattern compat */
|
|
25
25
|
ws: EventEmitter;
|
|
26
|
+
/**
|
|
27
|
+
* Where a failure goes when it has nowhere else to go: a dispatcher that
|
|
28
|
+
* threw, a wire batch that would not decode. Also what the socket exposes
|
|
29
|
+
* as `onUnexpectedError`, so the two are one reporter rather than two.
|
|
30
|
+
*/
|
|
31
|
+
reportUnexpectedError: (err: unknown, msg: string) => void;
|
|
26
32
|
}
|
|
27
33
|
/** Convert a bridge Jid struct to a string */
|
|
28
34
|
export declare const jidStr: (jid: {
|
package/lib/Types/Product.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
import type { CatalogResult as CatalogPageResult, CollectionsResult } from '@oxidezap/whatsapp-rust-bridge';
|
|
1
2
|
import type { WAMediaUpload } from './Message.js';
|
|
3
|
+
/**
|
|
4
|
+
* What `getCatalog` returns, under a name of its own. The `CatalogResult`
|
|
5
|
+
* below is the raw catalog envelope and is a different shape, so a consumer
|
|
6
|
+
* typing the call has one name to reach for and it is this one.
|
|
7
|
+
*/
|
|
8
|
+
export type CatalogPage = CatalogPageResult;
|
|
9
|
+
/** As `CatalogPage`, for `getCollections`. */
|
|
10
|
+
export type CollectionsPage = CollectionsResult;
|
|
2
11
|
export type CatalogResult = {
|
|
3
12
|
data: {
|
|
4
13
|
paging: {
|
package/lib/Utils/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './auth-utils.js';
|
|
|
3
3
|
export * from './crypto.js';
|
|
4
4
|
export * from './generics.js';
|
|
5
5
|
export * from './messages.js';
|
|
6
|
+
export { getUrlInfo, type URLGenerationOptions } from './link-preview.js';
|
|
6
7
|
export * from './messages-media.js';
|
|
7
8
|
export * from './process-history-message.js';
|
|
8
9
|
export * from './process-message.js';
|
package/lib/Utils/index.js
CHANGED
|
@@ -3,6 +3,9 @@ export * from './auth-utils.js';
|
|
|
3
3
|
export * from './crypto.js';
|
|
4
4
|
export * from './generics.js';
|
|
5
5
|
export * from './messages.js';
|
|
6
|
+
// Named rather than `*`: the underscore hooks in that file exist for the tests
|
|
7
|
+
// and would otherwise become released API.
|
|
8
|
+
export { getUrlInfo } from './link-preview.js';
|
|
6
9
|
export * from './messages-media.js';
|
|
7
10
|
export * from './process-history-message.js';
|
|
8
11
|
export * from './process-message.js';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { WAUrlInfo } from '../Types/Message.js';
|
|
2
|
+
import type { ILogger } from './logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* The first link in a piece of text, or undefined when there is none. Exported
|
|
5
|
+
* under an underscore so the extraction can be tested without a network.
|
|
6
|
+
*
|
|
7
|
+
* Trailing prose punctuation is dropped: a link at the end of a sentence
|
|
8
|
+
* carries the full stop with it, and `https://example.com.` is not what the
|
|
9
|
+
* writer meant. A closing bracket goes the same way, which costs the rare url
|
|
10
|
+
* that genuinely ends in one and saves the common case of a link in
|
|
11
|
+
* parentheses.
|
|
12
|
+
*/
|
|
13
|
+
export declare const _firstLink: (text: string) => string | undefined;
|
|
14
|
+
export type URLGenerationOptions = {
|
|
15
|
+
thumbnailWidth: number;
|
|
16
|
+
fetchOpts: {
|
|
17
|
+
/** Timeout in ms */
|
|
18
|
+
timeout: number;
|
|
19
|
+
proxyUrl?: string;
|
|
20
|
+
headers?: HeadersInit;
|
|
21
|
+
};
|
|
22
|
+
uploadImage?: (encFilePath: string, opts: {
|
|
23
|
+
fileEncSha256B64: string;
|
|
24
|
+
mediaType: string;
|
|
25
|
+
}) => Promise<unknown>;
|
|
26
|
+
logger?: ILogger;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Fetched here rather than through `getHttpStream`, which forwards neither the
|
|
30
|
+
* timeout nor the proxy and validates no destination. The credentials in
|
|
31
|
+
* `headers` are for the page, so they are sent to the thumbnail only when it
|
|
32
|
+
* is the same origin; another host advertised by that page must not receive
|
|
33
|
+
* them.
|
|
34
|
+
*/
|
|
35
|
+
/** Exported under an underscore so the destination guard can be driven directly. */
|
|
36
|
+
export declare const _getCompressedJpegThumbnail: (url: string, pageUrl: string, { thumbnailWidth, fetchOpts }: URLGenerationOptions) => Promise<{
|
|
37
|
+
buffer: any;
|
|
38
|
+
original: {
|
|
39
|
+
width: any;
|
|
40
|
+
height: any;
|
|
41
|
+
};
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Reads the first URL out of a piece of text and fetches what a link preview
|
|
45
|
+
* needs. Nothing here is protocol: it is an HTTP fetch, an OpenGraph parse and
|
|
46
|
+
* a thumbnail, which is why it belongs in this layer rather than the engine.
|
|
47
|
+
*
|
|
48
|
+
* Resolves to undefined for the two cases that mean "no preview": text with no
|
|
49
|
+
* link in it, and a page with no title. Everything else throws, including a
|
|
50
|
+
* timeout, because a swallowed failure is indistinguishable from a page that
|
|
51
|
+
* genuinely had nothing, and a caller retrying the first would give up on the
|
|
52
|
+
* second.
|
|
53
|
+
*
|
|
54
|
+
* The metadata parse comes from `link-preview-js`, an optional peer dependency
|
|
55
|
+
* this package already declares and had no reader for, so nothing new is
|
|
56
|
+
* pulled in: a consumer who does not want link previews does not install it
|
|
57
|
+
* and never calls this.
|
|
58
|
+
*/
|
|
59
|
+
export declare const getUrlInfo: (text: string, opts?: URLGenerationOptions) => Promise<WAUrlInfo | undefined>;
|
|
60
|
+
//# sourceMappingURL=link-preview.d.ts.map
|