diff --git a/index.d.ts b/index.d.ts index c8022ec..c2879ac 100644 --- a/index.d.ts +++ b/index.d.ts @@ -600,6 +600,12 @@ declare namespace WAWebJS { sendSeen?: boolean /** Media to be sent */ media?: MessageMedia + /** Sticker name, if sendMediaAsSticker is true */ + stickerName?: string + /** Sticker author, if sendMediaAsSticker is true */ + stickerAuthor?: string + /** Sticker categories, if sendMediaAsSticker is true */ + stickerCategories?: string[] } /** Media attached to a message */ diff --git a/package.json b/package.json index 1ff86ec..f3227f0 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "fluent-ffmpeg": "^2.1.2", "jsqr": "^1.3.1", "mime": "^2.4.5", + "node-webpmux":"^2.0.0", "puppeteer": "^5.2.1", "sharp": "^0.26.3" }, diff --git a/src/Client.js b/src/Client.js index c647bcf..e4358fa 100644 --- a/src/Client.js +++ b/src/Client.js @@ -437,6 +437,9 @@ class Client extends EventEmitter { * @property {string} [quotedMessageId] - Id of the message that is being quoted (or replied to) * @property {Contact[]} [mentions] - Contacts that are being mentioned in the message * @property {boolean} [sendSeen=true] - Mark the conversation as seen after sending the message + * @property {string} [stickerAuthor=undefined] - Sets the author of the sticker, (if sendMediaAsSticker is true). + * @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true). + * @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null. * @property {MessageMedia} [media] - Media to be sent */ @@ -481,7 +484,12 @@ class Client extends EventEmitter { } if (internalOptions.sendMediaAsSticker && internalOptions.attachment) { - internalOptions.attachment = await Util.formatToWebpSticker(internalOptions.attachment); + internalOptions.attachment = + await Util.formatToWebpSticker(internalOptions.attachment, { + name: options.stickerName, + author: options.stickerAuthor, + categories: options.stickerCategories + }); } const newMessage = await this.pupPage.evaluate(async (chatId, message, options, sendSeen) => { diff --git a/src/util/Util.js b/src/util/Util.js index ec245e1..89ae6c7 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -5,6 +5,7 @@ const path = require('path'); const Crypto = require('crypto'); const { tmpdir } = require('os'); const ffmpeg = require('fluent-ffmpeg'); +const webp = require('node-webpmux'); const fs = require('fs').promises; const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k); @@ -18,6 +19,16 @@ class Util { throw new Error(`The ${this.constructor.name} class may not be instantiated.`); } + static generateHash(length) { + var result = ''; + var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var charactersLength = characters.length; + for ( var i = 0; i < length; i++ ) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; + } + /** * Sets default properties on an object that aren't already specified. * @param {Object} def Default properties @@ -135,16 +146,49 @@ class Util { }; } + /** + * Sticker metadata. + * @typedef {Object} StickerMetadata + * @property {string} [name] + * @property {string} [author] + * @property {string[]} [categories] + */ + /** * Formats a media to webp * @param {MessageMedia} media + * @param {StickerMetadata} metadata * * @returns {Promise} media in webp format */ - static async formatToWebpSticker(media) { - if (media.mimetype.includes('image')) return this.formatImageToWebpSticker(media); - else if (media.mimetype.includes('video')) return this.formatVideoToWebpSticker(media); - else throw new Error('Invalid media format'); + static async formatToWebpSticker(media, metadata) { + let webpMedia; + + if (media.mimetype.includes('image')) + webpMedia = await this.formatImageToWebpSticker(media); + else if (media.mimetype.includes('video')) + webpMedia = await this.formatVideoToWebpSticker(media); + else + throw new Error('Invalid media format'); + + if (metadata.name || metadata.author) { + const img = new webp.Image(); + const hash = this.generateHash(32); + const stickerPackId = hash; + const packname = metadata.name; + const author = metadata.author; + const categories = metadata.categories || ['']; + const json = { 'sticker-pack-id': stickerPackId, 'sticker-pack-name': packname, 'sticker-pack-publisher': author, 'emojis': categories }; + let exifAttr = Buffer.from([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00]); + let jsonBuffer = Buffer.from(JSON.stringify(json), 'utf8'); + let exif = Buffer.concat([exifAttr, jsonBuffer]); + exif.writeUIntLE(jsonBuffer.length, 14, 4); + await img.loadBuffer(Buffer.from(webpMedia.data, 'base64')); + img.exif = exif; + webpMedia.data = (await img.saveBuffer()).toString('base64'); + } + + return webpMedia; } /** @@ -156,4 +200,4 @@ class Util { } } -module.exports = Util; \ No newline at end of file +module.exports = Util;