From ecf19eb9a59fd9bc24457694a78716ea892cde91 Mon Sep 17 00:00:00 2001 From: alechkos <93551621+alechkos@users.noreply.github.com> Date: Wed, 20 Sep 2023 23:11:52 +0300 Subject: [PATCH] fix: sending locations (#2339) * added function to to modify module functions * fix for locations to be sent * changes for class Location - description property was splited into name and address - added url property - added ability to pass name, address and url as options while creating an instance * docs updated * usage example updated * added url checking * fixed url * updated location property in Message class * fixed url when http prefix is provided * injectToFunction updated to be more flexible * docs clarification * ESLint fix * fix: injectToFunction fixed to work properly * minor fix in injectToFunction * refactor: added usage of url validator * small change to improve performance for findImpl fix * window.compareWwebVersions fixed --- example.js | 9 ++++++- index.d.ts | 18 ++++++++++--- src/Client.js | 4 +-- src/structures/Location.js | 36 ++++++++++++++++++++++--- src/structures/Message.js | 16 ++++++++++- src/util/Injected.js | 54 ++++++++++++++++++++++++++++++++------ 6 files changed, 117 insertions(+), 20 deletions(-) diff --git a/example.js b/example.js index 347e7c6..d447ff0 100644 --- a/example.js +++ b/example.js @@ -151,7 +151,14 @@ client.on('message', async msg => { await client.sendMessage(msg.from, media, { isViewOnce: true }); } } else if (msg.body === '!location') { - msg.reply(new Location(37.422, -122.084, 'Googleplex\nGoogle Headquarters')); + // only latitude and longitude + await msg.reply(new Location(37.422, -122.084)); + // location with name only + await msg.reply(new Location(37.422, -122.084, { name: 'Googleplex' })); + // location with address only + await msg.reply(new Location(37.422, -122.084, { address: '1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA' })); + // location with name, address and url + await msg.reply(new Location(37.422, -122.084, { name: 'Googleplex', address: '1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA', url: 'https://google.com' })); } else if (msg.location) { msg.reply(msg.location); } else if (msg.body.startsWith('!status ')) { diff --git a/index.d.ts b/index.d.ts index 73109a3..c3d18e4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -879,13 +879,23 @@ declare namespace WAWebJS { _serialized: string, } + /** Options for sending a location */ + export interface LocationSendOptions { + /** Location name */ + name?: string; + /** Location address */ + address?: string; + /** URL address to be shown within a location message */ + url?: string; + } + /** Location information */ export class Location { - description?: string | null - latitude: string - longitude: string + latitude: string; + longitude: string; + options?: LocationSendOptions; - constructor(latitude: number, longitude: number, description?: string) + constructor(latitude: number, longitude: number, options?: LocationSendOptions) } export interface Label { diff --git a/src/Client.js b/src/Client.js index bc6fe4b..81d54c4 100644 --- a/src/Client.js +++ b/src/Client.js @@ -285,13 +285,13 @@ class Client extends EventEmitter { */ window.compareWwebVersions = (lOperand, operator, rOperand) => { if (!['>', '>=', '<', '<=', '='].includes(operator)) { - throw class _ extends Error { + throw new class _ extends Error { constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; } }('Invalid comparison operator is provided'); } if (typeof lOperand !== 'string' || typeof rOperand !== 'string') { - throw class _ extends Error { + throw new class _ extends Error { constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; } }('A non-string WWeb version type is provided'); } diff --git a/src/structures/Location.js b/src/structures/Location.js index f9bc9dc..9e1ba5c 100644 --- a/src/structures/Location.js +++ b/src/structures/Location.js @@ -1,5 +1,13 @@ 'use strict'; +/** + * Location send options + * @typedef {Object} LocationSendOptions + * @property {string} [name] Location name + * @property {string} [address] Location address + * @property {string} [url] URL address to be shown within a location message + */ + /** * Location information */ @@ -7,9 +15,9 @@ class Location { /** * @param {number} latitude * @param {number} longitude - * @param {?string} description + * @param {LocationSendOptions} [options] Location send options */ - constructor(latitude, longitude, description) { + constructor(latitude, longitude, options = {}) { /** * Location latitude * @type {number} @@ -24,9 +32,29 @@ class Location { /** * Name for the location - * @type {?string} + * @type {string|undefined} */ - this.description = description; + this.name = options.name; + + /** + * Location address + * @type {string|undefined} + */ + this.address = options.address; + + /** + * URL address to be shown within a location message + * @type {string|undefined} + */ + this.url = options.url; + + /** + * Location full description + * @type {string|undefined} + */ + this.description = this.name && this.address + ? `${this.name}\n${this.address}` + : this.name || this.address || ''; } } diff --git a/src/structures/Message.js b/src/structures/Message.js index 956efa4..99a86a8 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -151,7 +151,21 @@ class Message extends Base { * Location information contained in the message, if the message is type "location" * @type {Location} */ - this.location = data.type === MessageTypes.LOCATION ? new Location(data.lat, data.lng, data.loc) : undefined; + this.location = (() => { + if (data.type !== MessageTypes.LOCATION) { + return undefined; + } + let description; + if (data.loc && typeof data.loc === 'string') { + let splitted = data.loc.split('\n'); + description = { + name: splitted[0], + address: splitted[1], + url: data.clientUrl + }; + } + return new Location(data.lat, data.lng, description); + })(); /** * List of vCards contained in the message. diff --git a/src/util/Injected.js b/src/util/Injected.js index cb9a0a0..b73cff6 100644 --- a/src/util/Injected.js +++ b/src/util/Injected.js @@ -80,11 +80,9 @@ exports.ExposeStore = (moduleRaidStr) => { }; } - if (window.mR.findModule('ChatCollection')[0] && window.mR.findModule('ChatCollection')[0].ChatCollection) { - if (typeof window.mR.findModule('ChatCollection')[0].ChatCollection.findImpl === 'undefined' && typeof window.mR.findModule('ChatCollection')[0].ChatCollection._find != 'undefined') { - window.mR.findModule('ChatCollection')[0].ChatCollection.findImpl = window.mR.findModule('ChatCollection')[0].ChatCollection._find; - } - } + // eslint-disable-next-line no-undef + if ((m = window.mR.findModule('ChatCollection')[0]) && m.ChatCollection && typeof m.ChatCollection.findImpl === 'undefined' && typeof m.ChatCollection._find !== 'undefined') m.ChatCollection.findImpl = m.ChatCollection._find; + // TODO remove these once everybody has been updated to WWebJS with legacy sessions removed const _linkPreview = window.mR.findModule('queryLinkPreview'); @@ -103,6 +101,42 @@ exports.ExposeStore = (moduleRaidStr) => { if(_features) { window.Store.Features = _features.LegacyPhoneFeatures; } + + /** + * Target options object description + * @typedef {Object} TargetOptions + * @property {string|number} moduleId The name or a key of the target module to search + * @property {number} index The index value of the target module + * @property {string} property The function name to get from a module + */ + + /** + * Function to modify functions + * Referenced from and modified: + * @see https://github.com/pedroslopez/whatsapp-web.js/pull/1636/commits/81111faa058d8e715285a2bfc9a42636074f7c3d#diff-de25cb4b9105890088bb033eac000d1dd2104d3498a8523082dc7eaf319738b8R75-R78 + * @param {TargetOptions} target Options specifying the target function to search for modifying + * @param {Function} callback Modified function + */ + window.injectToFunction = (target, callback) => { + const module = typeof target.moduleId === 'string' + ? window.mR.findModule(target.moduleId) + : window.mR.modules[target.moduleId]; + const originalFunction = module[target.index][target.property]; + const modifiedFunction = (...args) => callback(originalFunction, ...args); + module[target.index][target.property] = modifiedFunction; + }; + + /** + * Referenced from and modified: + * @see https://github.com/wppconnect-team/wa-js/blob/e19164e83cfa68b828493e6ff046c0a3d46a4942/src/chat/functions/sendLocationMessage.ts#L156 + */ + window.injectToFunction({ moduleId: 'mediaTypeFromProtobuf', index: 0, property: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); + + /** + * Referenced from and modified: + * @see https://github.com/wppconnect-team/wa-js/blob/e19164e83cfa68b828493e6ff046c0a3d46a4942/src/chat/functions/sendLocationMessage.ts#L164 + */ + window.injectToFunction({ moduleId: 'typeAttributeFromProtobuf', index: 0, property: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? 'text' : func(...args); }); }; exports.LoadUtils = () => { @@ -159,11 +193,15 @@ exports.LoadUtils = () => { let locationOptions = {}; if (options.location) { + let { latitude, longitude, description, url } = options.location; + url = window.Store.Validators.findLink(url)?.href; + url && !description && (description = url); locationOptions = { type: 'location', - loc: options.location.description, - lat: options.location.latitude, - lng: options.location.longitude + loc: description, + lat: latitude, + lng: longitude, + clientUrl: url }; delete options.location; }