sauruspro-bail 5.0.0 → 5.0.2-alpha.spr.test

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.
Files changed (41) hide show
  1. package/README.md +12 -9
  2. package/docs/CHANGELOG.md +35 -4
  3. package/docs/SPR.md +61 -24
  4. package/lib/Defaults/index.js +5 -1
  5. package/lib/Socket/dinonic-handler.d.ts +27 -0
  6. package/lib/Socket/dinonic-handler.js +882 -0
  7. package/lib/Socket/index.js +8 -1
  8. package/lib/Socket/messages-recv.js +26 -0
  9. package/lib/Socket/messages-send.js +25 -0
  10. package/lib/Utils/MessageBuilder.d.ts +215 -0
  11. package/lib/Utils/MessageBuilder.js +3991 -0
  12. package/lib/Utils/a2ui.d.ts +202 -0
  13. package/lib/Utils/a2ui.js +732 -0
  14. package/lib/Utils/airich-registry.js +3 -0
  15. package/lib/Utils/baileys-event-stream.js +58 -0
  16. package/lib/Utils/bot-signature.d.ts +22 -0
  17. package/lib/Utils/bot-signature.js +124 -0
  18. package/lib/Utils/dinonic-spr.d.ts +73 -0
  19. package/lib/Utils/dinonic-spr.js +739 -0
  20. package/lib/Utils/extras.d.ts +449 -0
  21. package/lib/Utils/extras.js +1377 -0
  22. package/lib/Utils/html-app.d.ts +39 -0
  23. package/lib/Utils/html-app.js +291 -0
  24. package/lib/Utils/index.js +14 -2
  25. package/lib/Utils/metaai.d.ts +438 -0
  26. package/lib/Utils/metaai.js +911 -0
  27. package/lib/Utils/modern-messages.d.ts +150 -0
  28. package/lib/Utils/modern-messages.js +496 -0
  29. package/lib/Utils/native-flow.d.ts +14 -0
  30. package/lib/Utils/native-flow.js +118 -0
  31. package/lib/Utils/newsletter-status.js +9 -53
  32. package/lib/Utils/payment-messages.d.ts +59 -0
  33. package/lib/Utils/payment-messages.js +156 -0
  34. package/lib/Utils/scheduled-message.d.ts +82 -0
  35. package/lib/Utils/scheduled-message.js +113 -0
  36. package/lib/Utils/status-stickers.d.ts +78 -0
  37. package/lib/Utils/status-stickers.js +261 -0
  38. package/lib/index.js +2 -2
  39. package/package.json +4 -4
  40. package/lib/Utils/dinolith-spr.d.ts +0 -26
  41. package/lib/Utils/dinolith-spr.js +0 -521
@@ -0,0 +1,118 @@
1
+
2
+ const NATIVE_FLOW_NAMES = Object.freeze([
3
+ 'booking_confirmation',
4
+ 'order_details',
5
+ 'order_status',
6
+ 'payment_status',
7
+ 'payment_method',
8
+ 'payment_reminder',
9
+ 'message_with_link',
10
+ 'message_with_link_status',
11
+ 'quick_reply',
12
+ 'cta_call',
13
+ 'cta_url',
14
+ 'cta_catalog',
15
+ 'catalog_message',
16
+ 'payment_info',
17
+ 'cta_copy',
18
+ 'mixed',
19
+ 'galaxy_message',
20
+ 'cta_app',
21
+ 'call_permission_request',
22
+ 'payment_request',
23
+ 'api_signup',
24
+ 'inapp_signup',
25
+ 'form_message',
26
+ 'menu_options',
27
+ 'a2ui_reply_action',
28
+ 'address_message',
29
+ 'mpm'
30
+ ]);
31
+
32
+ const BUTTON_NAME_TO_FLOW = Object.freeze({
33
+ review_and_pay: 'order_details',
34
+ payment_info: 'payment_info',
35
+ review_order: 'order_status',
36
+ order_status: 'order_status',
37
+ payment_status: 'payment_status',
38
+ payment_method: 'payment_method',
39
+ open_webview: 'message_with_link',
40
+ message_with_link_status: 'message_with_link_status',
41
+ cta_url: 'cta_url',
42
+ cta_call: 'cta_call',
43
+ quick_reply: 'quick_reply',
44
+ catalog_message: 'catalog_message',
45
+ cta_catalog: 'cta_catalog',
46
+ cta_copy: 'cta_copy',
47
+ galaxy_message: 'galaxy_message',
48
+ payment_reminder: 'payment_reminder',
49
+ booking_confirmation: 'booking_confirmation',
50
+ call_permission_request: 'call_permission_request',
51
+ payment_request: 'payment_request',
52
+ api_signup: 'api_signup',
53
+ inapp_signup: 'inapp_signup',
54
+ cta_app: 'cta_app',
55
+ form_message: 'form_message',
56
+ address_message: 'address_message',
57
+ mpm: 'mpm'
58
+ });
59
+
60
+ const WEB_SUPPORTED_INTERACTIVE_FLOWS = Object.freeze([
61
+ 'quick_reply',
62
+ 'cta_call',
63
+ 'cta_url',
64
+ 'cta_catalog',
65
+ 'catalog_message',
66
+ 'cta_copy',
67
+ 'galaxy_message',
68
+ 'order_status',
69
+ 'payment_reminder',
70
+ 'booking_confirmation',
71
+ 'payment_request',
72
+ 'api_signup',
73
+ 'inapp_signup',
74
+ 'cta_app',
75
+ 'form_message'
76
+ ]);
77
+
78
+ const NATIVE_FLOW_BUTTON_LIMIT = Object.freeze({
79
+ quickReply: 10,
80
+ other: 3
81
+ });
82
+
83
+ const getNativeFlowNameByButtonName = (name) => BUTTON_NAME_TO_FLOW[name];
84
+
85
+ const isWebSupportedButtonName = (name) => {
86
+ const flow = getNativeFlowNameByButtonName(name);
87
+ return flow !== undefined && WEB_SUPPORTED_INTERACTIVE_FLOWS.includes(flow);
88
+ };
89
+
90
+ const checkNativeFlowButtons = (buttons) => {
91
+ const list = (Array.isArray(buttons) ? buttons : []).map(button => (typeof button === 'string' ? { name: button } : button));
92
+ const problems = [];
93
+
94
+ if (!list.length) {
95
+ return { ok: true, limit: NATIVE_FLOW_BUTTON_LIMIT.other, problems, unsupported: [] };
96
+ }
97
+
98
+ const firstIsQuickReply = list[0]?.name === 'quick_reply';
99
+ const limit = firstIsQuickReply ? NATIVE_FLOW_BUTTON_LIMIT.quickReply : NATIVE_FLOW_BUTTON_LIMIT.other;
100
+
101
+ if (list.length > limit) {
102
+ problems.push(`WhatsApp Web renders at most ${limit} buttons when the first one is ${firstIsQuickReply ? 'quick_reply' : 'not quick_reply'}, got ${list.length}`);
103
+ }
104
+
105
+ const unsupported = [...new Set(list.map(button => button?.name).filter(name => !isWebSupportedButtonName(name)))];
106
+ for (const name of unsupported) {
107
+ problems.push(`"${name}" is not a native flow WhatsApp Web or iOS can render, only Android shows it`);
108
+ }
109
+
110
+ const mixesKinds = list.slice(1).some(button => (button?.name === 'quick_reply') !== firstIsQuickReply);
111
+ if (mixesKinds) {
112
+ problems.push('quick_reply buttons cannot be mixed with other button types in one message');
113
+ }
114
+
115
+ return { ok: problems.length === 0, limit, problems, unsupported };
116
+ };
117
+
118
+ export { NATIVE_FLOW_NAMES, WEB_SUPPORTED_INTERACTIVE_FLOWS, NATIVE_FLOW_BUTTON_LIMIT, getNativeFlowNameByButtonName, isWebSupportedButtonName, checkNativeFlowButtons };
@@ -1,58 +1,10 @@
1
- /**
2
- * Newsletter Status (Channel Status) for sauruspro-bail
3
- * Owner: Saurus / Dinolith
4
- *
5
- * Send, react, revoke and fetch WhatsApp Channel (newsletter) statuses.
6
- * ESM port, uses only local sauruspro-bail modules.
7
- */
8
1
  import { proto } from '../../WAProto/index.js';
9
2
  import { encodeNewsletterMessage, generateMessageIDV2 } from './generics.js';
10
3
  import { generateWAMessage, normalizeMessageContent } from './messages.js';
4
+ import { prepareModernMessageContent, makeNewsletterStatusAttribution } from './modern-messages.js';
11
5
  import { WAMessageStatus } from '../Types/Message.js';
12
- import { getBinaryNodeChild, getBinaryNodeChildren, S_WHATSAPP_NET, isJidNewsletter } from '../WABinary/index.js';
13
-
14
- /**
15
- * Minimal content preparer (replaces the big modern-messages layer).
16
- * Supports: content.newsletterStatus = { newsletterJid, messageId, actionUrl?, duration?, hasMultipleReshares? }
17
- * content.statusAttributions = [...]
18
- */
19
- export const makeNewsletterStatusAttribution = input => {
20
- if (!input || typeof input !== 'object' || Array.isArray(input)) throw new TypeError('newsletterStatus must be an object')
21
- if (!input.newsletterJid) throw new TypeError('newsletterStatus.newsletterJid is required')
22
- if (input.messageId === undefined || input.messageId === null) throw new TypeError('newsletterStatus.messageId is required')
23
- return proto.StatusAttribution.create({
24
- type: proto.StatusAttribution.Type.NEWSLETTER_STATUS,
25
- actionUrl: input.actionUrl,
26
- statusReshare: {
27
- source: proto.StatusAttribution.StatusReshare.Source.CHANNEL_RESHARE,
28
- metadata: {
29
- duration: input.duration,
30
- channelJid: input.newsletterJid,
31
- channelMessageId: Number(input.messageId),
32
- hasMultipleReshares: input.hasMultipleReshares ?? false
33
- }
34
- }
35
- })
36
- }
37
-
38
- export const prepareNewsletterStatusContent = content => {
39
- if (!content || typeof content !== 'object' || Array.isArray(content)) return content
40
- const output = { ...content }
41
- const attributions = Array.isArray(output.contextInfo?.statusAttributions) ? [...output.contextInfo.statusAttributions] : []
42
- if (output.newsletterStatus) {
43
- attributions.push(makeNewsletterStatusAttribution(output.newsletterStatus))
44
- delete output.newsletterStatus
45
- }
46
- if (Array.isArray(output.statusAttributions)) {
47
- attributions.push(...output.statusAttributions)
48
- delete output.statusAttributions
49
- }
50
- if (attributions.length) {
51
- output.contextInfo = { ...(output.contextInfo || {}), statusAttributions: attributions }
52
- }
53
- return output
54
- }
55
-
6
+ import { getBinaryNodeChild, getBinaryNodeChildren, S_WHATSAPP_NET } from '../WABinary/index.js';
7
+ import { isJidNewsletter } from '../WABinary/jid-utils.js';
56
8
 
57
9
  const NEWSLETTER_STATUS_MEDIA_TYPES = new Set(['audio', 'gif', 'image', 'video'])
58
10
  const NEWSLETTER_STATUS_WEB_MEDIA_TYPES = new Set(['image', 'video'])
@@ -448,7 +400,7 @@ export const makeNewsletterStatusSender = (sock, config) => async (jid, content,
448
400
  return result
449
401
  }
450
402
 
451
- const preparedContent = prepareNewsletterStatusContent(content)
403
+ const preparedContent = prepareModernMessageContent(content)
452
404
  const fullMsg = await generateWAMessage(jid, statusAttribution ? withNewsletterStatusAttribution(preparedContent) : preparedContent, {
453
405
  logger: config?.logger,
454
406
  userJid,
@@ -735,4 +687,8 @@ export const getNewsletterStatusUpdates = (sock, jid, options) => {
735
687
  return makeNewsletterStatusUpdatesFetcher(sock)(jid, options);
736
688
  };
737
689
 
738
- export { NEWSLETTER_STATUS_CONTENT_TYPES, NEWSLETTER_STATUS_MEDIA_TYPES, NEWSLETTER_STATUS_WEB_MEDIA_TYPES }
690
+ export { NEWSLETTER_STATUS_CONTENT_TYPES, NEWSLETTER_STATUS_MEDIA_TYPES, NEWSLETTER_STATUS_WEB_MEDIA_TYPES };
691
+
692
+ // compat dengan API sauruspro sebelumnya
693
+ export { makeNewsletterStatusAttribution };
694
+ export const prepareNewsletterStatusContent = prepareModernMessageContent;
@@ -0,0 +1,59 @@
1
+ export const SplitPaymentStatus: {
2
+ readonly PENDING: 0;
3
+ readonly PAID: 1;
4
+ };
5
+ export const ReminderFrequency: {
6
+ readonly REMINDER_FREQUENCY_UNKNOWN: 0;
7
+ readonly WEEKLY: 1;
8
+ readonly BI_WEEKLY: 2;
9
+ readonly MONTHLY: 3;
10
+ readonly QUARTERLY: 4;
11
+ };
12
+ export const ReminderStatus: {
13
+ readonly REMINDER_STATUS_UNKNOWN: 0;
14
+ readonly ACTIVE: 1;
15
+ readonly CANCELLED_BY_CREATOR: 2;
16
+ readonly STOPPED_BY_RECEIVER: 3;
17
+ readonly EXPIRED: 4;
18
+ readonly PAID: 5;
19
+ };
20
+ export const MONEY_OFFSET: 1000;
21
+ export const money: (amount: any, currencyCode: any, offset?: number) => {
22
+ value: number;
23
+ offset: number;
24
+ currencyCode: string;
25
+ };
26
+ export const readMoney: (value: any) => {
27
+ amount: number;
28
+ currencyCode: any;
29
+ } | undefined;
30
+ export const buildSplitPayment: ({ splitId, total, currency, description, requesterJid, participants, createdAt }?: {
31
+ splitId?: any;
32
+ total?: any;
33
+ currency?: any;
34
+ description?: any;
35
+ requesterJid?: any;
36
+ participants?: any[] | undefined;
37
+ createdAt?: any;
38
+ }) => any;
39
+ export const buildSplitPaymentUpdate: ({ splitId, participantJid }?: {
40
+ splitId?: any;
41
+ participantJid?: any;
42
+ }) => {
43
+ splitId: string;
44
+ participantJid: any;
45
+ };
46
+ export const buildPaymentReminder: ({ reminderId, instanceId, description, frequency, status, amount, currency, payeeVpa, payeeJid, payerJid }?: {
47
+ reminderId?: any;
48
+ instanceId?: any;
49
+ description?: any;
50
+ frequency?: number | undefined;
51
+ status?: number | undefined;
52
+ amount?: any;
53
+ currency?: any;
54
+ payeeVpa?: any;
55
+ payeeJid?: any;
56
+ payerJid?: any;
57
+ }) => any;
58
+ export const readSplitPayment: (msg: any) => any;
59
+ export const readPaymentReminder: (msg: any) => any;
@@ -0,0 +1,156 @@
1
+
2
+ const SplitPaymentStatus = Object.freeze({ PENDING: 0, PAID: 1 });
3
+
4
+ const ReminderFrequency = Object.freeze({
5
+ REMINDER_FREQUENCY_UNKNOWN: 0,
6
+ WEEKLY: 1,
7
+ BI_WEEKLY: 2,
8
+ MONTHLY: 3,
9
+ QUARTERLY: 4
10
+ });
11
+
12
+ const ReminderStatus = Object.freeze({
13
+ REMINDER_STATUS_UNKNOWN: 0,
14
+ ACTIVE: 1,
15
+ CANCELLED_BY_CREATOR: 2,
16
+ STOPPED_BY_RECEIVER: 3,
17
+ EXPIRED: 4,
18
+ PAID: 5
19
+ });
20
+
21
+ const MONEY_OFFSET = 1000;
22
+
23
+ const drop = (object) => {
24
+ for (const key of Object.keys(object)) {
25
+ if (object[key] === undefined || object[key] === null) {
26
+ delete object[key];
27
+ }
28
+ }
29
+ return object;
30
+ };
31
+
32
+ const money = (amount, currencyCode, offset = MONEY_OFFSET) => {
33
+ const value = Number(amount);
34
+ if (!Number.isFinite(value) || value < 0) {
35
+ throw new TypeError('money needs a non-negative amount');
36
+ }
37
+ if (typeof currencyCode !== 'string' || currencyCode.length !== 3) {
38
+ throw new TypeError('money needs a three letter currency code, like IDR or USD');
39
+ }
40
+ if (!Number.isInteger(offset) || offset <= 0) {
41
+ throw new TypeError('money offset must be a positive integer');
42
+ }
43
+ return { value: Math.round(value * offset), offset, currencyCode: currencyCode.toUpperCase() };
44
+ };
45
+
46
+ const readMoney = (value) => {
47
+ if (!value?.offset) {
48
+ return undefined;
49
+ }
50
+ return {
51
+ amount: Number(value.value) / Number(value.offset),
52
+ currencyCode: value.currencyCode
53
+ };
54
+ };
55
+
56
+ const buildSplitPayment = ({ splitId, total, currency, description, requesterJid, participants = [], createdAt } = {}) => {
57
+ if (!splitId) {
58
+ throw new TypeError('buildSplitPayment needs a splitId, the update message is keyed on it');
59
+ }
60
+ if (!participants.length) {
61
+ throw new TypeError('buildSplitPayment needs at least one participant');
62
+ }
63
+ return drop({
64
+ splitId: String(splitId),
65
+ totalAmount: total === undefined ? undefined : (0, money)(total, currency),
66
+ description,
67
+ requesterJid,
68
+ participants: participants.map((participant, index) => {
69
+ if (!participant?.jid) {
70
+ throw new TypeError(`participant ${index} needs a jid`);
71
+ }
72
+ return drop({
73
+ jid: participant.jid,
74
+ amount: participant.amount === undefined ? undefined : (0, money)(participant.amount, participant.currency ?? currency),
75
+ status: participant.status ?? SplitPaymentStatus.PENDING
76
+ });
77
+ }),
78
+ createdAtMs: createdAt === undefined ? Date.now() : Number(createdAt)
79
+ });
80
+ };
81
+
82
+ const buildSplitPaymentUpdate = ({ splitId, participantJid } = {}) => {
83
+ if (!splitId || !participantJid) {
84
+ throw new TypeError('buildSplitPaymentUpdate needs both splitId and participantJid');
85
+ }
86
+ return { splitId: String(splitId), participantJid };
87
+ };
88
+
89
+ const buildPaymentReminder = ({
90
+ reminderId,
91
+ instanceId,
92
+ description,
93
+ frequency = ReminderFrequency.MONTHLY,
94
+ status = ReminderStatus.ACTIVE,
95
+ amount,
96
+ currency,
97
+ payeeVpa,
98
+ payeeJid,
99
+ payerJid
100
+ } = {}) => {
101
+ if (!reminderId) {
102
+ throw new TypeError('buildPaymentReminder needs a reminderId');
103
+ }
104
+ return drop({
105
+ reminderId: String(reminderId),
106
+ instanceId: instanceId === undefined ? undefined : String(instanceId),
107
+ description,
108
+ frequency,
109
+ status,
110
+ amount: amount === undefined ? undefined : (0, money)(amount, currency),
111
+ payeeVpa,
112
+ payeeJid,
113
+ payerJid
114
+ });
115
+ };
116
+
117
+ const readSplitPayment = (msg) => {
118
+ const message = msg?.message ?? msg;
119
+ const split = message?.splitPaymentMessage;
120
+ if (!split) {
121
+ return null;
122
+ }
123
+ return drop({
124
+ splitId: split.splitId,
125
+ total: (0, readMoney)(split.totalAmount),
126
+ description: split.description,
127
+ requesterJid: split.requesterJid,
128
+ createdAtMs: split.createdAtMs === undefined ? undefined : Number(split.createdAtMs),
129
+ participants: (split.participants ?? []).map(participant => drop({
130
+ jid: participant.jid,
131
+ amount: (0, readMoney)(participant.amount),
132
+ status: participant.status
133
+ }))
134
+ });
135
+ };
136
+
137
+ const readPaymentReminder = (msg) => {
138
+ const message = msg?.message ?? msg;
139
+ const reminder = message?.paymentReminderMessage;
140
+ if (!reminder) {
141
+ return null;
142
+ }
143
+ return drop({
144
+ reminderId: reminder.reminderId,
145
+ instanceId: reminder.instanceId,
146
+ description: reminder.description,
147
+ frequency: reminder.frequency,
148
+ status: reminder.status,
149
+ amount: (0, readMoney)(reminder.amount),
150
+ payeeVpa: reminder.payeeVpa,
151
+ payeeJid: reminder.payeeJid,
152
+ payerJid: reminder.payerJid
153
+ });
154
+ };
155
+
156
+ export { SplitPaymentStatus, ReminderFrequency, ReminderStatus, MONEY_OFFSET, money, readMoney, buildSplitPayment, buildSplitPaymentUpdate, buildPaymentReminder, readSplitPayment, readPaymentReminder };
@@ -0,0 +1,82 @@
1
+ import { proto } from "../../WAProto";
2
+ export const SCHEDULED_MSG_META_TYPE: "scheduled_message";
3
+ export const SCHEDULED_MSG_MAX_PER_CHAT: 30;
4
+ export const SCHEDULED_MSG_MAX_MEDIA: 1;
5
+ export const SCHEDULED_MSG_RESOURCE_LIMIT_NACK_CODE: 419;
6
+ export const SCHEDULED_MSG_REVEAL_KEY_BYTES: 32;
7
+ export const SCHEDULED_MSG_REVEAL_KEY_IV_BYTES: 12;
8
+ export const SCHEDULED_MSG_REVEAL_KEY_RETENTION_DAYS: 30;
9
+ export const SCHEDULED_MSG_TAG_BYTES: 16;
10
+ export const SCHEDULED_MSG_WINDOW: {
11
+ readonly chat: {
12
+ readonly minSeconds: 600;
13
+ readonly maxSeconds: 1209600;
14
+ };
15
+ readonly newsletter: {
16
+ readonly minSeconds: 600;
17
+ readonly maxSeconds: 2592000;
18
+ };
19
+ };
20
+ export const generateRevealKey: () => Buffer;
21
+ export const generateRevealKeyId: () => string;
22
+ export const encryptWithRevealKey: (plaintext: any, revealKey: any) => {
23
+ encIv: Buffer;
24
+ encPayload: Buffer;
25
+ };
26
+ export const decryptWithRevealKey: (encPayload: any, encIv: any, revealKey: any) => Buffer;
27
+ export const buildConditionalRevealMessage: ({ encPayload, encIv, revealKeyId }: {
28
+ encPayload: any;
29
+ encIv: any;
30
+ revealKeyId: any;
31
+ }) => {
32
+ conditionalRevealMessage: {
33
+ conditionalRevealMessageType: proto.Message.ConditionalRevealMessage.ConditionalRevealMessageType;
34
+ encPayload: Buffer;
35
+ encIv: Buffer;
36
+ revealKeyId: any;
37
+ };
38
+ };
39
+ export const encodeScheduledMessage: (message: any, revealKey?: Buffer) => {
40
+ revealKey: Buffer;
41
+ revealKeyId: string;
42
+ encIv: Buffer;
43
+ encPayload: Buffer;
44
+ message: {
45
+ conditionalRevealMessage: {
46
+ conditionalRevealMessageType: proto.Message.ConditionalRevealMessage.ConditionalRevealMessageType;
47
+ encPayload: Buffer;
48
+ encIv: Buffer;
49
+ revealKeyId: any;
50
+ };
51
+ };
52
+ };
53
+ export const decodeScheduledMessage: (conditionalRevealMessage: any, revealKey: any) => proto.Message;
54
+ export const buildScheduledMsgMetaNode: ({ kind, scheduledTimestampS, revealKeyId, revealKey }: {
55
+ kind?: string | undefined;
56
+ scheduledTimestampS?: any;
57
+ revealKeyId: any;
58
+ revealKey: any;
59
+ }) => {
60
+ tag: string;
61
+ attrs: {
62
+ type: string;
63
+ st?: string;
64
+ };
65
+ content: {
66
+ tag: string;
67
+ attrs: {
68
+ rkid: any;
69
+ };
70
+ content: Buffer | undefined;
71
+ }[];
72
+ };
73
+ export const buildUnscheduleProtocolMessage: (key: any) => {
74
+ protocolMessage: {
75
+ key: any;
76
+ type: proto.Message.ProtocolMessage.Type;
77
+ };
78
+ };
79
+ export const isScheduledTimeValid: (scheduledTimestampS: any, nowSeconds?: number, window?: {
80
+ readonly minSeconds: 600;
81
+ readonly maxSeconds: 1209600;
82
+ }) => boolean;
@@ -0,0 +1,113 @@
1
+ import * as crypto_1 from 'crypto';
2
+ import * as WAProto_1 from '../../WAProto/index.js';
3
+ const SCHEDULED_MSG_META_TYPE = 'scheduled_message';
4
+ const SCHEDULED_MSG_MAX_PER_CHAT = 30;
5
+ const SCHEDULED_MSG_MAX_MEDIA = 1;
6
+ const SCHEDULED_MSG_RESOURCE_LIMIT_NACK_CODE = 419;
7
+ const SCHEDULED_MSG_REVEAL_KEY_BYTES = 32;
8
+ const SCHEDULED_MSG_REVEAL_KEY_IV_BYTES = 12;
9
+ const SCHEDULED_MSG_REVEAL_KEY_RETENTION_DAYS = 30;
10
+ const SCHEDULED_MSG_TAG_BYTES = 16;
11
+
12
+ const SCHEDULED_MSG_WINDOW = Object.freeze({
13
+ chat: { minSeconds: 600, maxSeconds: 1209600 },
14
+ newsletter: { minSeconds: 600, maxSeconds: 2592000 }
15
+ });
16
+
17
+ const generateRevealKey = () => (0, crypto_1.randomBytes)(SCHEDULED_MSG_REVEAL_KEY_BYTES);
18
+
19
+ const generateRevealKeyId = () => (0, crypto_1.randomUUID)();
20
+
21
+ const encryptWithRevealKey = (plaintext, revealKey) => {
22
+ const key = Buffer.from(revealKey);
23
+ if (key.length !== SCHEDULED_MSG_REVEAL_KEY_BYTES) {
24
+ throw new TypeError(`reveal key must be ${SCHEDULED_MSG_REVEAL_KEY_BYTES} bytes`);
25
+ }
26
+ const encIv = (0, crypto_1.randomBytes)(SCHEDULED_MSG_REVEAL_KEY_IV_BYTES);
27
+ const cipher = (0, crypto_1.createCipheriv)('aes-256-gcm', key, encIv);
28
+ const body = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);
29
+ return { encIv, encPayload: Buffer.concat([body, cipher.getAuthTag()]) };
30
+ };
31
+
32
+ const decryptWithRevealKey = (encPayload, encIv, revealKey) => {
33
+ const key = Buffer.from(revealKey);
34
+ const payload = Buffer.from(encPayload);
35
+ if (payload.length < SCHEDULED_MSG_TAG_BYTES) {
36
+ throw new TypeError('encrypted payload is too short to carry an auth tag');
37
+ }
38
+ const decipher = (0, crypto_1.createDecipheriv)('aes-256-gcm', key, Buffer.from(encIv));
39
+ decipher.setAuthTag(payload.subarray(payload.length - SCHEDULED_MSG_TAG_BYTES));
40
+ return Buffer.concat([
41
+ decipher.update(payload.subarray(0, payload.length - SCHEDULED_MSG_TAG_BYTES)),
42
+ decipher.final()
43
+ ]);
44
+ };
45
+
46
+ const buildConditionalRevealMessage = ({ encPayload, encIv, revealKeyId }) => ({
47
+ conditionalRevealMessage: {
48
+ conditionalRevealMessageType: WAProto_1.proto.Message.ConditionalRevealMessage.ConditionalRevealMessageType.SCHEDULED_MESSAGE,
49
+ encPayload: Buffer.from(encPayload),
50
+ encIv: Buffer.from(encIv),
51
+ revealKeyId
52
+ }
53
+ });
54
+
55
+ const encodeScheduledMessage = (message, revealKey = (0, generateRevealKey)()) => {
56
+ const plaintext = WAProto_1.proto.Message.encode(WAProto_1.proto.Message.fromObject(message)).finish();
57
+ const { encIv, encPayload } = (0, encryptWithRevealKey)(plaintext, revealKey);
58
+ const revealKeyId = (0, generateRevealKeyId)();
59
+ return {
60
+ revealKey,
61
+ revealKeyId,
62
+ encIv,
63
+ encPayload,
64
+ message: (0, buildConditionalRevealMessage)({ encPayload, encIv, revealKeyId })
65
+ };
66
+ };
67
+
68
+ const decodeScheduledMessage = (conditionalRevealMessage, revealKey) => {
69
+ const inner = conditionalRevealMessage?.conditionalRevealMessage ?? conditionalRevealMessage;
70
+ if (!inner?.encPayload || !inner?.encIv) {
71
+ throw new TypeError('conditionalRevealMessage is missing encPayload or encIv');
72
+ }
73
+ return WAProto_1.proto.Message.decode((0, decryptWithRevealKey)(inner.encPayload, inner.encIv, revealKey));
74
+ };
75
+
76
+ const buildScheduledMsgMetaNode = ({ kind = 'schedule', scheduledTimestampS, revealKeyId, revealKey }) => {
77
+ if (!revealKeyId) {
78
+ throw new TypeError('revealKeyId is required');
79
+ }
80
+ if (kind === 'schedule' && !revealKey) {
81
+ throw new TypeError('scheduling requires the reveal key');
82
+ }
83
+ const attrs = { type: SCHEDULED_MSG_META_TYPE };
84
+ if (kind === 'schedule') {
85
+ if (!Number.isFinite(Number(scheduledTimestampS))) {
86
+ throw new TypeError('scheduledTimestampS must be a unix timestamp in seconds');
87
+ }
88
+ attrs.st = String(Math.floor(Number(scheduledTimestampS)));
89
+ }
90
+ return {
91
+ tag: 'meta',
92
+ attrs,
93
+ content: [{
94
+ tag: 'key',
95
+ attrs: { rkid: revealKeyId },
96
+ content: kind === 'schedule' ? Buffer.from(revealKey) : undefined
97
+ }]
98
+ };
99
+ };
100
+
101
+ const buildUnscheduleProtocolMessage = (key) => ({
102
+ protocolMessage: {
103
+ key,
104
+ type: WAProto_1.proto.Message.ProtocolMessage.Type.MESSAGE_UNSCHEDULE
105
+ }
106
+ });
107
+
108
+ const isScheduledTimeValid = (scheduledTimestampS, nowSeconds = Math.floor(Date.now() / 1000), window = SCHEDULED_MSG_WINDOW.chat) => {
109
+ const rounded = Math.floor(nowSeconds / 60) * 60;
110
+ return scheduledTimestampS - rounded >= window.minSeconds && scheduledTimestampS - nowSeconds <= window.maxSeconds;
111
+ };
112
+
113
+ export { SCHEDULED_MSG_META_TYPE, SCHEDULED_MSG_MAX_PER_CHAT, SCHEDULED_MSG_MAX_MEDIA, SCHEDULED_MSG_RESOURCE_LIMIT_NACK_CODE, SCHEDULED_MSG_REVEAL_KEY_BYTES, SCHEDULED_MSG_REVEAL_KEY_IV_BYTES, SCHEDULED_MSG_REVEAL_KEY_RETENTION_DAYS, SCHEDULED_MSG_TAG_BYTES, SCHEDULED_MSG_WINDOW, generateRevealKey, generateRevealKeyId, encryptWithRevealKey, decryptWithRevealKey, buildConditionalRevealMessage, encodeScheduledMessage, decodeScheduledMessage, buildScheduledMsgMetaNode, buildUnscheduleProtocolMessage, isScheduledTimeValid };
@@ -0,0 +1,78 @@
1
+ import { proto } from "../../WAProto";
2
+ export const StatusLinkType: {
3
+ readonly RASTERIZED_LINK_PREVIEW: 1;
4
+ readonly RASTERIZED_LINK_TRUNCATED: 2;
5
+ readonly RASTERIZED_LINK_FULL_URL: 3;
6
+ };
7
+ export const STICKER_DEFAULT_AREA: {
8
+ readonly x: 0.25;
9
+ readonly y: 0.4;
10
+ readonly width: 0.5;
11
+ readonly height: 0.2;
12
+ };
13
+ export const stickerArea: ({ x, y, width, height }?: {
14
+ x?: number | undefined;
15
+ y?: number | undefined;
16
+ width?: number | undefined;
17
+ height?: number | undefined;
18
+ }) => {
19
+ x: number;
20
+ y: number;
21
+ }[];
22
+ export const locationSticker: ({ latitude, longitude, name, area, skipConfirmation }?: {
23
+ latitude?: any;
24
+ longitude?: any;
25
+ name?: any;
26
+ area?: any;
27
+ skipConfirmation?: any;
28
+ }) => any;
29
+ export const channelSticker: ({ jid, name, serverMessageId, accessibilityText, area }?: {
30
+ jid?: any;
31
+ name?: any;
32
+ serverMessageId?: number | undefined;
33
+ accessibilityText?: any;
34
+ area?: any;
35
+ }) => any;
36
+ export const linkSticker: ({ url, title, area, linkType }?: {
37
+ url?: any;
38
+ title?: any;
39
+ area?: any;
40
+ linkType?: number | undefined;
41
+ }) => any;
42
+ export const musicSticker: ({ songId, title, author, mediaId, artworkDirectPath, artworkSha256, artworkEncSha256, artworkMediaKey, artistAttribution, countryBlocklist, isExplicit, startTimeMs, derivedStartTimeMs, durationMs, area }?: {
43
+ songId?: any;
44
+ title?: any;
45
+ author?: any;
46
+ mediaId?: any;
47
+ artworkDirectPath?: any;
48
+ artworkSha256?: any;
49
+ artworkEncSha256?: any;
50
+ artworkMediaKey?: any;
51
+ artistAttribution?: any;
52
+ countryBlocklist?: any;
53
+ isExplicit?: any;
54
+ startTimeMs?: number | undefined;
55
+ derivedStartTimeMs?: any;
56
+ durationMs?: any;
57
+ area?: any;
58
+ }) => any;
59
+ export const messageSticker: ({ stanzaId, message, area }?: {
60
+ stanzaId?: any;
61
+ message?: any;
62
+ area?: any;
63
+ }) => any;
64
+ export const normalizeStickers: (stickers: any) => proto.InteractiveAnnotation[];
65
+ export const readStickers: (message: any) => any[];
66
+ export const MusicMessageStyle: {
67
+ readonly UNKNOWN: 0;
68
+ readonly VINYL: 1;
69
+ };
70
+ export const MUSIC_ALLOWED_HOSTS: readonly string[];
71
+ export const isMusicHostAllowed: (value: any) => boolean;
72
+ export const buildMusicMessage: ({ songUri, artworkUri, style, contextInfo, ...music }?: {
73
+ songUri?: any;
74
+ artworkUri?: any;
75
+ style?: number | undefined;
76
+ contextInfo?: any;
77
+ }) => any;
78
+ export const readMusicMessage: (msg: any) => any;