From dc16bbbdac45e4b40b88662e0c808850f87f627a Mon Sep 17 00:00:00 2001 From: Wictor Nogueira <57378387+wictornogueira@users.noreply.github.com> Date: Fri, 31 Mar 2023 22:21:09 -0300 Subject: [PATCH] Feat: change profile/group picture (#1916) * feat: pfp * Make eslint happy * Fix typo * Fix missing comma * Update src/util/Injected.js * ESLint * Update src/util/Injected.js * mfw purp breaks indentation --------- Co-authored-by: Rajeh Taher --- index.d.ts | 10 ++++++ src/Client.js | 25 +++++++++++++ src/structures/GroupChat.js | 25 +++++++++++++ src/util/Injected.js | 72 ++++++++++++++++++++++++++++++++++++- 4 files changed, 131 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 6abef66..cebbb75 100644 --- a/index.d.ts +++ b/index.d.ts @@ -148,6 +148,12 @@ declare namespace WAWebJS { /** Unmutes the Chat */ unmuteChat(chatId: string): Promise + /** Sets the current user's profile picture */ + setProfilePicture(media: MessageMedia): Promise + + /** Deletes the current user's profile picture */ + deleteProfilePicture(): Promise + /** Generic event */ on(event: string, listener: (...args: any) => void): this @@ -1162,6 +1168,10 @@ declare namespace WAWebJS { revokeInvite: () => Promise; /** Makes the bot leave the group */ leave: () => Promise; + /** Sets the group's picture.*/ + setPicture: (media: MessageMedia) => Promise; + /** Deletes the group's picture */ + deletePicture: () => Promise; } /** diff --git a/src/Client.js b/src/Client.js index 3d87ace..955aadc 100644 --- a/src/Client.js +++ b/src/Client.js @@ -1178,6 +1178,31 @@ class Client extends EventEmitter { return blockedContacts.map(contact => ContactFactory.create(this.client, contact)); } + + /** + * Sets the current user's profile picture. + * @param {MessageMedia} media + * @returns {Promise} Returns true if the picture was properly updated. + */ + async setProfilePicture(media) { + const success = await this.pupPage.evaluate((chatid, media) => { + return window.WWebJS.setPicture(chatid, media); + }, this.info.wid._serialized, media); + + return success; + } + + /** + * Deletes the current user's profile picture. + * @returns {Promise} Returns true if the picture was properly deleted. + */ + async deleteProfilePicture() { + const success = await this.pupPage.evaluate((chatid) => { + return window.WWebJS.deletePicture(chatid); + }, this.info.wid._serialized); + + return success; + } } module.exports = Client; diff --git a/src/structures/GroupChat.js b/src/structures/GroupChat.js index 0be6b62..51769a5 100644 --- a/src/structures/GroupChat.js +++ b/src/structures/GroupChat.js @@ -213,6 +213,31 @@ class GroupChat extends Chat { return true; } + /** + * Deletes the group's picture. + * @returns {Promise} Returns true if the picture was properly deleted. This can return false if the user does not have the necessary permissions. + */ + async deletePicture() { + const success = await this.client.pupPage.evaluate((chatid) => { + return window.WWebJS.deletePicture(chatid); + }, this.id._serialized); + + return success; + } + + /** + * Sets the group's picture. + * @param {MessageMedia} media + * @returns {Promise} Returns true if the picture was properly updated. This can return false if the user does not have the necessary permissions. + */ + async setPicture(media) { + const success = await this.client.pupPage.evaluate((chatid, media) => { + return window.WWebJS.setPicture(chatid, media); + }, this.id._serialized, media); + + return success; + } + /** * Gets the invite code for a specific group * @returns {Promise} Group's invite code diff --git a/src/util/Injected.js b/src/util/Injected.js index 229220c..b32d50b 100644 --- a/src/util/Injected.js +++ b/src/util/Injected.js @@ -62,7 +62,8 @@ exports.ExposeStore = (moduleRaidStr) => { window.Store.GroupUtils = { ...window.mR.findModule('createGroup')[0], ...window.mR.findModule('setGroupDescription')[0], - ...window.mR.findModule('sendExitGroup')[0] + ...window.mR.findModule('sendExitGroup')[0], + ...window.mR.findModule('sendSetPicture')[0] }; if (!window.Store.Chat._find) { @@ -625,4 +626,73 @@ exports.LoadUtils = () => { ]); await window.Store.Socket.deprecatedCastStanza(stanza); }; + + window.WWebJS.cropAndResizeImage = async (media, options = {}) => { + if (!media.mimetype.includes('image')) + throw new Error('Media is not an image'); + + if (options.mimetype && !options.mimetype.includes('image')) + delete options.mimetype; + + options = Object.assign({ size: 640, mimetype: media.mimetype, quality: .75, asDataUrl: false }, options); + + const img = await new Promise ((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = reject; + img.src = `data:${media.mimetype};base64,${media.data}`; + }); + + const sl = Math.min(img.width, img.height); + const sx = Math.floor((img.width - sl) / 2); + const sy = Math.floor((img.height - sl) / 2); + + const canvas = document.createElement('canvas'); + canvas.width = options.size; + canvas.height = options.size; + + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, sx, sy, sl, sl, 0, 0, options.size, options.size); + + const dataUrl = canvas.toDataURL(options.mimetype, options.quality); + + if (options.asDataUrl) + return dataUrl; + + return Object.assign(media, { + mimetype: options.mimeType, + data: dataUrl.replace(`data:${options.mimeType};base64,`, '') + }); + }; + + window.WWebJS.setPicture = async (chatid, media) => { + const thumbnail = await window.WWebJS.cropAndResizeImage(media, { asDataUrl: true, mimetype: 'image/jpeg', size: 96 }); + const profilePic = await window.WWebJS.cropAndResizeImage(media, { asDataUrl: true, mimetype: 'image/jpeg', size: 640 }); + + const chatWid = window.Store.WidFactory.createWid(chatid); + try { + const collection = window.Store.ProfilePicThumb.get(chatid); + if (!collection.canSet()) return; + + const res = await window.Store.GroupUtils.sendSetPicture(chatWid, thumbnail, profilePic); + return res ? res.status === 200 : false; + } catch (err) { + if(err.name === 'ServerStatusCodeError') return false; + throw err; + } + }; + + window.WWebJS.deletePicture = async (chatid) => { + const chatWid = window.Store.WidFactory.createWid(chatid); + try { + const collection = window.Store.ProfilePicThumb.get(chatid); + if (!collection.canDelete()) return; + + const res = await window.Store.GroupUtils.requestDeletePicture(chatWid); + return res ? res.status === 200 : false; + } catch (err) { + if(err.name === 'ServerStatusCodeError') return false; + throw err; + } + }; };