whalibmob 5.10.6 → 5.10.7

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 CHANGED
@@ -2149,7 +2149,7 @@ Both methods accept a **Buffer** (use `fs.readFileSync` to load a file).
2149
2149
  ```js
2150
2150
  const fs = require('fs')
2151
2151
 
2152
- // change your own profile picture
2152
+ // change your own profile picture — any image, any shape, any size
2153
2153
  // returns the new picture id, or 'remove' when it was taken down
2154
2154
  const picId = await client.changeProfilePicture(fs.readFileSync('./avatar.jpg'))
2155
2155
 
@@ -2165,8 +2165,25 @@ const picId = await client.changeGroupPicture('120363000000000000@g.us',
2165
2165
  await client.changeGroupPicture('120363000000000000@g.us', null)
2166
2166
  ```
2167
2167
 
2168
- `changeGroupPicture` throws when the server refuses — `406` for an image that is
2169
- not a JPEG it will take, `403` when you are not an admin of that group.
2168
+ `changeGroupPicture` throws when the server refuses — `406` for an image it will
2169
+ not take, `403` when you are not an admin of that group.
2170
+
2171
+ **Both calls re-encode the image before sending it.** WhatsApp wants a square
2172
+ 640×640 JPEG, and a picture that is not one is *dropped without an answer*
2173
+ rather than refused — so an unprepared file fails as a timeout that looks like a
2174
+ network fault. The image is cropped square, scaled and written out as a fresh
2175
+ JPEG, which also strips EXIF and any progressive encoding the file was carrying.
2176
+
2177
+ This needs `jimp` (an optional dependency) or `sharp`. Without either, the file
2178
+ is sent as it is and an already-correct picture still works.
2179
+
2180
+ ```js
2181
+ // skip the re-encode if you have prepared the image yourself
2182
+ await client.changeProfilePicture(buf, { raw: true })
2183
+
2184
+ // or choose the size
2185
+ await client.changeProfilePicture(buf, { size: 640, quality: 50 })
2186
+ ```
2170
2187
 
2171
2188
  ## Privacy
2172
2189
 
package/lib/Client.js CHANGED
@@ -10,6 +10,7 @@ const { NoiseSocket } = require('./noise');
10
10
  const { MessageSender, generateMessageId, makeJid, buildOrGetAdvIdentity } = require('./messages/MessageSender');
11
11
  const { checkIfRegistered, checkNumberStatus, requestSmsCode, verifyCode, assertRegistrationKeys, fetchIosVersion, fetchWaVersion } = require('./Registration');
12
12
  const { getDeviceConfig } = require('./DeviceConfig');
13
+ const { prepareProfilePicture } = require('./MediaThumbnail');
13
14
  const { createNewStore, saveStore, loadStore, toSixParts, fromSixParts } = require('./Store');
14
15
  const { BinaryNode } = require('./BinaryNode');
15
16
  const { AppStateStore, COLLECTIONS } = require('./appstate/AppStateStore');
@@ -4330,30 +4331,45 @@ class WhalibmobClient extends EventEmitter {
4330
4331
  // buf: Buffer with JPEG image data
4331
4332
  // Change your own profile picture.
4332
4333
  //
4333
- // buf: a Buffer of JPEG bytes, or null to remove the current picture.
4334
+ // buf: a Buffer holding any image, or null to remove the current picture.
4334
4335
  // Returns the new picture id, or 'remove' when it was taken down.
4335
4336
  //
4337
+ // The image is re-encoded to the square 640x640 JPEG the server expects
4338
+ // before it goes out. Sending a file as it came off disk is what a person
4339
+ // naturally tries and what the server silently drops — pass { raw: true } to
4340
+ // skip that if you have already prepared it yourself.
4341
+ //
4336
4342
  // The picture is not addressed by sending the IQ *to* yourself: it goes to
4337
4343
  // the server with the account named in `target`, the same shape a group
4338
4344
  // picture uses. Addressed the other way the server files it nowhere and never
4339
4345
  // answers, which looked from the outside like the picture had been set — the
4340
4346
  // stanza went out, nothing came back, and nothing checked.
4341
- async changeProfilePicture(buf) {
4347
+ async changeProfilePicture(buf, opts) {
4342
4348
  if (!this._store) throw new Error('Not connected');
4349
+ opts = opts || {};
4343
4350
  const selfJid = this._ownDeviceJid
4344
4351
  ? toNonAdJid(this._ownDeviceJid())
4345
4352
  : `${this._store.phoneNumber}@s.whatsapp.net`;
4346
4353
 
4354
+ const picture = (buf && !opts.raw) ? await prepareProfilePicture(buf, opts) : buf;
4355
+
4347
4356
  const node = new BinaryNode('iq', {
4348
4357
  id: this._genMsgId(),
4349
4358
  xmlns: 'w:profile:picture',
4350
4359
  to: 's.whatsapp.net',
4351
4360
  target: jidStrToObj(selfJid),
4352
4361
  type: 'set'
4353
- }, buf ? [new BinaryNode('picture', { type: 'image' }, buf)] : null);
4362
+ }, picture ? [new BinaryNode('picture', { type: 'image' }, picture)] : null);
4354
4363
 
4355
4364
  const resp = await this._sendIq(node);
4356
- if (!resp) throw new Error('changeProfilePicture: no reply from server (IQ timed out)');
4365
+ if (!resp) {
4366
+ throw new Error('changeProfilePicture: no reply from server (IQ timed out). ' +
4367
+ 'The server drops a picture it will not take rather than refusing it, so ' +
4368
+ 'this usually means the image was not in the form it wants' +
4369
+ (opts.raw ? ' — this call passed { raw: true }, so nothing was re-encoded'
4370
+ : '. Install jimp if it is not present, so the image can be ' +
4371
+ 're-encoded before sending') + '.');
4372
+ }
4357
4373
  if (resp.attrs && resp.attrs.type === 'error') {
4358
4374
  const errNode = findChild(resp, 'error');
4359
4375
  const code = errNode && errNode.attrs && errNode.attrs.code;
@@ -5923,7 +5939,12 @@ class WhalibmobClient extends EventEmitter {
5923
5939
  //
5924
5940
  // Removal is the IQ with no body at all — an empty <picture> node is a
5925
5941
  // malformed upload rather than a delete, and the server answers 406 for it.
5926
- async changeGroupPicture(groupJid, buf) {
5942
+ //
5943
+ // The image is re-encoded to a square 640x640 JPEG first, on the same terms
5944
+ // as a profile picture; { raw: true } skips that.
5945
+ async changeGroupPicture(groupJid, buf, opts) {
5946
+ opts = opts || {};
5947
+ const picture = (buf && !opts.raw) ? await prepareProfilePicture(buf, opts) : buf;
5927
5948
  const id = this._genMsgId();
5928
5949
  const node = new BinaryNode('iq', {
5929
5950
  id,
@@ -5931,9 +5952,13 @@ class WhalibmobClient extends EventEmitter {
5931
5952
  target: jidStrToObj(makeJid(groupJid)),
5932
5953
  to: 's.whatsapp.net',
5933
5954
  type: 'set'
5934
- }, buf ? [new BinaryNode('picture', { type: 'image' }, buf)] : null);
5955
+ }, picture ? [new BinaryNode('picture', { type: 'image' }, picture)] : null);
5935
5956
  const resp = await this._sendIq(node);
5936
- if (!resp) throw new Error('changeGroupPicture: no reply from server (IQ timed out)');
5957
+ if (!resp) {
5958
+ throw new Error('changeGroupPicture: no reply from server (IQ timed out). ' +
5959
+ 'The server drops a picture it will not take rather than refusing it, so ' +
5960
+ 'this usually means the image was not in the form it wants.');
5961
+ }
5937
5962
  if (resp.attrs && resp.attrs.type === 'error') {
5938
5963
  const errNode = findChild(resp, 'error');
5939
5964
  const code = errNode && errNode.attrs && errNode.attrs.code;
@@ -290,6 +290,70 @@ async function imageThumbnail(buf, opts) {
290
290
  return out;
291
291
  }
292
292
 
293
+ // ─── Profile pictures ────────────────────────────────────────────────────────
294
+
295
+ // What WhatsApp's own apps upload. The server will not take an arbitrary file:
296
+ // it wants a square JPEG at this size, and a picture that is not one is dropped
297
+ // without an answer rather than refused, so the failure gives no clue.
298
+ const AVATAR_SIZE = 640;
299
+ const AVATAR_QUALITY = 50;
300
+
301
+ /**
302
+ * Re-encode an image into the form a profile picture has to be in.
303
+ *
304
+ * Cropped square from the top-left of the shorter side, scaled to 640x640, and
305
+ * written out as a fresh JPEG. Re-encoding is the point rather than a side
306
+ * effect: it strips EXIF, progressive scans and unusual chroma subsampling —
307
+ * anything the file happened to carry — and produces a plain baseline JPEG.
308
+ *
309
+ * Returns the original buffer untouched when no image library is installed,
310
+ * which at least gives an already-correct picture a chance to go through.
311
+ */
312
+ async function prepareProfilePicture(buf, opts) {
313
+ opts = opts || {};
314
+ if (!Buffer.isBuffer(buf) || !buf.length) return buf;
315
+
316
+ const size = opts.size || AVATAR_SIZE;
317
+ const quality = opts.quality || AVATAR_QUALITY;
318
+
319
+ try {
320
+ const lib = await loadImageLib();
321
+
322
+ if (lib && lib.kind === 'sharp') {
323
+ return await lib.sharp(buf)
324
+ .resize(size, size, { fit: 'cover' })
325
+ .jpeg({ quality })
326
+ .toBuffer();
327
+ }
328
+
329
+ if (lib && lib.kind === 'jimp1') {
330
+ const img = await lib.Jimp.read(buf);
331
+ const min = Math.min(img.width, img.height);
332
+ const sq = img.crop({ x: 0, y: 0, w: min, h: min });
333
+ const resizeOpts = { w: size, h: size };
334
+ if (lib.ResizeStrategy && lib.ResizeStrategy.BILINEAR) {
335
+ resizeOpts.mode = lib.ResizeStrategy.BILINEAR;
336
+ }
337
+ return await sq.resize(resizeOpts).getBuffer('image/jpeg', { quality });
338
+ }
339
+
340
+ if (lib && lib.kind === 'jimp0') {
341
+ const Jimp = lib.Jimp;
342
+ const img = await Jimp.read(buf);
343
+ const min = Math.min(img.bitmap.width, img.bitmap.height);
344
+ img.crop(0, 0, min, min).resize(size, size).quality(quality);
345
+ return await img.getBufferAsync(Jimp.MIME_JPEG);
346
+ }
347
+ } catch (err) {
348
+ _whaDbg('[DBG] AVATAR could not re-encode: ' + (err && err.message));
349
+ return buf;
350
+ }
351
+
352
+ _whaDbg('[DBG] AVATAR no image library — sending the file as it is. ' +
353
+ 'WhatsApp wants a square JPEG; install jimp to have that done for you.');
354
+ return buf;
355
+ }
356
+
293
357
  // ─── ffmpeg ──────────────────────────────────────────────────────────────────
294
358
 
295
359
  function run(cmd, args) {
@@ -385,8 +449,11 @@ module.exports = {
385
449
  probeImageSize,
386
450
  extractExifThumbnail,
387
451
  imageThumbnail,
452
+ prepareProfilePicture,
388
453
  videoThumbnail,
389
454
  hasFfmpeg,
390
455
  THUMB_WIDTH,
391
- THUMB_QUALITY
456
+ THUMB_QUALITY,
457
+ AVATAR_SIZE,
458
+ AVATAR_QUALITY
392
459
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "5.10.6",
3
+ "version": "5.10.7",
4
4
  "description": "WhatsApp library for interaction with WhatsApp Mobile API no web",
5
5
  "author": "Kunboruto20",
6
6
  "main": "index.js",