@kanaraa/baileys 3.2.0 → 3.4.0

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.
@@ -16,9 +16,23 @@ export const makeGroupsSocket = (config) => {
16
16
  },
17
17
  content
18
18
  });
19
- const groupMetadata = async (jid) => {
19
+ const fetchJoinRequests = async (jid) => {
20
+ const req = await groupQuery(jid, 'get', [
21
+ {
22
+ tag: 'membership_approval_requests',
23
+ attrs: {}
24
+ }
25
+ ]);
26
+ const node = getBinaryNodeChild(req, 'membership_approval_requests');
27
+ return getBinaryNodeChildren(node, 'membership_approval_request').map(v => v.attrs);
28
+ };
29
+ const groupMetadata = async (jid, opts = {}) => {
20
30
  const result = await groupQuery(jid, 'get', [{ tag: 'query', attrs: { request: 'interactive' } }]);
21
- return extractGroupMetadata(result);
31
+ const metadata = extractGroupMetadata(result);
32
+ if (opts.joinRequests) {
33
+ metadata.joinRequests = await fetchJoinRequests(jid).catch(() => []);
34
+ }
35
+ return metadata;
22
36
  };
23
37
  const groupFetchAllParticipating = async () => {
24
38
  const result = await query({
@@ -103,15 +117,7 @@ export const makeGroupsSocket = (config) => {
103
117
  ]);
104
118
  },
105
119
  groupRequestParticipantsList: async (jid) => {
106
- const result = await groupQuery(jid, 'get', [
107
- {
108
- tag: 'membership_approval_requests',
109
- attrs: {}
110
- }
111
- ]);
112
- const node = getBinaryNodeChild(result, 'membership_approval_requests');
113
- const participants = getBinaryNodeChildren(node, 'membership_approval_request');
114
- return participants.map(v => v.attrs);
120
+ return fetchJoinRequests(jid).catch(() => []);
115
121
  },
116
122
  groupRequestParticipantsUpdate: async (jid, participants, action) => {
117
123
  const result = await groupQuery(jid, 'set', [
@@ -342,5 +348,6 @@ export const extractGroupMetadata = (result) => {
342
348
  }),
343
349
  ephemeralDuration: eph ? +eph : undefined
344
350
  };
351
+ metadata.admins = metadata.participants.filter(p => p.admin);
345
352
  return metadata;
346
353
  };
@@ -1,84 +1,122 @@
1
1
  import { prepareWAMessageMedia } from './messages.js';
2
2
  import { extractImageThumb, getHttpStream } from './messages-media.js';
3
+
3
4
  const THUMBNAIL_WIDTH_PX = 192;
4
- /** Fetches an image and generates a thumbnail for it */
5
+
6
+ const htmlDecode = (s) => s
7
+ .replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
8
+ .replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(Number(dec)))
9
+ .replace(/"/g, '"')
10
+ .replace(/'/g, "'")
11
+ .replace(/&lt;/g, '<')
12
+ .replace(/&gt;/g, '>')
13
+ .replace(/&amp;/g, '&');
14
+
15
+ const getMetaContent = (html, keys) => {
16
+ const pattern = new RegExp(`<meta[^>]+(?:property|name)=["'](?:${keys.join('|')})["'][^>]*content=["']([^"']*)["']`, 'i');
17
+ const match = html.match(pattern);
18
+ if (match) return htmlDecode(match[1]);
19
+ const reversed = new RegExp(`<meta[^>]+content=["']([^"']*)["'][^>]*(?:property|name)=["'](?:${keys.join('|')})["']`, 'i');
20
+ const alt = html.match(reversed);
21
+ if (alt) return htmlDecode(alt[1]);
22
+ return null;
23
+ };
24
+
25
+ const getTitle = (html) => {
26
+ const meta = getMetaContent(html, ['og:title', 'twitter:title']);
27
+ if (meta) return meta;
28
+ const match = html.match(/<title[^>]*>([^<]*)<\/title>/i);
29
+ return match ? htmlDecode(match[1].trim()) : null;
30
+ };
31
+
32
+ const getFavicon = (html, baseUrl) => {
33
+ const match = html.match(/<link[^>]+href=["']([^"']+)["'][^>]*rel=["'](?:shortcut\s+)?icon["']/i) ||
34
+ html.match(/<link[^>]+rel=["'](?:shortcut\s+)?icon["'][^>]*href=["']([^"']+)["']/i);
35
+ const href = match ? match[1] || match[2] : null;
36
+ try {
37
+ return new URL(href || '/favicon.ico', baseUrl).href;
38
+ } catch {
39
+ return href || `${baseUrl}/favicon.ico`;
40
+ }
41
+ };
42
+
5
43
  const getCompressedJpegThumbnail = async (url, { thumbnailWidth, fetchOpts }) => {
6
44
  const stream = await getHttpStream(url, fetchOpts);
7
45
  const result = await extractImageThumb(stream, thumbnailWidth);
8
46
  return result;
9
47
  };
10
- /**
11
- * Given a piece of text, checks for any URL present, generates link preview for the same and returns it
12
- * Return undefined if the fetch failed or no URL was found
13
- * @param text first matched URL in text
14
- * @returns the URL info required to generate link preview
15
- */
48
+
49
+ const fetchPage = async (url, fetchOpts = {}) => {
50
+ const controller = new AbortController();
51
+ const timer = setTimeout(() => controller.abort(), fetchOpts.timeout || 3000);
52
+ try {
53
+ const response = await fetch(url, {
54
+ method: 'GET',
55
+ redirect: 'follow',
56
+ signal: controller.signal,
57
+ headers: {
58
+ 'user-agent': 'WhatsApp/2.24.6.77',
59
+ accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
60
+ ...(fetchOpts.headers || {})
61
+ }
62
+ });
63
+ if (!response.ok) {
64
+ throw new Error(`HTTP ${response.status}`);
65
+ }
66
+ const html = await response.text();
67
+ return {
68
+ html,
69
+ finalUrl: response.url || url
70
+ };
71
+ } finally {
72
+ clearTimeout(timer);
73
+ }
74
+ };
75
+
16
76
  export const getUrlInfo = async (text, opts = {
17
77
  thumbnailWidth: THUMBNAIL_WIDTH_PX,
18
78
  fetchOpts: { timeout: 3000 }
19
79
  }) => {
20
80
  try {
21
- // retries
22
- let retries = 0;
23
- const maxRetry = 5;
24
- const { getLinkPreview } = await import('link-preview-js');
25
81
  let previewLink = text;
26
- if (!text.startsWith('https://') && !text.startsWith('http://')) {
82
+ if (!previewLink.startsWith('https://') && !previewLink.startsWith('http://')) {
27
83
  previewLink = 'https://' + previewLink;
28
84
  }
29
- const info = await getLinkPreview(previewLink, {
30
- ...opts.fetchOpts,
31
- followRedirects: 'follow',
32
- handleRedirects: (baseURL, forwardedURL) => {
33
- const urlObj = new URL(baseURL);
34
- const forwardedURLObj = new URL(forwardedURL);
35
- if (retries >= maxRetry) {
36
- return false;
37
- }
38
- if (forwardedURLObj.hostname === urlObj.hostname ||
39
- forwardedURLObj.hostname === 'www.' + urlObj.hostname ||
40
- 'www.' + forwardedURLObj.hostname === urlObj.hostname) {
41
- retries += 1;
42
- return true;
43
- }
44
- else {
45
- return false;
46
- }
47
- },
48
- headers: opts.fetchOpts?.headers
49
- });
50
- if (info && 'title' in info && info.title) {
51
- const [image] = info.images;
52
- const urlInfo = {
53
- 'canonical-url': info.url,
54
- 'matched-text': text,
55
- title: info.title,
56
- description: info.description,
57
- originalThumbnailUrl: image
58
- };
59
- if (opts.uploadImage) {
60
- const { imageMessage } = await prepareWAMessageMedia({ image: { url: image } }, {
61
- upload: opts.uploadImage,
62
- mediaTypeOverride: 'thumbnail-link',
63
- options: opts.fetchOpts
64
- });
65
- urlInfo.jpegThumbnail = imageMessage?.jpegThumbnail ? Buffer.from(imageMessage.jpegThumbnail) : undefined;
66
- urlInfo.highQualityThumbnail = imageMessage || undefined;
85
+ const { html, finalUrl } = await fetchPage(previewLink, opts.fetchOpts);
86
+ const title = getTitle(html);
87
+ if (!title) {
88
+ return undefined;
89
+ }
90
+ const description = getMetaContent(html, ['og:description', 'twitter:description', 'description']);
91
+ const image = getMetaContent(html, ['og:image', 'twitter:image']);
92
+ const favicon = getFavicon(html, finalUrl);
93
+ const urlInfo = {
94
+ 'canonical-url': finalUrl,
95
+ 'matched-text': text,
96
+ title,
97
+ description,
98
+ originalThumbnailUrl: image || favicon
99
+ };
100
+ if (opts.uploadImage) {
101
+ const { imageMessage } = await prepareWAMessageMedia({ image: { url: urlInfo.originalThumbnailUrl } }, {
102
+ upload: opts.uploadImage,
103
+ mediaTypeOverride: 'thumbnail-link',
104
+ options: opts.fetchOpts
105
+ });
106
+ urlInfo.jpegThumbnail = imageMessage?.jpegThumbnail ? Buffer.from(imageMessage.jpegThumbnail) : undefined;
107
+ urlInfo.highQualityThumbnail = imageMessage || undefined;
108
+ }
109
+ else {
110
+ try {
111
+ urlInfo.jpegThumbnail = (await getCompressedJpegThumbnail(urlInfo.originalThumbnailUrl, opts)).buffer;
67
112
  }
68
- else {
69
- try {
70
- urlInfo.jpegThumbnail = image ? (await getCompressedJpegThumbnail(image, opts)).buffer : undefined;
71
- }
72
- catch (error) {
73
- opts.logger?.debug({ err: error.stack, url: previewLink }, 'error in generating thumbnail');
74
- }
113
+ catch (error) {
114
+ opts.logger?.debug({ err: error.stack, url: previewLink }, 'error in generating thumbnail');
75
115
  }
76
- return urlInfo;
77
116
  }
117
+ return urlInfo;
78
118
  }
79
119
  catch (error) {
80
- if (!error.message.includes('receive a valid')) {
81
- throw error;
82
- }
120
+ opts.logger?.debug({ err: error.stack, url: text }, 'error in generating link preview');
83
121
  }
84
122
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kanaraa/baileys",
3
3
  "type": "module",
4
- "version": "3.2.0",
4
+ "version": "3.4.0",
5
5
  "description": "Modded Baileys v7, Rebuilt on top of official @whiskeysockets/baileys 7.0.0-rc13, with the interactive & rich-message content types (buttons, lists, carousel, cards, shop/collection, native flow, AI rich response, sticker packs, admin invite, payments, etc.).",
6
6
  "keywords": [
7
7
  "whatsapp",
@@ -43,10 +43,9 @@
43
43
  "peerDependencies": {
44
44
  "adm-zip": "^0.5.10",
45
45
  "audio-decode": "^2.1.3",
46
- "fluent-ffmpeg": "^2.1.2",
46
+ "fluent-ffmpeg": "^2.1.3",
47
47
  "jimp": "^1.6.1",
48
- "link-preview-js": "^3.0.0",
49
- "sharp": "*"
48
+ "sharp": "^0.35.3"
50
49
  },
51
50
  "peerDependenciesMeta": {
52
51
  "adm-zip": {
@@ -61,9 +60,6 @@
61
60
  "jimp": {
62
61
  "optional": true
63
62
  },
64
- "link-preview-js": {
65
- "optional": true
66
- },
67
63
  "sharp": {
68
64
  "optional": true
69
65
  }