sahil5426

wapi

Sep 4th, 2021 (edited)
200
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JSON 80.44 KB | None | 0 0
  1.                     /**
  2.  * This script contains WAPI functions that need to be run in the context of the webpage
  3.  */
  4.  
  5. /**
  6.  * Auto discovery the webpack object references of instances that contains all functions used by the WAPI
  7.  * functions and creates the Store object.
  8.  */
  9.  
  10. if (!window.Store||!window.Store.Msg) {
  11.     (function () {
  12.         function getStore(modules) {
  13.             let foundCount = 0;
  14.             let neededObjects = [
  15.                 { id: "Store", conditions: (module) => (module.default && module.default.Chat && module.default.Msg) ? module.default : null},
  16.                 { id: "MediaCollection", conditions: (module) => (module.default && module.default.prototype && (module.default.prototype.processFiles !== undefined||module.default.prototype.processAttachments !== undefined)) ? module.default : null },
  17.                 { id: "MediaProcess", conditions: (module) => (module.BLOB) ? module : null },
  18.                 { id: "Archive", conditions: (module) => (module.setArchive) ? module : null },
  19.                 { id: "Block", conditions: (module) => (module.blockContact && module.unblockContact) ? module : null },
  20.                 { id: "ChatUtil", conditions: (module) => (module.sendClear) ? module : null },
  21.                 { id: "GroupInvite", conditions: (module) => (module.queryGroupInviteCode) ? module : null },
  22.                 { id: "Wap", conditions: (module) => (module.createGroup) ? module : null },
  23.                 { id: "ServiceWorker", conditions: (module) => (module.default && module.default.killServiceWorker) ? module : null },
  24.                 { id: "State", conditions: (module) => (module.STATE && module.STREAM) ? module : null },
  25.                 { id: "_Presence", conditions: (module) => (module.setPresenceAvailable && module.setPresenceUnavailable) ? module : null },
  26.                 { id: "WapDelete", conditions: (module) => (module.sendConversationDelete && module.sendConversationDelete.length == 2) ? module : null },
  27.                 { id: "Conn", conditions: (module) => (module.default && module.default.ref && module.default.refTTL) ? module.default : null },
  28.                 { id: "WapQuery",
  29. conditions: (module) => (module.default && module.default.queryExist) ? module.default : null },
  30.                 { id: "CryptoLib", conditions: (module) => (module.decryptE2EMedia) ? module : null },
  31.                 { id: "OpenChat", conditions: (module) => (module.default && module.default.prototype && module.default.prototype.openChat) ? module.default : null },
  32.                 { id: "UserConstructor", conditions: (module) => (module.default && module.default.prototype && module.default.prototype.isServer && module.default.prototype.isUser) ? module.default : null },
  33.                 { id: "SendTextMsgToChat", conditions: (module) => (module.sendTextMsgToChat) ? module.sendTextMsgToChat : null },
  34.                 { id: "ReadSeen", conditions: (module) => (module.sendSeen) ? module : null },
  35.                 { id: "sendDelete", conditions: (module) => (module.sendDelete) ? module.sendDelete : null },
  36.                 { id: "addAndSendMsgToChat", conditions: (module) => (module.addAndSendMsgToChat) ? module.addAndSendMsgToChat : null },
  37.                 { id: "sendMsgToChat", conditions: (module) => (module.sendMsgToChat) ? module.sendMsgToChat : null },
  38.                 { id: "Catalog", conditions: (module) => (module.Catalog) ? module.Catalog : null },
  39.                 { id: "bp", conditions: (module) => (module.default&&module.default.toString&&module.default.toString().includes('bp_unknown_version')) ? module.default : null },
  40.                 { id: "MsgKey", conditions: (module) => (module.default&&module.default.toString&&module.default.toString().includes('MsgKey error: obj is null/undefined')) ? module.default : null },
  41.                 { id: "Parser", conditions: (module) => (module.convertToTextWithoutSpecialEmojis) ? module.default : null },
  42.                 { id: "Builders", conditions: (module) => (module.TemplateMessage && module.HydratedFourRowTemplate) ? module : null },
  43.                 { id: "Me", conditions: (module) => (module.PLATFORMS && module.Conn) ? module.default : null },
  44.                 { id: "CallUtils", conditions: (module) => (module.sendCallEnd && module.parseCall) ? module : null },
  45.                 { id: "Identity", conditions: (module) => (module.queryIdentity && module.updateIdentity) ? module : null },
  46.                 { id: "MyStatus", conditions: (module) => (module.getStatus && module.setMyStatus) ? module : null },
  47.                 { id: "ChatStates", conditions: (module) => (module.sendChatStatePaused && module.sendChatStateRecording && module.sendChatStateComposing) ? module : null },
  48.                 { id: "GroupActions", conditions: (module) => (module.sendExitGroup && module.localExitGroup) ? module : null },
  49.                 { id: "Features", conditions: (module) => (module.FEATURE_CHANGE_EVENT && module.features) ? module : null },
  50.                 { id: "MessageUtils", conditions: (module) => (module.storeMessages && module.appendMessage) ? module : null },
  51.                 { id: "WebMessageInfo", conditions: (module) => (module.WebMessageInfo && module.WebFeatures) ? module.WebMessageInfo : null },
  52.                 { id: "createMessageKey", conditions: (module) => (module.createMessageKey && module.createDeviceSentMessage) ? module.createMessageKey : null },
  53.                 { id: "Participants", conditions: (module) => (module.addParticipants && module.removeParticipants && module.promoteParticipants && module.demoteParticipants) ? module : null },
  54.                 { id: "WidFactory", conditions: (module) => (module.isWidlike && module.createWid && module.createWidFromWidLike) ? module : null },
  55.                 { id: "Base", conditions: (module) => (module.setSubProtocol && module.binSend && module.actionNode) ? module : null },
  56.                 { id: "Versions", conditions: (module) => (module.loadProtoVersions && module.default["15"] && module.default["16"] && module.default["17"]) ? module : null },
  57.                 { id: "Sticker", conditions: (module) => (module.default && module.default.Sticker) ? module.default.Sticker : null },
  58.                 { id: "MediaUpload", conditions: (module) => (module.default && module.default.mediaUpload) ? module.default : null },
  59.                 { id: "UploadUtils", conditions: (module) => (module.default && module.default.encryptAndUpload) ? module.default : null },
  60.                 {
  61.                     id: "Cmd",
  62.                     conditions: (e) =>
  63.                       e.default && e.default.openChatFromUnread ? e : null,
  64.                   }
  65.             ];
  66.             for (let idx in modules) {
  67.                 if ((typeof modules[idx] === "object") && (modules[idx] !== null)) {
  68.                     neededObjects.forEach((needObj) => {
  69.                         if (!needObj.conditions || needObj.foundedModule)
  70.                             return;
  71.                         let neededModule = needObj.conditions(modules[idx]);
  72.                         if (neededModule !== null) {
  73.                             foundCount++;
  74.                             needObj.foundedModule = neededModule;
  75.                         }
  76.             });
  77.  
  78.                     if (foundCount == neededObjects.length) {
  79.                         break;
  80.                     }
  81.                 }
  82.             }
  83.         let neededStore = neededObjects.find((needObj) => needObj.id === "Store");
  84.             window.Store = neededStore.foundedModule ? neededStore.foundedModule : {};
  85.             neededObjects.splice(neededObjects.indexOf(neededStore), 1);
  86.             neededObjects.forEach((needObj) => {
  87.                 if (needObj.foundedModule) {
  88.                     window.Store[needObj.id] = needObj.foundedModule;
  89.                 }
  90.             });
  91.         window.Store.Chat.modelClass.prototype.sendMessage = function (e) {
  92.         window.Store.SendTextMsgToChat(this, ...arguments);
  93.         }
  94.             return window.Store;
  95.         }
  96.         const parasite = `parasite$\{Date.now()}`
  97.         // webpackJsonp([], { [parasite]: (x, y, z) => getStore(z) }, [parasite]);
  98.         if (typeof webpackJsonp === 'function') webpackJsonp([], {[parasite]: (x, y, z) => getStore(z)}, [parasite]);
  99.         else webpackChunkwhatsapp_web_client.push([[parasite], {}, function (o, e, t) {let modules = []; for (let idx in o.m) {modules.push(o(idx));}   getStore(modules);}]);        
  100.     })();
  101. }
  102.  
  103. window.WAPI = {};
  104. window._WAPI = {};
  105.  
  106. window.WAPI._serializeRawObj = (obj) => {
  107.     if (obj && obj.toJSON) {
  108.         return obj.toJSON();
  109.     }
  110.     return {}
  111. };
  112.  
  113. /**
  114.  * Serializes a chat object
  115.  *
  116.  * @param rawChat Chat object
  117.  * @returns {{}}
  118.  */
  119.  
  120. window.WAPI._serializeChatObj = (obj) => {
  121.     if (obj == undefined) {
  122.         return null;
  123.     }
  124.     return Object.assign(window.WAPI._serializeRawObj(obj), {
  125.         id: obj.id._serialized,
  126.         kind: obj.kind,
  127.         isGroup: obj.isGroup,
  128.         formattedTitle: obj.formattedTitle,
  129.         contact: obj['contact'] ? window.WAPI._serializeContactObj(obj['contact']) : null,
  130.         groupMetadata: obj["groupMetadata"] ? window.WAPI._serializeRawObj(obj["groupMetadata"]) : null,
  131.         presence: obj["presence"] ? window.WAPI._serializeRawObj(obj["presence"]) : null,
  132.         msgs: null
  133.     });
  134. };
  135.  
  136. window.WAPI._serializeContactObj = (obj) => {
  137.     if (obj == undefined) {
  138.         return null;
  139.     }
  140.     return Object.assign(window.WAPI._serializeRawObj(obj), {
  141.         id: obj.id._serialized,
  142.         formattedName: obj.formattedName,
  143.         isHighLevelVerified: obj.isHighLevelVerified,
  144.         isMe: obj.isMe,
  145.         isMyContact: obj.isMyContact,
  146.         isPSA: obj.isPSA,
  147.         isUser: obj.isUser,
  148.         isVerified: obj.isVerified,
  149.         isWAContact: obj.isWAContact,
  150.         profilePicThumbObj: obj.profilePicThumb ? WAPI._serializeProfilePicThumb(obj.profilePicThumb) : {},
  151.         statusMute: obj.statusMute,
  152.         msgs: null
  153.     });
  154. };
  155.  
  156.  
  157. window.WAPI._serializeMessageObj = (obj) => {
  158.     if (obj == undefined) {
  159.         return null;
  160.     }
  161.     const _chat = obj['chat'] ? WAPI._serializeChatObj(obj['chat']) : {};
  162.     if(obj.quotedMsg) obj.quotedMsgObj();
  163.     return Object.assign(window.WAPI._serializeRawObj(obj), {
  164.         id: obj.id._serialized,
  165.         from: obj.from._serialized,
  166.         quotedParticipant: obj.quotedParticipant? obj.quotedParticipant._serialized ? obj.quotedParticipant._serialized : undefined : undefined,
  167.         author: obj.author? obj.author._serialized ? obj.author._serialized : undefined : undefined,
  168.         chatId: obj.chatId? obj.chatId._serialized ? obj.chatId._serialized : undefined : undefined,
  169.         to: obj.to? obj.to._serialized ? obj.to._serialized : undefined : undefined,
  170.         fromMe: obj.id.fromMe,
  171.         sender: obj["senderObj"] ? WAPI._serializeContactObj(obj["senderObj"]) : null,
  172.         timestamp: obj["t"],
  173.         content: obj["body"],
  174.         isGroupMsg: obj.isGroupMsg,
  175.         isLink: obj.isLink,
  176.         isMMS: obj.isMMS,
  177.         isMedia: obj.isMedia,
  178.         isNotification: obj.isNotification,
  179.         isPSA: obj.isPSA,
  180.         type: obj.type,
  181.         chat: _chat,
  182.         isOnline: _chat.isOnline,
  183.         lastSeen: _chat.lastSeen,
  184.         chatId: obj.id.remote,
  185.         quotedMsgObj: WAPI._serializeMessageObj(obj['_quotedMsgObj']),
  186.         mediaData: window.WAPI._serializeRawObj(obj['mediaData']),
  187.         reply: body => window.WAPI.reply(_chat.id._serialized, body, obj)
  188.     });
  189. };
  190.  
  191. window.WAPI._serializeNumberStatusObj = (obj) => {
  192.     if (obj == undefined) {
  193.         return null;
  194.     }
  195.  
  196.     return Object.assign({}, {
  197.         id: obj.jid,
  198.         status: obj.status,
  199.         isBusiness: (obj.biz === true),
  200.         canReceiveMessage: (obj.status === 200)
  201.     });
  202. };
  203.  
  204. window.WAPI._serializeProfilePicThumb = (obj) => {
  205.     if (obj == undefined) {
  206.         return null;
  207.     }
  208.  
  209.     return Object.assign({}, {
  210.         eurl: obj.eurl,
  211.         id: obj.id,
  212.         img: obj.img,
  213.         imgFull: obj.imgFull,
  214.         raw: obj.raw,
  215.         tag: obj.tag
  216.     });
  217. }
  218.  
  219. window.WAPI.createGroup = async function (name, contactsId) {
  220.     if (!Array.isArray(contactsId)) {
  221.         contactsId = [contactsId];
  222.     }
  223.     return await window.Store.WapQuery.createGroup(name, contactsId);
  224. };
  225.  
  226. window.WAPI.openChat = function (chatId) {
  227.     const chat = Store.Chat.get(chatId);
  228.     const result = Store.Cmd.default.openChatBottom(chat);
  229.     return result;
  230. }
  231.  
  232. /**
  233.  * Sends the command for your device to leave a group.
  234.  * @param groupId stirng, the is for the group.
  235.  * returns Promise<void>
  236.  */
  237. window.WAPI.leaveGroup = function (groupId) {
  238.     groupId = typeof groupId == "string" ? groupId : groupId._serialized;
  239.     var group = WAPI.getChat(groupId);
  240.     return Store.GroupActions.sendExitGroup(group)
  241. };
  242.  
  243.  
  244. window.WAPI.getAllContacts = function () {
  245.     return window.Store.Contact.map((contact) => WAPI._serializeContactObj(contact));
  246. };
  247.  
  248. /**
  249.  * Fetches all contact objects from store, filters them
  250.  *
  251.  * @returns {Array|*} List of contacts
  252.  */
  253. window.WAPI.getMyContacts = function () {
  254.     return window.Store.Contact.filter((contact) => contact.isMyContact === true).map((contact) => WAPI._serializeContactObj(contact));
  255. };
  256.  
  257. /**
  258.  * Fetches contact object from store by ID
  259.  *
  260.  * @param id ID of contact
  261.  * @returns {T|*} Contact object
  262.  */
  263. window.WAPI.getContact = function (id) {
  264.     const found = window.Store.Contact.get(id);
  265.     return window.WAPI._serializeContactObj(found);
  266. };
  267.  
  268. window.WAPI.syncContacts = function() {
  269.     Store.Contact.sync()
  270.     return true;
  271. }
  272.  
  273. /**
  274.  * Fetches all chat objects from store
  275.  *
  276.  * @returns {Array|*} List of chats
  277.  */
  278. window.WAPI.getAllChats = function () {
  279.     return window.Store.Chat.map((chat) => WAPI._serializeChatObj(chat));
  280. };
  281.  
  282. window.WAPI.haveNewMsg = function (chat) {
  283.     return chat.unreadCount > 0;
  284. };
  285.  
  286. window.WAPI.getAllChatsWithNewMsg = function () {
  287.     return window.Store.Chat.filter(window.WAPI.haveNewMsg).map((chat) => WAPI._serializeChatObj(chat));
  288. };
  289.  
  290. /**
  291.  * Fetches all chat IDs from store
  292.  *
  293.  * @returns {Array|*} List of chat id's
  294.  */
  295. window.WAPI.getAllChatIds = function () {
  296.     return window.Store.Chat.map((chat) => chat.id._serialized || chat.id);
  297. };
  298.  
  299. window.WAPI.getAllNewMessages = async function () {
  300.     return WAPI.getAllChatsWithNewMsg().map(c => WAPI.getChat(c.id)).flatMap(c => c.msgs._models.filter(x => x.isNewMsg)).map(WAPI._serializeMessageObj) || [];
  301. }
  302.  
  303. // nnoo longer determined by x.ack==-1
  304. window.WAPI.getAllUnreadMessages = async function () {
  305.     return Store.Chat.models.filter(chat=>chat.unreadCount&&chat.unreadCount>0).map(unreadChat=>unreadChat.msgs.models.slice(-1*unreadChat.unreadCount)).flat().map(WAPI._serializeMessageObj)
  306. }
  307.  
  308. window.WAPI.getIndicatedNewMessages = async function () {
  309.     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)}}))
  310. }
  311.  
  312. window.WAPI.getSingleProperty = function (namespace,id,property){
  313.     if(Store[namespace] && Store[namespace].get(id) && Object.keys(Store[namespace].get(id)).find(x=>x.includes(property))) return Store[namespace].get(id)[property];
  314.     return 404
  315. }
  316.  
  317. window.WAPI.getAllChatsWithMessages = async function (onlyNew) {
  318.     let x = [];
  319.     if (onlyNew) { x.push(WAPI.getAllChatsWithNewMsg().map(c => WAPI.getChat(c.id._serialized))); }
  320.     else {
  321.         x.push(WAPI.getAllChatIds().map((c) => WAPI.getChat(c)));
  322.     }
  323.     const result = (await Promise.all(x)).flatMap(x => x);
  324.     return JSON.stringify(result);
  325. }
  326.  
  327. /**
  328.  * Fetches all groups objects from store
  329.  *
  330.  * @returns {Array|*} List of chats
  331.  */
  332. window.WAPI.getAllGroups = function () {
  333.     return window.WAPI.getAllChats().filter((chat) => chat.isGroup);
  334. };
  335.  
  336. /**
  337.  * Sets the chat state
  338.  *
  339.  * @param {0|1|2} chatState The state you want to set for the chat. Can be TYPING (1), RECRDING (2) or PAUSED (3);
  340.  * returns {boolean}
  341.  */
  342. window.WAPI.sendChatstate = async function (state, chatId) {
  343.     switch(state) {
  344.         case 0:
  345.             await window.Store.ChatStates.sendChatStateComposing(chatId);
  346.             break;
  347.         case 1:
  348.             await window.Store.ChatStates.sendChatStateRecording(chatId);
  349.             break;
  350.         case 2:
  351.             await window.Store.ChatStates.sendChatStatePaused(chatId);
  352.             break;
  353.         default:
  354.             return false
  355.     }
  356.     return true;
  357. };
  358.  
  359. /**
  360.  * Fetches chat object from store by ID
  361.  *
  362.  * @param id ID of chat
  363.  * @returns {T|*} Chat object
  364.  */
  365. window.WAPI.getChat = function (id) {
  366.     if (!id) return false;
  367.     id = typeof id == "string" ? id : id._serialized;
  368.     const found = window.Store.Chat.get(id);
  369.     if (found) found.sendMessage = (found.sendMessage) ? found.sendMessage : function () { return window.Store.sendMessage.apply(this, arguments); };
  370.     return found;
  371. }
  372.  
  373. /**
  374.  * Get your status
  375.  * @param {string} to '000000000000@c.us'
  376.  * returns: {string,string} and string -"Hi, I am using WA"
  377.  */
  378. window.WAPI.getStatus = async (id) => {
  379. return await Store.MyStatus.getStatus(id)
  380. }
  381.  
  382. window.WAPI.getChatByName = function (name) {
  383.     return window.WAPI.getAllChats().find((chat) => chat.name === name);
  384. };
  385.  
  386. window.WAPI.sendImageFromDatabasePicBot = function (picId, chatId, caption) {
  387.     var chatDatabase = window.WAPI.getChatByName('DATABASEPICBOT');
  388.     var msgWithImg = chatDatabase.msgs.find((msg) => msg.caption == picId);
  389.  
  390.     if (msgWithImg === undefined) {
  391.         return false;
  392.     }
  393.     var chatSend = WAPI.getChat(chatId);
  394.     if (chatSend === undefined) {
  395.         return false;
  396.     }
  397.     const oldCaption = msgWithImg.caption;
  398.  
  399.     msgWithImg.id.id = window.WAPI.getNewId();
  400.     msgWithImg.id.remote = chatId;
  401.     msgWithImg.t = Math.ceil(new Date().getTime() / 1000);
  402.     msgWithImg.to = chatId;
  403.  
  404.     if (caption !== undefined && caption !== '') {
  405.         msgWithImg.caption = caption;
  406.     } else {
  407.         msgWithImg.caption = '';
  408.     }
  409.  
  410.     msgWithImg.collection.send(msgWithImg).then(function (e) {
  411.         msgWithImg.caption = oldCaption;
  412.     });
  413.  
  414.     return true;
  415. };
  416.  
  417. window.WAPI.getGeneratedUserAgent = function (useragent) {
  418.     if (!useragent.includes('WhatsApp')) return 'WhatsApp/0.4.315 ' + useragent;
  419.     return useragent.replace(useragent.match(/WhatsApp\/([.\d])*/g)[0].match(/[.\d]*/g).find(x => x), window.Debug.VERSION)
  420. }
  421.  
  422. window.WAPI.getWAVersion = function () {
  423.     return window.Debug.VERSION;
  424. }
  425.  
  426. /**
  427.  * Automatically sends a link with the auto generated link preview. You can also add a custom message to be added.
  428.  * @param chatId
  429.  * @param url string A link, for example for youtube. e.g https://www.youtube.com/watch?v=61O-Galzc5M
  430.  * @param text string Custom text as body of the message, this needs to include the link or it will be appended after the link.
  431.  */
  432. window.WAPI.sendLinkWithAutoPreview = async function (chatId, url, text) {
  433.     text = text || '';
  434.     var chatSend = WAPI.getChat(chatId);
  435.     if (chatSend === undefined) {
  436.         return false;
  437.     }
  438.     const linkPreview = await Store.WapQuery.queryLinkPreview(url);
  439.     return (await chatSend.sendMessage(text.includes(url) ? text : `
  440. `, {linkPreview}))=='OK'
  441. }
  442.  
  443. window.WAPI.sendMessageWithThumb = function (thumb, url, title, description, text, chatId) {
  444.     var chatSend = WAPI.getChat(chatId);
  445.     if (chatSend === undefined) {
  446.         return false;
  447.     }
  448.     var linkPreview = {
  449.         canonicalUrl: url,
  450.         description: description,
  451.         matchedText: url,
  452.         title: title,
  453.         thumbnail: thumb // Thumbnail max size allowed: 200x200
  454.     };
  455.     chatSend.sendMessage(text.includes(url) ? text : `
  456. `, { linkPreview: linkPreview, mentionedJidList: [], quotedMsg: null, quotedMsgAdminGroupJid: null });
  457.     return true;
  458. };
  459.  
  460. window.WAPI.revokeGroupInviteLink = async function (chatId) {
  461.     var chat = Store.Chat.get(chatId);
  462.     if(!chat.isGroup) return false;
  463.     await Store.GroupInvite.revokeGroupInvite(chat);
  464.     return true;
  465. }
  466.  
  467. window.WAPI.getGroupInviteLink = async function (chatId) {
  468.     var chat = Store.Chat.get(chatId);
  469.     if(!chat.isGroup) return false;
  470.     await Store.GroupInvite.queryGroupInviteCode(chat);
  471.     return `https://chat.whatsapp.com/$\{chat.inviteCode}`
  472. }
  473.  
  474. window.WAPI.inviteInfo = async function(link){
  475.     return await Store.WapQuery.groupInviteInfo(link.split('\/').pop()).then(r=>r.status===200?WAPI.quickClean(r):r.status);
  476. }
  477.  
  478. window.WAPI.getNewId = function () {
  479.     var text = "";
  480.     var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  481.  
  482.     for (var i = 0; i < 20; i++)
  483.         text += possible.charAt(Math.floor(Math.random() * possible.length));
  484.     return text;
  485. };
  486.  
  487. window.WAPI.getChatById = function (id) {
  488.     let found = WAPI.getChat(id);
  489.     if (found) {
  490.         found = WAPI._serializeChatObj(found);
  491.     } else {
  492.         found = false;
  493.     }
  494.     return found;
  495. };
  496.  
  497.  
  498. /**
  499.  * I return all unread messages from an asked chat and mark them as read.
  500.  *
  501.  * :param id: chat id
  502.  * :type  id: string
  503.  *
  504.  * :param includeMe: indicates if user messages have to be included
  505.  * :type  includeMe: boolean
  506.  *
  507.  * :param includeNotifications: indicates if notifications have to be included
  508.  * :type  includeNotifications: boolean
  509.  *
  510.  * :returns: list of unread messages from asked chat
  511.  * :rtype: object
  512.  */
  513. window.WAPI.getUnreadMessagesInChat = function (id, includeMe, includeNotifications) {
  514.     // get chat and its messages
  515.     let chat = WAPI.getChat(id);
  516.     let messages = chat.msgs._models;
  517.  
  518.     // initialize result list
  519.     let output = [];
  520.  
  521.     // look for unread messages, newest is at the end of array
  522.     for (let i = messages.length - 1; i >= 0; i--) {
  523.         // system message: skip it
  524.         if (i === "remove") {
  525.             continue;
  526.         }
  527.  
  528.         // get message
  529.         let messageObj = messages[i];
  530.  
  531.         // found a read message: stop looking for others
  532.         if (typeof (messageObj.isNewMsg) !== "boolean" || messageObj.isNewMsg === false) {
  533.             continue;
  534.         } else {
  535.             messageObj.isNewMsg = false;
  536.             // process it
  537.             let message = WAPI.processMessageObj(messageObj,
  538.                 includeMe,
  539.                 includeNotifications);
  540.  
  541.             // save processed message on result list
  542.             if (message)
  543.                 output.push(message);
  544.         }
  545.     }
  546.     // return result list
  547.     return output;
  548. };
  549.  
  550.  
  551. /**
  552.  * Load more messages in chat object from server. Use this in a while loop
  553.  *
  554.  * @param id ID of chat
  555.  * @returns None
  556.  */
  557. window.WAPI.loadEarlierMessages = async function (id) {
  558.     const chat = WAPI.getChat(id);
  559.     if(chat){
  560.         const someEarlierMessages = await chat.loadEarlierMsgs();
  561.         if(someEarlierMessages) return someEarlierMessages.map(WAPI._serializeMessageObj);
  562.     }
  563.     return false;
  564. };
  565.  
  566. /**
  567.  * Load more messages in chat object from store by ID
  568.  *
  569.  * @param id ID of chat
  570.  * @returns None
  571.  */
  572. window.WAPI.loadAllEarlierMessages = async function (id) {
  573.     const found = WAPI.getChat(id);
  574.     while (!found.msgs.msgLoadState.noEarlierMsgs) {
  575.         console.log('loading more messages')
  576.         await found.loadEarlierMsgs();
  577.     }
  578.     return true
  579. };
  580.  
  581. window.WAPI.asyncLoadAllEarlierMessages = async function (id) {
  582.     return await window.WAPI.loadAllEarlierMessages(id);
  583. };
  584.  
  585. window.WAPI.areAllMessagesLoaded = function (id) {
  586.     const found = WAPI.getChat(id);
  587.     if (!found.msgs.msgLoadState.noEarlierMsgs) {
  588.         return false
  589.     }
  590.     return true
  591. };
  592.  
  593. /**
  594.  * Load more messages in chat object from store by ID till a particular date
  595.  *
  596.  * @param id ID of chat
  597.  * @param lastMessage UTC timestamp of last message to be loaded
  598.  * @returns None
  599.  */
  600.  
  601. window.WAPI.loadEarlierMessagesTillDate = async function (id, lastMessage) {
  602.     const found = WAPI.getChat(id);
  603.     x = async function () {
  604.         if (found.msgs.models[0].t > lastMessage && !found.msgs.msgLoadState.noEarlierMsgs) {
  605.             return await found.loadEarlierMsgs().then(x);
  606.         } else {
  607.             return true
  608.         }
  609.     };
  610.     return await x();
  611. };
  612.  
  613.  
  614. /**
  615.  * Fetches all group metadata objects from store
  616.  *
  617.  * @returns {Array|*} List of group metadata
  618.  */
  619. window.WAPI.getAllGroupMetadata = function () {
  620.     return window.Store.GroupMetadata.map((groupData) => groupData.all);
  621. };
  622.  
  623. /**
  624.  * Fetches group metadata object from store by ID
  625.  *
  626.  * @param id ID of group
  627.  * @returns {T|*} Group metadata object
  628.  */
  629. window.WAPI.getGroupMetadata = async function (id) {
  630.     return window.Store.GroupMetadata.find(id);
  631. };
  632.  
  633.  
  634. /**
  635.  * Fetches group participants
  636.  *
  637.  * @param id ID of group
  638.  * @returns {Promise.<*>} Yields group metadata
  639.  * @private
  640.  */
  641. window.WAPI._getGroupParticipants = async function (id) {
  642.     return (await WAPI.getGroupMetadata(id)).participants;
  643. };
  644.  
  645. /**
  646.  * Fetches IDs of group participants
  647.  *
  648.  * @param id ID of group
  649.  * @returns {Promise.<Array|*>} Yields list of IDs
  650.  */
  651. window.WAPI.getGroupParticipantIDs = async function (id) {
  652.     return (await WAPI._getGroupParticipants(id))
  653.         .map((participant) => participant.id._serialized);
  654. };
  655.  
  656. window.WAPI.getGroupAdmins = async function (id) {
  657.     return (await WAPI._getGroupParticipants(id))
  658.         .filter((participant) => participant.isAdmin)
  659.         .map((admin) => admin.id._serialized);
  660. };
  661.  
  662. WAPI.iAmAdmin = async function(){
  663.     return (await Promise.all(Store.GroupMetadata.models.map(({id})=>Store.GroupMetadata.find(id)))).filter(({participants})=>participants.iAmAdmin()||participants.iAmSuperAdmin()).map(({id})=>id._serialized);
  664. }
  665.  
  666. /**
  667.  * Returns an object with all of your host device details
  668.  */
  669. window.WAPI.getMe = function(){
  670.     return {...WAPI.quickClean({
  671.         ...Store.Contact.get(Store.Me.wid).attributes,
  672.         ...Store.Me.attributes
  673.     }),
  674.     me:Store.Me.me};
  675. }
  676.  
  677. window.WAPI.isLoggedIn = function () {
  678.     // Contact always exists when logged in
  679.     const isLogged = window.Store.Contact && window.Store.Contact.checksum !== undefined;
  680.     return isLogged;
  681. };
  682.  
  683. window.WAPI.isConnected = function () {
  684.     // Phone or connection Disconnected icon appears when phone or connection is disconnected
  685.     const isConnected=(document.querySelector('[data-testid="alert-phone"]') == null && document.querySelector('[data-testid="alert-computer"]') == null) ? true : false;  
  686.     return isConnected;
  687. };
  688.  
  689. //I dont think this will work for group chats.
  690. window.WAPI.isChatOnline = async function (id) {
  691.     return Store.Chat.get(id)?await Store.Chat.get(id).presence.subscribe().then(_=>Store.Chat.get(id).presence.attributes.isOnline):false;
  692. }
  693.  
  694. window.WAPI.processMessageObj = function (messageObj, includeMe, includeNotifications) {
  695.     if (messageObj.isNotification) {
  696.         if (includeNotifications)
  697.             return WAPI._serializeMessageObj(messageObj);
  698.         else
  699.             return;
  700.         // System message
  701.         // (i.e. "Messages you send to this chat and calls are now secured with end-to-end encryption...")
  702.     } else if (messageObj.id.fromMe === false || includeMe) {
  703.         return WAPI._serializeMessageObj(messageObj);
  704.     }
  705.     return;
  706. };
  707.  
  708. window.WAPI.getAllMessagesInChat = function (id, includeMe = false, includeNotifications = false, clean = false) {
  709.     const chat = WAPI.getChat(id);
  710.     let output = chat.msgs._models || [];
  711.     if(!includeMe) output =  output.filter(m=> !m.id.fromMe)
  712.     if(!includeNotifications) output = output.filter(m=> !m.isNotification)
  713.     return (clean ? output.map(WAPI.quickClean) : output.map(WAPI._serializeMessageObj)) || [];
  714. };
  715.  
  716. window.WAPI.loadAndGetAllMessagesInChat = function (id, includeMe, includeNotifications) {
  717.     return WAPI.loadAllEarlierMessages(id).then(_ => {
  718.         const chat = WAPI.getChat(id);
  719.         let output = [];
  720.         const messages = chat.msgs._models;
  721.  
  722.         for (const i in messages) {
  723.             if (i === "remove") {
  724.                 continue;
  725.             }
  726.             const messageObj = messages[i];
  727.  
  728.             let message = WAPI.processMessageObj(messageObj, includeMe, includeNotifications)
  729.             if (message)
  730.                 output.push(message);
  731.         }
  732.         return output;
  733.     })
  734. };
  735.  
  736. window.WAPI.getAllMessageIdsInChat = function (id, includeMe, includeNotifications) {
  737.     const chat = WAPI.getChat(id);
  738.     let output = [];
  739.     const messages = chat.msgs._models;
  740.  
  741.     for (const i in messages) {
  742.         if ((i === "remove")
  743.             || (!includeMe && messages[i].isMe)
  744.             || (!includeNotifications && messages[i].isNotification)) {
  745.             continue;
  746.         }
  747.         output.push(messages[i].id._serialized);
  748.     }
  749.     return output;
  750. };
  751.  
  752. window.WAPI.getMessageById = function (id) {
  753.     let result = false;
  754.     try {
  755.         let msg = window.Store.Msg.get(id);
  756.         if (msg) {
  757.             result = WAPI.processMessageObj(msg, true, true);
  758.         }
  759.     } catch (err) { }
  760.         return result;
  761. };
  762.  
  763. window.WAPI.sendMessageWithMentions = async function (ch, body) {
  764.     var chat = ch.id ? ch : Store.Chat.get(ch);
  765.     var chatId = chat.id._serialized;
  766.     var msgIveSent = chat.msgs.filter(msg => msg.__x_isSentByMe)[0];
  767.     if(!msgIveSent) return chat.sendMessage(body);
  768.     var tempMsg = Object.create(msgIveSent);
  769.     var newId = window.WAPI.getNewMessageId(chatId);
  770.     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;
  771.     var extend = {
  772.         ack: 0,
  773.         id: newId,
  774.         local: !0,
  775.         self: "out",
  776.         t: parseInt(new Date().getTime() / 1000),
  777.         to: new Store.WidFactory.createWid(chatId),
  778.         isNewMsg: !0,
  779.         type: "chat",
  780.         body,
  781.         quotedMsg:null,
  782.         mentionedJidList
  783.     };
  784.     Object.assign(tempMsg, extend);
  785.     await Store.addAndSendMsgToChat(chat, tempMsg)
  786.     return newId._serialized;
  787. }
  788.  
  789. window.WAPI.sendMessageReturnId = async function (ch, body) {
  790.     var chat = ch.id ? ch : Store.Chat.get(ch);
  791.     var chatId = chat.id._serialized;
  792.     var msgIveSent = chat.msgs.filter(msg => msg.__x_isSentByMe)[0];
  793.     if(!msgIveSent) return chat.sendMessage(body);
  794.     var tempMsg = Object.create(msgIveSent);
  795.     var newId = window.WAPI.getNewMessageId(chatId);
  796.     var extend = {
  797.         ack: 0,
  798.         id: newId,
  799.         local: !0,
  800.         self: "out",
  801.         t: parseInt(new Date().getTime() / 1000),
  802.         to: new Store.WidFactory.createWid(chatId),
  803.         isNewMsg: !0,
  804.         type: "chat",
  805.         body,
  806.         quotedMsg:null
  807.     };
  808.     Object.assign(tempMsg, extend);
  809.     await Store.addAndSendMsgToChat(chat, tempMsg)
  810.     return newId._serialized;
  811. }
  812.  
  813.  
  814. window.WAPI.sendMessage = async function (id, message) {
  815.     if(id==='status@broadcast') return 'Not able to send message to broadcast';
  816.     let chat = WAPI.getChat(id);
  817.     if((!chat && !id.includes('g') || chat.msgs.models.length == 0)) {
  818.         var contact = WAPI.getContact(id)
  819.         if(!contact || !contact.isMyContact) return 'Not a contact';
  820.         await Store.Chat.find(Store.Contact.get(id).id)
  821.         chat = WAPI.getChat(id);
  822.     }
  823.     if (chat !== undefined) {
  824.             // return WAPI.sendMessageReturnId(chat,message).then(id=>{return id})
  825.             return await chat.sendMessage(message).then(_=>chat.lastReceivedKey._serialized);
  826.     }
  827.     return false;
  828.     };
  829.  
  830.  
  831. window.WAPI.sendSeen = async function (id) {
  832.     if (!id) return false;
  833.     var chat = window.WAPI.getChat(id);
  834.     if (chat !== undefined) {
  835.             await Store.ReadSeen.sendSeen(chat, false);
  836.             return true;
  837.     }
  838.     return false;
  839. };
  840.  
  841. window.WAPI.markAsUnread = async function (id) {
  842.     var chat = window.WAPI.getChat(id);
  843.     if (chat !== undefined) {
  844.             await Store.ReadSeen.markUnread(chat, true);
  845.             return true;
  846.     }
  847.     return false;
  848. };
  849.  
  850. function isChatMessage(message) {
  851.     if (message.isSentByMe) {
  852.         return false;
  853.     }
  854.     if (message.isNotification) {
  855.         return false;
  856.     }
  857.     if (!message.isUserCreatedType) {
  858.         return false;
  859.     }
  860.     return true;
  861. }
  862.  
  863. window.WAPI.setPresence = function (available) {
  864.     if(available)Store._Presence.setPresenceAvailable();
  865.     else Store._Presence.setPresenceUnavailable();
  866. }
  867.  
  868. window.WAPI.getUnreadMessages = function (includeMe, includeNotifications, use_unread_count) {
  869.     const chats = window.Store.Chat.models;
  870.     let output = [];
  871.  
  872.     for (let chat in chats) {
  873.         if (isNaN(chat)) {
  874.             continue;
  875.         }
  876.  
  877.         let messageGroupObj = chats[chat];
  878.         let messageGroup = WAPI._serializeChatObj(messageGroupObj);
  879.  
  880.         messageGroup.messages = [];
  881.  
  882.         const messages = messageGroupObj.msgs._models;
  883.         for (let i = messages.length - 1; i >= 0; i--) {
  884.             let messageObj = messages[i];
  885.             if (typeof (messageObj.isNewMsg) != "boolean" || messageObj.isNewMsg === false) {
  886.                 continue;
  887.             } else {
  888.                 messageObj.isNewMsg = false;
  889.                 let message = WAPI.processMessageObj(messageObj, includeMe, includeNotifications);
  890.                 if (message) {
  891.                     messageGroup.messages.push(message);
  892.                 }
  893.             }
  894.         }
  895.  
  896.         if (messageGroup.messages.length > 0) {
  897.             output.push(messageGroup);
  898.         } else { // no messages with isNewMsg true
  899.             if (use_unread_count) {
  900.                 let n = messageGroupObj.unreadCount; // will use unreadCount attribute to fetch last n messages from sender
  901.                 for (let i = messages.length - 1; i >= 0; i--) {
  902.                     let messageObj = messages[i];
  903.                     if (n > 0) {
  904.                         if (!messageObj.isSentByMe) {
  905.                             let message = WAPI.processMessageObj(messageObj, includeMe, includeNotifications);
  906.                             messageGroup.messages.unshift(message);
  907.                             n -= 1;
  908.                         }
  909.                     } else if (n === -1) { // chat was marked as unread so will fetch last message as unread
  910.                         if (!messageObj.isSentByMe) {
  911.                             let message = WAPI.processMessageObj(messageObj, includeMe, includeNotifications);
  912.                             messageGroup.messages.unshift(message);
  913.                             break;
  914.                         }
  915.                     } else { // unreadCount = 0
  916.                         break;
  917.                     }
  918.                 }
  919.                 if (messageGroup.messages.length > 0) {
  920.                     messageGroupObj.unreadCount = 0; // reset unread counter
  921.                     output.push(messageGroup);
  922.                 }
  923.             }
  924.         }
  925.     }
  926.     return output;
  927. };
  928.  
  929. window.WAPI.getGroupOwnerID = async function (id) {
  930.     const output = (await WAPI.getGroupMetadata(id)).owner.id;
  931.     return output;
  932.  
  933. };
  934.  
  935. window.WAPI.getCommonGroups = async function (id) {
  936.     let output = [];
  937.  
  938.     groups = window.WAPI.getAllGroups();
  939.  
  940.     for (let idx in groups) {
  941.         try {
  942.             participants = await window.WAPI.getGroupParticipantIDs(groups[idx].id);
  943.             if (participants.filter((participant) => participant == id).length) {
  944.                 output.push(groups[idx]);
  945.             }
  946.         } catch (err) {
  947.             console.log("Error in group:");
  948.             console.log(groups[idx]);
  949.             console.log(err);
  950.         }
  951.     }
  952.     return output;
  953. };
  954.  
  955. window.WAPI.getProfilePicFromServer = function (id) {
  956.     return Store.WapQuery.profilePicFind(id).then(x => x.eurl);
  957. }
  958.  
  959. window.WAPI.getProfilePicSmallFromId = async function (id) {
  960.     return await window.Store.ProfilePicThumb.find(id).then(async d=> {
  961.         if (d.img !== undefined) {
  962.             return await window.WAPI.downloadFileWithCredentials(d.img);
  963.         } else {
  964.             return false
  965.         }
  966.     }, function (e) {
  967.         return false
  968.     })
  969. };
  970.  
  971. window.WAPI.getProfilePicFromId = async function (id) {
  972.     return await window.Store.ProfilePicThumb.find(id).then(async d => {
  973.         if (d.imgFull !== undefined) {
  974.             return await window.WAPI.downloadFileWithCredentials(d.imgFull);
  975.         } else {
  976.             return false
  977.         }
  978.     }, function (e) {
  979.         return false
  980.     })
  981. };
  982.  
  983. window.WAPI.downloadFileWithCredentials = async function (url) {
  984.     if(!axios || !url) return false;
  985.     const ab = (await axios.get(url,{responseType: 'arraybuffer'})).data
  986.     return btoa(new Uint8Array(ab).reduce((data, byte) => data + String.fromCharCode(byte), ''));
  987. };
  988.  
  989. window.WAPI.downloadFile = async function (url) {
  990.     return await new Promise((resolve,reject) => {
  991.     let xhr = new XMLHttpRequest();
  992.     xhr.onload = function () {
  993.         if (xhr.readyState == 4) {
  994.             if (xhr.status == 200) {
  995.                 let reader = new FileReader();
  996.                 reader.readAsDataURL(xhr.response);
  997.                 reader.onload = function (e) {
  998.                     resolve(reader.result.substr(reader.result.indexOf(',') + 1))
  999.                 };
  1000.             } else {
  1001.                 console.error(xhr.statusText);
  1002.             }
  1003.         } else {
  1004.             console.log(err);
  1005.             resolve(false);
  1006.         }
  1007.     };
  1008.  
  1009.     xhr.open("GET", url, true);
  1010.     xhr.responseType = 'blob';
  1011.     xhr.send(null);
  1012. })
  1013. };
  1014.  
  1015. window.WAPI.getBatteryLevel = function () {
  1016.     return Store.Conn.battery;
  1017. };
  1018.  
  1019. window.WAPI.getIsPlugged = function () {
  1020.     return Store.Conn.plugged;
  1021. };
  1022.  
  1023. window.WAPI.deleteConversation = async function (chatId) {
  1024.     let userId = new window.Store.UserConstructor(chatId, { intentionallyUsePrivateConstructor: true });
  1025.     let conversation = WAPI.getChat(userId);
  1026.     if (!conversation) {
  1027.         return false;
  1028.     }
  1029.     return await window.Store.sendDelete(conversation, false).then(() => {
  1030.         return true;
  1031.     }).catch(() => {
  1032.         return false;
  1033.     });
  1034. };
  1035.  
  1036. window.WAPI.smartDeleteMessages = async function (chatId, messageArray, onlyLocal) {
  1037.     var userId = new Store.WidFactory.createWid(chatId);
  1038.     let conversation = WAPI.getChat(userId);
  1039.     if (!conversation) return false;
  1040.  
  1041.     if (!Array.isArray(messageArray)) {
  1042.         messageArray = [messageArray];
  1043.     }
  1044.  
  1045.     let messagesToDelete = messageArray.map(msgId => (typeof msgId == 'string')?window.Store.Msg.get(msgId):msgId).filter(x=>x);
  1046.     if(messagesToDelete.length==0) return true;
  1047.     let jobs = onlyLocal ? [conversation.sendDeleteMsgs(messagesToDelete,conversation)] :[
  1048.         conversation.sendRevokeMsgs(messagesToDelete.filter(msg=>msg.isSentByMe),conversation),
  1049.         conversation.sendDeleteMsgs(messagesToDelete.filter(msg=>!msg.isSentByMe),conversation)
  1050.     ]
  1051.     return Promise.all(jobs).then(_=>true)
  1052. };
  1053.  
  1054. window.WAPI.deleteMessage = async function (chatId, messageArray, revoke = false) {
  1055.     let userId = new window.Store.UserConstructor(chatId, { intentionallyUsePrivateConstructor: true });
  1056.     let conversation = WAPI.getChat(userId);
  1057.  
  1058.     if (!conversation)return false;
  1059.  
  1060.     if (!Array.isArray(messageArray)) {
  1061.         messageArray = [messageArray];
  1062.     }
  1063.  
  1064.     let messagesToDelete = messageArray.map(msgId => window.Store.Msg.get(msgId));
  1065.  
  1066.     if (revoke) {
  1067.         conversation.sendRevokeMsgs(messagesToDelete, conversation);
  1068.     } else {
  1069.         conversation.sendDeleteMsgs(messagesToDelete, conversation);
  1070.     }
  1071.  
  1072.     return true;
  1073. };
  1074.  
  1075. window.WAPI.clearChat = async function (id) {
  1076.     return await Store.ChatUtil.sendClear(Store.Chat.get(id),true);
  1077. }
  1078.  
  1079. /**
  1080.  * @param id The id of the conversation
  1081.  * @param archive boolean true => archive, false => unarchive
  1082.  * @return boolean true: worked, false: didnt work (probably already in desired state)
  1083.  */
  1084. window.WAPI.archiveChat = async function (id, archive) {
  1085.     return await Store.Archive.setArchive(Store.Chat.get(id),archive).then(_=>true).catch(_=>false)
  1086. }
  1087.  
  1088. /**
  1089.  * Extracts vcards from a message
  1090.  * @param id string id of the message to extract the vcards from
  1091.  * @returns [vcard]
  1092.  * ```
  1093.  * [
  1094.  * {
  1095.  * displayName:"Contact name",
  1096.  * vcard: "loong vcard string"
  1097.  * }
  1098.  * ]
  1099.  * ``` or false if no valid vcards found
  1100.  */
  1101. window.WAPI.getVCards = function(id) {
  1102.     var msg = Store.Msg.get(id);
  1103.     if(msg) {
  1104.         if(msg.type=='vcard') {
  1105.             return [
  1106.                 {
  1107.                     displayName:msg.subtype,
  1108.                     vcard:msg.body
  1109.                 }
  1110.             ]
  1111.         } else if (msg.type=='multi_vcard') {
  1112.             return msg.vcardList
  1113.         } else return false;
  1114.     } else {
  1115.         return false
  1116.     }
  1117. }
  1118.  
  1119. window.WAPI.checkNumberStatus = async function (id) {
  1120.     try {
  1121.         const result = await window.Store.WapQuery.queryExist(id);
  1122.         if (result.jid === undefined) throw 404;
  1123.         const data = window.WAPI._serializeNumberStatusObj(result);
  1124.         if (data.status == 200) data.numberExists = true
  1125.         return data;
  1126.     } catch (e) {
  1127.             return window.WAPI._serializeNumberStatusObj({
  1128.                 status: e,
  1129.                 jid: id
  1130.             });
  1131.     }
  1132. };
  1133.  
  1134. window.WAPI.onAnyMessage = callback => window.Store.Msg.on('add', (newMessage) => {
  1135.     if (newMessage && newMessage.isNewMsg) {
  1136.     if(!newMessage.clientUrl && (newMessage.mediaKeyTimestamp || newMessage.filehash)){
  1137.         const cb = (msg) => {
  1138.             if(msg.id._serialized === newMessage.id._serialized && msg.clientUrl) {
  1139.                 callback(WAPI.processMessageObj(msg, true, false));
  1140.                 Store.Msg.off('change:isUnsentMedia',cb);
  1141.             }
  1142.         };
  1143.         Store.Msg.on('change:isUnsentMedia',cb);
  1144.     } else {
  1145.         let pm = window.WAPI.processMessageObj(newMessage, true, true);
  1146.         let message = pm? JSON.parse(JSON.stringify(pm)) : WAPI.quickClean(newMessage.attributes);
  1147.         if (message) {
  1148.             callback(message)
  1149.         }
  1150.     }}
  1151. });
  1152.  
  1153. /**
  1154.  * Registers a callback to be called when a the acknowledgement state of the phone connection.
  1155.  * @param callback - function - Callback function to be called when the device state changes. this returns 'CONNECTED' or 'TIMEOUT'
  1156.  * @returns {boolean}
  1157.  */
  1158. window.WAPI.onStateChanged = function (callback) {
  1159.     window.Store.State.default.on('change:state', ({state})=>callback(state))
  1160.     return true;
  1161. }
  1162.  
  1163. /**
  1164.  * Returns the current state of the session. Possible state values:
  1165.  * "CONFLICT"
  1166.  * "CONNECTED"
  1167.  * "DEPRECATED_VERSION"
  1168.  * "OPENING"
  1169.  * "PAIRING"
  1170.  * "PROXYBLOCK"
  1171.  * "SMB_TOS_BLOCK"
  1172.  * "TIMEOUT"
  1173.  * "TOS_BLOCK"
  1174.  * "UNLAUNCHED"
  1175.  * "UNPAIRED"
  1176.  * "UNPAIRED_IDLE"
  1177.  */
  1178. window.WAPI.getState = function (){
  1179.     return Store.State.default.state;
  1180. }
  1181.  
  1182. /**
  1183.  * Registers a callback to be called when your phone receives a new call request.
  1184.  * @param callback - function - Callback function to be called upon a new call. returns a call object.
  1185.  * @returns {boolean}
  1186.  */
  1187. window.WAPI.onIncomingCall = function (callback) {
  1188.     window.Store.Call.on('add',callback);
  1189.     return true;
  1190. }
  1191.  
  1192. /**
  1193.  * @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.
  1194.  * @param objectId The Chat, message or contact id to which you want to add a label
  1195.  * @param type The type of the action. It can be either "add" or "remove"
  1196.  * @returns boolean true if it worked otherwise false
  1197.  */
  1198. window.WAPI.addOrRemoveLabels = async function (label, objectId, type) {
  1199.     var {id} = Store.Label.models.find(x=>x.id==label||x.name==label)
  1200.     var to = Store.Chat.get(objectId) || Store.Msg.get(objectId) || Store.Contact.get(objectId);
  1201.     if(!id || !to) return false;
  1202.     const {status} = await Store.Label.addOrRemoveLabels([{id,type}],[to]);
  1203.     return status===200;
  1204. }
  1205.  
  1206. /**
  1207.  * Registers a callback to be called when a the acknowledgement state of a message changes.
  1208.  * @param callback - function - Callback function to be called when a message acknowledgement changes.
  1209.  * @returns {boolean}
  1210.  */
  1211. window.WAPI.onAck = function (callback) {
  1212.     Store.Msg.on("change:ack", m=>callback(WAPI.quickClean(m)));
  1213.     return true;
  1214. }
  1215.  
  1216. //returns an array of liveLocationChangeObjects
  1217. window.WAPI.forceUpdateLiveLocation = async function (chatId) {
  1218.     if(!Store.LiveLocation.get(chatId)) return false;
  1219.     return WAPI.quickClean(await Store.LiveLocation.update(chatId)).participants.map(l=>{
  1220.         return {
  1221.         ...l,
  1222.         msgId:l.msg.id._serialized
  1223.         }
  1224.         });
  1225. }
  1226.  
  1227. window.WAPI.onLiveLocation = function (chatId, callback) {
  1228.     var lLChat = Store.LiveLocation.get(chatId);
  1229.     if(lLChat) {
  1230.         var validLocs = lLChat.participants.validLocations();
  1231.         validLocs.map(x=>x.on('change:lastUpdated',(x,y,z)=>{
  1232.             const {id,lat,lng,accuracy,degrees,speed,lastUpdated}=x;
  1233.         const l = {
  1234.             id:id.toString(),lat,lng,accuracy,degrees,speed,lastUpdated};
  1235.         callback(l);
  1236.         }));
  1237.         return true;
  1238.     } else {
  1239.         return false;
  1240.     }
  1241. }
  1242.  
  1243. window.WAPI.onBattery = function(callback) {
  1244.     window.Store.Conn.on('change:battery', ({battery}) =>  callback(battery));
  1245.     return true;
  1246. }
  1247.  
  1248. window.WAPI.onPlugged = function(callback) {
  1249.     window.Store.Conn.on('change:plugged', ({plugged}) =>  callback(plugged));
  1250.     return true;
  1251. }
  1252.  
  1253. /**
  1254.  * A new approach to listening to add and remove events from groups. This takes only a callback and is prevents memory leaks
  1255.  */
  1256. WAPI.onGlobalParicipantsChanged = function(callback) {
  1257.     const events = [
  1258.         'change:isAdmin',
  1259.         'remove',
  1260.         'add'
  1261.     ]
  1262.     //const id = eventName.replace('group_participant_change','');
  1263.     const chats = Store.GroupMetadata.models
  1264.         //.filter(group=>group.participants.models.find(participant=>participant.id._serialized===id))
  1265.         .filter(x => x.id.server !== 'broadcast').map(group => window.Store.Chat.get(group.id._serialized));
  1266.     const cb = (eventName, eventData, extra) => {
  1267.         if (events.includes(eventName)) {
  1268.             let action = eventName;
  1269.             if (eventName == 'change:isAdmin') {
  1270.                 action = extra ? 'promote' : 'demote';
  1271.             }
  1272.             callback({
  1273.                 by: undefined,
  1274.                 action: action,
  1275.                 who: eventData.id._serialized,
  1276.                 chat: extra.parent.id._serialized
  1277.             });
  1278.             chats.map(chat => {
  1279.                 chat.groupMetadata.participants.off('all', cb)
  1280.                 chat.groupMetadata.participants.off(cb)
  1281.             });
  1282.         }
  1283.     }
  1284.     chats.map(chat => chat.groupMetadata.participants.on('all', cb));
  1285.     Store.GroupMetadata.on('all', (eventName, groupId) => chats.map(chat => chat.groupMetadata.participants.on('all', cb)))
  1286.     return true;
  1287. }
  1288.  
  1289. /**
  1290.  * Registers a callback to participant changes on a certain, specific group
  1291.  * @param groupId - string - The id of the group that you want to attach the callback to.
  1292.  * @param callback - function - Callback function to be called when a message acknowledgement changes. The callback returns 3 variables
  1293.  * @returns {boolean}
  1294.  */
  1295. window.WAPI.onParticipantsChanged = function (groupId, callback) {
  1296.     const subtypeEvents = [
  1297.         "invite" ,
  1298.         "add" ,
  1299.         "remove" ,
  1300.         "leave" ,
  1301.         "promote" ,
  1302.         "demote"
  1303.     ];
  1304.     const events = [
  1305.         'change:isAdmin',
  1306.         'remove',
  1307.         'add'
  1308.     ]
  1309.     const chat = window.Store.Chat.get(groupId);
  1310.     chat.groupMetadata.participants.on('all', (eventName, eventData, extra) => {
  1311.         if(events.includes(eventName)) {
  1312.             let action = eventName;
  1313.             if(eventName=='change:isAdmin') {
  1314.                 action = extra ? 'promote' : 'demote';
  1315.             }
  1316.         callback({
  1317.             by: undefined,
  1318.             action: action,
  1319.             who: eventData.id._serialized
  1320.         });
  1321.         }
  1322.     })
  1323. }
  1324.  
  1325. /**
  1326.  * Registers a callback to participant changes on a certain, specific group
  1327.  * @param groupId - string - The id of the group that you want to attach the callback to.
  1328.  * @param callback - function - Callback function to be called when a message acknowledgement changes. The callback returns 3 variables
  1329.  * @returns {boolean}
  1330.  */
  1331. var groupParticpiantsEvents = {};
  1332. window.WAPI._onParticipantsChanged = function (groupId, callback) {
  1333.     const subtypeEvents = [
  1334.         "invite" ,
  1335.         "add" ,
  1336.         "remove" ,
  1337.         "leave" ,
  1338.         "promote" ,
  1339.         "demote"
  1340.     ];
  1341.     const chat = window.Store.Chat.get(groupId);
  1342.     //attach all group Participants to the events object as 'add'
  1343.     const metadata = window.Store.GroupMetadata.get(groupId);
  1344.     if (!groupParticpiantsEvents[groupId]) {
  1345.         groupParticpiantsEvents[groupId] = {};
  1346.         metadata.participants.forEach(participant => {
  1347.             groupParticpiantsEvents[groupId][participant.id.toString()] = {
  1348.                 subtype: "add",
  1349.                 from: metadata.owner
  1350.             }
  1351.         });
  1352.     }
  1353.     let i = 0;
  1354.     chat.on("change:groupMetadata.participants",
  1355.         _ => chat.on("all", (x, y) => {
  1356.             const { isGroup, previewMessage } = y;
  1357.             if (isGroup && x === "change" && previewMessage && previewMessage.type === "gp2" && subtypeEvents.includes(previewMessage.subtype)) {
  1358.                 const { subtype, author, recipients } = previewMessage;
  1359.                 const rec = recipients[0].toString();
  1360.                 if (groupParticpiantsEvents[groupId][rec] && groupParticpiantsEvents[groupId][recipients[0]].subtype == subtype) {
  1361.                     //ignore, this is a duplicate entry
  1362.                     // console.log('duplicate event')
  1363.                 } else {
  1364.                     //ignore the first message
  1365.                     if (i == 0) {
  1366.                         //ignore it, plus 1,
  1367.                         i++;
  1368.                     } else {
  1369.                         groupParticpiantsEvents[groupId][rec] = { subtype, author };
  1370.                         //fire the callback
  1371.                         // // previewMessage.from.toString()
  1372.                         // x removed y
  1373.                         // x added y
  1374.                         callback({
  1375.                             by: author.toString(),
  1376.                             action: subtype,
  1377.                             who: recipients
  1378.                         });
  1379.                         chat.off("all", this)
  1380.                         i = 0;
  1381.                     }
  1382.                 }
  1383.             }
  1384.         })
  1385.     )
  1386.     return true;
  1387. }
  1388.  
  1389.  
  1390. /**
  1391.  * Registers a callback that fires when your host phone is added to a group.
  1392.  * @param callback - function - Callback function to be called when a message acknowledgement changes. The callback returns 3 variables
  1393.  * @returns {boolean}
  1394.  */
  1395. window.WAPI.onAddedToGroup = function(callback){
  1396.     Store.Chat.on('change:previewMessage', async event => {
  1397.         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)) {
  1398.             const tdiff = (Date.now()-Store.Msg.get(event.previewMessage.id._serialized).t*1000)/1000;
  1399.             if(tdiff<10.0) {
  1400.                 console.log('added', tdiff,'seconds ago')
  1401.                 await WAPI.sendSeen(event.id);
  1402.                 callback(WAPI._serializeChatObj(Store.Chat.get(event.id)));
  1403.             } else console.log('Not a new group add', event.id._serialized)
  1404.         }
  1405.     })
  1406.     return true;
  1407. }
  1408.  
  1409. /**
  1410.  * Reads buffered new messages.
  1411.  * @returns {Array}
  1412.  */
  1413. window.WAPI.getBufferedNewMessages = function () {
  1414.     let bufferedMessages = window._WAPI._newMessagesBuffer;
  1415.     window._WAPI._newMessagesBuffer = [];
  1416.     return bufferedMessages;
  1417. };
  1418. /** End new messages observable functions **/
  1419.  
  1420. /** Joins a group via the invite link, code, or message
  1421.  * @param link This param is the string which includes the invite link or code. The following work:
  1422.  * - Follow this link to join my WA group: https://chat.whatsapp.com/DHTGJUfFJAV9MxOpZO1fBZ
  1423.  * - https://chat.whatsapp.com/DHTGJUfFJAV9MxOpZO1fBZ
  1424.  * - DHTGJUfFJAV9MxOpZO1fBZ
  1425.  * @returns Promise<string | boolean> Either false if it didn't work, or the group id.
  1426.  */
  1427. window.WAPI.joinGroupViaLink = async function(link){
  1428.     return await Store.WapQuery.acceptGroupInvite(link.split('\/').pop()).then(res=>res.status===200?res.gid._serialized:res.status);
  1429.     let code = link;
  1430.     //is it a link? if not, assume it's a code, otherwise, process the link to get the code.
  1431.     if(link.includes('chat.whatsapp.com')) {
  1432.         if(!link.match(/chat.whatsapp.com\/([\w\d]*)/g).length) return false;
  1433.         code = link.match(/chat.whatsapp.com\/([\w\d]*)/g)[0].replace('chat.whatsapp.com\/','');
  1434.     }
  1435.     const group = await Store.GroupInvite.joinGroupViaInvite(code);
  1436.     if(!group.id) return false;
  1437.     return group.id._serialized
  1438. }
  1439.  
  1440. window.WAPI.sendImage = async function (imgBase64, chatid, filename, caption, quotedMsg, waitForKey, ptt) {
  1441.     if(!chatid.includes('@g')&&!chatid.includes('@c')) return false;
  1442.     let extras = {};
  1443.     if(quotedMsg){
  1444.         if (typeof quotedMsg !== "object") quotedMsg = Store.Msg.get(quotedMsg);
  1445.         extras = {
  1446.             quotedMsg,
  1447.             quotedParticipant: quotedMsg.author || quotedMsg.from,
  1448.             quotedStanzaID:quotedMsg.id.id
  1449.         };
  1450.     }
  1451.     return await Store.Chat.find(chatid).then(async (chat) => {
  1452.         var mediaBlob = window.WAPI.base64ImageToFile(imgBase64, filename);
  1453.         return await window.WAPI.procFiles(chat,mediaBlob).then(async mc => {
  1454.             var media = mc.models[0];
  1455.             if(ptt) media.mediaPrep._mediaData.type = 'ptt';
  1456.             await media.sendToChat(chat, { caption,...extras });
  1457.             return waitForKey ? await new Promise(async (resolve,reject) => {
  1458.                 const cb = msg=>{
  1459.                     if(media.attributes.file.size === msg.size) resolve(msg.id._serialized);
  1460.                     Store.Msg.off('change:clientUrl',cb);
  1461.                 };
  1462.                 Store.Msg.on('change:clientUrl',cb);
  1463.             }) : true
  1464.         });
  1465.     });
  1466. }
  1467.  
  1468. /**
  1469.  * This function sts the profile name of the number.
  1470.  *
  1471.  * Please note this DOES NOT WORK ON BUSINESS ACCOUNTS!
  1472.  *
  1473.  * @param newName - string the new name to set as profile name
  1474.  */
  1475. window.WAPI.setMyName = async function (newName) {
  1476.     if(!Store.Versions.default[12].BinaryProtocol) Store.Versions.default[12].BinaryProtocol=new Store.bp(Store.Me.binVersion);
  1477.     return (await Store.Versions.default[12].setPushname(newName)).status===200;
  1478. }
  1479.  
  1480. /** Change the icon for the group chat
  1481.  * @param groupId 123123123123_1312313123@g.us The id of the group
  1482.  * @param imgData 'data:image/jpeg;base64,...` The base 64 data uri
  1483.  * @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.
  1484.  */
  1485. window.WAPI.setGroupIcon = async function(groupId, imgData) {
  1486.     const {status} = await Store.WapQuery.sendSetPicture(groupId,imgData,imgData);
  1487.     return status==200;
  1488. }
  1489.  
  1490. /**
  1491. * Update your status
  1492. *   @param newStatus string new Status
  1493. */
  1494. window.WAPI.setMyStatus = function (newStatus) {
  1495.     return Store.MyStatus.setMyStatus(newStatus)
  1496. }
  1497.  
  1498. window.WAPI.sendVideoAsGif = async function (imgBase64, chatid, filename, caption, quotedMsg) {
  1499.     let extras = {};
  1500.     if(quotedMsg){
  1501.         if (typeof quotedMsg !== "object") quotedMsg = Store.Msg.get(quotedMsg);
  1502.         extras = {
  1503.             quotedMsg,
  1504.             quotedParticipant: quotedMsg.author || quotedMsg.from,
  1505.             quotedStanzaID:quotedMsg.id.id
  1506.         };
  1507.     }
  1508.     // create new chat
  1509.     return await Store.Chat.find(chatid).then(async (chat) => {
  1510.         var mediaBlob = window.WAPI.base64ImageToFile(imgBase64, filename);
  1511.         var mc = new Store.MediaCollection(chat);
  1512.         return await window.WAPI.procFiles(chat,mediaBlob).then(async mc => {
  1513.             var media = mc.models[0];
  1514.             media.mediaPrep._mediaData.isGif = true;
  1515.             media.mediaPrep._mediaData.gifAttribution = 1;
  1516.             await media.mediaPrep.sendToChat(chat, { caption,...extras });
  1517.             return chat.lastReceivedKey._serialized;
  1518.         });
  1519.     });
  1520. }
  1521.  
  1522. window.WAPI.refreshBusinessProfileProducts = async function (){
  1523.     await Promise.all(Store.BusinessProfile.models.map(async x=>{
  1524.         try{
  1525.         await Store.Catalog.findCarouselCatalog(x.id._serialized)
  1526.         } catch(error){}
  1527.         }));
  1528.         return true;
  1529. }
  1530.  
  1531. /**
  1532.  * Find any product listings of the given number. Use this to query a catalog
  1533.  *
  1534.  * @param id id of buseinss profile (i.e the number with @c.us)
  1535.  * @returns None
  1536.  */
  1537. window.WAPI.getBusinessProfilesProducts = async function (id) {
  1538.     try{
  1539.         if(!Store.Catalog.get(id)) await Store.Catalog.findCarouselCatalog(id)
  1540.         const catalog = Store.Catalog.get(id);
  1541.         if (catalog.productCollection && catalog.productCollection._models.length)
  1542.         return JSON.parse(JSON.stringify(catalog.productCollection._models));
  1543.         else return [];
  1544.     } catch(error){
  1545.         return false;
  1546.     }
  1547. };
  1548.  
  1549.  
  1550. window.WAPI.procFiles= async function(chat, blobs) {
  1551.     if (!Array.isArray(blobs)) {
  1552.         blobs = [blobs];
  1553.     }
  1554.     var mc = new Store.MediaCollection(chat);
  1555.     await mc.processFiles((Debug.VERSION === '0.4.613')?blobs:blobs.map(blob=>{return{file:blob}}) , chat, 1);
  1556.     return mc
  1557. }
  1558. /**
  1559.  * Sends product with image to chat
  1560.  * @param imgBase64 Base64 image data
  1561.  * @param chatid string the id of the chat that you want to send this product to
  1562.  * @param caption string the caption you want to add to this message
  1563.  * @param bizNumber string the @c.us number of the business account from which you want to grab the product
  1564.  * @param productId string the id of the product within the main catalog of the aforementioned business
  1565.  * @returns
  1566.  */
  1567. window.WAPI.sendImageWithProduct = async function (imgBase64, chatid, caption, bizNumber, productId) {
  1568.     await WAPI.refreshBusinessProfileProducts();
  1569.     return await Store.Catalog.findCarouselCatalog(bizNumber).then(async cat => {
  1570.         if (cat && cat[0]) {
  1571.             const product = cat[0].productCollection.get(productId);
  1572.             const temp = {
  1573.                 productMsgOptions: {
  1574.                     businessOwnerJid: product.catalogWid.toString({
  1575.                         legacy: !0
  1576.                     }),
  1577.                     productId: product.id.toString(),
  1578.                     url: product.url,
  1579.                     productImageCount: product.productImageCollection.length,
  1580.                     title: product.name,
  1581.                     description: product.description,
  1582.                     currencyCode: product.currency,
  1583.                     priceAmount1000: product.priceAmount1000,
  1584.                     type: "product"
  1585.                 },
  1586.                 caption
  1587.             }
  1588.  
  1589.             // var idUser = new Store.WidFactory.createWid(chatid);
  1590.  
  1591.             return Store.Chat.find(chatid).then(async (chat) => {
  1592.                 var mediaBlob = window.WAPI.base64ImageToFile(imgBase64, "filename.jpg");
  1593.                 // var mc = new Store.MediaCollection(chat);
  1594.                 // mc.processFiles([mediaBlob], chat, 1)
  1595.                 return await window.WAPI.procFiles(chat,mediaBlob).then(async mc => {
  1596.                     var media = mc.models[0];
  1597.                     Object.entries(temp.productMsgOptions).map(([k, v]) => media.mediaPrep._mediaData[k] = v)
  1598.                     await media.mediaPrep.sendToChat(chat, temp);
  1599.                     return chat.lastReceivedKey._serialized;
  1600.                 });
  1601.             });
  1602.         }
  1603.     })
  1604. }
  1605.  
  1606. window.WAPI.base64ImageToFile = function (b64Data, filename) {
  1607.     var arr = b64Data.split(',');
  1608.     var mime = arr[0].match(/:(.*?);/)[1];
  1609.     var bstr = window.Base64 ? window.Base64.atob(arr[1]) : atob(arr[1]);
  1610.     var n = bstr.length;
  1611.     var u8arr = new Uint8Array(n);
  1612.  
  1613.     while (n--) {
  1614.         u8arr[n] = bstr.charCodeAt(n);
  1615.     }
  1616.  
  1617.     return new File([u8arr], filename, { type: mime });
  1618. };
  1619.  
  1620. /**
  1621.  * Send contact card to a specific chat using the chat ids
  1622.  *
  1623.  * @param {string} to '000000000000@c.us'
  1624.  * @param {string|array} contact '111111111111@c.us' | ['222222222222@c.us', '333333333333@c.us, ... 'nnnnnnnnnnnn@c.us']
  1625.  */
  1626. window.WAPI.sendContact = function (to, contact) {
  1627.     if (!Array.isArray(contact)) {
  1628.         contact = [contact];
  1629.     }
  1630.     contact = contact.map((c) => {
  1631.         return WAPI.getChat(c).__x_contact;
  1632.     });
  1633.  
  1634.     if (contact.length > 1) {
  1635.         window.WAPI.getChat(to).sendContactList(contact);
  1636.     } else if (contact.length === 1) {
  1637.         window.WAPI.getChat(to).sendContact(contact[0]);
  1638.     }
  1639. };
  1640.  
  1641. /**
  1642.  * Ghost forwarding is like a normal forward but as if it were sent from the host phone.
  1643.  */
  1644. window.WAPI.ghostForward = async function(chatId, messageId) {
  1645.     if(!chatId.includes('@g')&&!chatId.includes('@c')) return false;
  1646.     var chat = Store.Chat.get(chatId);
  1647.     if(!Store.Msg.get(messageId)) return false;
  1648.     var tempMsg = Object.create(Store.Msg.get(messageId));
  1649.     var newId = window.WAPI.getNewMessageId(chatId);
  1650.     var extend = {
  1651.         ...JSON.parse(JSON.stringify(tempMsg)),
  1652.         ack: 0,
  1653.         id: newId,
  1654.         local: !0,
  1655.         self: "out",
  1656.         t: parseInt(new Date().getTime() / 1000),
  1657.         to: new Store.WidFactory.createWid(chatId),
  1658.         from: Store.Me.wid,
  1659.         isNewMsg: true
  1660.     };
  1661.     Object.assign(tempMsg, extend);
  1662.     const res = await Promise.all(Store.addAndSendMsgToChat(chat, extend))
  1663.     return res[1]=='success';
  1664. }
  1665.  
  1666.  
  1667. /**
  1668.  * Forward an array of messages to a specific chat using the message ids or Objects
  1669.  *
  1670.  * @param {string} to '000000000000@c.us'
  1671.  * @param {string|array[Message | string]} messages this can be any mixture of message ids or message objects
  1672.  * @param {boolean} skipMyMessages This indicates whether or not to skip your own messages from the array
  1673.  */
  1674. window.WAPI.forwardMessages = async function (to, messages, skipMyMessages) {
  1675.     if (!Array.isArray(messages)) {
  1676.         messages = [messages];
  1677.     }
  1678.     const finalForwardMessages = messages.map(msg => {
  1679.         if (typeof msg == 'string') {
  1680.             //msg is string, get the message object
  1681.             return window.Store.Msg.get(msg);
  1682.         } else {
  1683.             return window.Store.Msg.get(msg.id);
  1684.         }
  1685.     }).filter(msg => skipMyMessages ? !msg.__x_isSentByMe : true);
  1686.  
  1687.     // let userId = new window.Store.UserConstructor(to);
  1688.     let conversation = window.Store.Chat.get(to);
  1689.     return await conversation.forwardMessages(finalForwardMessages)
  1690. };
  1691.  
  1692. /**
  1693.  * Create an chat ID based in a cloned one
  1694.  *
  1695.  * @param {string} chatId '000000000000@c.us'
  1696.  */
  1697. window.WAPI.getNewMessageId = function (chatId) {
  1698.     var newMsgId = new Store.MsgKey(Object.assign({}, Store.Msg.models[0].__x_id))
  1699.     // .clone();
  1700.  
  1701.     newMsgId.fromMe = true;
  1702.     newMsgId.id = WAPI.getNewId().toUpperCase();
  1703.     newMsgId.remote = new Store.WidFactory.createWid(chatId);
  1704.     newMsgId._serialized = `$\{newMsgId.fromMe}_$\{newMsgId.remote}_$\{newMsgId.id}`
  1705.  
  1706.     return newMsgId;
  1707. };
  1708.  
  1709.  
  1710. /**
  1711.  * Simulate '...typing' in the chat.
  1712.  *
  1713.  * @param {string} chatId '000000000000@c.us'
  1714.  * @param {boolean} on true to turn on similated typing, false to turn it off //you need to manually turn this off.
  1715.  */
  1716. window.WAPI.simulateTyping = async function (chatId, on) {
  1717.     if (on) Store.ChatStates.sendChatStateComposing(chatId)
  1718.     else Store.ChatStates.sendChatStatePaused(chatId)
  1719.     return true
  1720. };
  1721.  
  1722. /**
  1723.  * Send location
  1724.  *
  1725.  * @param {string} chatId '000000000000@c.us'
  1726.  * @param {string} lat latitude
  1727.  * @param {string} lng longitude
  1728.  * @param {string} loc Text to go with the location message
  1729.  */
  1730. window.WAPI.sendLocation = async function (chatId, lat, lng, loc) {
  1731.     loc = loc || '';
  1732.     var chat = Store.Chat.get(chatId);
  1733.     if(!chat) return false;
  1734.     var tempMsg = Object.create(Store.Msg.models.filter(msg => msg.__x_isSentByMe && !msg.quotedMsg)[0]);
  1735.     var newId = window.WAPI.getNewMessageId(chatId);
  1736.     var extend = {
  1737.         ack: 0,
  1738.         id: newId,
  1739.         local: !0,
  1740.         self: "out",
  1741.         t: parseInt(new Date().getTime() / 1000),
  1742.         to: chatId,
  1743.         isNewMsg: !0,
  1744.         type: "location",
  1745.         lat,
  1746.         lng,
  1747.         loc,
  1748.         clientUrl:undefined,
  1749.         directPath:undefined,
  1750.         filehash:undefined,
  1751.         uploadhash:undefined,
  1752.         mediaKey:undefined,
  1753.         isQuotedMsgAvailable:false,
  1754.         invis:false,
  1755.         mediaKeyTimestamp:undefined,
  1756.         mimetype:undefined,
  1757.         height:undefined,
  1758.         width:undefined,
  1759.         ephemeralStartTimestamp:undefined,
  1760.         body:undefined,
  1761.         mediaData:undefined,
  1762.         isQuotedMsgAvailable: false
  1763.     };
  1764.     Object.assign(tempMsg, extend);
  1765.     return (await Promise.all(Store.addAndSendMsgToChat(chat, tempMsg)))[1]==='success' ? newId._serialized : false;
  1766. };
  1767.  
  1768. /**
  1769.  * Send VCARD
  1770.  *
  1771.  * @param {string} chatId '000000000000@c.us'
  1772.  * @param {string} vcard vcard as a string
  1773.  * @param {string} contactName The display name for the contact. CANNOT BE NULL OTHERWISE IT WILL SEND SOME RANDOM CONTACT FROM YOUR ADDRESS BOOK.
  1774.  * @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.
  1775.  */
  1776. window.WAPI.sendVCard = async function (chatId, vcard, contactName, contactNumber) {
  1777.     var chat = Store.Chat.get(chatId);
  1778.     var tempMsg = Object.create(Store.Msg.models.filter(msg => msg.__x_isSentByMe && !msg.quotedMsg)[0]);
  1779.     var newId = window.WAPI.getNewMessageId(chatId);
  1780.     var extend = {
  1781.         ack: 0,
  1782.         id: newId,
  1783.         local: !0,
  1784.         self: "out",
  1785.         t: parseInt(new Date().getTime() / 1000),
  1786.         to: chatId,
  1787.         isNewMsg: !0,
  1788.         type: "vcard",
  1789.         clientUrl:undefined,
  1790.         directPath:undefined,
  1791.         filehash:undefined,
  1792.         uploadhash:undefined,
  1793.         mediaKey:undefined,
  1794.         isQuotedMsgAvailable:false,
  1795.         invis:false,
  1796.         mediaKeyTimestamp:undefined,
  1797.         mimetype:undefined,
  1798.         height:undefined,
  1799.         width:undefined,
  1800.         ephemeralStartTimestamp:undefined,
  1801.         body:contactNumber?vcard.replace('TEL;TYPE=WORK,VOICE:',`TEL;TYPE=WORK,VOICE;waid=:`):vcard,
  1802.         mediaData:undefined,
  1803.         isQuotedMsgAvailable: false,
  1804.         subtype: contactName
  1805.     };
  1806.     Object.assign(tempMsg, extend);
  1807.     return (await Promise.all(Store.addAndSendMsgToChat(chat, tempMsg)))[1]=="success"
  1808. };
  1809.  
  1810. window.WAPI.reply = async function (chatId, body, quotedMsg) {
  1811.     if (typeof quotedMsg !== "object") quotedMsg = Store.Msg.get(quotedMsg)
  1812.     var chat = Store.Chat.get(chatId);
  1813.     if(!chat) return false;
  1814.         let extras = {};
  1815.         if(quotedMsg) {
  1816.             extras = {
  1817.                 quotedParticipant: quotedMsg.author || quotedMsg.from,
  1818.                 quotedStanzaID:quotedMsg.id.id
  1819.             };
  1820.         }
  1821.     var tempMsg = Object.create(Store.Msg.models.filter(msg => msg.__x_isSentByMe && !msg.quotedMsg)[0]);
  1822.     var newId = window.WAPI.getNewMessageId(chatId);
  1823.     var extend = {
  1824.         ack: 0,
  1825.         id: newId,
  1826.         local: !0,
  1827.         self: "out",
  1828.         t: parseInt(new Date().getTime() / 1000),
  1829.         to:  new Store.WidFactory.createWid(chatId),
  1830.         isNewMsg: !0,
  1831.         type: "chat",
  1832.         quotedMsg,
  1833.         body,
  1834.         ...extras
  1835.     };
  1836.     Object.assign(tempMsg, extend);
  1837.     const res = await Promise.all(await Store.addAndSendMsgToChat(chat, tempMsg));
  1838.     if(res[1]!='success') return false;
  1839.     return res[0].id._serialized
  1840. };
  1841.  
  1842. /**
  1843.  * Send Payment Request
  1844.  *
  1845.  * @param {string} chatId '000000000000@c.us'
  1846.  * @param {string} amount1000 The amount in base value / 10 (e.g 50000 in GBP = £50)
  1847.  * @param {string} currency Three letter currency code (e.g SAR, GBP, USD, INR, AED, EUR)
  1848.  * @param {string} note message to send with the payment request
  1849.  */
  1850. window.WAPI.sendPaymentRequest = async function (chatId, amount1000, currency, noteMessage) {
  1851.     var chat = Store.Chat.get(chatId);
  1852.     var tempMsg = Object.create(Store.Msg.models.filter(msg => msg.__x_isSentByMe && !msg.quotedMsg)[0]);
  1853.     var newId = window.WAPI.getNewMessageId(chatId);
  1854.     var extend = {
  1855.         ack: 0,
  1856.         id: newId,
  1857.         local: !0,
  1858.         self: "out",
  1859.         t: parseInt(new Date().getTime() / 1000),
  1860.         to: chatId,
  1861.         isNewMsg: !0,
  1862.         type: "payment",
  1863.         subtype: "request",
  1864.         amount1000,
  1865.         requestFrom: chatId,
  1866.         currency,
  1867.         noteMessage,
  1868.         expiryTimestamp: parseInt(new Date(new Date().setDate(new Date().getDate() + 1)).getTime() / 1000)
  1869.     };
  1870.     Object.assign(tempMsg, extend);
  1871.     await Store.addAndSendMsgToChat(chat, tempMsg)
  1872. };
  1873.  
  1874.  
  1875.  
  1876. /**
  1877.  * Send Customized VCard without the necessity of contact be a WA Contact
  1878.  *
  1879.  * @param {string} chatId '000000000000@c.us'
  1880.  * @param {object|array} vcard { displayName: 'Contact Name', vcard: 'BEGIN:VCARD
  1881. VERSION:3.0
  1882. N:;Contact Name;;;
  1883. END:VCARD' } | [{ displayName: 'Contact Name 1', vcard: 'BEGIN:VCARD
  1884. VERSION:3.0
  1885. N:;Contact Name 1;;;
  1886. END:VCARD' }, { displayName: 'Contact Name 2', vcard: 'BEGIN:VCARD
  1887. VERSION:3.0
  1888. N:;Contact Name 2;;;
  1889. END:VCARD' }]
  1890.  */
  1891. window.WAPI._sendVCard = function (chatId, vcard) {
  1892.     var chat = Store.Chat.get(chatId);
  1893.     var tempMsg = Object.create(Store.Msg.models.filter(msg => msg.__x_isSentByMe && !msg.quotedMsg)[0]);
  1894.     var newId = window.WAPI.getNewMessageId(chatId);
  1895.  
  1896.     var extend = {
  1897.         ack: 0,
  1898.         id: newId,
  1899.         local: !0,
  1900.         self: "out",
  1901.         t: parseInt(new Date().getTime() / 1000),
  1902.         to: chatId,
  1903.         isNewMsg: !0,
  1904.         isQuotedMsgAvailable:false,
  1905.     };
  1906.  
  1907.     if (Array.isArray(vcard)) {
  1908.         Object.assign(extend, {
  1909.             type: "multi_vcard",
  1910.             vcardList: vcard
  1911.         });
  1912.  
  1913.         delete extend.body;
  1914.     } else {
  1915.         Object.assign(extend, {
  1916.             type: "vcard",
  1917.             subtype: vcard.displayName,
  1918.             body: vcard.vcard
  1919.         });
  1920.  
  1921.         delete extend.vcardList;
  1922.     }
  1923.  
  1924.     Object.assign(tempMsg, extend);
  1925.  
  1926.     Store.addAndSendMsgToChat(chat, tempMsg)
  1927. };
  1928.  
  1929. /**
  1930.  * Block contact
  1931.  * @param {string} id '000000000000@c.us'
  1932.  */
  1933. window.WAPI.contactBlock = async function (id) {
  1934.     const contact = window.Store.Contact.get(id);
  1935.     if (contact !== undefined) {
  1936.         await Store.Block.blockContact(contact)
  1937.         return true;
  1938.     }
  1939.     return false;
  1940. }
  1941. /**
  1942.  * Unblock contact
  1943.  * @param {string} id '000000000000@c.us'
  1944.  */
  1945. window.WAPI.contactUnblock = async function (id) {
  1946.     const contact = window.Store.Contact.get(id);
  1947.     if (contact !== undefined) {
  1948.         await Store.Block.unblockContact(contact)
  1949.         return true;
  1950.     }
  1951.     return false;
  1952. }
  1953.  
  1954. /**
  1955.  * Remove participant of Group
  1956.  * @param {*} idGroup '0000000000-00000000@g.us'
  1957.  * @param {*} idParticipant '000000000000@c.us'
  1958.  */
  1959. window.WAPI.removeParticipant = async function (idGroup, idParticipant) {
  1960.     const chat = Store.Chat.get(idGroup);
  1961.     const rm = chat.groupMetadata.participants.get(idParticipant);
  1962.     await window.Store.Participants.removeParticipants(chat, [rm]);
  1963.     return true;
  1964. }
  1965.  
  1966.  
  1967. /**
  1968.  * Add participant to Group
  1969.  * @param {*} idGroup '0000000000-00000000@g.us'
  1970.  * @param {*} idParticipant '000000000000@c.us'
  1971.  */
  1972. window.WAPI.addParticipant = async function (idGroup, idParticipant) {
  1973.     const chat = Store.Chat.get(idGroup);
  1974.     const add = Store.Contact.get(idParticipant);
  1975.     await window.Store.Participants.addParticipants(chat, [add]);
  1976.     return true;
  1977. }
  1978.  
  1979. /**
  1980.  * Promote Participant to Admin in Group
  1981.  * @param {*} idGroup '0000000000-00000000@g.us'
  1982.  * @param {*} idParticipant '000000000000@c.us'
  1983.  */
  1984. window.WAPI.promoteParticipant = async function (idGroup, idParticipant) {
  1985.     const chat = Store.Chat.get(idGroup);
  1986.     const promote = chat.groupMetadata.participants.get(idParticipant);
  1987.     await window.Store.Participants.promoteParticipants(chat, [promote]);
  1988.     return true;
  1989. }
  1990.  
  1991. /**
  1992.  * Demote Admin of Group
  1993.  * @param {*} idGroup '0000000000-00000000@g.us'
  1994.  * @param {*} idParticipant '000000000000@c.us'
  1995.  */
  1996. window.WAPI.demoteParticipant = async function (idGroup, idParticipant) {
  1997.     await window.Store.WapQuery.demoteParticipants(idGroup, [idParticipant])
  1998.     const chat = Store.Chat.get(idGroup);
  1999.     const demote = chat.groupMetadata.participants.get(idParticipant);
  2000.     await window.Store.Participants.demoteParticipants(chat, [demote])
  2001.     return true
  2002.    
  2003. }
  2004.  
  2005. /**
  2006.  * @private
  2007.  * Send Sticker
  2008.  * @param {*} sticker
  2009.  * @param {*} chatId '000000000000@c.us'
  2010.  * @param metadata about the image. Based on [sharp metadata](https://sharp.pixelplumbing.com/api-input#metadata)
  2011.  */
  2012. window.WAPI._sendSticker = async function (sticker, chatId, metadata) {
  2013.     var chat = Store.Chat.get(chatId)
  2014.         let stick = new window.Store.Sticker.modelClass();
  2015.         stick.__x_clientUrl = sticker.clientUrl;
  2016.         stick.__x_filehash = sticker.filehash;
  2017.         stick.__x_id = sticker.filehash;
  2018.         stick.__x_uploadhash = sticker.uploadhash;
  2019.         stick.__x_mediaKey = sticker.mediaKey;
  2020.         stick.__x_initialized = false;
  2021.         stick.__x_mediaData.mediaStage = 'INIT';
  2022.         stick.mimetype = 'image/webp';
  2023.         stick.height = (metadata && metadata.height) ?  metadata.height : 512;
  2024.         stick.width = (metadata && metadata.width) ?  metadata.width : 512;
  2025.         await stick.initialize();
  2026.         return await stick.sendToChat(chat);
  2027. };
  2028.  
  2029. window.WAPI.getFileHash = async (data) => {
  2030.     let buffer = await data.arrayBuffer();
  2031.     var sha = new jsSHA("SHA-256", "ARRAYBUFFER");
  2032.     sha.update(buffer);
  2033.     return sha.getHash("B64");
  2034. };
  2035.  
  2036. window.WAPI.generateMediaKey = async (length) => {
  2037.     var result = '';
  2038.     var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  2039.     var charactersLength = characters.length;
  2040.     for ( var i = 0; i < length; i++ ) {
  2041.         result += characters.charAt(Math.floor(Math.random() * charactersLength));
  2042.     }
  2043.     return result;
  2044. };
  2045.  
  2046. /**
  2047.  * @param type: The type of file.  {'audio' | 'sticker' | 'video' | 'product' | 'document' | 'gif' | 'image' | 'ptt' | 'template' | 'history' | 'ppic'}
  2048.  * @param blob: file
  2049.  */
  2050. window.WAPI.encryptAndUploadFile = async function (type, blob) {   
  2051.     let filehash = await window.WAPI.getFileHash(blob);
  2052.     let mediaKey = await window.WAPI.generateMediaKey(32);
  2053.     let controller = new AbortController();
  2054.     let signal = controller.signal;
  2055.     let encrypted = await window.Store.UploadUtils.encryptAndUpload({
  2056.         blob,
  2057.         type,
  2058.         signal,
  2059.         mediaKey
  2060.     });
  2061.     return {
  2062.         ...encrypted,
  2063.         clientUrl: encrypted.url,
  2064.         filehash,
  2065.         id: filehash,
  2066.         uploadhash: encrypted.encFilehash,
  2067.     };
  2068. };
  2069.  
  2070. /**
  2071.  * Send Image As Sticker
  2072.  * @param {*} imageBase64 A valid webp image is required.
  2073.  * @param {*} chatId '000000000000@c.us'
  2074.  * @param metadata about the image. Based on [sharp metadata](https://sharp.pixelplumbing.com/api-input#metadata)
  2075.  */
  2076. window.WAPI.sendImageAsSticker = async function (imageBase64,chatId, metadata) {
  2077.     let mediaBlob = await window.WAPI.base64ImageToFile(
  2078.         'data:image/webp;base64,'+imageBase64,
  2079.         'file.webp'
  2080.     );
  2081.     let encrypted = await window.WAPI.encryptAndUploadFile("sticker", mediaBlob);
  2082.     return await window.WAPI._sendSticker(encrypted, chatId, metadata);
  2083. };
  2084.  
  2085. /**
  2086. This will dump all possible stickers into the chat. ONLY FOR TESTING. THIS IS REALLY ANNOYING!!
  2087.  */
  2088. window.WAPI._STICKERDUMP = async function (chatId) {
  2089.     var chat = Store.Chat.get(chatId);
  2090.     let prIdx = await Store.StickerPack.pageWithIndex(0);
  2091.     await Store.StickerPack.fetchAt(0);        
  2092.     await Store.StickerPack._pageFetchPromises[prIdx];
  2093.     return await Promise.race(Store.StickerPack.models.forEach(pack=>pack.stickers.fetch().then(_=>pack.stickers.models.forEach(stkr => stkr.sendToChat(chat))))).catch(e=>{})
  2094. }
  2095.  
  2096.  
  2097. window.WAPI.getLastSeen = async function (id) {
  2098.     if(!Store.Chat.get(id)) return false;
  2099.     let {presence} = Store.Chat.get(id)
  2100.     await presence.subscribe();
  2101.     return presence.chatstate.t;
  2102.   }
  2103.  
  2104. window.WAPI.getUseHereString = async function() {
  2105.     if (!window.l10n.localeStrings['en']){
  2106.     const originalLocale = window.l10n.getLocale();
  2107.     await window.l10n.init('en');
  2108.     await window.l10n.init(originalLocale)
  2109.   }
  2110.   return window.l10n.localeStrings[window.l10n.getLocale()][0][window.l10n.localeStrings.en[0].findIndex(x=>x.toLowerCase()==='use here')]
  2111.  }
  2112.  
  2113.  window.WAPI.getAmountOfLoadedMessages = function() {
  2114.     return Store.Msg.models.length;
  2115. }
  2116.  
  2117. WAPI.getChatWithNonContacts = async function(){
  2118.     return Store.Chat.models.map(chat=>chat.contact && !chat.contact.isMyContact ?chat.contact :null).filter(x=>x && !x.isGroup).map(WAPI._serializeContactObj)
  2119. }
  2120.  
  2121. window.WAPI.cutMsgCache = function (){
  2122.     Store.Msg.models.map(msg=>Store.Msg.remove(msg));
  2123.     return true;
  2124. }
  2125.  
  2126. window.WAPI.getHostNumber = function() {
  2127.     return WAPI.getMe().me.user;
  2128. }
  2129.  
  2130. //All of the following features can be unlocked using a license key: https://github.com/open-wa/wa-automate-nodejs#license-key
  2131. window.WAPI.getStoryStatusByTimeStamp = function(){return false;}
  2132. window.WAPI.deleteAllStatus = function(){return false;}
  2133. window.WAPI.getMyStatusArray = function(){return false;}
  2134. window.WAPI.deleteStatus = function(){return false;}
  2135. window.WAPI.setGroupToAdminsOnly = function(){return false;}
  2136. window.WAPI.setGroupEditToAdminsOnly = function(){return false;}
  2137. window.WAPI.postTextStatus = function(){return false;}
  2138. window.WAPI.postImageStatus = function(){return false;}
  2139. window.WAPI.postVideoStatus = function(){return false;}
  2140. window.WAPI.onRemovedFromGroup = function(){return false;}
  2141. window.WAPI.onContactAdded = function(){return false;}
  2142. window.WAPI.sendReplyWithMentions = function(){return false;}
  2143. window.WAPI.clearAllChats = function(){return false;}
  2144. window.WAPI.getCommonGroups = function(){return false;}
  2145. window.WAPI.setChatBackgroundColourHex = function(){return false;}
  2146. window.WAPI.darkMode = function(){return false;}
  2147. window.WAPI.onChatOpened = function(){return false;}
  2148. window.WAPI.onStory = function(){return false;}
  2149. window.WAPI.getStoryViewers = function(){return false;}
  2150. window.WAPI.onChatState = function(){return false;}
  2151. window.WAPI.getStickerDecryptable = function(){return false;}
  2152. window.WAPI.forceStaleMediaUpdate = function(){return false;}
  2153. window.WAPI.setProfilePic = function(){return false;}
  2154. window.WAPI.setGroupDescription = function(){return false;}
  2155. window.WAPI.setGroupTitle = function(){return false;}
  2156. window.WAPI.tagEveryone = function(){return false;}
  2157.  
  2158. /**
  2159.  * Patches
  2160.  */
  2161. window.WAPI.sendGiphyAsSticker = function(){return false;}
  2162. window.WAPI.getBlockedIds = function(){return false;}
  2163.  
  2164. window.WAPI.quickClean = function (ob) {
  2165.     var r = JSON.parse(JSON.stringify(ob));
  2166.     if(r.mediaData && Object.keys(r.mediaData).length==0) delete r.mediaData;
  2167.     if(r.chat && Object.keys(r.chat).length==0) delete r.chat;
  2168.     Object.keys(r).filter(k=>r[k]==""||r[k]==[]||r[k]=={}||r[k]==null).forEach(k=>delete r[k]);
  2169.     Object.keys(r).filter(k=>r[k]?r[k]._serialized:false).forEach(k=>r[k]=r[k]._serialized);
  2170.     Object.keys(r).filter(k=>r[k]?r[k].id:false).forEach(k=>r[k]=r[k].id);
  2171.     return r;
  2172. };
  2173.  
  2174. window.WAPI.pyFunc = async function (fn, done) {
  2175.     return done(await fn())
  2176. }
  2177.  
  2178. /**
  2179.  * If you're using WAPI.js outside of open-wa: https://github.com/open-wa/wa-automate-nodejs/ then you can use the following code to enable the locked features above if you've got a license keu.
  2180.  *
  2181.  * THIS WILL NOT WORK OUT OF THE BOX. YOU WILL NEED TO DISAVLE CONTENT SECURITY POLICY (WHICH IS HIGHLY DISCOURAGED AND THE MAINTAINERS OF THIS CODE ASSUME NO RESPONSIBILITY FOR AY SECURITY VUNERABILITIES RESULTING IN DISABLING CSP)
  2182.  *
  2183.  * This is meant to act as an example of how to enable new features in wapi.js. You should implement this outside of the WA WEB browser context.
  2184.  *
  2185.  * Please use google to find out how to disable CSP. You can also use this extension: https://chrome.google.com/webstore/detail/disable-content-security/ieelmcmcagommplceebfedjlakkhpden/related?hl=en
  2186.  */
  2187. window.WAPI.addLicenseKey = async function (key){
  2188.     const pkgR =  await fetch('https://raw.githubusercontent.com/open-wa/wa-automate-nodejs/master/package.json');
  2189.     const pkg = await pkgR.json();
  2190.     const body = JSON.stringify({
  2191.             number: Store.Me.me._serialized,
  2192.             key
  2193.         });
  2194.     const r = await fetch(pkg.licenseCheckUrl, {
  2195.         method: 'POST',
  2196.         mode: 'cors',
  2197.         cache: 'no-cache',
  2198.         headers: {
  2199.           'Content-Type': 'application/json'
  2200.         },
  2201.         credentials: 'same-origin',
  2202.         redirect: 'follow',
  2203.         referrerPolicy: 'no-referrer',
  2204.         body
  2205.       })
  2206.       const x = await r.text()
  2207.       return eval(x);
  2208.     }
Add Comment
Please, Sign In to add comment