diff --git a/src/webwhatsapi/js/wapi.js b/src/webwhatsapi/js/wapi.js new file mode 100644 index 0000000..e8d7e49 --- /dev/null +++ b/src/webwhatsapi/js/wapi.js @@ -0,0 +1,1420 @@ +/** + * This script contains WAPI functions that need to be run in the context of the webpage + */ + +/** + * 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) { + (function () { + function getStore(modules) { + let foundCount = 0; + let neededObjects = [ + { 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.processAttachments) ? module.default : null }, + { id: "MediaProcess", conditions: (module) => (module.BLOB) ? 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: "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) }, + { id: "CryptoLib", conditions: (module) => (module.decryptE2EMedia) ? module : null }, + { 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 } + ]; + for (let idx in modules) { + if ((typeof modules[idx] === "object") && (modules[idx] !== null)) { + neededObjects.forEach((needObj) => { + if (!needObj.conditions || needObj.foundedModule) + return; + let neededModule = needObj.conditions(modules[idx]); + if (neededModule !== null) { + foundCount++; + needObj.foundedModule = neededModule; + } + }); + + if (foundCount == neededObjects.length) { + break; + } + } + } + + let neededStore = neededObjects.find((needObj) => needObj.id === "Store"); + window.Store = neededStore.foundedModule ? neededStore.foundedModule : {}; + neededObjects.splice(neededObjects.indexOf(neededStore), 1); + neededObjects.forEach((needObj) => { + if (needObj.foundedModule) { + window.Store[needObj.id] = needObj.foundedModule; + } + }); + + window.Store.Chat.modelClass.prototype.sendMessage = function (e) { + window.Store.SendTextMsgToChat(this, ...arguments); + } + + return window.Store; + } + + if (typeof webpackJsonp === 'function') { + webpackJsonp([], {'parasite': (x, y, z) => getStore(z)}, ['parasite']); + } else { + let tag = new Date().getTime(); + webpackChunkbuild.push([ + ["parasite" + tag], + { + + }, + function (o, e, t) { + let modules = []; + for (let idx in o.m) { + let module = o(idx); + modules.push(module); + } + getStore(modules); + } + ]); + } + + })(); +} + +window.WAPI = { + lastRead: {} +}; + +window.WAPI._serializeRawObj = (obj) => { + if (obj) { + return obj.toJSON(); + } + return {} +}; + +/** + * Serializes a chat object + * + * @param rawChat Chat object + * @returns {{}} + */ + +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 + }); +}; + +window.WAPI._serializeContactObj = (obj) => { + if (obj == undefined) { + return null; + } + + return Object.assign(window.WAPI._serializeRawObj(obj), { + 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 + }); +}; + +window.WAPI._serializeMessageObj = (obj) => { + if (obj == undefined) { + return null; + } + + 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, + 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']) + }); +}; + +window.WAPI._serializeNumberStatusObj = (obj) => { + if (obj == undefined) { + return null; + } + + return Object.assign({}, { + id : obj.jid, + status : obj.status, + isBusiness : (obj.biz === true), + canReceiveMessage: (obj.status === 200) + }); +}; + +window.WAPI._serializeProfilePicThumb = (obj) => { + if (obj == undefined) { + return null; + } + + return Object.assign({}, { + eurl : obj.eurl, + id : obj.id, + img : obj.img, + imgFull: obj.imgFull, + raw : obj.raw, + tag : obj.tag + }); +} + +window.WAPI.createGroup = function (name, contactsId) { + if (!Array.isArray(contactsId)) { + contactsId = [contactsId]; + } + + return window.Store.Wap.createGroup(name, contactsId); +}; + +window.WAPI.leaveGroup = function (groupId) { + groupId = typeof groupId == "string" ? groupId : groupId._serialized; + var group = WAPI.getChat(groupId); + return group.sendExit() +}; + + +window.WAPI.getAllContacts = function (done) { + const contacts = window.Store.Contact.map((contact) => WAPI._serializeContactObj(contact)); + + if (done !== undefined) done(contacts); + return contacts; +}; + +/** + * 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; +}; + +/** + * 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) { + const found = window.Store.Contact.get(id); + + if (done !== undefined) done(window.WAPI._serializeContactObj(found)) + return window.WAPI._serializeContactObj(found); +}; + +/** + * 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.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; +}; + +/** + * 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; +}; + +/** + * 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); + + if (done !== undefined) done(groups); + return groups; +}; + +/** + * 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) { + 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); + return found; +} + +window.WAPI.getChatByName = function (name, done) { + const found = window.WAPI.getAllChats().find(val => val.name.includes(name)) + if (done !== undefined) done(found); + return found; +}; + +window.WAPI.sendImageFromDatabasePicBot = function (picId, chatId, caption) { + var chatDatabase = window.WAPI.getChatByName('DATABASEPICBOT'); + var msgWithImg = chatDatabase.msgs.find((msg) => msg.caption == picId); + + if (msgWithImg === undefined) { + return false; + } + var chatSend = WAPI.getChat(chatId); + if (chatSend === undefined) { + return false; + } + const oldCaption = msgWithImg.caption; + + msgWithImg.id.id = window.WAPI.getNewId(); + msgWithImg.id.remote = chatId; + msgWithImg.t = Math.ceil(new Date().getTime() / 1000); + msgWithImg.to = chatId; + + if (caption !== undefined && caption !== '') { + msgWithImg.caption = caption; + } else { + msgWithImg.caption = ''; + } + + msgWithImg.collection.send(msgWithImg).then(function (e) { + msgWithImg.caption = oldCaption; + }); + + return true; +}; + +window.WAPI.sendMessageWithThumb = function (thumb, url, title, description, text, chatId, done) { + 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, + compose: true + }; + chatSend.sendMessage(text, { linkPreview: linkPreview, + mentionedJidList: [], + quotedMsg: null, + quotedMsgAdminGroupJid: null }); + if (done !== undefined) done(true); + return true; +}; + +window.WAPI.getNewId = function () { + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for (var i = 0; i < 20; i++) + text += possible.charAt(Math.floor(Math.random() * possible.length)); + return text; +}; + +window.WAPI.getChatById = function (id, done) { + let found = WAPI.getChat(id); + if (found) { + found = WAPI._serializeChatObj(found); + } else { + found = false; + } + + if (done !== undefined) done(found); + return found; +}; + + +/** + * I return all unread messages from an asked chat and mark them as read. + * + * :param id: chat id + * :type id: string + * + * :param includeMe: indicates if user messages have to be included + * :type includeMe: boolean + * + * :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) { + // get chat and its messages + let chat = WAPI.getChat(id); + let messages = chat.msgs._models; + + // initialize result list + let output = []; + + // look for unread messages, newest is at the end of array + for (let i = messages.length - 1; i >= 0; i--) { + // system message: skip it + if (i === "remove") { + continue; + } + + // get message + let messageObj = messages[i]; + + // found a read message: stop looking for others + if (typeof (messageObj.isNewMsg) !== "boolean" || messageObj.isNewMsg === false) { + continue; + } else { + messageObj.isNewMsg = false; + // process it + let message = WAPI.processMessageObj(messageObj, + 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 + * + * @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(); + } +}; + +/** + * 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) { + const found = WAPI.getChat(id); + x = function () { + if (!found.msgs.msgLoadState.noEarlierMsgs) { + found.loadEarlierMsgs().then(x); + } else if (done) { + done(); + } + }; + x(); +}; + +window.WAPI.asyncLoadAllEarlierMessages = function (id, done) { + done(); + window.WAPI.loadAllEarlierMessages(id); +}; + +window.WAPI.areAllMessagesLoaded = function (id, done) { + const found = WAPI.getChat(id); + if (!found.msgs.msgLoadState.noEarlierMsgs) { + if (done) done(false); + return false + } + if (done) done(true); + return true +}; + +/** + * Load more messages in chat object from store by ID till a particular date + * + * @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) { + const found = WAPI.getChat(id); + x = function () { + if (found.msgs.models[0].t > lastMessage && !found.msgs.msgLoadState.noEarlierMsgs) { + found.loadEarlierMsgs().then(x); + } else { + done(); + } + }; + 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; +}; + +/** + * 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 window.Store.GroupMetadata.update(id); + } + } + + if (done !== undefined) done(output); + return output; + +}; + + +/** + * Fetches group participants + * + * @param id ID of group + * @returns {Promise.<*>} Yields group metadata + * @private + */ +window.WAPI._getGroupParticipants = async function (id) { + const metadata = await WAPI.getGroupMetadata(id); + return metadata.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.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; +}; + +/** + * Gets object representing the logged in user + * + * @returns {Array|*|$q.all} + */ +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.isLoggedIn = function (done) { + // 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) { + // 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; +}; + +window.WAPI.processMessageObj = function (messageObj, includeMe, includeNotifications) { + if (messageObj.isNotification) { + if (includeNotifications) + return WAPI._serializeMessageObj(messageObj); + else + return; + // System message + // (i.e. "Messages you send to this chat and calls are now secured with end-to-end encryption...") + } else if (messageObj.id.fromMe === false || includeMe) { + return WAPI._serializeMessageObj(messageObj); + } + return; +}; + +window.WAPI.getAllMessagesInChat = function (id, includeMe, includeNotifications, done) { + 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); + } + if (done !== undefined) done(output); + return output; +}; + +window.WAPI.getAllMessageIdsInChat = function (id, includeMe, includeNotifications, done) { + 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)) { + continue; + } + output.push(messages[i].id._serialized); + } + if (done !== undefined) done(output); + return output; +}; + +window.WAPI.getMessageById = function (id, done) { + let result = false; + try { + let msg = window.Store.Msg.get(id); + if (msg) { + 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.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; + + 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.sendMessage2 = function (id, message, done) { + 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) { + var chat = window.WAPI.getChat(id); + if (chat !== undefined) { + if (done !== undefined) { + if (chat.getLastMsgKeyForAction === undefined) + chat.getLastMsgKeyForAction = function () { }; + Store.SendSeen(chat, false).then(function () { + done(true); + }); + return true; + } else { + Store.SendSeen(chat, false); + return true; + } + } + if (done !== undefined) done(); + return false; +}; + +function isChatMessage(message) { + if (message.isSentByMe) { + return false; + } + if (message.isNotification) { + return false; + } + if (!message.isUserCreatedType) { + return false; + } + return true; +} + + +window.WAPI.getUnreadMessages = function (includeMe, includeNotifications, use_unread_count, done) { + const chats = window.Store.Chat.models; + let output = []; + + for (let chat in chats) { + if (isNaN(chat)) { + continue; + } + + let messageGroupObj = chats[chat]; + let messageGroup = WAPI._serializeChatObj(messageGroupObj); + + messageGroup.messages = []; + + const messages = messageGroupObj.msgs._models; + for (let i = messages.length - 1; i >= 0; i--) { + let messageObj = messages[i]; + if (typeof (messageObj.isNewMsg) != "boolean" || messageObj.isNewMsg === false) { + continue; + } else { + messageObj.isNewMsg = false; + let message = WAPI.processMessageObj(messageObj, includeMe, includeNotifications); + if (message) { + messageGroup.messages.push(message); + } + } + } + + if (messageGroup.messages.length > 0) { + output.push(messageGroup); + } else { // no messages with isNewMsg true + if (use_unread_count) { + let n = messageGroupObj.unreadCount; // will use unreadCount attribute to fetch last n messages from sender + for (let i = messages.length - 1; i >= 0; i--) { + let messageObj = messages[i]; + if (n > 0) { + if (!messageObj.isSentByMe) { + let message = WAPI.processMessageObj(messageObj, includeMe, includeNotifications); + messageGroup.messages.unshift(message); + n -= 1; + } + } else if (n === -1) { // chat was marked as unread so will fetch last message as unread + if (!messageObj.isSentByMe) { + let message = WAPI.processMessageObj(messageObj, includeMe, includeNotifications); + messageGroup.messages.unshift(message); + break; + } + } else { // unreadCount = 0 + break; + } + } + if (messageGroup.messages.length > 0) { + messageGroupObj.unreadCount = 0; // reset unread counter + output.push(messageGroup); + } + } + } + } + if (done !== undefined) { + done(output); + } + return output; +}; + +window.WAPI.getGroupOwnerID = async function (id, done) { + const output = (await WAPI.getGroupMetadata(id)).owner.id; + if (done !== undefined) { + done(output); + } + return output; + +}; + +window.WAPI.getCommonGroups = async function (id, done) { + let output = []; + + groups = window.WAPI.getAllGroups(); + + for (let idx in groups) { + try { + participants = await window.WAPI.getGroupParticipantIDs(groups[idx].id); + if (participants.filter((participant) => participant == id).length) { + output.push(groups[idx]); + } + } catch (err) { + console.log("Error in group:"); + console.log(groups[idx]); + console.log(err); + } + } + + if (done !== undefined) { + done(output); + } + return output; +}; + + +window.WAPI.getProfilePicSmallFromId = function (id, done) { + window.Store.ProfilePicThumb.find(id).then(function (d) { + if (d.img !== undefined) { + window.WAPI.downloadFileWithCredentials(d.img, done); + } else { + done(false); + } + }, function (e) { + done(false); + }) +}; + +window.WAPI.getProfilePicFromId = function (id, done) { + window.Store.ProfilePicThumb.find(id).then(function (d) { + if (d.imgFull !== undefined) { + window.WAPI.downloadFileWithCredentials(d.imgFull, done); + } else { + done(false); + } + }, function (e) { + done(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.downloadFile = 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.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.deleteConversation = function (chatId, done) { + 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); + } + }).catch(() => { + if (done !== undefined) { + done(false); + } + }); + + return true; +}; + +window.WAPI.deleteMessage = function (chatId, messageArray, revoke=false, done) { + let userId = new window.Store.UserConstructor(chatId, {intentionallyUsePrivateConstructor: true}); + let conversation = WAPI.getChat(userId); + + if(!conversation) { + if(done !== undefined) { + done(false); + } + return false; + } + + if (!Array.isArray(messageArray)) { + messageArray = [messageArray]; + } + let messagesToDelete = messageArray.map(msgId => window.Store.Msg.get(msgId)); + + if (revoke) { + conversation.sendRevokeMsgs(messagesToDelete, conversation); + } else { + conversation.sendDeleteMsgs(messagesToDelete, conversation); + } + + + if (done !== undefined) { + done(true); + } + + 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 + })); + } + }); + + return true; +}; + +/** + * 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.Store.Msg.off('add'); +sessionStorage.removeItem('saved_msgs'); + +window.WAPI._newMessagesListener = window.Store.Msg.on('add', (newMessage) => { + if (newMessage && newMessage.isNewMsg && !newMessage.isSentByMe) { + let message = window.WAPI.processMessageObj(newMessage, false, false); + if (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; + + window.WAPI._newMessagesDebouncer = null; + window.WAPI._newMessagesQueue = []; + + let removeCallbacks = []; + + window.WAPI._newMessagesCallbacks.forEach(function (callbackObj) { + if (callbackObj.callback !== undefined) { + callbackObj.callback(queuedMessages); + } + if (callbackObj.rmAfterUse === true) { + removeCallbacks.push(callbackObj); + } + }); + + // Remove removable callbacks. + removeCallbacks.forEach(function (rmCallbackObj) { + 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) => { + Object.keys(message).forEach(key => message[key] === undefined ? delete message[key] : ''); + }); + sessionStorage.setItem("saved_msgs", JSON.stringify(window.WAPI._newMessagesBuffer)); + + // Inform callbacks that the page will be reloaded. + window.WAPI._newMessagesCallbacks.forEach(function (callbackObj) { + if (callbackObj.callback !== undefined) { + callbackObj.callback({ status: -1, message: 'page will be reloaded, wait and register callback again.' }); + } + }); +}; + +window.addEventListener("unload", window.WAPI._unloadInform, false); +window.addEventListener("beforeunload", window.WAPI._unloadInform, false); +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. + * @returns {boolean} + */ +window.WAPI.waitNewMessages = function (rmCallbackAfterUse = true, done) { + window.WAPI._newMessagesCallbacks.push({ callback: done, rmAfterUse: rmCallbackAfterUse }); + 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); + } + 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); + }); +}); +} + +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 u8arr = new Uint8Array(n); + + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + + return new File([u8arr], filename, {type: mime}); +}; + +/** + * Send contact card to a specific chat using the chat ids + * + * @param {string} to '000000000000@c.us' + * @param {string|array} contact '111111111111@c.us' | ['222222222222@c.us', '333333333333@c.us, ... 'nnnnnnnnnnnn@c.us'] + */ +window.WAPI.sendContact = function (to, contact) { + if (!Array.isArray(contact)) { + contact = [contact]; + } + contact = contact.map((c) => { + return WAPI.getChat(c).__x_contact; + }); + + if (contact.length > 1) { + window.WAPI.getChat(to).sendContactList(contact); + } else if (contact.length === 1) { + window.WAPI.getChat(to).sendContact(contact[0]); + } +}; + +/** + * 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(); + + newMsgId.fromMe = true; + newMsgId.id = WAPI.getNewId().toUpperCase(); + newMsgId.remote = chatId; + newMsgId._serialized = `${newMsgId.fromMe}_${newMsgId.remote}_${newMsgId.id}` + + return newMsgId; +}; + +/** + * Send Customized VCard without the necessity of contact be a Whatsapp 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); + + var extend = { + ack : 0, + id : newId, + local : !0, + self : "out", + t : parseInt(new Date().getTime() / 1000), + to : chatId, + isNewMsg: !0, + }; + + if (Array.isArray(vcard)) { + Object.assign(extend, { + type : "multi_vcard", + vcardList: vcard + }); + + delete extend.body; + } else { + Object.assign(extend, { + type : "vcard", + subtype: vcard.displayName, + body : vcard.vcard + }); + + delete extend.vcardList; + } + + Object.assign(tempMsg, extend); + + chat.addAndSendMsg(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) { + const contact = window.Store.Contact.get(id); + if (contact !== undefined) { + contact.setBlock(!0); + done(true); + return true; + } + done(false); + return false; +} +/** + * 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) { + const contact = window.Store.Contact.get(id); + if (contact !== undefined) { + contact.setBlock(!1); + done(true); + return true; + } + done(false); + return false; +} + +/** + * 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; + } + }) +} + +/** + * 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; + }) +} + +/** + * 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; + }) +}