plogme 1.0.5 → 1.0.6
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 +88 -1
- package/lib/Socket/chats.js +73 -0
- package/lib/Socket/messages-send.js +85 -4
- package/lib/Utils/games/miniapp.js +5 -0
- package/lib/Utils/index.js +2 -0
- package/lib/Utils/message-composer.js +2 -2
- package/lib/Utils/messages.js +143 -10
- package/lib/Utils/rich-message-utils.js +59 -0
- package/lib/Utils/source-grep.js +45 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
npm install plogme
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The current package version is **1.0.
|
|
13
|
+
The current package version is **1.0.6**. Use Node.js 20 or newer.
|
|
14
14
|
|
|
15
15
|
## Quick start
|
|
16
16
|
|
|
@@ -474,6 +474,93 @@ sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
|
|
|
474
474
|
})
|
|
475
475
|
```
|
|
476
476
|
|
|
477
|
+
## Reporting, group history, rich HTML, and source grep
|
|
478
|
+
|
|
479
|
+
Version 1.0.6 adds the realvare-style `w:report` methods without replacing plogme's existing evidence-based `reportSpam` method. The methods use different WhatsApp IQ namespaces and should be selected according to the operation you intend to perform.
|
|
480
|
+
|
|
481
|
+
| API | Wire operation | Use |
|
|
482
|
+
| --- | --- | --- |
|
|
483
|
+
| `reportSpam(jid, messages, spamFlow, subject)` | `xmlns="spam"` → `spam_list` | Report a chat or group with message evidence. |
|
|
484
|
+
| `reportUser(jid, reason, waitForAckMs)` | `xmlns="w:report"` → `user` | Report one user. |
|
|
485
|
+
| `reportMessage(jid, messageId, reason, participantJid, waitForAckMs)` | `xmlns="w:report"` → `message` | Report one message. |
|
|
486
|
+
| `reportAndBlockUser(jid, reason, waitForAckMs)` | `w:report` followed by block | Report a user and then block them. |
|
|
487
|
+
|
|
488
|
+
```js
|
|
489
|
+
// Existing plogme spam-list report with evidence.
|
|
490
|
+
await sock.reportSpam(
|
|
491
|
+
'1203630@g.us',
|
|
492
|
+
[{ id: 'ABCD1234', from: '15550001111@s.whatsapp.net', t: 1710000000 }],
|
|
493
|
+
'group_info_report',
|
|
494
|
+
'Example group'
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
// New direct report methods.
|
|
498
|
+
const acknowledged = await sock.reportUser('15550001111@s.whatsapp.net', 'spam')
|
|
499
|
+
const messageAcknowledged = await sock.reportMessage(
|
|
500
|
+
'15550001111@s.whatsapp.net', 'ABCD1234', 'harassment',
|
|
501
|
+
'15550001111@s.whatsapp.net'
|
|
502
|
+
)
|
|
503
|
+
await sock.reportAndBlockUser('15550001111@s.whatsapp.net', 'scam')
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
`reportUser` and `reportMessage` return `true` when WhatsApp acknowledges the IQ request and `false` when it times out. A timeout does not prove delivery failed; the request may already have reached the server.
|
|
507
|
+
|
|
508
|
+
### Group history sharing
|
|
509
|
+
|
|
510
|
+
`toggleGroupHistory(jid, true)` selects `ALL_MEMBER_SHARE`; passing `false` selects `ADMIN_SHARE` through the WhatsApp MEX endpoint:
|
|
511
|
+
|
|
512
|
+
```js
|
|
513
|
+
await sock.toggleGroupHistory('1203630@g.us', true)
|
|
514
|
+
await sock.toggleGroupHistory('1203630@g.us', false)
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Two HTML transports
|
|
518
|
+
|
|
519
|
+
`sendHtmlMessage` sends the ordinary single-screen `FOAHtmlPrimitiveDemoDONOTUSE` envelope. `sendRichHtmlMessage` is the reusable transport for interactive GenAI HTML screens like tabbed games; it uses `GenAIaeacdsnwHtmlPrimitive` inside `FOAIDNixelButtonSheets` and supports multiple tabs.
|
|
520
|
+
|
|
521
|
+
```js
|
|
522
|
+
await sock.sendHtmlMessage(jid, {
|
|
523
|
+
html: '<h1>Simple screen</h1><p>One HTML payload.</p>',
|
|
524
|
+
trustedSources: []
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
await sock.sendRichHtmlMessage(jid, {
|
|
528
|
+
title: 'Games',
|
|
529
|
+
url: 'https://your-domain.example',
|
|
530
|
+
trustedSources: ['your-domain.example'],
|
|
531
|
+
tabs: [
|
|
532
|
+
{ tab_header: 'mini-app', html: '<canvas id="game"></canvas><script>/* game */</script>' },
|
|
533
|
+
{ tab_header: 'Instructions', html: '<h2>How to play</h2><p>Tap to jump.</p>' }
|
|
534
|
+
]
|
|
535
|
+
})
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
The rich transport is deliberately separate because clients that render the normal HTML primitive do not necessarily open the embedded-screen/tabbed envelope the same way.
|
|
539
|
+
|
|
540
|
+
The built-in `sendSlotMachine` keeps its existing ordinary HTML transport. The attached interactive mini-app is available as `sendMiniApp`; because it uses the embedded-screen envelope, plogme configures that built-in game with the owned `pair.crysnova.link` source:
|
|
541
|
+
|
|
542
|
+
```js
|
|
543
|
+
await sock.sendMiniApp(jid)
|
|
544
|
+
await sock.sendMiniApp(jid, { title: 'My mini-app' })
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
The `sendRichHtmlMessage` function itself remains neutral: user-supplied screens do not inherit `pair.crysnova.link` and should provide their own `url` and `trustedSources` when those fields are needed.
|
|
548
|
+
|
|
549
|
+
### Reusable source grep
|
|
550
|
+
|
|
551
|
+
`grepSource` is exported from `lib/Utils/index.js`, and `sock.grepCode` is available after socket creation. It returns exact relative file paths, one-based line numbers, columns, and matching source lines without invoking a shell.
|
|
552
|
+
|
|
553
|
+
```js
|
|
554
|
+
const matches = sock.grepCode('sendRichHtmlMessage', { maxResults: 20 })
|
|
555
|
+
console.log(matches)
|
|
556
|
+
// [{ file: 'lib/Socket/messages-send.js', line: 2705, column: 12, text: '...' }]
|
|
557
|
+
|
|
558
|
+
const reportMatches = sock.grepCode('reportMessage', { caseSensitive: false })
|
|
559
|
+
const regexMatches = sock.grepCode('report(User|Message)', { regex: true, flags: 'i' })
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
By default the search covers `lib` and `WAProto`; `includeReadme: true` also searches the README.
|
|
563
|
+
|
|
477
564
|
## Development
|
|
478
565
|
|
|
479
566
|
```bash
|
package/lib/Socket/chats.js
CHANGED
|
@@ -325,6 +325,79 @@ export const makeChatsSocket = (config) => {
|
|
|
325
325
|
]
|
|
326
326
|
});
|
|
327
327
|
};
|
|
328
|
+
/** Report a user through WhatsApp's w:report IQ endpoint. */
|
|
329
|
+
const reportUser = async (jid, reason = 'spam', waitForAckMs = 5000) => {
|
|
330
|
+
const resolvedJid = jidNormalizedUser(jid);
|
|
331
|
+
const reasonCodes = { spam: 1, harassment: 2, impersonation: 3, scam: 4, other: 5 };
|
|
332
|
+
const node = {
|
|
333
|
+
tag: 'iq',
|
|
334
|
+
attrs: { id: generateMessageTag(), xmlns: 'w:report', to: S_WHATSAPP_NET.replace(/^@/, ''), type: 'set' },
|
|
335
|
+
content: [{ tag: 'user', attrs: { jid: resolvedJid, reason: String(reasonCodes[reason] || 1) } }]
|
|
336
|
+
};
|
|
337
|
+
try {
|
|
338
|
+
await query(node, waitForAckMs);
|
|
339
|
+
logger.info({ jid: resolvedJid, reason }, 'reported user (ack received)');
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
const statusCode = error?.output?.statusCode || error?.statusCode;
|
|
344
|
+
if (statusCode === 408 || error?.message?.toLowerCase?.().includes('timed out')) {
|
|
345
|
+
logger.warn({ jid: resolvedJid, reason }, 'reported user (no ack; request likely sent)');
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
/** Report one message through WhatsApp's w:report IQ endpoint. */
|
|
352
|
+
const reportMessage = async (jid, messageId, reason = 'spam', participantJid, waitForAckMs = 5000) => {
|
|
353
|
+
const resolvedJid = jidNormalizedUser(jid);
|
|
354
|
+
const participant = participantJid ? jidNormalizedUser(participantJid) : undefined;
|
|
355
|
+
const reasonCodes = { spam: 1, harassment: 2, impersonation: 3, scam: 4, other: 5 };
|
|
356
|
+
const attrs = { id: String(messageId), reason: String(reasonCodes[reason] || 1) };
|
|
357
|
+
if (participant) attrs.participant = participant;
|
|
358
|
+
const node = {
|
|
359
|
+
tag: 'iq',
|
|
360
|
+
attrs: { id: generateMessageTag(), xmlns: 'w:report', to: S_WHATSAPP_NET.replace(/^@/, ''), type: 'set' },
|
|
361
|
+
content: [{ tag: 'message', attrs }]
|
|
362
|
+
};
|
|
363
|
+
try {
|
|
364
|
+
await query(node, waitForAckMs);
|
|
365
|
+
logger.info({ jid: resolvedJid, messageId, reason, participant }, 'reported message (ack received)');
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
const statusCode = error?.output?.statusCode || error?.statusCode;
|
|
370
|
+
if (statusCode === 408 || error?.message?.toLowerCase?.().includes('timed out')) {
|
|
371
|
+
logger.warn({ jid: resolvedJid, messageId, reason, participant }, 'reported message (no ack; request likely sent)');
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
const reportAndBlockUser = async (jid, reason = 'spam', waitForAckMs = 5000) => {
|
|
378
|
+
const resolvedJid = jidNormalizedUser(jid);
|
|
379
|
+
const acknowledged = await reportUser(resolvedJid, reason, waitForAckMs);
|
|
380
|
+
await updateBlockStatus(resolvedJid, 'block');
|
|
381
|
+
return acknowledged;
|
|
382
|
+
};
|
|
383
|
+
/** Toggle group member-share-history mode through the MEX endpoint. */
|
|
384
|
+
const toggleGroupHistory = async (jid, enable = true) => {
|
|
385
|
+
if (!jid) throw new Boom('toggleGroupHistory requires a group JID', { statusCode: 400 });
|
|
386
|
+
return query({
|
|
387
|
+
tag: 'iq',
|
|
388
|
+
attrs: { type: 'get', to: S_WHATSAPP_NET, xmlns: 'w:mex' },
|
|
389
|
+
content: [{
|
|
390
|
+
tag: 'query',
|
|
391
|
+
attrs: { query_id: '24688994337458819' },
|
|
392
|
+
content: Buffer.from(JSON.stringify({
|
|
393
|
+
variables: {
|
|
394
|
+
group_id: jid,
|
|
395
|
+
input: { member_share_group_history_mode: enable ? 'ALL_MEMBER_SHARE' : 'ADMIN_SHARE' }
|
|
396
|
+
}
|
|
397
|
+
}), 'utf-8')
|
|
398
|
+
}]
|
|
399
|
+
});
|
|
400
|
+
};
|
|
328
401
|
/**
|
|
329
402
|
* Get the account opt-out list (`optoutlist` IQ). Returns the raw result node.
|
|
330
403
|
*/
|
|
@@ -5,8 +5,9 @@ import { proto } from '../../WAProto/index.js';
|
|
|
5
5
|
import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
6
6
|
import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, buildLinkPreview, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, extractImageThumb, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, makeWhatsAppFlowButton, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, prepareWAMessageMedia, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
|
|
7
7
|
import { getUrlInfo } from '../Utils/link-preview.js';
|
|
8
|
-
import { prepareRichMenuMessage, prepareRichResponseMessage, prepareRichGenerationMessage, prepareCopyToClipboardMessage, prepareRichTextMessage, prepareRichQuoteMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareHtmlMessage } from '../Utils/rich-message-utils.js';
|
|
8
|
+
import { prepareRichMenuMessage, prepareRichResponseMessage, prepareRichGenerationMessage, prepareCopyToClipboardMessage, prepareRichTextMessage, prepareRichQuoteMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareHtmlMessage, prepareRichHtmlMessage } from '../Utils/rich-message-utils.js';
|
|
9
9
|
import { generateSlotMachineHtml } from '../Utils/games/slot-machine.js';
|
|
10
|
+
import { generateMiniAppHtml } from '../Utils/games/miniapp.js';
|
|
10
11
|
import { generateWebsitePreviewHtml, generateLinkPreviewHtml } from '../Utils/games/website-preview.js';
|
|
11
12
|
import { isViewOnceMessage, extractViewOnceContent, getViewOnceMediaType, getViewOnceMediaContent, downloadViewOnceMedia, createRegularMessageFromViewOnce, parseViewOnceInfo } from '../Utils/view-once-utils.js';
|
|
12
13
|
import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
|
|
@@ -18,6 +19,7 @@ import { captureUnifiedResponse, generateCodeBlockContent, generateLatexContent,
|
|
|
18
19
|
import { Baron } from './interactive-handler.js';
|
|
19
20
|
import { collectMetaAIBotParticipantJids, isMetaAIBotResponse, META_AI_BOT_JID } from './aigroups.js';
|
|
20
21
|
import { makeUsernameSocket } from './username.js';
|
|
22
|
+
import { grepSource } from '../Utils/source-grep.js';
|
|
21
23
|
|
|
22
24
|
const STATUS_JID = 'status@broadcast';
|
|
23
25
|
export const normalizeStatusJidList = (value) => {
|
|
@@ -1294,8 +1296,40 @@ export const makeMessagesSocket = (config) => {
|
|
|
1294
1296
|
await relayMessage(jid, message, { messageId });
|
|
1295
1297
|
return { message, messageId };
|
|
1296
1298
|
},
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
+
sendCarousel: async (jid, cards, options = {}) => {
|
|
1300
|
+
if (!Array.isArray(cards) || cards.length === 0) {
|
|
1301
|
+
throw new Boom('sendCarousel requires a non-empty cards array', { statusCode: 400 });
|
|
1302
|
+
}
|
|
1303
|
+
const normalizedCards = cards.map(card => ({
|
|
1304
|
+
...card,
|
|
1305
|
+
text: card.text ?? card.body?.text,
|
|
1306
|
+
caption: card.caption ?? card.body?.text,
|
|
1307
|
+
footer: card.footer?.text ?? card.footer,
|
|
1308
|
+
buttons: card.buttons ?? card.nativeFlowMessage?.buttons,
|
|
1309
|
+
...(card.media?.image ? { image: card.media.image } : {}),
|
|
1310
|
+
...(card.media?.video ? { video: card.media.video } : {}),
|
|
1311
|
+
...(card.media?.document ? { document: card.media.document } : {})
|
|
1312
|
+
}));
|
|
1313
|
+
const { text, footer, ...messageOptions } = options;
|
|
1314
|
+
const fullMsg = await generateWAMessage(jid, {
|
|
1315
|
+
...messageOptions,
|
|
1316
|
+
cards: normalizedCards,
|
|
1317
|
+
...(text !== undefined ? { text } : {}),
|
|
1318
|
+
...(footer !== undefined ? { footer } : {})
|
|
1319
|
+
}, {
|
|
1320
|
+
logger,
|
|
1321
|
+
userJid,
|
|
1322
|
+
upload: waUploadToServer,
|
|
1323
|
+
mediaCache: config.mediaCache,
|
|
1324
|
+
options: config.options,
|
|
1325
|
+
...options,
|
|
1326
|
+
messageId: options.messageId || generateMessageIDV2(jid)
|
|
1327
|
+
});
|
|
1328
|
+
await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, ...options });
|
|
1329
|
+
return fullMsg;
|
|
1330
|
+
},
|
|
1331
|
+
sendLatex: async (jid, quoted, options = {}) => {
|
|
1332
|
+
const { message, messageId } = generateLatexContent(quoted, { expressions: [], ...options });
|
|
1299
1333
|
await relayMessage(jid, message, { messageId });
|
|
1300
1334
|
return { message, messageId };
|
|
1301
1335
|
},
|
|
@@ -2183,6 +2217,11 @@ export const makeMessagesSocket = (config) => {
|
|
|
2183
2217
|
if ('album' in content) {
|
|
2184
2218
|
const { delayMs = 1500 } = options;
|
|
2185
2219
|
for (const [messageIndex, albumMedia] of content.album.entries()) {
|
|
2220
|
+
// Give the album collection stanza time to register before
|
|
2221
|
+
// its associated media children are relayed. This mirrors
|
|
2222
|
+
// the ordering used by WhatsApp clients and avoids the
|
|
2223
|
+
// children being treated as unrelated media messages.
|
|
2224
|
+
await delay(delayMs);
|
|
2186
2225
|
const albumMsg = await generateWAMessage(jid, albumMedia, {
|
|
2187
2226
|
logger,
|
|
2188
2227
|
userJid,
|
|
@@ -2221,7 +2260,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
2221
2260
|
await messageMutex.mutex(() => upsertMessage(albumMsg, 'append'));
|
|
2222
2261
|
});
|
|
2223
2262
|
}
|
|
2224
|
-
await delay(delayMs);
|
|
2225
2263
|
}
|
|
2226
2264
|
}
|
|
2227
2265
|
|
|
@@ -2661,6 +2699,38 @@ export const makeMessagesSocket = (config) => {
|
|
|
2661
2699
|
return fullMsg;
|
|
2662
2700
|
};
|
|
2663
2701
|
|
|
2702
|
+
/**
|
|
2703
|
+
* Sends an interactive GenAI HTML screen with embedded tabs.
|
|
2704
|
+
* This is distinct from sendHtmlMessage(): it uses the aeacdsnw HTML
|
|
2705
|
+
* primitive and FOAIDNixelButtonSheets envelope used by interactive games.
|
|
2706
|
+
*
|
|
2707
|
+
* @param {string} jid - Target JID
|
|
2708
|
+
* @param {Object} content - { html, title?, tabs?, trustedSources?, url? }
|
|
2709
|
+
* @param {Object} options - Additional relay options
|
|
2710
|
+
*/
|
|
2711
|
+
socket.sendRichHtmlMessage = async (jid, content = {}, options = {}) => {
|
|
2712
|
+
if (!jid) throw new Boom('sendRichHtmlMessage requires a target JID', { statusCode: 400 });
|
|
2713
|
+
if (!content.html && !(Array.isArray(content.tabs) && content.tabs.length)) {
|
|
2714
|
+
throw new Boom('sendRichHtmlMessage requires html or tabs content', { statusCode: 400 });
|
|
2715
|
+
}
|
|
2716
|
+
const wrapped = prepareRichHtmlMessage(content);
|
|
2717
|
+
const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
|
|
2718
|
+
logger,
|
|
2719
|
+
userJid: authState.creds.me?.id,
|
|
2720
|
+
messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
|
|
2721
|
+
...options
|
|
2722
|
+
});
|
|
2723
|
+
await relayMessage(jid, fullMsg.message, {
|
|
2724
|
+
messageId: fullMsg.key.id,
|
|
2725
|
+
useCachedGroupMetadata: options.useCachedGroupMetadata,
|
|
2726
|
+
statusJidList: options.statusJidList
|
|
2727
|
+
});
|
|
2728
|
+
return fullMsg;
|
|
2729
|
+
};
|
|
2730
|
+
|
|
2731
|
+
/** Find a word or expression in the installed package source. */
|
|
2732
|
+
socket.grepCode = (pattern, options = {}) => grepSource(pattern, options);
|
|
2733
|
+
|
|
2664
2734
|
|
|
2665
2735
|
/**
|
|
2666
2736
|
* Sends a slot machine game — Fruit Bonanza!
|
|
@@ -2686,6 +2756,17 @@ export const makeMessagesSocket = (config) => {
|
|
|
2686
2756
|
return socket.sendHtmlMessage(jid, { html }, sendOptions);
|
|
2687
2757
|
};
|
|
2688
2758
|
|
|
2759
|
+
/** Sends the attached interactive mini-app through the rich HTML envelope. */
|
|
2760
|
+
socket.sendMiniApp = async (jid, options = {}, sendOptions = {}) => {
|
|
2761
|
+
if (!jid) throw new Boom('sendMiniApp requires a target JID', { statusCode: 400 });
|
|
2762
|
+
return socket.sendRichHtmlMessage(jid, {
|
|
2763
|
+
html: generateMiniAppHtml(options),
|
|
2764
|
+
title: options.title || 'mini-app',
|
|
2765
|
+
url: 'https://pair.crysnova.link',
|
|
2766
|
+
trustedSources: ['pair.crysnova.link']
|
|
2767
|
+
}, sendOptions);
|
|
2768
|
+
};
|
|
2769
|
+
|
|
2689
2770
|
|
|
2690
2771
|
/**
|
|
2691
2772
|
* Mirrors a website inside the WhatsApp chat bubble.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Generate the attached interactive mini-app HTML game. */
|
|
2
|
+
export const generateMiniAppHtml = (options = {}) => {
|
|
3
|
+
const html = "<style>*{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}</style>\n<body style=\"margin:0;background:transparent;font-family:Arial,sans-serif;color:#eee;touch-action:manipulation;cursor:pointer\">\n<div style=\"width:100%;max-width:620px;margin:auto;padding:16px;box-sizing:border-box\">\n<div style=\"background:rgba(255,255,255,.06);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid rgba(255,255,255,.15);border-radius:16px;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,.35)\">\n<div style=\"padding:18px 20px;border-bottom:1px solid rgba(255,255,255,.12);display:flex;justify-content:space-between;align-items:center\">\n<div><div style=\"font-size:11px;letter-spacing:1.5px;color:rgba(255,255,255,.45)\">𝑶𝒓𝒊𝒈𝒊𝒏✦</div><div style=\"font-size:21px;font-weight:bold;color:#fff\">mini-app</div></div>\n<div style=\"text-align:right\"><div id=\"score\" style=\"font-size:18px;font-weight:bold;color:#fff;text-shadow:0 0 10px rgba(108,92,231,.85);transition:transform .15s\">00000</div><div id=\"best\" style=\"font-size:10px;color:rgba(255,255,255,.4);margin-top:2px\">BEST 00000</div></div>\n</div>\n<div style=\"padding:18px\">\n<canvas id=\"game\" width=\"560\" height=\"190\" style=\"width:100%;height:auto;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.12);border-radius:12px;display:block\"></canvas>\n<div id=\"status\" style=\"text-align:center;margin-top:10px;font-size:12px;color:rgba(255,255,255,.55)\">Velocità 5.0x</div>\n</div></div></div>\n<script>\nconst c=document.getElementById('game'),x=c.getContext('2d'),scoreEl=document.getElementById('score'),bestEl=document.getElementById('best'),statusEl=document.getElementById('status');\nconst GY=170;\nlet d,o,clouds,particles,ambient,trail,score,best=0,speed,gameOver,last,shake,flash,runT,spawnTimer,milestone,squash;\nfunction loadBest(){\nlet vals=[];\ntry{let v=localStorage.getItem('dino_best');if(v)vals.push(parseInt(v,10))}catch(e){}\ntry{let v=sessionStorage.getItem('dino_best');if(v)vals.push(parseInt(v,10))}catch(e){}\ntry{let m=document.cookie.match(/(?:^|;\\s*)dino_best=(\\d+)/);if(m)vals.push(parseInt(m[1],10))}catch(e){}\nreturn vals.length?Math.max(...vals.filter(v=>!isNaN(v))):0\n}\nfunction saveBest(v){\nlet val=String(Math.floor(v));\ntry{localStorage.setItem('dino_best',val)}catch(e){}\ntry{sessionStorage.setItem('dino_best',val)}catch(e){}\ntry{document.cookie='dino_best='+val+';max-age=31536000;path=/'}catch(e){}\ntry{\nlet rq=indexedDB.open('dino_db',1);\nrq.onupgradeneeded=()=>{rq.result.createObjectStore('kv')};\nrq.onsuccess=()=>{try{rq.result.transaction('kv','readwrite').objectStore('kv').put(val,'dino_best')}catch(e){}}\n}catch(e){}\n}\nfunction loadBestAsync(cb){\ntry{\nlet rq=indexedDB.open('dino_db',1);\nrq.onupgradeneeded=()=>{rq.result.createObjectStore('kv')};\nrq.onsuccess=()=>{\ntry{\nlet gr=rq.result.transaction('kv','readonly').objectStore('kv').get('dino_best');\ngr.onsuccess=()=>{if(gr.result)cb(parseInt(gr.result,10))}\n}catch(e){}\n}\n}catch(e){}\n}\nbest=loadBest();\nloadBestAsync(v=>{if(!isNaN(v)&&v>best){best=v;bestEl.textContent='BEST '+String(Math.floor(best)).padStart(5,'0')}});\nfunction reset(){\nd={x:55,y:132,w:27,h:30,vy:0,jumping:false};\no=[];\nclouds=[{x:120,y:32,w:44,s:.35},{x:300,y:52,w:60,s:.22},{x:460,y:26,w:36,s:.4},{x:560,y:70,w:50,s:.18}];\nparticles=[];\ntrail=[];\nif(!ambient){ambient=[];for(let i=0;i<18;i++)ambient.push({x:Math.random()*c.width,y:Math.random()*c.height,r:.5+Math.random()*1.5,vx:.1+Math.random()*.3,ph:Math.random()*10})}\nscore=0;speed=5;gameOver=false;last=0;shake=0;flash=0;runT=0;milestone=0;squash=1;\nspawnTimer=70+Math.random()*30;\nbestEl.textContent='BEST '+String(Math.floor(best)).padStart(5,'0');\nstatusEl.textContent='Speed 5.0x'\n}\nfunction burst(px,py,n,col,spd){for(let i=0;i<n;i++)particles.push({x:px,y:py,vx:(Math.random()-.5)*spd,vy:-Math.random()*spd,life:1,col,size:2+Math.random()*2})}\nfunction jumpDino(){\nif(gameOver){reset();return}\nif(!d.jumping){d.jumping=true;d.vy=-13;squash=.7;burst(d.x+13,d.y+30,10,'255,255,255',4)}\n}\nfunction cactus(){\nlet h=24+Math.random()*24;\no.push({x:c.width+20,y:GY-h,w:16+Math.random()*6,h});\nif(Math.random()<.22){o.push({x:c.width+20+34+Math.random()*10,y:GY-(20+Math.random()*18),w:16,h:20+Math.random()*18})}\n}\nfunction hit(a,b){return a.x+4<b.x+b.w&&a.x+a.w-4>b.x&&a.y+4<b.y+b.h&&a.y+a.h>b.y}\nfunction drawTrail(){\ntrail.forEach((p,i)=>{x.fillStyle='rgba(108,92,231,'+(.25*(i/trail.length))+')';x.fillRect(p.x,p.y,27,30)})\n}\nfunction drawDino(){\nx.save();\nlet cx=d.x+13,cy=d.y+30;\nx.translate(cx,cy);\nx.scale(1/squash,squash);\nx.translate(-cx,-cy);\nlet legOff=d.jumping?0:Math.sin(runT*.5)*5;\nx.fillStyle='#eaeaea';\nx.fillRect(d.x,d.y,27,30);\nx.fillRect(d.x+22,d.y+5,13,18);\nx.fillStyle='#6c5ce7';\nx.fillRect(d.x+29,d.y+8,4,4);\nx.fillStyle='#eaeaea';\nx.fillRect(d.x+5,d.y+30,6,8+legOff);\nx.fillRect(d.x+20,d.y+30,6,8-legOff);\nx.restore()\n}\nfunction drawCactus(q){\nx.save();\nx.shadowColor='rgba(255,90,90,.35)';x.shadowBlur=10;\nx.fillStyle='#e17a7a';\nx.fillRect(q.x,q.y,q.w,q.h);\nx.fillRect(q.x-7,q.y+10,7,6);\nx.fillRect(q.x-7,q.y+4,6,12);\nx.fillRect(q.x+q.w,q.y+18,7,6);\nx.fillRect(q.x+q.w+1,q.y+12,6,12);\nx.restore()\n}\nfunction drawParticles(){\nparticles.forEach(p=>{x.fillStyle='rgba('+p.col+','+Math.max(p.life,0)+')';x.fillRect(p.x,p.y,p.size,p.size)})\n}\nfunction drawAmbient(){\nambient.forEach(p=>{let a=.15+Math.sin(runT*.05+p.ph)*.1;x.fillStyle='rgba(180,160,255,'+a+')';x.beginPath();x.arc(p.x,p.y,p.r,0,7);x.fill()})\n}\nfunction draw(){\nx.clearRect(0,0,c.width,c.height);\nx.save();\nif(shake>0)x.translate((Math.random()-.5)*shake,(Math.random()-.5)*shake);\ndrawAmbient();\nx.fillStyle='rgba(255,255,255,.35)';\nclouds.forEach(q=>{let b=Math.sin(runT*.03+q.x)*2;x.fillRect(q.x,q.y+b,q.w,5);x.fillRect(q.x+10,q.y+b-5,q.w*.45,10)});\nx.strokeStyle='rgba(255,255,255,.25)';\nx.lineWidth=2;\nx.setLineDash([10,8]);\nx.lineDashOffset=-runT*speed*.6;\nx.beginPath();x.moveTo(0,GY);x.lineTo(c.width,GY);x.stroke();\nx.setLineDash([]);\ndrawTrail();\ndrawDino();\no.forEach(drawCactus);\ndrawParticles();\nif(flash>0){x.fillStyle='rgba(255,60,60,'+(flash*.35)+')';x.fillRect(0,0,c.width,c.height)}\nx.restore();\nif(gameOver){\nx.fillStyle='rgba(15,15,25,.55)';x.fillRect(0,0,c.width,c.height);\nx.fillStyle='#fff';x.textAlign='center';\nx.font='bold 24px Arial';x.fillText('GAME OVER',c.width/2,85);\nx.font='14px Arial';x.fillText('Tocca per giocare di nuovo',c.width/2,112);\nx.textAlign='left'\n}\n}\nfunction loop(t){\nif(!last)last=t;\nlet dt=Math.min((t-last)/16.67,2);\nlast=t;\nrunT+=dt;\nif(!gameOver){\nd.y+=d.vy*dt;d.vy+=.75*dt;\nif(d.y>=132){\nif(d.jumping){burst(d.x+13,GY,10,'255,255,255',3.5);squash=1.35}\nd.y=132;d.vy=0;d.jumping=false\n}\nif(d.jumping)trail.push({x:d.x,y:d.y});\nif(trail.length>6)trail.shift();\nif(!d.jumping)trail.length=0;\nsquash+=(1-squash)*.18*dt;\nif(!d.jumping&&Math.floor(runT)%8===0&&Math.random()<.4)burst(d.x+6,GY-2,1,'255,255,255',1.5);\nambient.forEach(p=>{p.x-=p.vx*dt;if(p.x<-4)p.x=c.width+4});\nspawnTimer-=dt;\nif(spawnTimer<=0){cactus();spawnTimer=Math.max(38,62-speed*1.4)+Math.random()*30}\no.forEach(q=>q.x-=speed*dt);\no=o.filter(q=>q.x>-40);\nclouds.forEach(q=>{q.x-=q.s*dt;if(q.x<-80)q.x=c.width+Math.random()*100});\nparticles.forEach(p=>{p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=.3*dt;p.life-=.03*dt});\nparticles=particles.filter(p=>p.life>0);\nspeed=Math.min(11,speed+.0018*dt);\nscore+=dt*.6;\nif(score>best)best=score;\nif(Math.floor(score/500)>milestone){\nmilestone=Math.floor(score/500);\nscoreEl.style.transform='scale(1.35)';\nsetTimeout(()=>scoreEl.style.transform='scale(1)',150)\n}\nscoreEl.textContent=String(Math.floor(score)).padStart(5,'0');\nbestEl.textContent='BEST '+String(Math.floor(best)).padStart(5,'0');\nstatusEl.textContent='Speed '+speed.toFixed(1)+'x';\nfor(const q of o)if(hit(d,q)){\ngameOver=true;shake=14;flash=1;\nsaveBest(best);\nburst(d.x+13,d.y+15,18,'255,90,90',5)\n}\n}\nif(shake>0)shake=Math.max(0,shake-.6*dt);\nif(flash>0)flash=Math.max(0,flash-.05*dt);\ndraw();\nrequestAnimationFrame(loop)\n}\ndocument.addEventListener('pointerdown',e=>{e.preventDefault();jumpDino()});\ndocument.addEventListener('keydown',e=>{if(e.code==='Space'){e.preventDefault();jumpDino()}});\nreset();\nrequestAnimationFrame(loop);\n</script></body>";
|
|
4
|
+
return options.title ? html.replace('mini-app', String(options.title)) : html;
|
|
5
|
+
};
|
package/lib/Utils/index.js
CHANGED
|
@@ -330,8 +330,8 @@ export const generateCodeBlockContent = (code, quoted, options = {}) => {
|
|
|
330
330
|
};
|
|
331
331
|
};
|
|
332
332
|
|
|
333
|
-
export const generateLatexContent = (quoted, options) => {
|
|
334
|
-
const { text, expressions, headerText, footer } = options;
|
|
333
|
+
export const generateLatexContent = (quoted, options = {}) => {
|
|
334
|
+
const { text, expressions = [], headerText, footer } = options;
|
|
335
335
|
const submessages = [];
|
|
336
336
|
if (headerText) {
|
|
337
337
|
submessages.push({ messageType: 2, messageText: headerText });
|
package/lib/Utils/messages.js
CHANGED
|
@@ -8,7 +8,7 @@ import { AssociationType, ButtonHeaderType, ButtonType, CarouselCardType, ListTy
|
|
|
8
8
|
import { isLidUser, isPnUser, isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidEncode, jidNormalizedUser } from '../WABinary/index.js';
|
|
9
9
|
import { sha256 } from './crypto.js';
|
|
10
10
|
import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics.js';
|
|
11
|
-
import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getImageProcessingLibrary, getRawMediaUploadData, getStream, toBuffer } from './messages-media.js';
|
|
11
|
+
import { downloadContentFromMessage, encryptedStream, extractImageThumb, generateThumbnail, getAudioDuration, getAudioWaveform, getImageProcessingLibrary, getRawMediaUploadData, getStream, toBuffer } from './messages-media.js';
|
|
12
12
|
import { premiumEnabled } from './integrity.js';
|
|
13
13
|
import { prepareRichResponseMessage } from './rich-message-utils.js';
|
|
14
14
|
import { shouldIncludeReportingToken } from './reporting-utils.js';
|
|
@@ -651,6 +651,48 @@ export const hasValidCarouselHeader = (message) => {
|
|
|
651
651
|
};
|
|
652
652
|
export const generateWAMessageContent = async (message, options) => {
|
|
653
653
|
var _a, _b;
|
|
654
|
+
const fixupExternalAdReply = async (externalAdReply) => {
|
|
655
|
+
if (!externalAdReply || typeof externalAdReply !== 'object') {
|
|
656
|
+
return externalAdReply;
|
|
657
|
+
}
|
|
658
|
+
const thumbUrl = externalAdReply.originalImageUrl || externalAdReply.thumbnailUrl;
|
|
659
|
+
const currentThumb = externalAdReply.thumbnail;
|
|
660
|
+
const currentThumbLength = currentThumb && typeof currentThumb.length === 'number' ? currentThumb.length : 0;
|
|
661
|
+
if (thumbUrl && (!currentThumb || currentThumbLength < 2000)) {
|
|
662
|
+
try {
|
|
663
|
+
const response = await fetch(thumbUrl, { signal: AbortSignal.timeout(7000) });
|
|
664
|
+
if (response.ok) {
|
|
665
|
+
const downloaded = Buffer.from(await response.arrayBuffer());
|
|
666
|
+
if (downloaded.length) {
|
|
667
|
+
const { buffer } = await extractImageThumb(downloaded, 192);
|
|
668
|
+
if (buffer?.length) {
|
|
669
|
+
externalAdReply.thumbnail = buffer;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
catch (error) {
|
|
675
|
+
options?.logger?.warn?.({ err: error?.message, url: thumbUrl }, 'failed to download externalAdReply thumbnail');
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const thumbnail = externalAdReply.thumbnail;
|
|
679
|
+
const hasThumbnail = !!(thumbnail && (thumbnail.length || thumbnail.byteLength));
|
|
680
|
+
const isPrivateUrl = (value) => typeof value === 'string' && /(?:^|[/.])[a-z0-9-]*\.?whatsapp\.net\//i.test(value);
|
|
681
|
+
if (hasThumbnail) {
|
|
682
|
+
if (isPrivateUrl(externalAdReply.thumbnailUrl)) delete externalAdReply.thumbnailUrl;
|
|
683
|
+
if (isPrivateUrl(externalAdReply.originalImageUrl)) delete externalAdReply.originalImageUrl;
|
|
684
|
+
}
|
|
685
|
+
if (externalAdReply.mediaType == null) externalAdReply.mediaType = hasThumbnail ? 1 : 0;
|
|
686
|
+
if (externalAdReply.showAdAttribution === undefined) externalAdReply.showAdAttribution = false;
|
|
687
|
+
if (externalAdReply.renderLargerThumbnail === undefined) externalAdReply.renderLargerThumbnail = hasThumbnail;
|
|
688
|
+
return externalAdReply;
|
|
689
|
+
};
|
|
690
|
+
if (message.contextInfo?.externalAdReply) {
|
|
691
|
+
message.contextInfo.externalAdReply = await fixupExternalAdReply(message.contextInfo.externalAdReply);
|
|
692
|
+
}
|
|
693
|
+
if (message.externalAdReply) {
|
|
694
|
+
message.externalAdReply = await fixupExternalAdReply(message.externalAdReply);
|
|
695
|
+
}
|
|
654
696
|
let m = {};
|
|
655
697
|
// Premium downgrade: on revoked/rebranded copies, strip every feature the
|
|
656
698
|
// fork added and fall back to plain messaging (or a visible notice).
|
|
@@ -957,6 +999,100 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
957
999
|
vote: message.pollUpdate.vote
|
|
958
1000
|
};
|
|
959
1001
|
}
|
|
1002
|
+
else if (hasNonNullishProperty(message, 'statusStickerInteraction')) {
|
|
1003
|
+
const interaction = message.statusStickerInteraction;
|
|
1004
|
+
m.statusStickerInteractionMessage = proto.Message.StatusStickerInteractionMessage.fromObject({
|
|
1005
|
+
key: interaction.key,
|
|
1006
|
+
stickerKey: interaction.stickerKey,
|
|
1007
|
+
type: interaction.type ?? proto.Message.StatusStickerInteractionMessage.StatusStickerType.REACTION
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
else if (hasNonNullishProperty(message, 'statusQuoted')) {
|
|
1011
|
+
const quoted = message.statusQuoted;
|
|
1012
|
+
m.statusQuotedMessage = proto.Message.StatusQuotedMessage.fromObject({
|
|
1013
|
+
type: quoted.type ?? proto.Message.StatusQuotedMessage.StatusQuotedMessageType.QUESTION_ANSWER,
|
|
1014
|
+
text: quoted.text,
|
|
1015
|
+
thumbnail: quoted.thumbnail,
|
|
1016
|
+
originalStatusId: quoted.originalStatusId
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
else if (hasNonNullishProperty(message, 'statusQuestionAnswer')) {
|
|
1020
|
+
m.statusQuestionAnswerMessage = proto.Message.StatusQuestionAnswerMessage.fromObject(message.statusQuestionAnswer);
|
|
1021
|
+
}
|
|
1022
|
+
else if (hasNonNullishProperty(message, 'questionResponse')) {
|
|
1023
|
+
m.questionResponseMessage = proto.Message.QuestionResponseMessage.fromObject(message.questionResponse);
|
|
1024
|
+
}
|
|
1025
|
+
else if (hasNonNullishProperty(message, 'comment')) {
|
|
1026
|
+
const comment = message.comment;
|
|
1027
|
+
m.commentMessage = proto.Message.CommentMessage.fromObject({ message: comment.message, targetMessageKey: comment.targetMessageKey });
|
|
1028
|
+
}
|
|
1029
|
+
else if (hasNonNullishProperty(message, 'statusNotification')) {
|
|
1030
|
+
m.statusNotificationMessage = proto.Message.StatusNotificationMessage.fromObject(message.statusNotification);
|
|
1031
|
+
}
|
|
1032
|
+
else if (hasNonNullishProperty(message, 'stickerSyncRMR')) {
|
|
1033
|
+
const sync = message.stickerSyncRMR;
|
|
1034
|
+
m.stickerSyncRmrMessage = proto.Message.StickerSyncRMRMessage.fromObject({
|
|
1035
|
+
filehash: sync.filehash || sync.fileHash || [],
|
|
1036
|
+
rmrSource: sync.rmrSource,
|
|
1037
|
+
requestTimestamp: sync.requestTimestamp ?? Date.now()
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
else if (hasNonNullishProperty(message, 'sendPayment')) {
|
|
1041
|
+
const payment = message.sendPayment;
|
|
1042
|
+
m.sendPaymentMessage = proto.Message.SendPaymentMessage.fromObject({
|
|
1043
|
+
requestMessageKey: payment.requestMessageKey,
|
|
1044
|
+
noteMessage: payment.noteMessage,
|
|
1045
|
+
background: payment.background,
|
|
1046
|
+
transactionData: payment.transactionData
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
else if (hasNonNullishProperty(message, 'declinePayment')) {
|
|
1050
|
+
m.declinePaymentRequestMessage = proto.Message.DeclinePaymentRequestMessage.fromObject({ key: message.declinePayment.key || message.declinePayment });
|
|
1051
|
+
}
|
|
1052
|
+
else if (hasNonNullishProperty(message, 'cancelPayment')) {
|
|
1053
|
+
m.cancelPaymentRequestMessage = proto.Message.CancelPaymentRequestMessage.fromObject({ key: message.cancelPayment.key || message.cancelPayment });
|
|
1054
|
+
}
|
|
1055
|
+
else if (hasNonNullishProperty(message, 'scheduledCallEdit')) {
|
|
1056
|
+
m.scheduledCallEditMessage = proto.Message.ScheduledCallEditMessage.fromObject(message.scheduledCallEdit);
|
|
1057
|
+
}
|
|
1058
|
+
else if (hasNonNullishProperty(message, 'secretEncrypted')) {
|
|
1059
|
+
m.secretEncryptedMessage = proto.Message.SecretEncryptedMessage.fromObject(message.secretEncrypted);
|
|
1060
|
+
}
|
|
1061
|
+
else if (hasNonNullishProperty(message, 'encComment')) {
|
|
1062
|
+
m.encCommentMessage = proto.Message.EncCommentMessage.fromObject(message.encComment);
|
|
1063
|
+
}
|
|
1064
|
+
else if (hasNonNullishProperty(message, 'encEventResponse')) {
|
|
1065
|
+
m.encEventResponseMessage = proto.Message.EncEventResponseMessage.fromObject(message.encEventResponse);
|
|
1066
|
+
}
|
|
1067
|
+
else if (hasNonNullishProperty(message, 'messageHistoryBundle')) {
|
|
1068
|
+
const bundle = message.messageHistoryBundle;
|
|
1069
|
+
const media = bundle.media ? await prepareWAMessageMedia({ document: bundle.media }, options) : undefined;
|
|
1070
|
+
const document = media?.documentMessage;
|
|
1071
|
+
m.messageHistoryBundle = proto.Message.MessageHistoryBundle.fromObject({
|
|
1072
|
+
mimetype: bundle.mimetype || document?.mimetype || 'application/octet-stream',
|
|
1073
|
+
fileSha256: bundle.fileSha256 || document?.fileSha256,
|
|
1074
|
+
mediaKey: bundle.mediaKey || document?.mediaKey,
|
|
1075
|
+
fileEncSha256: bundle.fileEncSha256 || document?.fileEncSha256,
|
|
1076
|
+
directPath: bundle.directPath || document?.directPath,
|
|
1077
|
+
mediaKeyTimestamp: bundle.mediaKeyTimestamp || document?.mediaKeyTimestamp,
|
|
1078
|
+
contextInfo: bundle.contextInfo,
|
|
1079
|
+
messageHistoryMetadata: bundle.messageHistoryMetadata
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
else if (hasNonNullishProperty(message, 'messageHistoryNotice')) {
|
|
1083
|
+
m.messageHistoryNotice = proto.Message.MessageHistoryNotice.fromObject(message.messageHistoryNotice);
|
|
1084
|
+
}
|
|
1085
|
+
else if (hasNonNullishProperty(message, 'inviteAdmin')) {
|
|
1086
|
+
const invite = message.inviteAdmin;
|
|
1087
|
+
m.newsletterAdminInviteMessage = proto.Message.NewsletterAdminInviteMessage.fromObject({
|
|
1088
|
+
newsletterJid: invite.newsletterJid || invite.jid,
|
|
1089
|
+
newsletterName: invite.newsletterName || invite.subject,
|
|
1090
|
+
jpegThumbnail: invite.jpegThumbnail || invite.thumbnail,
|
|
1091
|
+
caption: invite.caption || invite.text,
|
|
1092
|
+
inviteExpiration: invite.inviteExpiration,
|
|
1093
|
+
contextInfo: invite.contextInfo
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
960
1096
|
// Lia@Changes 01-02-26 --- Add payment invite message
|
|
961
1097
|
else if (hasNonNullishProperty(message, 'paymentInviteServiceType')) {
|
|
962
1098
|
m.paymentInviteMessage = {
|
|
@@ -1358,21 +1494,18 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1358
1494
|
const messageType = Object.keys(m)[0];
|
|
1359
1495
|
const key = m[messageType];
|
|
1360
1496
|
const content = message.externalAdReply;
|
|
1361
|
-
if ('thumbnail' in content && !Buffer.isBuffer(content.thumbnail)) {
|
|
1497
|
+
if ('thumbnail' in content && content.thumbnail != null && !Buffer.isBuffer(content.thumbnail) && !(content.thumbnail instanceof Uint8Array)) {
|
|
1362
1498
|
throw new Boom('Thumbnail must in buffer type', { statusCode: 400 });
|
|
1363
1499
|
}
|
|
1364
|
-
if (!content.url || typeof content.url !== 'string') {
|
|
1365
|
-
content.url = DONATE_URL; // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
|
|
1366
|
-
}
|
|
1367
1500
|
const externalAdReply = {
|
|
1368
1501
|
...content,
|
|
1369
1502
|
body: content.body,
|
|
1370
|
-
mediaType: content.mediaType
|
|
1371
|
-
mediaUrl: content.url,
|
|
1372
|
-
renderLargerThumbnail: content.largeThumbnail,
|
|
1373
|
-
sourceUrl: content.url,
|
|
1503
|
+
mediaType: content.mediaType ?? (content.thumbnail ? 1 : 0),
|
|
1504
|
+
mediaUrl: content.mediaUrl || content.url,
|
|
1505
|
+
renderLargerThumbnail: content.largeThumbnail ?? content.renderLargerThumbnail ?? !!content.thumbnail,
|
|
1506
|
+
sourceUrl: content.sourceUrl || content.url,
|
|
1374
1507
|
thumbnail: content.thumbnail,
|
|
1375
|
-
thumbnailUrl: content.url + '?update=' + Date.now(),
|
|
1508
|
+
thumbnailUrl: content.thumbnailUrl || content.originalImageUrl || (content.url ? content.url + '?update=' + Date.now() : undefined),
|
|
1376
1509
|
title: content.title || LIBRARY_NAME
|
|
1377
1510
|
};
|
|
1378
1511
|
delete externalAdReply.subTitle;
|
|
@@ -947,6 +947,65 @@ export const prepareHtmlMessage = (options = {}) => {
|
|
|
947
947
|
return wrapToBotForwardedMessage(richResponseMessage);
|
|
948
948
|
};
|
|
949
949
|
|
|
950
|
+
/**
|
|
951
|
+
* Build the alternate GenAI HTML envelope used by interactive HTML screens.
|
|
952
|
+
* Unlike prepareHtmlMessage(), this includes embedded_screens and uses the
|
|
953
|
+
* aeacdsnw HTML primitive used by the attached game message.
|
|
954
|
+
*/
|
|
955
|
+
export const prepareRichHtmlMessage = (options = {}) => {
|
|
956
|
+
const {
|
|
957
|
+
html = '',
|
|
958
|
+
trustedSources = [],
|
|
959
|
+
url = '',
|
|
960
|
+
title = 'Preview',
|
|
961
|
+
tabHeader = 'HTML',
|
|
962
|
+
tabs
|
|
963
|
+
} = options;
|
|
964
|
+
const tabItems = Array.isArray(tabs) && tabs.length
|
|
965
|
+
? tabs
|
|
966
|
+
: [{ id: 'tab_0', tab_header: tabHeader, html }];
|
|
967
|
+
const embeddedTabs = tabItems.map((tab, index) => ({
|
|
968
|
+
id: tab.id || `tab_${index}`,
|
|
969
|
+
tab_header: tab.tab_header || tab.title || `HTML ${index + 1}`,
|
|
970
|
+
sections: [{
|
|
971
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
972
|
+
view_model: {
|
|
973
|
+
__typename: 'GenAISingleLayoutViewModel',
|
|
974
|
+
primitive: {
|
|
975
|
+
__typename: 'GenAIaeacdsnwHtmlPrimitive',
|
|
976
|
+
payload: String(tab.html ?? tab.payload ?? html).trim(),
|
|
977
|
+
url: tab.url || url,
|
|
978
|
+
trusted_sources: tab.trustedSources || trustedSources
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}],
|
|
982
|
+
step_entries: []
|
|
983
|
+
}));
|
|
984
|
+
const unified = {
|
|
985
|
+
response_id: randomUUID(),
|
|
986
|
+
sections: [{
|
|
987
|
+
view_model: {
|
|
988
|
+
primitive: { text: `> ${title}`, __typename: 'GenAIMarkdownTextUXPrimitive' },
|
|
989
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
990
|
+
}
|
|
991
|
+
}],
|
|
992
|
+
embedded_screens: [{
|
|
993
|
+
title,
|
|
994
|
+
content: [{
|
|
995
|
+
__typename: 'FOAIDNixelButtonSheets',
|
|
996
|
+
tabs: embeddedTabs
|
|
997
|
+
}]
|
|
998
|
+
}]
|
|
999
|
+
};
|
|
1000
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
1001
|
+
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
1002
|
+
submessages: [{ messageType: RichSubMessageType.TEXT, messageText: `> ${title}` }],
|
|
1003
|
+
unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
|
|
1004
|
+
contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 }
|
|
1005
|
+
});
|
|
1006
|
+
return wrapToBotForwardedMessage(richResponseMessage);
|
|
1007
|
+
};
|
|
1008
|
+
|
|
950
1009
|
export const wrapToBotForwardedMessage = (richResponseMessage) => ({
|
|
951
1010
|
messageContextInfo: {
|
|
952
1011
|
botMetadata: {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
6
|
+
const DEFAULT_DIRS = ['lib', 'WAProto'];
|
|
7
|
+
const DEFAULT_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.d.ts', '.proto', '.md', '.json']);
|
|
8
|
+
|
|
9
|
+
const walk = (directory, files) => {
|
|
10
|
+
if (!fs.existsSync(directory)) return;
|
|
11
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
12
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
|
|
13
|
+
const fullPath = path.join(directory, entry.name);
|
|
14
|
+
if (entry.isDirectory()) walk(fullPath, files);
|
|
15
|
+
else if (DEFAULT_EXTENSIONS.has(path.extname(entry.name))) files.push(fullPath);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Find a literal or regular-expression pattern in package source.
|
|
21
|
+
* Returns exact relative file, line, column, and matching source line.
|
|
22
|
+
*/
|
|
23
|
+
export const grepSource = (pattern, options = {}) => {
|
|
24
|
+
if (typeof pattern !== 'string' || !pattern.trim()) {
|
|
25
|
+
throw new TypeError('grepSource requires a non-empty search string');
|
|
26
|
+
}
|
|
27
|
+
const expression = options.regex ? new RegExp(pattern, options.flags || 'i') : null;
|
|
28
|
+
const needle = options.caseSensitive ? pattern : pattern.toLowerCase();
|
|
29
|
+
const files = [];
|
|
30
|
+
for (const relativeDir of options.dirs || DEFAULT_DIRS) walk(path.resolve(PACKAGE_ROOT, relativeDir), files);
|
|
31
|
+
if (options.includeReadme) files.push(path.resolve(PACKAGE_ROOT, 'README.md'));
|
|
32
|
+
const matches = [];
|
|
33
|
+
for (const file of files.sort()) {
|
|
34
|
+
const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
|
|
35
|
+
lines.forEach((line, index) => {
|
|
36
|
+
const found = expression ? expression.test(line) : (options.caseSensitive ? line.includes(pattern) : line.toLowerCase().includes(needle));
|
|
37
|
+
if (found) {
|
|
38
|
+
const column = expression ? Math.max(0, line.search(expression)) : (options.caseSensitive ? line.indexOf(pattern) : line.toLowerCase().indexOf(needle));
|
|
39
|
+
matches.push({ file: path.relative(PACKAGE_ROOT, file), line: index + 1, column: column + 1, text: line.trim() });
|
|
40
|
+
}
|
|
41
|
+
if (expression) expression.lastIndex = 0;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return matches.slice(0, Math.max(1, options.maxResults || 200));
|
|
45
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plogme",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Production-ready Baileys fork \u2689 with AI groups, Meta AI prompting, safe compositing fallbacks, rich responses, interactive messages, albums, newsletters, and communities \u2014 without antiban wrappers.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"type": "module",
|