diff --git a/bot.json b/bot.json index 3f7c6f6..5a56cad 100644 --- a/bot.json +++ b/bot.json @@ -2,7 +2,7 @@ "appconfig": { "headless": false, "isGroupReply":false, - "webhook":"https://lazy-cow-87.localtunnel.me/api/messages" + "webhook":"" }, "bot": [{ "contains": [], diff --git a/package.json b/package.json index e48be10..5656e31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wbot", - "version": "0.12.7", + "version": "0.12.8", "description": "A simple whatsapp reply bot using puppeteer.", "main": "src/index.js", "scripts": { diff --git a/src/WAPI.js b/src/WAPI.js index a40fe8f..15dec23 100644 --- a/src/WAPI.js +++ b/src/WAPI.js @@ -3,10 +3,7 @@ * A Python library which is worth checking out. * The new WAPI is made from https://github.com/Theblood/Wapi_NEW/blob/master/wapi.js */ -/* eslint-disable */ -/** - * This script contains WAPI functions that need to be run in the context of the webpage - */ + /** * This script contains WAPI functions that need to be run in the context of the webpage */ @@ -15,17 +12,23 @@ * Auto discovery the webpack object references of instances that contains all functions used by the WAPI * functions and creates the Store object. */ -if (!window.Store) { + +if (!window.Store||!window.Store.Msg) { (function () { function getStore(modules) { let foundCount = 0; let neededObjects = [ - { id: "Store", conditions: (module) => (module.Chat && module.Msg) ? module : null }, - { id: "MediaCollection", conditions: (module) => (module.default && module.default.prototype && module.default.prototype.processAttachments) ? module.default : null }, + { id: "Store", conditions: (module) => (module.default && module.default.Chat && module.default.Msg) ? module.default : null}, + { id: "MediaCollection", conditions: (module) => (module.default && module.default.prototype && (module.default.prototype.processFiles !== undefined||module.default.prototype.processAttachments !== undefined)) ? module.default : null }, { id: "MediaProcess", conditions: (module) => (module.BLOB) ? module : null }, + { id: "Archive", conditions: (module) => (module.setArchive) ? module : null }, + { id: "Block", conditions: (module) => (module.blockContact && module.unblockContact) ? module : null }, + { id: "ChatUtil", conditions: (module) => (module.sendClear) ? module : null }, + { id: "GroupInvite", conditions: (module) => (module.queryGroupInviteCode) ? module : null }, { id: "Wap", conditions: (module) => (module.createGroup) ? module : null }, { id: "ServiceWorker", conditions: (module) => (module.default && module.default.killServiceWorker) ? module : null }, { id: "State", conditions: (module) => (module.STATE && module.STREAM) ? module : null }, + { id: "_Presence", conditions: (module) => (module.setPresenceAvailable && module.setPresenceUnavailable) ? module : null }, { id: "WapDelete", conditions: (module) => (module.sendConversationDelete && module.sendConversationDelete.length == 2) ? module : null }, { id: "Conn", conditions: (module) => (module.default && module.default.ref && module.default.refTTL) ? module.default : null }, { id: "WapQuery", conditions: (module) => (module.queryExist) ? module : ((module.default && module.default.queryExist) ? module.default : null) }, @@ -33,8 +36,32 @@ if (!window.Store) { { id: "OpenChat", conditions: (module) => (module.default && module.default.prototype && module.default.prototype.openChat) ? module.default : null }, { id: "UserConstructor", conditions: (module) => (module.default && module.default.prototype && module.default.prototype.isServer && module.default.prototype.isUser) ? module.default : null }, { id: "SendTextMsgToChat", conditions: (module) => (module.sendTextMsgToChat) ? module.sendTextMsgToChat : null }, - { id: "SendSeen", conditions: (module) => (module.sendSeen) ? module.sendSeen : null }, - { id: "sendDelete", conditions: (module) => (module.sendDelete) ? module.sendDelete : null } + { id: "ReadSeen", conditions: (module) => (module.sendSeen) ? module : null }, + { id: "sendDelete", conditions: (module) => (module.sendDelete) ? module.sendDelete : null }, + { id: "addAndSendMsgToChat", conditions: (module) => (module.addAndSendMsgToChat) ? module.addAndSendMsgToChat : null }, + { id: "sendMsgToChat", conditions: (module) => (module.sendMsgToChat) ? module.sendMsgToChat : null }, + { id: "Catalog", conditions: (module) => (module.Catalog) ? module.Catalog : null }, + { id: "bp", conditions: (module) => (module.default&&module.default.toString().includes('bp_unknown_version')) ? module.default : null }, + { id: "MsgKey", conditions: (module) => (module.default&&module.default.toString().includes('MsgKey error: obj is null/undefined')) ? module.default : null }, + { id: "Parser", conditions: (module) => (module.convertToTextWithoutSpecialEmojis) ? module.default : null }, + { id: "Builders", conditions: (module) => (module.TemplateMessage && module.HydratedFourRowTemplate) ? module : null }, + { id: "Me", conditions: (module) => (module.PLATFORMS && module.Conn) ? module.default : null }, + { id: "CallUtils", conditions: (module) => (module.sendCallEnd && module.parseCall) ? module : null }, + { id: "Identity", conditions: (module) => (module.queryIdentity && module.updateIdentity) ? module : null }, + { id: "MyStatus", conditions: (module) => (module.getStatus && module.setMyStatus) ? module : null }, + { id: "ChatStates", conditions: (module) => (module.sendChatStatePaused && module.sendChatStateRecording && module.sendChatStateComposing) ? module : null }, + { id: "GroupActions", conditions: (module) => (module.sendExitGroup && module.localExitGroup) ? module : null }, + { id: "Features", conditions: (module) => (module.FEATURE_CHANGE_EVENT && module.features) ? module : null }, + { id: "MessageUtils", conditions: (module) => (module.storeMessages && module.appendMessage) ? module : null }, + { id: "WebMessageInfo", conditions: (module) => (module.WebMessageInfo && module.WebFeatures) ? module.WebMessageInfo : null }, + { id: "createMessageKey", conditions: (module) => (module.createMessageKey && module.createDeviceSentMessage) ? module.createMessageKey : null }, + { id: "Participants", conditions: (module) => (module.addParticipants && module.removeParticipants && module.promoteParticipants && module.demoteParticipants) ? module : null }, + { id: "WidFactory", conditions: (module) => (module.isWidlike && module.createWid && module.createWidFromWidLike) ? module : null }, + { id: "Base", conditions: (module) => (module.setSubProtocol && module.binSend && module.actionNode) ? module : null }, + { id: "Versions", conditions: (module) => (module.loadProtoVersions && module.default["15"] && module.default["16"] && module.default["17"]) ? module : null }, + { id: "Sticker", conditions: (module) => (module.default && module.default.Sticker) ? module.default.Sticker : null }, + { id: "MediaUpload", conditions: (module) => (module.default && module.default.mediaUpload) ? module.default : null }, + { id: "UploadUtils", conditions: (module) => (module.default && module.default.encryptAndUpload) ? module.default : null } ]; for (let idx in modules) { if ((typeof modules[idx] === "object") && (modules[idx] !== null)) { @@ -42,6 +69,7 @@ if (!window.Store) { if ((typeof first === "object") && (first.exports)) { for (let idx2 in modules[idx]) { let module = modules(idx2); + // console.log("TCL: getStore -> module", module ? Object.getOwnPropertyNames(module.default || module).filter(item => typeof (module.default || module)[item] === 'function').length ? module.default || module : "":'') if (!module) { continue; } @@ -70,34 +98,25 @@ if (!window.Store) { window.Store.sendMessage = function (e) { return window.Store.SendTextMsgToChat(this, ...arguments); } + if(window.Store.MediaCollection) window.Store.MediaCollection.prototype.processFiles = window.Store.MediaCollection.prototype.processFiles || window.Store.MediaCollection.prototype.processAttachments; return window.Store; } } } } - - if (typeof webpackJsonp === 'function') { - webpackJsonp([], {'parasite': (x, y, z) => getStore(z)}, ['parasite']); - } else { - webpackJsonp.push([ - ['parasite'], - { - parasite: function (o, e, t) { - getStore(t); - } - }, - [['parasite']] - ]); - } + const parasite = `parasite${Date.now()}` + // webpackJsonp([], { [parasite]: (x, y, z) => getStore(z) }, [parasite]); + if (typeof webpackJsonp === 'function') webpackJsonp([], {[parasite]: (x, y, z) => getStore(z)}, [parasite]); + else webpackJsonp.push([[parasite],{[parasite]: (x, y, z) => getStore(z)},[[parasite]]]); + })(); } -window.WAPI = { - lastRead: {} -}; +window.WAPI = {}; +window._WAPI = {}; window.WAPI._serializeRawObj = (obj) => { - if (obj) { + if (obj && obj.toJSON) { return obj.toJSON(); } return {} @@ -114,14 +133,14 @@ window.WAPI._serializeChatObj = (obj) => { if (obj == undefined) { return null; } - return Object.assign(window.WAPI._serializeRawObj(obj), { - kind : obj.kind, - isGroup : obj.isGroup, - contact : obj['contact'] ? window.WAPI._serializeContactObj(obj['contact']) : null, - groupMetadata: obj["groupMetadata"] ? window.WAPI._serializeRawObj(obj["groupMetadata"]): null, - presence : obj["presence"] ? window.WAPI._serializeRawObj(obj["presence"]) : null, - msgs : null + kind: obj.kind, + isGroup: obj.isGroup, + formattedTitle: obj.formattedTitle, + contact: obj['contact'] ? window.WAPI._serializeContactObj(obj['contact']) : null, + groupMetadata: obj["groupMetadata"] ? window.WAPI._serializeRawObj(obj["groupMetadata"]) : null, + presence: obj["presence"] ? window.WAPI._serializeRawObj(obj["presence"]) : null, + msgs: null }); }; @@ -129,43 +148,53 @@ window.WAPI._serializeContactObj = (obj) => { if (obj == undefined) { return null; } - return Object.assign(window.WAPI._serializeRawObj(obj), { - formattedName : obj.formattedName, + formattedName: obj.formattedName, isHighLevelVerified: obj.isHighLevelVerified, - isMe : obj.isMe, - isMyContact : obj.isMyContact, - isPSA : obj.isPSA, - isUser : obj.isUser, - isVerified : obj.isVerified, - isWAContact : obj.isWAContact, - profilePicThumbObj : obj.profilePicThumb ? WAPI._serializeProfilePicThumb(obj.profilePicThumb): {}, - statusMute : obj.statusMute, - msgs : null + isMe: obj.isMe, + isMyContact: obj.isMyContact, + isPSA: obj.isPSA, + isUser: obj.isUser, + isVerified: obj.isVerified, + isWAContact: obj.isWAContact, + profilePicThumbObj: obj.profilePicThumb ? WAPI._serializeProfilePicThumb(obj.profilePicThumb) : {}, + statusMute: obj.statusMute, + msgs: null }); }; + window.WAPI._serializeMessageObj = (obj) => { if (obj == undefined) { return null; } - + const _chat = obj['chat'] ? WAPI._serializeChatObj(obj['chat']) : {}; + if(obj.quotedMsg) obj.quotedMsgObj(); return Object.assign(window.WAPI._serializeRawObj(obj), { - id : obj.id._serialized, - sender : obj["senderObj"] ? WAPI._serializeContactObj(obj["senderObj"]): null, - timestamp : obj["t"], - content : obj["body"], - isGroupMsg : obj.isGroupMsg, - isLink : obj.isLink, - isMMS : obj.isMMS, - isMedia : obj.isMedia, + id: obj.id._serialized, + from: obj.from._serialized, + quotedParticipant: obj.quotedParticipant? obj.quotedParticipant._serialized ? obj.quotedParticipant._serialized : undefined : undefined, + author: obj.author? obj.author._serialized ? obj.author._serialized : undefined : undefined, + chatId: obj.chatId? obj.chatId._serialized ? obj.chatId._serialized : undefined : undefined, + to: obj.to? obj.to._serialized ? obj.to._serialized : undefined : undefined, + fromMe: obj.id.fromMe, + sender: obj["senderObj"] ? WAPI._serializeContactObj(obj["senderObj"]) : null, + timestamp: obj["t"], + content: obj["body"], + isGroupMsg: obj.isGroupMsg, + isLink: obj.isLink, + isMMS: obj.isMMS, + isMedia: obj.isMedia, isNotification: obj.isNotification, - isPSA : obj.isPSA, - type : obj.type, - chat : WAPI._serializeChatObj(obj['chat']), - chatId : obj.id.remote, - quotedMsgObj : WAPI._serializeMessageObj(obj['_quotedMsgObj']), - mediaData : window.WAPI._serializeRawObj(obj['mediaData']) + isPSA: obj.isPSA, + type: obj.type, + chat: _chat, + isOnline: _chat.isOnline, + lastSeen: _chat.lastSeen, + chatId: obj.id.remote, + quotedMsgObj: WAPI._serializeMessageObj(obj['_quotedMsgObj']), + mediaData: window.WAPI._serializeRawObj(obj['mediaData']), + reply: body => window.WAPI.reply(_chat.id._serialized, body, obj) }); }; @@ -175,9 +204,9 @@ window.WAPI._serializeNumberStatusObj = (obj) => { } return Object.assign({}, { - id : obj.jid, - status : obj.status, - isBusiness : (obj.biz === true), + id: obj.jid, + status: obj.status, + isBusiness: (obj.biz === true), canReceiveMessage: (obj.status === 200) }); }; @@ -188,137 +217,179 @@ window.WAPI._serializeProfilePicThumb = (obj) => { } return Object.assign({}, { - eurl : obj.eurl, - id : obj.id, - img : obj.img, + eurl: obj.eurl, + id: obj.id, + img: obj.img, imgFull: obj.imgFull, - raw : obj.raw, - tag : obj.tag + raw: obj.raw, + tag: obj.tag }); } -window.WAPI.createGroup = function (name, contactsId) { +window.WAPI.createGroup = async function (name, contactsId) { if (!Array.isArray(contactsId)) { contactsId = [contactsId]; } - - return window.Store.Wap.createGroup(name, contactsId); + return await window.Store.WapQuery.createGroup(name, contactsId); }; +/** + * Sends the command for your device to leave a group. + * @param groupId stirng, the is for the group. + * returns Promise + */ window.WAPI.leaveGroup = function (groupId) { groupId = typeof groupId == "string" ? groupId : groupId._serialized; var group = WAPI.getChat(groupId); - return group.sendExit() + return Store.GroupActions.sendExitGroup(group) }; -window.WAPI.getAllContacts = function (done) { - const contacts = window.Store.Contact.map((contact) => WAPI._serializeContactObj(contact)); - - if (done !== undefined) done(contacts); - return contacts; +window.WAPI.getAllContacts = function () { + return window.Store.Contact.map((contact) => WAPI._serializeContactObj(contact)); }; /** * Fetches all contact objects from store, filters them * - * @param done Optional callback function for async execution * @returns {Array|*} List of contacts */ -window.WAPI.getMyContacts = function (done) { - const contacts = window.Store.Contact.filter((contact) => contact.isMyContact === true).map((contact) => WAPI._serializeContactObj(contact)); - if (done !== undefined) done(contacts); - return contacts; +window.WAPI.getMyContacts = function () { + return window.Store.Contact.filter((contact) => contact.isMyContact === true).map((contact) => WAPI._serializeContactObj(contact)); }; /** * Fetches contact object from store by ID * * @param id ID of contact - * @param done Optional callback function for async execution * @returns {T|*} Contact object */ -window.WAPI.getContact = function (id, done) { +window.WAPI.getContact = function (id) { const found = window.Store.Contact.get(id); - - if (done !== undefined) done(window.WAPI._serializeContactObj(found)) return window.WAPI._serializeContactObj(found); }; +window.WAPI.syncContacts = function() { + Store.Contact.sync() + return true; +} + /** * Fetches all chat objects from store * - * @param done Optional callback function for async execution * @returns {Array|*} List of chats */ -window.WAPI.getAllChats = function (done) { - const chats = window.Store.Chat.map((chat) => WAPI._serializeChatObj(chat)); - - if (done !== undefined) done(chats); - return chats; +window.WAPI.getAllChats = function () { + return window.Store.Chat.map((chat) => WAPI._serializeChatObj(chat)); }; window.WAPI.haveNewMsg = function (chat) { return chat.unreadCount > 0; }; -window.WAPI.getAllChatsWithNewMsg = function (done) { - const chats = window.Store.Chat.filter(window.WAPI.haveNewMsg).map((chat) => WAPI._serializeChatObj(chat)); - - if (done !== undefined) done(chats); - return chats; +window.WAPI.getAllChatsWithNewMsg = function () { + return window.Store.Chat.filter(window.WAPI.haveNewMsg).map((chat) => WAPI._serializeChatObj(chat)); }; /** * Fetches all chat IDs from store * - * @param done Optional callback function for async execution * @returns {Array|*} List of chat id's */ -window.WAPI.getAllChatIds = function (done) { - const chatIds = window.Store.Chat.map((chat) => chat.id._serialized || chat.id); - - if (done !== undefined) done(chatIds); - return chatIds; +window.WAPI.getAllChatIds = function () { + return window.Store.Chat.map((chat) => chat.id._serialized || chat.id); }; +window.WAPI.getAllNewMessages = async function () { + return JSON.stringify(WAPI.getAllChatsWithNewMsg().map(c => WAPI.getChat(c.id._serialized)).map(c => c.msgs._models.filter(x => x.isNewMsg)) || []) +} + +// nnoo longer determined by x.ack==-1 +window.WAPI.getAllUnreadMessages = async function () { + return Store.Chat.models.filter(chat=>chat.unreadCount&&chat.unreadCount>0).map(unreadChat=>unreadChat.msgs.models.slice(-1*unreadChat.unreadCount)).flat().map(WAPI._serializeMessageObj) +} + +window.WAPI.getIndicatedNewMessages = async function () { + return JSON.stringify(Store.Chat.models.filter(chat=>chat.unreadCount).map(chat=>{return {id:chat.id,indicatedNewMessages: chat.msgs.models.slice(Math.max(chat.msgs.length - chat.unreadCount, 0)).filter(msg=>!msg.id.fromMe)}})) +} + +window.WAPI.getSingleProperty = function (namespace,id,property){ + if(Store[namespace] && Store[namespace].get(id) && Object.keys(Store[namespace].get(id)).find(x=>x.includes(property))) return Store[namespace].get(id)[property]; + return 404 +} + +window.WAPI.getAllChatsWithMessages = async function (onlyNew) { + let x = []; + if (onlyNew) { x.push(WAPI.getAllChatsWithNewMsg().map(c => WAPI.getChat(c.id._serialized))); } + else { + x.push(WAPI.getAllChatIds().map((c) => WAPI.getChat(c))); + } + const result = (await Promise.all(x)).flatMap(x => x); + return JSON.stringify(result); +} + /** * Fetches all groups objects from store * - * @param done Optional callback function for async execution * @returns {Array|*} List of chats */ -window.WAPI.getAllGroups = function (done) { - const groups = window.Store.Chat.filter((chat) => chat.isGroup); +window.WAPI.getAllGroups = function () { + return window.Store.Chat.filter((chat) => chat.isGroup); +}; - if (done !== undefined) done(groups); - return groups; +/** + * Sets the chat state + * + * @param {0|1|2} chatState The state you want to set for the chat. Can be TYPING (1), RECRDING (2) or PAUSED (3); + * returns {boolean} + */ +window.WAPI.sendChatstate = async function (state, chatId) { + switch(state) { + case 0: + await window.Store.ChatStates.sendChatStateComposing(chatId); + break; + case 1: + await window.Store.ChatStates.sendChatStateRecording(chatId); + break; + case 2: + await window.Store.ChatStates.sendChatStatePaused(chatId); + break; + default: + return false + } + return true; }; /** * Fetches chat object from store by ID * * @param id ID of chat - * @param done Optional callback function for async execution * @returns {T|*} Chat object */ -window.WAPI.getChat = function (id, done) { +window.WAPI.getChat = function (id) { + if (!id) return false; id = typeof id == "string" ? id : id._serialized; const found = window.Store.Chat.get(id); - found.sendMessage = (found.sendMessage) ? found.sendMessage : function () { return window.Store.sendMessage.apply(this, arguments); }; - if (done !== undefined) done(found); + if (found) found.sendMessage = (found.sendMessage) ? found.sendMessage : function () { return window.Store.sendMessage.apply(this, arguments); }; return found; } -window.WAPI.getChatByName = function (name, done) { - const found = window.Store.Chat.find((chat) => chat.name === name); - if (done !== undefined) done(found); - return found; +/** + * Get your status + * @param {string} to '000000000000@c.us' + * returns: {string,string} and string -"Hi, I am using WA" + */ +window.WAPI.getStatus = async (id) => { +return await Store.MyStatus.getStatus(id) +} + +window.WAPI.getChatByName = function (name) { + return window.Store.Chat.find((chat) => chat.name === name); }; window.WAPI.sendImageFromDatabasePicBot = function (picId, chatId, caption) { var chatDatabase = window.WAPI.getChatByName('DATABASEPICBOT'); - var msgWithImg = chatDatabase.msgs.find((msg) => msg.caption == picId); + var msgWithImg = chatDatabase.msgs.find((msg) => msg.caption == picId); if (msgWithImg === undefined) { return false; @@ -329,10 +400,10 @@ window.WAPI.sendImageFromDatabasePicBot = function (picId, chatId, caption) { } const oldCaption = msgWithImg.caption; - msgWithImg.id.id = window.WAPI.getNewId(); + msgWithImg.id.id = window.WAPI.getNewId(); msgWithImg.id.remote = chatId; - msgWithImg.t = Math.ceil(new Date().getTime() / 1000); - msgWithImg.to = chatId; + msgWithImg.t = Math.ceil(new Date().getTime() / 1000); + msgWithImg.to = chatId; if (caption !== undefined && caption !== '') { msgWithImg.caption = caption; @@ -347,26 +418,62 @@ window.WAPI.sendImageFromDatabasePicBot = function (picId, chatId, caption) { return true; }; -window.WAPI.sendMessageWithThumb = function (thumb, url, title, description, chatId, done) { +window.WAPI.getGeneratedUserAgent = function (useragent) { + if (!useragent.includes('WhatsApp')) return 'WhatsApp/0.4.315 ' + useragent; + return useragent.replace(useragent.match(/WhatsApp\/([.\d])*/g)[0].match(/[.\d]*/g).find(x => x), window.Debug.VERSION) +} + +window.WAPI.getWAVersion = function () { + return window.Debug.VERSION; +} + +/** + * Automatically sends a link with the auto generated link preview. You can also add a custom message to be added. + * @param chatId + * @param url string A link, for example for youtube. e.g https://www.youtube.com/watch?v=61O-Galzc5M + * @param text string Custom text as body of the message, this needs to include the link or it will be appended after the link. + */ +window.WAPI.sendLinkWithAutoPreview = async function (chatId, url, text) { + var chatSend = WAPI.getChat(chatId); + if (chatSend === undefined) { + return false; + } + const linkPreview = await Store.WapQuery.queryLinkPreview(url); + return (await chatSend.sendMessage(text.includes(url) ? text : `${url}\n${text}`, {linkPreview}))=='success' +} + +window.WAPI.sendMessageWithThumb = function (thumb, url, title, description, text, chatId) { var chatSend = WAPI.getChat(chatId); if (chatSend === undefined) { - if (done !== undefined) done(false); return false; } var linkPreview = { canonicalUrl: url, - description : description, - matchedText : url, - title : title, - thumbnail : thumb + description: description, + matchedText: url, + title: title, + thumbnail: thumb // Thumbnail max size allowed: 200x200 }; - chatSend.sendMessage(url, { linkPreview: linkPreview, mentionedJidList: [], quotedMsg: null, quotedMsgAdminGroupJid: null }); - if (done !== undefined) done(true); + chatSend.sendMessage(text.includes(url) ? text : `${url}\n${text}`, { linkPreview: linkPreview, mentionedJidList: [], quotedMsg: null, quotedMsgAdminGroupJid: null }); return true; }; +window.WAPI.revokeGroupInviteLink = async function (chatId) { + var chat = Store.Chat.get(chatId); + if(!chat.isGroup) return false; + await Store.GroupInvite.revokeGroupInvite(chat); + return true; +} + +window.WAPI.getGroupInviteLink = async function (chatId) { + var chat = Store.Chat.get(chatId); + if(!chat.isGroup) return false; + await Store.GroupInvite.queryGroupInviteCode(chat); + return `https://chat.whatsapp.com/${chat.inviteCode}` +} + window.WAPI.getNewId = function () { - var text = ""; + var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (var i = 0; i < 20; i++) @@ -374,15 +481,13 @@ window.WAPI.getNewId = function () { return text; }; -window.WAPI.getChatById = function (id, done) { +window.WAPI.getChatById = function (id) { let found = WAPI.getChat(id); if (found) { found = WAPI._serializeChatObj(found); } else { found = false; } - - if (done !== undefined) done(found); return found; }; @@ -399,15 +504,12 @@ window.WAPI.getChatById = function (id, done) { * :param includeNotifications: indicates if notifications have to be included * :type includeNotifications: boolean * - * :param done: callback passed by selenium - * :type done: function - * * :returns: list of unread messages from asked chat * :rtype: object */ -window.WAPI.getUnreadMessagesInChat = function (id, includeMe, includeNotifications, done) { +window.WAPI.getUnreadMessagesInChat = function (id, includeMe, includeNotifications) { // get chat and its messages - let chat = WAPI.getChat(id); + let chat = WAPI.getChat(id); let messages = chat.msgs._models; // initialize result list @@ -430,71 +532,58 @@ window.WAPI.getUnreadMessagesInChat = function (id, includeMe, includeNotificati messageObj.isNewMsg = false; // process it let message = WAPI.processMessageObj(messageObj, - includeMe, - includeNotifications); + includeMe, + includeNotifications); // save processed message on result list if (message) output.push(message); } } - // callback was passed: run it - if (done !== undefined) done(output); // return result list return output; -} -; +}; /** - * Load more messages in chat object from store by ID + * Load more messages in chat object from server. Use this in a while loop * * @param id ID of chat - * @param done Optional callback function for async execution * @returns None */ -window.WAPI.loadEarlierMessages = function (id, done) { - const found = WAPI.getChat(id); - if (done !== undefined) { - found.loadEarlierMsgs().then(function () { - done() - }); - } else { - found.loadEarlierMsgs(); +window.WAPI.loadEarlierMessages = async function (id) { + const chat = WAPI.getChat(id); + if(chat){ + const someEarlierMessages = await chat.loadEarlierMsgs(); + if(someEarlierMessages) return someEarlierMessages.map(WAPI._serializeMessageObj); } + return false; }; /** * Load more messages in chat object from store by ID * * @param id ID of chat - * @param done Optional callback function for async execution * @returns None */ -window.WAPI.loadAllEarlierMessages = function (id, done) { +window.WAPI.loadAllEarlierMessages = async function (id) { const found = WAPI.getChat(id); - x = function () { - if (!found.msgs.msgLoadState.noEarlierMsgs) { - found.loadEarlierMsgs().then(x); - } else if (done) { - done(); - } - }; - x(); + while (!found.msgs.msgLoadState.noEarlierMsgs) { + console.log('loading more messages') + await found.loadEarlierMsgs(); + } + return true }; -window.WAPI.asyncLoadAllEarlierMessages = function (id, done) { - done(); - window.WAPI.loadAllEarlierMessages(id); +window.WAPI.asyncLoadAllEarlierMessages = async function (id) { + return await window.WAPI.loadAllEarlierMessages(id); }; -window.WAPI.areAllMessagesLoaded = function (id, done) { +window.WAPI.areAllMessagesLoaded = function (id) { const found = WAPI.getChat(id); if (!found.msgs.msgLoadState.noEarlierMsgs) { - if (done) done(false); return false } - if (done) done(true); return true }; @@ -503,55 +592,39 @@ window.WAPI.areAllMessagesLoaded = function (id, done) { * * @param id ID of chat * @param lastMessage UTC timestamp of last message to be loaded - * @param done Optional callback function for async execution * @returns None */ -window.WAPI.loadEarlierMessagesTillDate = function (id, lastMessage, done) { +window.WAPI.loadEarlierMessagesTillDate = async function (id, lastMessage) { const found = WAPI.getChat(id); - x = function () { + x = async function () { if (found.msgs.models[0].t > lastMessage && !found.msgs.msgLoadState.noEarlierMsgs) { - found.loadEarlierMsgs().then(x); + return await found.loadEarlierMsgs().then(x); } else { - done(); + return true } }; - x(); + return await x(); }; /** * Fetches all group metadata objects from store * - * @param done Optional callback function for async execution * @returns {Array|*} List of group metadata */ -window.WAPI.getAllGroupMetadata = function (done) { - const groupData = window.Store.GroupMetadata.map((groupData) => groupData.all); - - if (done !== undefined) done(groupData); - return groupData; +window.WAPI.getAllGroupMetadata = function () { + return window.Store.GroupMetadata.map((groupData) => groupData.all); }; /** * Fetches group metadata object from store by ID * * @param id ID of group - * @param done Optional callback function for async execution * @returns {T|*} Group metadata object */ -window.WAPI.getGroupMetadata = async function (id, done) { - let output = window.Store.GroupMetadata.get(id); - - if (output !== undefined) { - if (output.stale) { - await output.update(); - } - } - - if (done !== undefined) done(output); - return output; - +window.WAPI.getGroupMetadata = async function (id) { + return window.Store.GroupMetadata.find(id); }; @@ -563,62 +636,54 @@ window.WAPI.getGroupMetadata = async function (id, done) { * @private */ window.WAPI._getGroupParticipants = async function (id) { - const metadata = await WAPI.getGroupMetadata(id); - return metadata.participants; + return (await WAPI.getGroupMetadata(id)).participants; }; /** * Fetches IDs of group participants * * @param id ID of group - * @param done Optional callback function for async execution * @returns {Promise.} Yields list of IDs */ -window.WAPI.getGroupParticipantIDs = async function (id, done) { - const output = (await WAPI._getGroupParticipants(id)) - .map((participant) => participant.id); - - if (done !== undefined) done(output); - return output; +window.WAPI.getGroupParticipantIDs = async function (id) { + return (await WAPI._getGroupParticipants(id)) + .map((participant) => participant.id); }; -window.WAPI.getGroupAdmins = async function (id, done) { - const output = (await WAPI._getGroupParticipants(id)) - .filter((participant) => participant.isAdmin) - .map((admin) => admin.id); - - if (done !== undefined) done(output); - return output; +window.WAPI.getGroupAdmins = async function (id) { + return (await WAPI._getGroupParticipants(id)) + .filter((participant) => participant.isAdmin) + .map((admin) => admin.id); }; /** - * Gets object representing the logged in user - * - * @returns {Array|*|$q.all} + * Returns an object with all of your host device details */ -window.WAPI.getMe = function (done) { - const rawMe = window.Store.Contact.get(window.Store.Conn.me); - - if (done !== undefined) done(rawMe.all); - return rawMe.all; -}; +window.WAPI.getMe = function(){ + return {...WAPI.quickClean({ + ...Store.Contact.get(Store.Me.wid).attributes, + ...Store.Me.attributes + }), + me:Store.Me.me}; +} -window.WAPI.isLoggedIn = function (done) { +window.WAPI.isLoggedIn = function () { // Contact always exists when logged in const isLogged = window.Store.Contact && window.Store.Contact.checksum !== undefined; - - if (done !== undefined) done(isLogged); return isLogged; }; -window.WAPI.isConnected = function (done) { +window.WAPI.isConnected = function () { // Phone Disconnected icon appears when phone is disconnected from the tnternet const isConnected = document.querySelector('*[data-icon="alert-phone"]') !== null ? false : true; - - if (done !== undefined) done(isConnected); return isConnected; }; +//I dont think this will work for group chats. +window.WAPI.isChatOnline = async function (id) { + return Store.Chat.get(id)?await Store.Chat.get(id).presence.subscribe().then(_=>Store.Chat.get(id).presence.attributes.isOnline):false; +} + window.WAPI.processMessageObj = function (messageObj, includeMe, includeNotifications) { if (messageObj.isNotification) { if (includeNotifications) @@ -633,9 +698,9 @@ window.WAPI.processMessageObj = function (messageObj, includeMe, includeNotifica return; }; -window.WAPI.getAllMessagesInChat = function (id, includeMe, includeNotifications, done) { - const chat = WAPI.getChat(id); - let output = []; +window.WAPI.getAllMessagesInChat = function (id, includeMe, includeNotifications) { + const chat = WAPI.getChat(id); + let output = []; const messages = chat.msgs._models; for (const i in messages) { @@ -648,28 +713,46 @@ window.WAPI.getAllMessagesInChat = function (id, includeMe, includeNotifications if (message) output.push(message); } - if (done !== undefined) done(output); - return output; + return WAPI.quickClean(output); +}; + +window.WAPI.loadAndGetAllMessagesInChat = function (id, includeMe, includeNotifications) { + return WAPI.loadAllEarlierMessages(id).then(_ => { + const chat = WAPI.getChat(id); + let output = []; + const messages = chat.msgs._models; + + for (const i in messages) { + if (i === "remove") { + continue; + } + const messageObj = messages[i]; + + let message = WAPI.processMessageObj(messageObj, includeMe, includeNotifications) + if (message) + output.push(message); + } + return output; + }) }; -window.WAPI.getAllMessageIdsInChat = function (id, includeMe, includeNotifications, done) { - const chat = WAPI.getChat(id); - let output = []; +window.WAPI.getAllMessageIdsInChat = function (id, includeMe, includeNotifications) { + const chat = WAPI.getChat(id); + let output = []; const messages = chat.msgs._models; for (const i in messages) { if ((i === "remove") - || (!includeMe && messages[i].isMe) - || (!includeNotifications && messages[i].isNotification)) { + || (!includeMe && messages[i].isMe) + || (!includeNotifications && messages[i].isNotification)) { continue; } output.push(messages[i].id._serialized); } - if (done !== undefined) done(output); return output; }; -window.WAPI.getMessageById = function (id, done) { +window.WAPI.getMessageById = function (id) { let result = false; try { let msg = window.Store.Msg.get(id); @@ -677,186 +760,105 @@ window.WAPI.getMessageById = function (id, done) { result = WAPI.processMessageObj(msg, true, true); } } catch (err) { } - - if (done !== undefined) { - done(result); - } else { return result; - } -}; - -window.WAPI.ReplyMessage = function (idMessage, message, done) { - var messageObject = window.Store.Msg.get(idMessage); - if (messageObject === undefined) { - if (done !== undefined) done(false); - return false; - } - messageObject = messageObject.value(); - - const chat = WAPI.getChat(messageObject.chat.id) - if (chat !== undefined) { - if (done !== undefined) { - chat.sendMessage(message, null, messageObject).then(function () { - function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - - var trials = 0; - - function check() { - for (let i = chat.msgs.models.length - 1; i >= 0; i--) { - let msg = chat.msgs.models[i]; - - if (!msg.senderObj.isMe || msg.body != message) { - continue; - } - done(WAPI._serializeMessageObj(msg)); - return True; - } - trials += 1; - console.log(trials); - if (trials > 30) { - done(true); - return; - } - sleep(500).then(check); - } - check(); - }); - return true; - } else { - chat.sendMessage(message, null, messageObject); - return true; - } - } else { - if (done !== undefined) done(false); - return false; - } }; -window.WAPI.sendMessageToID = function (id, message, done) { - try { - window.getContact = (id) => { - return Store.WapQuery.queryExist(id); - } - window.getContact(id).then(contact => { - if (contact.status === 404) { - done(true); - } else { - Store.Chat.find(contact.jid).then(chat => { - chat.sendMessage(message); - return true; - }).catch(reject => { - if (WAPI.sendMessage(id, message)) { - done(true); - return true; - }else{ - done(false); - return false; - } - }); - } - }); - } catch (e) { - if (window.Store.Chat.length === 0) - return false; - - firstChat = Store.Chat.models[0]; - var originalID = firstChat.id; - firstChat.id = typeof originalID === "string" ? id : new window.Store.UserConstructor(id, { intentionallyUsePrivateConstructor: true }); - if (done !== undefined) { - firstChat.sendMessage(message).then(function () { - firstChat.id = originalID; - done(true); - }); - return true; - } else { - firstChat.sendMessage(message); - firstChat.id = originalID; - return true; - } - } - if (done !== undefined) done(false); - return false; +window.WAPI.sendMessageWithMentions = async function (ch, body) { + var chat = ch.id ? ch : Store.Chat.get(ch); + var chatId = chat.id._serialized; + var msgIveSent = chat.msgs.filter(msg => msg.__x_isSentByMe)[0]; + if(!msgIveSent) return chat.sendMessage(body); + var tempMsg = Object.create(msgIveSent); + var newId = window.WAPI.getNewMessageId(chatId); + var mentionedJidList = body.match(/@(\d*)/g).filter(x=>x.length>5).map(x=>Store.Contact.get(x.replace("@","")+"@c.us") ? new Store.WidFactory.createUserWid(x.replace("@","")) : '') || undefined; + var extend = { + ack: 0, + id: newId, + local: !0, + self: "out", + t: parseInt(new Date().getTime() / 1000), + to: new Store.WidFactory.createWid(chatId), + isNewMsg: !0, + type: "chat", + body, + quotedMsg:null, + mentionedJidList + }; + Object.assign(tempMsg, extend); + await Store.addAndSendMsgToChat(chat, tempMsg) + return newId._serialized; } -window.WAPI.sendMessage = function (id, message, done) { - var chat = WAPI.getChat(id); - if (chat !== undefined) { - if (done !== undefined) { - chat.sendMessage(message).then(function () { - function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - - var trials = 0; +window.WAPI.sendMessageReturnId = async function (ch, body) { + var chat = ch.id ? ch : Store.Chat.get(ch); + var chatId = chat.id._serialized; + var msgIveSent = chat.msgs.filter(msg => msg.__x_isSentByMe)[0]; + if(!msgIveSent) return chat.sendMessage(body); + var tempMsg = Object.create(msgIveSent); + var newId = window.WAPI.getNewMessageId(chatId); + var extend = { + ack: 0, + id: newId, + local: !0, + self: "out", + t: parseInt(new Date().getTime() / 1000), + to: new Store.WidFactory.createWid(chatId), + isNewMsg: !0, + type: "chat", + body, + quotedMsg:null + }; + Object.assign(tempMsg, extend); + await Store.addAndSendMsgToChat(chat, tempMsg) + return newId._serialized; +} - function check() { - for (let i = chat.msgs.models.length - 1; i >= 0; i--) { - let msg = chat.msgs.models[i]; - if (!msg.senderObj.isMe || msg.body != message) { - continue; - } - done(WAPI._serializeMessageObj(msg)); - return True; - } - trials += 1; - console.log(trials); - if (trials > 30) { - done(true); - return; - } - sleep(500).then(check); - } - check(); - }); - return true; - } else { - chat.sendMessage(message); - return true; - } - } else { - if (done !== undefined) done(false); - return false; +window.WAPI.sendMessage = async function (id, message) { + if(id==='status@broadcast') return false; + let chat = WAPI.getChat(id); + if(!chat && !id.includes('g')) { + var contact = WAPI.getContact(id) + if(!contact) return false; + await Store.Chat.find(contact.id) + chat = WAPI.getChat(id); } -}; + if (chat !== undefined) { + // return WAPI.sendMessageReturnId(chat,message).then(id=>{return id}) + return await chat.sendMessage(message).then(_=>chat.lastReceivedKey._serialized); + } + return false; + }; -window.WAPI.sendMessage2 = function (id, message, done) { +window.WAPI.sendMessage2 = function (id, message) { var chat = WAPI.getChat(id); if (chat !== undefined) { try { - if (done !== undefined) { - chat.sendMessage(message).then(function () { - done(true); - }); - } else { chat.sendMessage(message); - } return true; } catch (error) { - if (done !== undefined) done(false) return false; } } - if (done !== undefined) done(false) return false; }; -window.WAPI.sendSeen = function (id, done) { +window.WAPI.sendSeen = async function (id) { + if (!id) return false; var chat = window.WAPI.getChat(id); if (chat !== undefined) { - if (done !== undefined) { - Store.SendSeen(chat, false).then(function () { - done(true); - }); + await Store.ReadSeen.sendSeen(chat, false); return true; - } else { - Store.SendSeen(chat, false); + } + return false; +}; + +window.WAPI.markAsUnread = async function (id) { + var chat = window.WAPI.getChat(id); + if (chat !== undefined) { + await Store.ReadSeen.markUnread(chat, true); return true; - } } - if (done !== undefined) done(); return false; }; @@ -873,10 +875,14 @@ function isChatMessage(message) { return true; } +window.WAPI.setPresence = function (available) { + if(available)Store._Presence.setPresenceAvailable(); + else Store._Presence.setPresenceUnavailable(); +} -window.WAPI.getUnreadMessages = function (includeMe, includeNotifications, use_unread_count, done) { - const chats = window.Store.Chat.models; - let output = []; +window.WAPI.getUnreadMessages = function (includeMe, includeNotifications, use_unread_count) { + const chats = window.Store.Chat.models; + let output = []; for (let chat in chats) { if (isNaN(chat)) { @@ -884,7 +890,7 @@ window.WAPI.getUnreadMessages = function (includeMe, includeNotifications, use_u } let messageGroupObj = chats[chat]; - let messageGroup = WAPI._serializeChatObj(messageGroupObj); + let messageGroup = WAPI._serializeChatObj(messageGroupObj); messageGroup.messages = []; @@ -932,22 +938,16 @@ window.WAPI.getUnreadMessages = function (includeMe, includeNotifications, use_u } } } - if (done !== undefined) { - done(output); - } return output; }; -window.WAPI.getGroupOwnerID = async function (id, done) { +window.WAPI.getGroupOwnerID = async function (id) { const output = (await WAPI.getGroupMetadata(id)).owner.id; - if (done !== undefined) { - done(output); - } return output; }; -window.WAPI.getCommonGroups = async function (id, done) { +window.WAPI.getCommonGroups = async function (id) { let output = []; groups = window.WAPI.getAllGroups(); @@ -964,207 +964,219 @@ window.WAPI.getCommonGroups = async function (id, done) { console.log(err); } } - - if (done !== undefined) { - done(output); - } return output; }; +window.WAPI.getProfilePicFromServer = function (id) { + return Store.WapQuery.profilePicFind(id).then(x => x.eurl); +} -window.WAPI.getProfilePicSmallFromId = function (id, done) { - window.Store.ProfilePicThumb.find(id).then(function (d) { +window.WAPI.getProfilePicSmallFromId = async function (id) { + return await window.Store.ProfilePicThumb.find(id).then(async d=> { if (d.img !== undefined) { - window.WAPI.downloadFileWithCredentials(d.img, done); + return await window.WAPI.downloadFileWithCredentials(d.img); } else { - done(false); + return false } }, function (e) { - done(false); + return false }) }; -window.WAPI.getProfilePicFromId = function (id, done) { - window.Store.ProfilePicThumb.find(id).then(function (d) { +window.WAPI.getProfilePicFromId = async function (id) { + return await window.Store.ProfilePicThumb.find(id).then(async d => { if (d.imgFull !== undefined) { - window.WAPI.downloadFileWithCredentials(d.imgFull, done); + return await window.WAPI.downloadFileWithCredentials(d.imgFull); } else { - done(false); + return false } }, function (e) { - done(false); + return false }) }; -window.WAPI.downloadFileWithCredentials = function (url, done) { - let xhr = new XMLHttpRequest(); - - xhr.onload = function () { - if (xhr.readyState == 4) { - if (xhr.status == 200) { - let reader = new FileReader(); - reader.readAsDataURL(xhr.response); - reader.onload = function (e) { - done(reader.result.substr(reader.result.indexOf(',') + 1)) - }; - } else { - console.error(xhr.statusText); - } - } else { - console.log(err); - done(false); - } - }; - - xhr.open("GET", url, true); - xhr.withCredentials = true; - xhr.responseType = 'blob'; - xhr.send(null); +window.WAPI.downloadFileWithCredentials = async function (url) { + if(!axios || !url) return false; + const ab = (await axios.get(url,{responseType: 'arraybuffer'})).data + return btoa(new Uint8Array(ab).reduce((data, byte) => data + String.fromCharCode(byte), '')); }; - -window.WAPI.downloadFile = function (url, done) { +window.WAPI.downloadFile = async function (url) { + return await new Promise((resolve,reject) => { let xhr = new XMLHttpRequest(); - - xhr.onload = function () { if (xhr.readyState == 4) { if (xhr.status == 200) { let reader = new FileReader(); reader.readAsDataURL(xhr.response); reader.onload = function (e) { - done(reader.result.substr(reader.result.indexOf(',') + 1)) + resolve(reader.result.substr(reader.result.indexOf(',') + 1)) }; } else { console.error(xhr.statusText); } } else { console.log(err); - done(false); + resolve(false); } }; xhr.open("GET", url, true); xhr.responseType = 'blob'; xhr.send(null); +}) }; -window.WAPI.getBatteryLevel = function (done) { - if (window.Store.Conn.plugged) { - if (done !== undefined) { - done(100); - } - return 100; - } - output = window.Store.Conn.battery; - if (done !== undefined) { - done(output); - } - return output; +window.WAPI.getBatteryLevel = function () { + return Store.Conn.battery; }; -window.WAPI.deleteConversation = function (chatId, done) { - let userId = new window.Store.UserConstructor(chatId, {intentionallyUsePrivateConstructor: true}); - let conversation = WAPI.getChat(userId); +window.WAPI.getIsPlugged = function () { + return Store.Conn.plugged; +}; +window.WAPI.deleteConversation = async function (chatId) { + let userId = new window.Store.UserConstructor(chatId, { intentionallyUsePrivateConstructor: true }); + let conversation = WAPI.getChat(userId); if (!conversation) { - if (done !== undefined) { - done(false); - } return false; } - - window.Store.sendDelete(conversation, false).then(() => { - if (done !== undefined) { - done(true); - } + return await window.Store.sendDelete(conversation, false).then(() => { + return true; }).catch(() => { - if (done !== undefined) { - done(false); - } + return false; }); - - return true; }; -window.WAPI.deleteMessage = function (chatId, messageArray, revoke=false, done) { - let userId = new window.Store.UserConstructor(chatId, {intentionallyUsePrivateConstructor: true}); +window.WAPI.smartDeleteMessages = async function (chatId, messageArray, onlyLocal) { + var userId = new Store.WidFactory.createWid(chatId); let conversation = WAPI.getChat(userId); + if (!conversation) return false; - if(!conversation) { - if(done !== undefined) { - done(false); - } - return false; + if (!Array.isArray(messageArray)) { + messageArray = [messageArray]; } + let messagesToDelete = messageArray.map(msgId => (typeof msgId == 'string')?window.Store.Msg.get(msgId):msgId).filter(x=>x); + if(messagesToDelete.length==0) return true; + let jobs = onlyLocal ? [conversation.sendDeleteMsgs(messagesToDelete,conversation)] :[ + conversation.sendRevokeMsgs(messagesToDelete.filter(msg=>msg.isSentByMe),conversation), + conversation.sendDeleteMsgs(messagesToDelete.filter(msg=>!msg.isSentByMe),conversation) + ] + return Promise.all(jobs).then(_=>true) +}; + +window.WAPI.deleteMessage = async function (chatId, messageArray, revoke = false) { + let userId = new window.Store.UserConstructor(chatId, { intentionallyUsePrivateConstructor: true }); + let conversation = WAPI.getChat(userId); + + if (!conversation)return false; + if (!Array.isArray(messageArray)) { messageArray = [messageArray]; } + let messagesToDelete = messageArray.map(msgId => window.Store.Msg.get(msgId)); + if (revoke) { - conversation.sendRevokeMsgs(messageArray, conversation); + conversation.sendRevokeMsgs(messagesToDelete, conversation); } else { - conversation.sendDeleteMsgs(messageArray, conversation); - } - - - if (done !== undefined) { - done(true); + conversation.sendDeleteMsgs(messagesToDelete, conversation); } return true; }; -window.WAPI.checkNumberStatus = function (id, done) { - window.Store.WapQuery.queryExist(id).then((result) => { - if( done !== undefined) { - if (result.jid === undefined) throw 404; - done(window.WAPI._serializeNumberStatusObj(result)); - } - }).catch((e) => { - if (done !== undefined) { - done(window.WAPI._serializeNumberStatusObj({ - status: e, - jid : id - })); - } - }); +window.WAPI.clearChat = async function (id) { + return await Store.ChatUtil.sendClear(Store.Chat.get(id),true); +} - return true; +/** + * @param id The id of the conversation + * @param archive boolean true => archive, false => unarchive + * @return boolean true: worked, false: didnt work (probably already in desired state) + */ +window.WAPI.archiveChat = async function (id, archive) { + return await Store.Archive.setArchive(Store.Chat.get(id),archive).then(_=>true).catch(_=>false) +} + +/** + * Extracts vcards from a message + * @param id string id of the message to extract the vcards from + * @returns [vcard] + * ``` + * [ + * { + * displayName:"Contact name", + * vcard: "loong vcard string" + * } + * ] + * ``` or false if no valid vcards found + */ +window.WAPI.getVCards = function(id) { + var msg = Store.Msg.get(id); + if(msg) { + if(msg.type=='vcard') { + return [ + { + displayName:msg.subtype, + vcard:msg.body + } + ] + } else if (msg.type=='multi_vcard') { + return msg.vcardList + } else return false; + } else { + return false + } +} + +window.WAPI.checkNumberStatus = async function (id) { + try { + const result = await window.Store.WapQuery.queryExist(id); + if (result.jid === undefined) throw 404; + const data = window.WAPI._serializeNumberStatusObj(result); + if (data.status == 200) data.numberExists = true + return data; + } catch (e) { + return window.WAPI._serializeNumberStatusObj({ + status: e, + jid: id + }); + } }; /** * New messages observable functions. */ -window.WAPI._newMessagesQueue = []; -window.WAPI._newMessagesBuffer = (sessionStorage.getItem('saved_msgs') != null) ? JSON.parse(sessionStorage.getItem('saved_msgs')) : []; -window.WAPI._newMessagesDebouncer = null; -window.WAPI._newMessagesCallbacks = []; +window._WAPI._newMessagesQueue = []; +window._WAPI._newMessagesBuffer = (sessionStorage.getItem('saved_msgs') != null) ? JSON.parse(sessionStorage.getItem('saved_msgs')) : []; +window._WAPI._newMessagesDebouncer = null; +window._WAPI._newMessagesCallbacks = []; window.Store.Msg.off('add'); sessionStorage.removeItem('saved_msgs'); -window.WAPI._newMessagesListener = window.Store.Msg.on('add', (newMessage) => { - if (newMessage && newMessage.isNewMsg && !newMessage.isSentByMe) { +window._WAPI._newMessagesListener = window.Store.Msg.on('add', (newMessage) => { + if (newMessage && newMessage.isNewMsg && !newMessage.isSentByMe && !newMessage.isStatusV3) { let message = window.WAPI.processMessageObj(newMessage, false, false); if (message) { - window.WAPI._newMessagesQueue.push(message); - window.WAPI._newMessagesBuffer.push(message); + window._WAPI._newMessagesQueue.push(message); + window._WAPI._newMessagesBuffer.push(message); } // Starts debouncer time to don't call a callback for each message if more than one message arrives // in the same second - if (!window.WAPI._newMessagesDebouncer && window.WAPI._newMessagesQueue.length > 0) { - window.WAPI._newMessagesDebouncer = setTimeout(() => { - let queuedMessages = window.WAPI._newMessagesQueue; + if (!window._WAPI._newMessagesDebouncer && window._WAPI._newMessagesQueue.length > 0) { + window._WAPI._newMessagesDebouncer = setTimeout(() => { + let queuedMessages = window._WAPI._newMessagesQueue; - window.WAPI._newMessagesDebouncer = null; - window.WAPI._newMessagesQueue = []; + window._WAPI._newMessagesDebouncer = null; + window._WAPI._newMessagesQueue = []; let removeCallbacks = []; - window.WAPI._newMessagesCallbacks.forEach(function (callbackObj) { + window._WAPI._newMessagesCallbacks.forEach(function (callbackObj) { if (callbackObj.callback !== undefined) { callbackObj.callback(queuedMessages); } @@ -1175,23 +1187,25 @@ window.WAPI._newMessagesListener = window.Store.Msg.on('add', (newMessage) => { // Remove removable callbacks. removeCallbacks.forEach(function (rmCallbackObj) { - let callbackIndex = window.WAPI._newMessagesCallbacks.indexOf(rmCallbackObj); - window.WAPI._newMessagesCallbacks.splice(callbackIndex, 1); + let callbackIndex = window._WAPI._newMessagesCallbacks.indexOf(rmCallbackObj); + window._WAPI._newMessagesCallbacks.splice(callbackIndex, 1); }); }, 1000); } } }); + + window.WAPI._unloadInform = (event) => { // Save in the buffer the ungot unreaded messages - window.WAPI._newMessagesBuffer.forEach((message) => { + window._WAPI._newMessagesBuffer.forEach((message) => { Object.keys(message).forEach(key => message[key] === undefined ? delete message[key] : ''); }); - sessionStorage.setItem("saved_msgs", JSON.stringify(window.WAPI._newMessagesBuffer)); + sessionStorage.setItem("saved_msgs", JSON.stringify(window._WAPI._newMessagesBuffer)); // Inform callbacks that the page will be reloaded. - window.WAPI._newMessagesCallbacks.forEach(function (callbackObj) { + window._WAPI._newMessagesCallbacks.forEach(function (callbackObj) { if (callbackObj.callback !== undefined) { callbackObj.callback({ status: -1, message: 'page will be reloaded, wait and register callback again.' }); } @@ -1205,56 +1219,435 @@ window.addEventListener("pageunload", window.WAPI._unloadInform, false); /** * Registers a callback to be called when a new message arrives the WAPI. * @param rmCallbackAfterUse - Boolean - Specify if the callback need to be executed only once - * @param done - function - Callback function to be called when a new message arrives. + * @param callback - function - Callback function to be called when a new message arrives. * @returns {boolean} */ -window.WAPI.waitNewMessages = function (rmCallbackAfterUse = true, done) { - window.WAPI._newMessagesCallbacks.push({ callback: done, rmAfterUse: rmCallbackAfterUse }); +window.WAPI.waitNewMessages = function (rmCallbackAfterUse = true, callback) { + window._WAPI._newMessagesCallbacks.push({ callback, rmAfterUse: rmCallbackAfterUse }); return true; }; + +window.WAPI.addAllNewMessagesListener = callback => window.Store.Msg.on('add', (newMessage) => { + if (newMessage && newMessage.isNewMsg) { + if(!newMessage.clientUrl && (newMessage.mediaKeyTimestamp || newMessage.filehash)){ + const cb = (msg) => { + if(msg.id._serialized === newMessage.id._serialized && msg.clientUrl) { + callback(WAPI.processMessageObj(msg, true, false)); + Store.Msg.off('change:isUnsentMedia',cb); + } + }; + Store.Msg.on('change:isUnsentMedia',cb); + } else { + let message = window.WAPI.processMessageObj(newMessage, true, false); + if (message) { + callback(message) + } + }} +}); + +/** + * Registers a callback to be called when a the acknowledgement state of the phone connection. + * @param callback - function - Callback function to be called when the device state changes. this returns 'CONNECTED' or 'TIMEOUT' + * @returns {boolean} + */ +window.WAPI.onStateChanged = function (callback) { + window.Store.State.default.on('change:state', callback) + return true; +} + +/** + * Registers a callback to be called when your phone receives a new call request. + * @param callback - function - Callback function to be called upon a new call. returns a call object. + * @returns {boolean} + */ +window.WAPI.onIncomingCall = function (callback) { + window.Store.Call.on('add',callback); + return true; +} + +/** + * @param label: either the id or the name of the label. id will be something simple like anhy nnumber from 1-10, name is the label of the label if that makes sense. + * @param objectId The Chat, message or contact id to which you want to add a label + * @param type The type of the action. It can be either "add" or "remove" + * @returns boolean true if it worked otherwise false + */ +window.WAPI.addOrRemoveLabels = async function (label, objectId, type) { + var {id} = Store.Label.models.find(x=>x.id==label||x.name==label) + var to = Store.Chat.get(objectId) || Store.Msg.get(objectId) || Store.Contact.get(objectId); + if(!id || !to) return false; + const {status} = await Store.Label.addOrRemoveLabels([{id,type}],[to]); + return status===200; +} + +/** + * Registers a callback to be called when a the acknowledgement state of a message changes. + * @param callback - function - Callback function to be called when a message acknowledgement changes. + * @returns {boolean} + */ +window.WAPI.waitNewAcknowledgements = function (callback) { + Store.Msg.on("change:ack", callback); + return true; +} + +//returns an array of liveLocationChangeObjects +window.WAPI.forceUpdateLiveLocation = async function (chatId) { + if(!Store.LiveLocation.get(chatId)) return false; + return WAPI.quickClean(await Store.LiveLocation.update(chatId)).participants.map(l=>{ + return { + ...l, + msgId:l.msg.id._serialized + } + }); +} + +window.WAPI.onLiveLocation = function (chatId, callback) { + var lLChat = Store.LiveLocation.get(chatId); + if(lLChat) { + var validLocs = lLChat.participants.validLocations(); + validLocs.map(x=>x.on('change:lastUpdated',(x,y,z)=>{ + const {id,lat,lng,accuracy,degrees,speed,lastUpdated}=x; + const l = { + id:id.toString(),lat,lng,accuracy,degrees,speed,lastUpdated}; + callback(l); + })); + return true; + } else { + return false; + } +} + +window.WAPI.onBattery = function(callback) { + window.Store.Conn.on('change:battery', ({battery}) => callback(battery)); + return true; +} + +window.WAPI.onPlugged = function(callback) { + window.Store.Conn.on('change:plugged', ({plugged}) => callback(plugged)); + return true; +} + +/** + * Registers a callback to participant changes on a certain, specific group + * @param groupId - string - The id of the group that you want to attach the callback to. + * @param callback - function - Callback function to be called when a message acknowledgement changes. The callback returns 3 variables + * @returns {boolean} + */ +window.WAPI.onParticipantsChanged = function (groupId, callback) { + const subtypeEvents = [ + "invite" , + "add" , + "remove" , + "leave" , + "promote" , + "demote" + ]; + const events = [ + 'change:isAdmin', + 'remove', + 'add' + ] + const chat = window.Store.Chat.get(groupId); + chat.groupMetadata.participants.on('all', (eventName, eventData, extra) => { + if(events.includes(eventName)) { + let action = eventName; + if(eventName=='change:isAdmin') { + action = extra ? 'promote' : 'demote'; + } + callback({ + by: undefined, + action: action, + who: eventData.id._serialized + }); + } + }) +} + +/** + * Registers a callback to participant changes on a certain, specific group + * @param groupId - string - The id of the group that you want to attach the callback to. + * @param callback - function - Callback function to be called when a message acknowledgement changes. The callback returns 3 variables + * @returns {boolean} + */ +var groupParticpiantsEvents = {}; +window.WAPI._onParticipantsChanged = function (groupId, callback) { + const subtypeEvents = [ + "invite" , + "add" , + "remove" , + "leave" , + "promote" , + "demote" + ]; + const chat = window.Store.Chat.get(groupId); + //attach all group Participants to the events object as 'add' + const metadata = window.Store.GroupMetadata.get(groupId); + if (!groupParticpiantsEvents[groupId]) { + groupParticpiantsEvents[groupId] = {}; + metadata.participants.forEach(participant => { + groupParticpiantsEvents[groupId][participant.id.toString()] = { + subtype: "add", + from: metadata.owner + } + }); + } + let i = 0; + chat.on("change:groupMetadata.participants", + _ => chat.on("all", (x, y) => { + const { isGroup, previewMessage } = y; + if (isGroup && x === "change" && previewMessage && previewMessage.type === "gp2" && subtypeEvents.includes(previewMessage.subtype)) { + const { subtype, author, recipients } = previewMessage; + const rec = recipients[0].toString(); + if (groupParticpiantsEvents[groupId][rec] && groupParticpiantsEvents[groupId][recipients[0]].subtype == subtype) { + //ignore, this is a duplicate entry + // console.log('duplicate event') + } else { + //ignore the first message + if (i == 0) { + //ignore it, plus 1, + i++; + } else { + groupParticpiantsEvents[groupId][rec] = { subtype, author }; + //fire the callback + // // previewMessage.from.toString() + // x removed y + // x added y + callback({ + by: author.toString(), + action: subtype, + who: recipients + }); + chat.off("all", this) + i = 0; + } + } + } + }) + ) + return true; +} + + +/** + * Registers a callback that fires when your host phone is added to a group. + * @param callback - function - Callback function to be called when a message acknowledgement changes. The callback returns 3 variables + * @returns {boolean} + */ +window.WAPI.onAddedToGroup = function(callback){ + Store.Chat.on('change:previewMessage', async event => { + if(event.isGroup && event.previewMessage && event.previewMessage.type=='gp2' && event.previewMessage.subtype =='add' && event.previewMessage.recipients && event.previewMessage.recipients.map(x=>x._serialized).includes(Store.Me.wid._serialized)) { + const tdiff = (Date.now()-Store.Msg.get(event.previewMessage.id._serialized).t*1000)/1000; + if(tdiff<10.0) { + console.log('added', tdiff,'seconds ago') + await WAPI.sendSeen(event.id); + callback(WAPI._serializeChatObj(Store.Chat.get(event.id))); + } else console.log('Not a new group add', event.id._serialized) + } + }) + return true; +} + /** * Reads buffered new messages. - * @param done - function - Callback function to be called contained the buffered messages. * @returns {Array} */ -window.WAPI.getBufferedNewMessages = function (done) { - let bufferedMessages = window.WAPI._newMessagesBuffer; - window.WAPI._newMessagesBuffer = []; - if (done !== undefined) { - done(bufferedMessages); - } +window.WAPI.getBufferedNewMessages = function () { + let bufferedMessages = window._WAPI._newMessagesBuffer; + window._WAPI._newMessagesBuffer = []; return bufferedMessages; }; /** End new messages observable functions **/ -window.WAPI.sendImage = function (imgBase64, chatid, filename, caption, done) { -//var idUser = new window.Store.UserConstructor(chatid); -var idUser = new window.Store.UserConstructor(chatid, { intentionallyUsePrivateConstructor: true }); -// create new chat -return Store.Chat.find(idUser).then((chat) => { - var mediaBlob = window.WAPI.base64ImageToFile(imgBase64, filename); - var mc = new Store.MediaCollection(chat); - mc.processAttachments([{file: mediaBlob}, 1], chat, 1).then(() => { - var media = mc.models[0]; - media.sendToChat(chat, { caption: caption }); - if (done !== undefined) done(true); +/** Joins a group via the invite link, code, or message + * @param link This param is the string which includes the invite link or code. The following work: + * - Follow this link to join my WA group: https://chat.whatsapp.com/DHTGJUfFJAV9MxOpZO1fBZ + * - https://chat.whatsapp.com/DHTGJUfFJAV9MxOpZO1fBZ + * - DHTGJUfFJAV9MxOpZO1fBZ + * @returns Promise Either false if it didn't work, or the group id. + */ +window.WAPI.joinGroupViaLink = async function(link){ + let code = link; + //is it a link? if not, assume it's a code, otherwise, process the link to get the code. + if(link.includes('chat.whatsapp.com')) { + if(!link.match(/chat.whatsapp.com\/([\w\d]*)/g).length) return false; + code = link.match(/chat.whatsapp.com\/([\w\d]*)/g)[0].replace('chat.whatsapp.com\/',''); + } + const group = await Store.GroupInvite.joinGroupViaInvite(code); + if(!group.id) return false; + return group.id._serialized +} + +window.WAPI.sendImage = async function (imgBase64, chatid, filename, caption, quotedMsg, waitForKey, ptt) { + if(!chatid.includes('@g')&&!chatid.includes('@c')) return false; + let extras = {}; + if(quotedMsg){ + if (typeof quotedMsg !== "object") quotedMsg = Store.Msg.get(quotedMsg); + extras = { + quotedMsg, + quotedParticipant: quotedMsg.author || quotedMsg.from, + quotedStanzaID:quotedMsg.id.id + }; + } + return await Store.Chat.find(chatid).then(async (chat) => { + var mediaBlob = window.WAPI.base64ImageToFile(imgBase64, filename); + return await window.WAPI.procFiles(chat,mediaBlob).then(async mc => { + var media = mc.models[0]; + if(ptt) media.mediaPrep._mediaData.type = 'ptt'; + await media.sendToChat(chat, { caption,...extras }); + return waitForKey ? await new Promise(async (resolve,reject) => { + const cb = msg=>{ + if(media.attributes.file.size === msg.size) resolve(msg.id._serialized); + Store.Msg.off('change:clientUrl',cb); + }; + Store.Msg.on('change:clientUrl',cb); + }) : true + }); }); -}); +} + +/** + * This function sts the profile name of the number. + * @param newName - string the new name to set as profile name + */ +window.WAPI.setMyName = async function (newName) { + if(!Store.Versions.default[11].BinaryProtocol) Store.Versions.default[11].BinaryProtocol=new Store.bp(11); + return await Store.Versions.default[11].setPushname(newName); +} + +/** Change the icon for the group chat + * @param groupId 123123123123_1312313123@g.us The id of the group + * @param imgData 'data:image/jpeg;base64,...` The base 64 data uri + * @returns boolean true if it was set, false if it didn't work. It usually doesn't work if the image file is too big. + */ +window.WAPI.setGroupIcon = async function(groupId, imgData) { + const {status} = await Store.WapQuery.sendSetPicture(groupId,imgData,imgData); + return status==200; +} + +/** +* Update your status +* @param newStatus string new Status +*/ +window.WAPI.setMyStatus = function (newStatus) { + return Store.MyStatus.setMyStatus(newStatus) +} + +window.WAPI.sendVideoAsGif = async function (imgBase64, chatid, filename, caption, quotedMsg) { + let extras = {}; + if(quotedMsg){ + if (typeof quotedMsg !== "object") quotedMsg = Store.Msg.get(quotedMsg); + extras = { + quotedMsg, + quotedParticipant: quotedMsg.author || quotedMsg.from, + quotedStanzaID:quotedMsg.id.id + }; + } + // create new chat + return await Store.Chat.find(chatid).then(async (chat) => { + var mediaBlob = window.WAPI.base64ImageToFile(imgBase64, filename); + var mc = new Store.MediaCollection(chat); + return await window.WAPI.procFiles(chat,mediaBlob).then(async mc => { + var media = mc.models[0]; + media.mediaPrep._mediaData.isGif = true; + media.mediaPrep._mediaData.gifAttribution = 1; + await media.mediaPrep.sendToChat(chat, { caption,...extras }); + return chat.lastReceivedKey._serialized; + }); + }); +} + +window.WAPI.refreshBusinessProfileProducts = async function (){ + await Promise.all(Store.BusinessProfile.models.map(async x=>{ + try{ + await Store.Catalog.findCarouselCatalog(x.id._serialized) + } catch(error){} + })); + return true; +} + +/** + * Find any product listings of the given number. Use this to query a catalog + * + * @param id id of buseinss profile (i.e the number with @c.us) + * @returns None + */ +window.WAPI.getBusinessProfilesProducts = async function (id) { + await WAPI.refreshBusinessProfileProducts(); + if(!Store.Catalog.get(id)) await Store.Catalog.findCarouselCatalog(id) + const catalog = Store.Catalog.get(id); + if (catalog.productCollection && catalog.productCollection._models.length) + return catalog.productCollection._models; +}; + + +window.WAPI.procFiles= async function(chat, blobs) { + if (!Array.isArray(blobs)) { + blobs = [blobs]; + } + var mc = new Store.MediaCollection(chat); + await mc.processFiles((Debug.VERSION === '0.4.613')?blobs:blobs.map(blob=>{return{file:blob}}) , chat, 1); + return mc +} +/** + * Sends product with image to chat + * @param imgBase64 Base64 image data + * @param chatid string the id of the chat that you want to send this product to + * @param caption string the caption you want to add to this message + * @param bizNumber string the @c.us number of the business account from which you want to grab the product + * @param productId string the id of the product within the main catalog of the aforementioned business + * @returns + */ +window.WAPI.sendImageWithProduct = async function (imgBase64, chatid, caption, bizNumber, productId) { + await WAPI.refreshBusinessProfileProducts(); + return await Store.Catalog.findCarouselCatalog(bizNumber).then(async cat => { + if (cat && cat[0]) { + const product = cat[0].productCollection.get(productId); + const temp = { + productMsgOptions: { + businessOwnerJid: product.catalogWid.toString({ + legacy: !0 + }), + productId: product.id.toString(), + url: product.url, + productImageCount: product.productImageCollection.length, + title: product.name, + description: product.description, + currencyCode: product.currency, + priceAmount1000: product.priceAmount1000, + type: "product" + }, + caption + } + + // var idUser = new Store.WidFactory.createWid(chatid); + + return Store.Chat.find(chatid).then(async (chat) => { + var mediaBlob = window.WAPI.base64ImageToFile(imgBase64, "filename.jpg"); + // var mc = new Store.MediaCollection(chat); + // mc.processFiles([mediaBlob], chat, 1) + return await window.WAPI.procFiles(chat,mediaBlob).then(async mc => { + var media = mc.models[0]; + Object.entries(temp.productMsgOptions).map(([k, v]) => media.mediaPrep._mediaData[k] = v) + await media.mediaPrep.sendToChat(chat, temp); + return chat.lastReceivedKey._serialized; + }); + }); + } + }) } window.WAPI.base64ImageToFile = function (b64Data, filename) { - var arr = b64Data.split(','); - var mime = arr[0].match(/:(.*?);/)[1]; - var bstr = atob(arr[1]); - var n = bstr.length; + var arr = b64Data.split(','); + var mime = arr[0].match(/:(.*?);/)[1]; + var bstr = window.Base64 ? window.Base64.atob(arr[1]) : atob(arr[1]); + var n = bstr.length; var u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } - return new File([u8arr], filename, {type: mime}); + return new File([u8arr], filename, { type: mime }); }; /** @@ -1278,55 +1671,274 @@ window.WAPI.sendContact = function (to, contact) { } }; +/** + * Ghost forwarding is like a normal forward but as if it were sent from the host phone. + */ +window.WAPI.ghostForward = async function(chatId, messageId) { + if(!chatId.includes('@g')&&!chatId.includes('@c')) return false; + var chat = Store.Chat.get(chatId); + if(!Store.Msg.get(messageId)) return false; + var tempMsg = Object.create(Store.Msg.get(messageId)); + var newId = window.WAPI.getNewMessageId(chatId); + var extend = { + ...JSON.parse(JSON.stringify(tempMsg)), + ack: 0, + id: newId, + local: !0, + self: "out", + t: parseInt(new Date().getTime() / 1000), + to: new Store.WidFactory.createWid(chatId), + from: Store.Me.wid, + isNewMsg: true + }; + Object.assign(tempMsg, extend); + const res = await Promise.all(Store.addAndSendMsgToChat(chat, extend)) + return res[1]=='success'; +} + + +/** + * Forward an array of messages to a specific chat using the message ids or Objects + * + * @param {string} to '000000000000@c.us' + * @param {string|array[Message | string]} messages this can be any mixture of message ids or message objects + * @param {boolean} skipMyMessages This indicates whether or not to skip your own messages from the array + */ +window.WAPI.forwardMessages = async function (to, messages, skipMyMessages) { + if (!Array.isArray(messages)) { + messages = [messages]; + } + const finalForwardMessages = messages.map(msg => { + if (typeof msg == 'string') { + //msg is string, get the message object + return window.Store.Msg.get(msg); + } else { + return window.Store.Msg.get(msg.id); + } + }).filter(msg => skipMyMessages ? !msg.__x_isSentByMe : true); + + // let userId = new window.Store.UserConstructor(to); + let conversation = window.Store.Chat.get(to); + return await conversation.forwardMessages(finalForwardMessages) +}; + /** * Create an chat ID based in a cloned one * * @param {string} chatId '000000000000@c.us' */ window.WAPI.getNewMessageId = function (chatId) { - var newMsgId = Store.Msg.models[0].__x_id.clone(); + var newMsgId = new Store.MsgKey(Object.assign({}, Store.Msg.models[0].__x_id)) + // .clone(); - newMsgId.fromMe = true; - newMsgId.id = WAPI.getNewId().toUpperCase(); - newMsgId.remote = chatId; + newMsgId.fromMe = true; + newMsgId.id = WAPI.getNewId().toUpperCase(); + newMsgId.remote = new Store.WidFactory.createWid(chatId); newMsgId._serialized = `${newMsgId.fromMe}_${newMsgId.remote}_${newMsgId.id}` return newMsgId; }; + +/** + * Simulate '...typing' in the chat. + * + * @param {string} chatId '000000000000@c.us' + * @param {boolean} on true to turn on similated typing, false to turn it off //you need to manually turn this off. + */ +window.WAPI.simulateTyping = async function (chatId, on) { + if (on) Store.ChatStates.sendChatStateComposing(chatId) + else Store.ChatStates.sendChatStatePaused(chatId) + return true +}; + /** - * Send Customized VCard without the necessity of contact be a Whatsapp Contact + * Send location + * + * @param {string} chatId '000000000000@c.us' + * @param {string} lat latitude + * @param {string} lng longitude + * @param {string} loc Text to go with the location message + */ +window.WAPI.sendLocation = async function (chatId, lat, lng, loc) { + var chat = Store.Chat.get(chatId); + if(!chat) return false; + var tempMsg = Object.create(Store.Msg.models.filter(msg => msg.__x_isSentByMe && !msg.quotedMsg)[0]); + var newId = window.WAPI.getNewMessageId(chatId); + var extend = { + ack: 0, + id: newId, + local: !0, + self: "out", + t: parseInt(new Date().getTime() / 1000), + to: chatId, + isNewMsg: !0, + type: "location", + lat, + lng, + loc, + clientUrl:undefined, + directPath:undefined, + filehash:undefined, + uploadhash:undefined, + mediaKey:undefined, + isQuotedMsgAvailable:false, + invis:false, + mediaKeyTimestamp:undefined, + mimetype:undefined, + height:undefined, + width:undefined, + ephemeralStartTimestamp:undefined, + body:undefined, + mediaData:undefined, + isQuotedMsgAvailable: false + }; + Object.assign(tempMsg, extend); + return await Promise.all(Store.addAndSendMsgToChat(chat, tempMsg)) +}; + +/** + * Send VCARD + * + * @param {string} chatId '000000000000@c.us' + * @param {string} vcard vcard as a string + * @param {string} contactName The display name for the contact. CANNOT BE NULL OTHERWISE IT WILL SEND SOME RANDOM CONTACT FROM YOUR ADDRESS BOOK. + * @param {string} contactNumber If supplied, this will be injected into the vcard (VERSION 3 ONLY FROM VCARDJS) with the WA id to make it show up with the correct buttons on WA. + */ +window.WAPI.sendVCard = async function (chatId, vcard, contactName, contactNumber) { + var chat = Store.Chat.get(chatId); + var tempMsg = Object.create(Store.Msg.models.filter(msg => msg.__x_isSentByMe && !msg.quotedMsg)[0]); + var newId = window.WAPI.getNewMessageId(chatId); + var extend = { + ack: 0, + id: newId, + local: !0, + self: "out", + t: parseInt(new Date().getTime() / 1000), + to: chatId, + isNewMsg: !0, + type: "vcard", + clientUrl:undefined, + directPath:undefined, + filehash:undefined, + uploadhash:undefined, + mediaKey:undefined, + isQuotedMsgAvailable:false, + invis:false, + mediaKeyTimestamp:undefined, + mimetype:undefined, + height:undefined, + width:undefined, + ephemeralStartTimestamp:undefined, + body:contactNumber?vcard.replace('TEL;TYPE=WORK,VOICE:',`TEL;TYPE=WORK,VOICE;waid=${contactNumber}:`):vcard, + mediaData:undefined, + isQuotedMsgAvailable: false, + subtype: contactName + }; + Object.assign(tempMsg, extend); + return (await Promise.all(Store.addAndSendMsgToChat(chat, tempMsg)))[1]=="success" +}; + +window.WAPI.reply = async function (chatId, body, quotedMsg) { + if (typeof quotedMsg !== "object") quotedMsg = Store.Msg.get(quotedMsg) + var chat = Store.Chat.get(chatId); + if(!chat) return false; + let extras = {}; + if(quotedMsg) { + extras = { + quotedParticipant: quotedMsg.author || quotedMsg.from, + quotedStanzaID:quotedMsg.id.id + }; + } + var tempMsg = Object.create(Store.Msg.models.filter(msg => msg.__x_isSentByMe && !msg.quotedMsg)[0]); + var newId = window.WAPI.getNewMessageId(chatId); + var extend = { + ack: 0, + id: newId, + local: !0, + self: "out", + t: parseInt(new Date().getTime() / 1000), + to: new Store.WidFactory.createWid(chatId), + isNewMsg: !0, + type: "chat", + quotedMsg, + body, + ...extras + }; + Object.assign(tempMsg, extend); + const res = await Promise.all(await Store.addAndSendMsgToChat(chat, tempMsg)); + if(res[1]!='success') return false; + return res[0].id._serialized +}; + +/** + * Send Payment Request + * + * @param {string} chatId '000000000000@c.us' + * @param {string} amount1000 The amount in base value / 10 (e.g 50000 in GBP = £50) + * @param {string} currency Three letter currency code (e.g SAR, GBP, USD, INR, AED, EUR) + * @param {string} note message to send with the payment request + */ +window.WAPI.sendPaymentRequest = async function (chatId, amount1000, currency, noteMessage) { + var chat = Store.Chat.get(chatId); + var tempMsg = Object.create(Store.Msg.models.filter(msg => msg.__x_isSentByMe && !msg.quotedMsg)[0]); + var newId = window.WAPI.getNewMessageId(chatId); + var extend = { + ack: 0, + id: newId, + local: !0, + self: "out", + t: parseInt(new Date().getTime() / 1000), + to: chatId, + isNewMsg: !0, + type: "payment", + subtype: "request", + amount1000, + requestFrom: chatId, + currency, + noteMessage, + expiryTimestamp: parseInt(new Date(new Date().setDate(new Date().getDate() + 1)).getTime() / 1000) + }; + Object.assign(tempMsg, extend); + await Store.addAndSendMsgToChat(chat, tempMsg) +}; + + + +/** + * Send Customized VCard without the necessity of contact be a WA Contact * * @param {string} chatId '000000000000@c.us' * @param {object|array} vcard { displayName: 'Contact Name', vcard: 'BEGIN:VCARD\nVERSION:3.0\nN:;Contact Name;;;\nEND:VCARD' } | [{ displayName: 'Contact Name 1', vcard: 'BEGIN:VCARD\nVERSION:3.0\nN:;Contact Name 1;;;\nEND:VCARD' }, { displayName: 'Contact Name 2', vcard: 'BEGIN:VCARD\nVERSION:3.0\nN:;Contact Name 2;;;\nEND:VCARD' }] */ -window.WAPI.sendVCard = function (chatId, vcard) { - var chat = Store.Chat.get(chatId); - var tempMsg = Object.create(Store.Msg.models.filter(msg => msg.__x_isSentByMe)[0]); - var newId = window.WAPI.getNewMessageId(chatId); +window.WAPI._sendVCard = function (chatId, vcard) { + var chat = Store.Chat.get(chatId); + var tempMsg = Object.create(Store.Msg.models.filter(msg => msg.__x_isSentByMe && !msg.quotedMsg)[0]); + var newId = window.WAPI.getNewMessageId(chatId); var extend = { - ack : 0, - id : newId, - local : !0, - self : "out", - t : parseInt(new Date().getTime() / 1000), - to : chatId, + ack: 0, + id: newId, + local: !0, + self: "out", + t: parseInt(new Date().getTime() / 1000), + to: chatId, isNewMsg: !0, + isQuotedMsgAvailable:false, }; if (Array.isArray(vcard)) { Object.assign(extend, { - type : "multi_vcard", + type: "multi_vcard", vcardList: vcard }); delete extend.body; } else { Object.assign(extend, { - type : "vcard", + type: "vcard", subtype: vcard.displayName, - body : vcard.vcard + body: vcard.vcard }); delete extend.vcardList; @@ -1334,36 +1946,31 @@ window.WAPI.sendVCard = function (chatId, vcard) { Object.assign(tempMsg, extend); - chat.addAndSendMsg(tempMsg); + Store.addAndSendMsgToChat(chat, tempMsg) }; + /** * Block contact * @param {string} id '000000000000@c.us' - * @param {*} done - function - Callback function to be called when a new message arrives. */ -window.WAPI.contactBlock = function (id, done) { +window.WAPI.contactBlock = async function (id) { const contact = window.Store.Contact.get(id); if (contact !== undefined) { - contact.setBlock(!0); - done(true); + await Store.Block.blockContact(contact) return true; } - done(false); return false; } /** - * unBlock contact + * Unblock contact * @param {string} id '000000000000@c.us' - * @param {*} done - function - Callback function to be called when a new message arrives. */ -window.WAPI.contactUnblock = function (id, done) { +window.WAPI.contactUnblock = async function (id) { const contact = window.Store.Contact.get(id); if (contact !== undefined) { - contact.setBlock(!1); - done(true); + await Store.Block.unblockContact(contact) return true; } - done(false); return false; } @@ -1371,51 +1978,192 @@ window.WAPI.contactUnblock = function (id, done) { * Remove participant of Group * @param {*} idGroup '0000000000-00000000@g.us' * @param {*} idParticipant '000000000000@c.us' - * @param {*} done - function - Callback function to be called when a new message arrives. */ -window.WAPI.removeParticipantGroup = function (idGroup, idParticipant, done) { - window.Store.WapQuery.removeParticipants(idGroup, [idParticipant]).then(() => { - const metaDataGroup = window.Store.GroupMetadata.get(id) - checkParticipant = metaDataGroup.participants._index[idParticipant]; - if (checkParticipant === undefined) { - done(true); return true; - } - }) +window.WAPI.removeParticipant = async function (idGroup, idParticipant) { + const chat = Store.Chat.get(idGroup); + const rm = chat.groupMetadata.participants.get(idParticipant); + await window.Store.Participants.removeParticipants(chat, [rm]); + return true; +} + + +/** + * Add participant to Group + * @param {*} idGroup '0000000000-00000000@g.us' + * @param {*} idParticipant '000000000000@c.us' + */ +window.WAPI.addParticipant = async function (idGroup, idParticipant) { + const chat = Store.Chat.get(idGroup); + const add = Store.Contact.get(idParticipant); + await window.Store.Participants.addParticipants(chat, [add]); + return true; } /** * Promote Participant to Admin in Group * @param {*} idGroup '0000000000-00000000@g.us' * @param {*} idParticipant '000000000000@c.us' - * @param {*} done - function - Callback function to be called when a new message arrives. */ -window.WAPI.promoteParticipantAdminGroup = function (idGroup, idParticipant, done) { - window.Store.WapQuery.promoteParticipants(idGroup, [idParticipant]).then(() => { - const metaDataGroup = window.Store.GroupMetadata.get(id) - checkParticipant = metaDataGroup.participants._index[idParticipant]; - if (checkParticipant !== undefined && checkParticipant.isAdmin) { - done(true); return true; - } - done(false); return false; - }) +window.WAPI.promoteParticipant = async function (idGroup, idParticipant) { + const chat = Store.Chat.get(idGroup); + const promote = chat.groupMetadata.participants.get(idParticipant); + await window.Store.Participants.promoteParticipants(chat, [promote]); + return true; } /** * Demote Admin of Group * @param {*} idGroup '0000000000-00000000@g.us' * @param {*} idParticipant '000000000000@c.us' - * @param {*} done - function - Callback function to be called when a new message arrives. */ -window.WAPI.demoteParticipantAdminGroup = function (idGroup, idParticipant, done) { - window.Store.WapQuery.demoteParticipants(idGroup, [idParticipant]).then(() => { - const metaDataGroup = window.Store.GroupMetadata.get(id) - if (metaDataGroup === undefined) { - done(false); return false; - } - checkParticipant = metaDataGroup.participants._index[idParticipant]; - if (checkParticipant !== undefined && checkParticipant.isAdmin) { - done(false); return false; - } - done(true); return true; - }) +window.WAPI.demoteParticipant = async function (idGroup, idParticipant) { + await window.Store.WapQuery.demoteParticipants(idGroup, [idParticipant]) + const chat = Store.Chat.get(idGroup); + const demote = chat.groupMetadata.participants.get(idParticipant); + await window.Store.Participants.demoteParticipants(chat, [demote]) + return true + +} + +/** + * @private + * Send Sticker + * @param {*} sticker + * @param {*} chatId '000000000000@c.us' + * @param metadata about the image. Based on [sharp metadata](https://sharp.pixelplumbing.com/api-input#metadata) + */ +window.WAPI._sendSticker = async function (sticker, chatId, metadata) { + var chat = Store.Chat.get(chatId) + let stick = new window.Store.Sticker.modelClass(); + stick.__x_clientUrl = sticker.clientUrl; + stick.__x_filehash = sticker.filehash; + stick.__x_id = sticker.filehash; + stick.__x_uploadhash = sticker.uploadhash; + stick.__x_mediaKey = sticker.mediaKey; + stick.__x_initialized = false; + stick.__x_mediaData.mediaStage = 'INIT'; + stick.mimetype = 'image/webp'; + stick.height = (metadata && metadata.height) ? metadata.height : 512; + stick.width = (metadata && metadata.width) ? metadata.width : 512; + await stick.initialize(); + return await stick.sendToChat(chat); +}; + +window.WAPI.getFileHash = async (data) => { + let buffer = await data.arrayBuffer(); + var sha = new jsSHA("SHA-256", "ARRAYBUFFER"); + sha.update(buffer); + return sha.getHash("B64"); +}; + +window.WAPI.generateMediaKey = async (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; +}; + +/** + * @param type: The type of file. {'audio' | 'sticker' | 'video' | 'product' | 'document' | 'gif' | 'image' | 'ptt' | 'template' | 'history' | 'ppic'} + * @param blob: file + */ +window.WAPI.encryptAndUploadFile = async function (type, blob) { + let filehash = await window.WAPI.getFileHash(blob); + let mediaKey = await window.WAPI.generateMediaKey(32); + let controller = new AbortController(); + let signal = controller.signal; + let encrypted = await window.Store.UploadUtils.encryptAndUpload({ + blob, + type, + signal, + mediaKey + }); + return { + ...encrypted, + clientUrl: encrypted.url, + filehash, + id: filehash, + uploadhash: encrypted.encFilehash, + }; +}; + +/** + * Send Image As Sticker + * @param {*} imageBase64 A valid webp image is required. + * @param {*} chatId '000000000000@c.us' + * @param metadata about the image. Based on [sharp metadata](https://sharp.pixelplumbing.com/api-input#metadata) + */ +window.WAPI.sendImageAsSticker = async function (imageBase64,chatId, metadata) { + let mediaBlob = await window.WAPI.base64ImageToFile( + 'data:image/webp;base64,'+imageBase64, + 'file.webp' + ); + let encrypted = await window.WAPI.encryptAndUploadFile("sticker", mediaBlob); + return await window.WAPI._sendSticker(encrypted, chatId, metadata); +}; + +/** +This will dump all possible stickers into the chat. ONLY FOR TESTING. THIS IS REALLY ANNOYING!! + */ +window.WAPI._STICKERDUMP = async function (chatId) { + var chat = Store.Chat.get(chatId); + let prIdx = await Store.StickerPack.pageWithIndex(0); + await Store.StickerPack.fetchAt(0); + await Store.StickerPack._pageFetchPromises[prIdx]; + return await Promise.race(Store.StickerPack.models.forEach(pack=>pack.stickers.fetch().then(_=>pack.stickers.models.forEach(stkr => stkr.sendToChat(chat))))).catch(e=>{}) +} + + +window.WAPI.getLastSeen = async function (id) { + if(!Store.Chat.get(id)) return false; + let {presence} = Store.Chat.get(id) + await presence.subscribe(); + return presence.chatstate.t; + } + +window.WAPI.getUseHereString = async function() { + if (!window.l10n.localeStrings['en']){ + const originalLocale = window.l10n.getLocale(); + await window.l10n.init('en'); + await window.l10n.init(originalLocale) + } + return window.l10n.localeStrings[window.l10n.getLocale()][0][window.l10n.localeStrings.en[0].findIndex(x=>x.toLowerCase()==='use here')] + } + + window.WAPI.getAmountOfLoadedMessages = function() { + return Store.Msg.models.length; } + +window.WAPI.cutMsgCache = function (){ + Store.Msg.models.map(msg=>Store.Msg.remove(msg)); + return true; +} + +//All of the following features can be unlocked using a license key: https://github.com/open-wa/wa-automate-nodejs#license-key +window.WAPI.getStoryStatusByTimeStamp = function(){return false;} +window.WAPI.deleteAllStatus = function(){return false;} +window.WAPI.getMyStatusArray = function(){return false;} +window.WAPI.deleteStatus = function(){return false;} +window.WAPI.setGroupToAdminsOnly = function(){return false;} +window.WAPI.setGroupEditToAdminsOnly = function(){return false;} +window.WAPI.postTextStatus = function(){return false;} +window.WAPI.postImageStatus = function(){return false;} +window.WAPI.postVideoStatus = function(){return false;} +window.WAPI.onRemovedFromGroup = function(){return false;} +window.WAPI.onContactAdded = function(){return false;} +window.WAPI.sendReplyWithMentions = function(){return false;} +window.WAPI.clearAllChats = function(){return false;} +window.WAPI.getCommonGroups = function(){return false;} +window.WAPI.setChatBackgroundColourHex = function(){return false;} +window.WAPI.darkMode = function(){return false;} +window.WAPI.onChatOpened = function(){return false;} +window.WAPI.onStory = function(){return false;} + +window.WAPI.quickClean = function (ob) {return JSON.parse(JSON.stringify(ob))}; + +window.WAPI.pyFunc = async function (fn, done) { + return done(await fn()) +} \ No newline at end of file diff --git a/src/inject.js b/src/inject.js index 2e0c18b..c24fd29 100644 --- a/src/inject.js +++ b/src/inject.js @@ -5,35 +5,37 @@ WAPI.waitNewMessages(false, async (data) => { body = {}; body.text = message.body; body.type = 'message'; - body.user = message.from._serialized; + body.user = message.chatId._serialized; //body.original = message; - fetch(intents.appconfig.webhook, { - method: "POST", - body: JSON.stringify(body), - headers: { - 'Content-Type': 'application/json' - } - }).then((resp) => resp.json()).then(function (response) { - //response received from server - console.log(response); - WAPI.sendSeen(message.from._serialized); - //replying to the user based on response - if (response && response.length > 0){ - response.forEach(itemResponse => { - WAPI.sendMessage2(message.from._serialized, itemResponse.text); - //sending files if there is any - if(itemResponse.files && itemResponse.files.length > 0 ){ - itemResponse.files.forEach((itemFile) => { - WAPI.sendImage(itemFile.file, message.from._serialized , itemFile.name); - }) - } - }); - } - }).catch(function (error) { - console.log(error); - }); - window.log(`Message from ${message.from.user} checking..`); - if (intents.blocked.indexOf(message.from.user) >= 0) { + if (intents.appconfig.webhook) { + fetch(intents.appconfig.webhook, { + method: "POST", + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json' + } + }).then((resp) => resp.json()).then(function (response) { + //response received from server + console.log(response); + WAPI.sendSeen(message.chatId._serialized); + //replying to the user based on response + if (response && response.length > 0) { + response.forEach(itemResponse => { + WAPI.sendMessage2(message.chatId._serialized, itemResponse.text); + //sending files if there is any + if (itemResponse.files && itemResponse.files.length > 0) { + itemResponse.files.forEach((itemFile) => { + WAPI.sendImage(itemFile.file, message.chatId._serialized, itemFile.name); + }) + } + }); + } + }).catch(function (error) { + console.log(error); + }); + } + window.log(`Message from ${message.chatId.user} checking..`); + if (intents.blocked.indexOf(message.chatId.user) >= 0) { window.log("number is blocked by BOT. no reply"); return; } @@ -59,13 +61,13 @@ WAPI.waitNewMessages(false, async (data) => { } else { console.log("No partial match found"); } - WAPI.sendSeen(message.from._serialized); - WAPI.sendMessage2(message.from._serialized, response); + WAPI.sendSeen(message.chatId._serialized); + WAPI.sendMessage2(message.chatId._serialized, response); console.log(); if ((exactMatch || PartialMatch).file != undefined) { window.getFile((exactMatch || PartialMatch).file).then((base64Data) => { //console.log(file); - WAPI.sendImage(base64Data, message.from._serialized, (exactMatch || PartialMatch).file); + WAPI.sendImage(base64Data, message.chatId._serialized, (exactMatch || PartialMatch).file); }).catch((error) => { window.log("Error in sending file\n" + error); })