2021-03-15 01:40:56 -04:00
|
|
|
// matrix-puppeteer-line - A very hacky Matrix-LINE bridge based on running LINE's Chrome extension in Puppeteer
|
2021-02-26 01:28:54 -05:00
|
|
|
// Copyright (C) 2020-2021 Tulir Asokan, Andrew Ferrazzutti
|
2020-08-17 15:03:10 -04:00
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
// Definitions and docs for methods that the Puppeteer script exposes for the content script
|
|
|
|
/**
|
|
|
|
* @param {string} text - The date string to parse
|
|
|
|
* @param {Date} [ref] - Reference date to parse relative times
|
|
|
|
* @param {{[forwardDate]: boolean}} [option] - Extra options for parser
|
|
|
|
* @return {Promise<Date>}
|
|
|
|
*/
|
2020-08-24 15:24:19 -04:00
|
|
|
window.__chronoParseDate = function (text, ref, option) {}
|
2020-08-17 15:03:10 -04:00
|
|
|
/**
|
2020-08-24 16:00:32 -04:00
|
|
|
* @param {string[]} changes - The hrefs of the chats that changed.
|
2020-08-24 15:24:19 -04:00
|
|
|
* @return {Promise<void>}
|
2020-08-17 15:03:10 -04:00
|
|
|
*/
|
2020-08-24 15:24:19 -04:00
|
|
|
window.__mautrixReceiveChanges = function (changes) {}
|
2021-04-23 03:38:13 -04:00
|
|
|
/**
|
|
|
|
* @param {string} messages - The ID of the chat receiving messages.
|
|
|
|
* @param {MessageData[]} messages - The messages added to a chat.
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
|
|
|
window.__mautrixReceiveMessages = function (chatID, messages) {}
|
2021-04-20 20:01:50 -04:00
|
|
|
/**
|
|
|
|
* @param {str} chatID - The ID of the chat whose receipts are being processed.
|
|
|
|
* @param {str} receipt_id - The ID of the most recently-read message for the current chat.
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
|
|
|
window.__mautrixReceiveReceiptDirectLatest = function (chat_id, receipt_id) {}
|
|
|
|
/**
|
|
|
|
* @param {str} chatID - The ID of the chat whose receipts are being processed.
|
|
|
|
* @param {[Receipt]} receipts - All newly-seen receipts for the current chat.
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
|
|
|
window.__mautrixReceiveReceiptMulti = function (chat_id, receipts) {}
|
2020-08-17 15:03:10 -04:00
|
|
|
/**
|
|
|
|
* @param {string} url - The URL for the QR code.
|
2020-08-24 15:24:19 -04:00
|
|
|
* @return {Promise<void>}
|
2020-08-17 15:03:10 -04:00
|
|
|
*/
|
2020-08-24 15:24:19 -04:00
|
|
|
window.__mautrixReceiveQR = function (url) {}
|
2021-02-10 02:34:19 -05:00
|
|
|
/**
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
|
|
|
window.__mautrixSendEmailCredentials = function () {}
|
|
|
|
/**
|
|
|
|
* @param {string} pin - The login PIN.
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
|
|
|
window.__mautrixReceivePIN = function (pin) {}
|
|
|
|
/**
|
|
|
|
* @param {Element} button - The button to click when a QR code or PIN expires.
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
|
|
|
window.__mautrixExpiry = function (button) {}
|
2020-08-28 12:34:13 -04:00
|
|
|
/**
|
|
|
|
* @param {number} id - The ID of the message that was sent
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
|
|
|
window.__mautrixReceiveMessageID = function(id) {}
|
2021-02-25 22:21:11 -05:00
|
|
|
/**
|
|
|
|
* @return {Promise<Element>}
|
|
|
|
*/
|
2021-04-30 01:55:51 -04:00
|
|
|
window.__mautrixShowParticipantsList = function() {}
|
2021-02-25 22:21:11 -05:00
|
|
|
|
|
|
|
const ChatTypeEnum = Object.freeze({
|
|
|
|
DIRECT: 1,
|
|
|
|
GROUP: 2,
|
|
|
|
ROOM: 3,
|
|
|
|
})
|
2020-08-17 15:03:10 -04:00
|
|
|
|
|
|
|
class MautrixController {
|
2021-04-27 02:59:16 -04:00
|
|
|
constructor() {
|
2020-08-17 15:03:10 -04:00
|
|
|
this.chatListObserver = null
|
2021-04-20 20:01:50 -04:00
|
|
|
this.msgListObserver = null
|
2021-04-23 03:38:13 -04:00
|
|
|
this.receiptObserver = null
|
2021-04-27 02:59:16 -04:00
|
|
|
|
2021-02-10 02:34:19 -05:00
|
|
|
this.qrChangeObserver = null
|
|
|
|
this.qrAppearObserver = null
|
|
|
|
this.emailAppearObserver = null
|
|
|
|
this.pinAppearObserver = null
|
|
|
|
this.expiryObserver = null
|
2021-02-25 22:21:11 -05:00
|
|
|
this.ownID = null
|
2021-04-27 02:59:16 -04:00
|
|
|
|
|
|
|
this.ownMsgPromise = Promise.resolve(-1)
|
|
|
|
this._promiseOwnMsgReset()
|
2021-02-25 22:21:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
setOwnID(ownID) {
|
|
|
|
// Remove characters that will conflict with mxid grammar
|
|
|
|
const suffix = ownID.slice(1).replace(":", "_ON_")
|
|
|
|
this.ownID = `_OWN_${suffix}`
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO Commonize with Node context
|
|
|
|
getChatType(id) {
|
|
|
|
switch (id.charAt(0)) {
|
|
|
|
case "u":
|
|
|
|
return ChatTypeEnum.DIRECT
|
|
|
|
case "c":
|
|
|
|
return ChatTypeEnum.GROUP
|
|
|
|
case "r":
|
|
|
|
return ChatTypeEnum.ROOM
|
|
|
|
default:
|
|
|
|
throw `Invalid chat ID: ${id}`
|
|
|
|
}
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
|
2021-04-23 03:38:13 -04:00
|
|
|
getCurrentChatID() {
|
2021-04-20 20:01:50 -04:00
|
|
|
const chatListElement = document.querySelector("#_chat_list_body > .ExSelected > .chatList")
|
2021-04-23 03:38:13 -04:00
|
|
|
return chatListElement ? this.getChatListItemID(chatListElement) : null
|
2021-04-20 20:01:50 -04:00
|
|
|
}
|
|
|
|
|
2020-08-17 15:03:10 -04:00
|
|
|
/**
|
|
|
|
* Parse a date string.
|
|
|
|
*
|
|
|
|
* @param {string} text - The string to parse
|
2020-08-28 09:38:06 -04:00
|
|
|
* @param {Date} [ref] - Reference date to parse relative times
|
|
|
|
* @param {{[forwardDate]: boolean}} [option] - Extra options for parser
|
2021-04-06 01:56:46 -04:00
|
|
|
* @return {Promise<?Date>} - The date, or null if parsing failed.
|
2020-08-17 15:03:10 -04:00
|
|
|
* @private
|
|
|
|
*/
|
2020-08-28 09:38:06 -04:00
|
|
|
async _tryParseDate(text, ref, option) {
|
|
|
|
const parsed = await window.__chronoParseDate(text, ref, option)
|
2021-02-20 20:00:32 -05:00
|
|
|
return parsed ? new Date(parsed) : null
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse a date separator (mws-relative-timestamp)
|
|
|
|
*
|
2020-08-28 09:38:06 -04:00
|
|
|
* @param {string} text - The text in the mws-relative-timestamp element.
|
2020-08-17 15:03:10 -04:00
|
|
|
* @return {?Date} - The value in the date separator.
|
|
|
|
* @private
|
|
|
|
*/
|
2021-02-20 20:00:32 -05:00
|
|
|
async _tryParseDayDate(text) {
|
2020-08-17 15:03:10 -04:00
|
|
|
if (!text) {
|
|
|
|
return null
|
|
|
|
}
|
2021-02-20 20:00:32 -05:00
|
|
|
text = text.replace(/\. /, "/")
|
2020-08-17 15:03:10 -04:00
|
|
|
const now = new Date()
|
|
|
|
let newDate = await this._tryParseDate(text)
|
|
|
|
if (!newDate || newDate > now) {
|
|
|
|
const lastWeek = new Date()
|
|
|
|
lastWeek.setDate(lastWeek.getDate() - 7)
|
|
|
|
newDate = await this._tryParseDate(text, lastWeek, { forwardDate: true })
|
|
|
|
}
|
2021-02-20 20:00:32 -05:00
|
|
|
return newDate && newDate <= now ? newDate : null
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
|
2021-02-25 22:21:11 -05:00
|
|
|
/**
|
|
|
|
* Try to match a user against an entry in the friends list to get their ID.
|
|
|
|
*
|
|
|
|
* @param {Element} element - The display name of the user to find the ID for.
|
2021-04-06 01:56:46 -04:00
|
|
|
* @return {?str} - The user's ID if found.
|
2021-02-25 22:21:11 -05:00
|
|
|
*/
|
|
|
|
getUserIdFromFriendsList(senderName) {
|
|
|
|
return document.querySelector(`#contact_wrap_friends > ul > li[title='${senderName}']`)?.getAttribute("data-mid")
|
|
|
|
}
|
|
|
|
|
2020-08-17 15:03:10 -04:00
|
|
|
/**
|
|
|
|
* @typedef MessageData
|
|
|
|
* @type {object}
|
2020-08-18 09:47:06 -04:00
|
|
|
* @property {number} id - The ID of the message. Seems to be sequential.
|
|
|
|
* @property {number} timestamp - The unix timestamp of the message. Not very accurate.
|
|
|
|
* @property {boolean} is_outgoing - Whether or not this user sent the message.
|
2021-04-05 03:54:35 -04:00
|
|
|
* @property {?Participant} sender - Full data of the participant who sent the message, if needed and available.
|
|
|
|
* @property {?string} html - The HTML format of the message, if necessary.
|
|
|
|
* @property {?string} image_url - The URL to the image in the message, if it's an image-only message.
|
2021-04-20 20:01:50 -04:00
|
|
|
* @property {?int} receipt_count - The number of users who have read the message.
|
2020-08-17 15:03:10 -04:00
|
|
|
*/
|
|
|
|
|
2021-03-29 01:25:05 -04:00
|
|
|
_isLoadedImageURL(src) {
|
2021-04-05 03:54:35 -04:00
|
|
|
return src && (src.startsWith("blob:") || src.startsWith("res/"))
|
2021-03-29 01:25:05 -04:00
|
|
|
}
|
|
|
|
|
2020-08-17 15:03:10 -04:00
|
|
|
/**
|
|
|
|
* Parse a message element (mws-message-wrapper)
|
|
|
|
*
|
|
|
|
* @param {Date} date - The most recent date indicator.
|
|
|
|
* @param {Element} element - The message element.
|
2021-02-25 22:21:11 -05:00
|
|
|
* @param {int} chatType - What kind of chat this message is part of.
|
2020-08-17 15:03:10 -04:00
|
|
|
* @return {MessageData}
|
|
|
|
* @private
|
|
|
|
*/
|
2021-04-23 03:38:13 -04:00
|
|
|
async _parseMessage(date, element, chatType) {
|
2021-02-25 22:21:11 -05:00
|
|
|
const is_outgoing = element.classList.contains("mdRGT07Own")
|
|
|
|
let sender = {}
|
|
|
|
|
2021-04-20 20:01:50 -04:00
|
|
|
const receipt = element.querySelector(".mdRGT07Own .mdRGT07Read:not(.MdNonDisp)")
|
|
|
|
let receipt_count
|
|
|
|
|
2021-02-25 22:21:11 -05:00
|
|
|
// TODO Clean up participantsList access...
|
|
|
|
const participantsListSelector = "#_chat_detail_area > .mdRGT02Info ul.mdRGT13Ul"
|
|
|
|
|
|
|
|
// Don't need sender ID for direct chats, since the portal will have it already.
|
|
|
|
if (chatType == ChatTypeEnum.DIRECT) {
|
|
|
|
sender = null
|
2021-04-20 20:01:50 -04:00
|
|
|
receipt_count = is_outgoing ? (receipt ? 1 : 0) : null
|
2021-02-25 22:21:11 -05:00
|
|
|
} else if (!is_outgoing) {
|
|
|
|
sender.name = element.querySelector(".mdRGT07Body > .mdRGT07Ttl").innerText
|
|
|
|
// Room members are always friends (right?),
|
|
|
|
// so search the friend list for the sender's name
|
|
|
|
// and get their ID from there.
|
|
|
|
sender.id = this.getUserIdFromFriendsList(sender.name)
|
|
|
|
// Group members aren't necessarily friends,
|
|
|
|
// but the participant list includes their ID.
|
|
|
|
if (!sender.id) {
|
|
|
|
await window.__mautrixShowParticipantsList()
|
|
|
|
const participantsList = document.querySelector(participantsListSelector)
|
|
|
|
sender.id = participantsList.querySelector(`img[alt='${senderName}'`).parentElement.parentElement.getAttribute("data-mid")
|
|
|
|
}
|
2021-03-26 02:27:21 -04:00
|
|
|
sender.avatar = this.getParticipantListItemAvatar(element)
|
2021-04-20 20:01:50 -04:00
|
|
|
receipt_count = null
|
2021-02-25 22:21:11 -05:00
|
|
|
} else {
|
|
|
|
// TODO Get own ID and store it somewhere appropriate.
|
|
|
|
// Unable to get own ID from a room chat...
|
|
|
|
// if (chatType == ChatTypeEnum.GROUP) {
|
|
|
|
// await window.__mautrixShowParticipantsList()
|
|
|
|
// const participantsList = document.querySelector("#_chat_detail_area > .mdRGT02Info ul.mdRGT13Ul")
|
|
|
|
// // TODO The first member is always yourself, right?
|
|
|
|
// // TODO Cache this so own ID can be used later
|
|
|
|
// sender = participantsList.children[0].getAttribute("data-mid")
|
|
|
|
// }
|
|
|
|
await window.__mautrixShowParticipantsList()
|
|
|
|
const participantsList = document.querySelector(participantsListSelector)
|
|
|
|
sender.name = this.getParticipantListItemName(participantsList.children[0])
|
2021-03-26 02:27:21 -04:00
|
|
|
sender.avatar = this.getParticipantListItemAvatar(participantsList.children[0])
|
2021-02-25 22:21:11 -05:00
|
|
|
sender.id = this.ownID
|
2021-04-20 20:01:50 -04:00
|
|
|
|
|
|
|
receipt_count = receipt ? this._getReceiptCount(receipt) : null
|
2021-02-25 22:21:11 -05:00
|
|
|
}
|
|
|
|
|
2020-08-17 15:03:10 -04:00
|
|
|
const messageData = {
|
2021-02-20 20:00:32 -05:00
|
|
|
id: +element.getAttribute("data-local-id"),
|
2020-08-17 15:03:10 -04:00
|
|
|
timestamp: date ? date.getTime() : null,
|
2021-02-25 22:21:11 -05:00
|
|
|
is_outgoing: is_outgoing,
|
|
|
|
sender: sender,
|
2021-04-20 20:01:50 -04:00
|
|
|
receipt_count: receipt_count
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
2021-02-20 20:00:32 -05:00
|
|
|
const messageElement = element.querySelector(".mdRGT07Body > .mdRGT07Msg")
|
|
|
|
if (messageElement.classList.contains("mdRGT07Text")) {
|
2021-04-05 03:54:35 -04:00
|
|
|
messageData.html = messageElement.querySelector(".mdRGT07MsgTextInner")?.innerHTML
|
2021-04-02 03:21:30 -04:00
|
|
|
} else if (
|
2021-04-05 03:54:35 -04:00
|
|
|
messageElement.classList.contains("mdRGT07Image") ||
|
|
|
|
messageElement.classList.contains("mdRGT07Sticker")
|
2021-04-02 03:21:30 -04:00
|
|
|
) {
|
2021-03-28 04:23:07 -04:00
|
|
|
const img = messageElement.querySelector(".mdRGT07MsgImg > img")
|
|
|
|
if (img) {
|
2021-04-27 02:59:16 -04:00
|
|
|
let imgResolve
|
2021-03-29 01:25:05 -04:00
|
|
|
// TODO Should reject on "#_chat_message_image_failure"
|
2021-04-27 02:59:16 -04:00
|
|
|
let observer = new MutationObserver(changes => {
|
2021-03-29 01:25:05 -04:00
|
|
|
for (const change of changes) {
|
|
|
|
if (this._isLoadedImageURL(change.target.src) && observer) {
|
|
|
|
observer.disconnect()
|
|
|
|
observer = null
|
2021-04-27 02:59:16 -04:00
|
|
|
imgResolve(change.target.src)
|
2021-03-29 01:25:05 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
observer.observe(img, { attributes: true, attributeFilter: ["src"] })
|
|
|
|
|
|
|
|
if (this._isLoadedImageURL(img.src)) {
|
|
|
|
// Check for this AFTER attaching the observer, in case
|
|
|
|
// the image loaded after the img element was found but
|
|
|
|
// before the observer was attached.
|
2021-03-28 04:23:07 -04:00
|
|
|
messageData.image_url = img.src
|
2021-03-29 01:25:05 -04:00
|
|
|
observer.disconnect()
|
2021-03-28 04:23:07 -04:00
|
|
|
} else {
|
2021-04-27 02:59:16 -04:00
|
|
|
messageData.image_url = await new Promise(resolve => {
|
|
|
|
imgResolve = resolve
|
2021-03-28 04:23:07 -04:00
|
|
|
setTimeout(() => {
|
|
|
|
if (observer) {
|
|
|
|
observer.disconnect()
|
|
|
|
resolve(img.src)
|
|
|
|
}
|
2021-03-29 01:25:05 -04:00
|
|
|
}, 10000) // Longer timeout for image downloads
|
2021-03-28 04:23:07 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
return messageData
|
|
|
|
}
|
|
|
|
|
2021-04-20 20:01:50 -04:00
|
|
|
/**
|
|
|
|
* Find the number in the "Read #" receipt message.
|
|
|
|
* Don't look for "Read" specifically, to support multiple languages.
|
|
|
|
*
|
|
|
|
* @param {Element} receipt - The element containing the receipt message.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_getReceiptCount(receipt) {
|
|
|
|
const match = receipt.innerText.match(/\d+/)
|
|
|
|
return Number.parseInt(match ? match[0] : 0) || null
|
|
|
|
}
|
|
|
|
|
2021-02-20 03:47:26 -05:00
|
|
|
|
2021-04-27 02:59:16 -04:00
|
|
|
/**
|
|
|
|
* Create and store a promise that resolves when a message written
|
|
|
|
* by the user finishes getting sent.
|
|
|
|
* Accepts selectors for elements that become visible once the message
|
|
|
|
* has succeeded or failed to be sent.
|
|
|
|
*
|
|
|
|
* @param {int} timeoutLimitMillis - The maximum amount of time to wait for the message to be sent.
|
|
|
|
* @param {str} successSelector - The selector for the element that indicates the message was sent.
|
|
|
|
* @param {str} failureSelector - The selector for the element that indicates the message failed to be sent.
|
|
|
|
*/
|
2021-03-29 01:25:05 -04:00
|
|
|
promiseOwnMessage(timeoutLimitMillis, successSelector, failureSelector=null) {
|
2021-04-27 02:59:16 -04:00
|
|
|
this.promiseOwnMsgSuccessSelector = successSelector
|
|
|
|
this.promiseOwnMsgFailureSelector = failureSelector
|
2021-02-20 03:47:26 -05:00
|
|
|
|
2021-04-27 02:59:16 -04:00
|
|
|
this.ownMsgPromise = new Promise((resolve, reject) => {
|
|
|
|
this.promiseOwnMsgResolve = resolve
|
|
|
|
this.promiseOwnMsgReject = reject
|
2021-03-28 03:16:07 -04:00
|
|
|
setTimeout(() => {
|
2021-05-06 00:43:26 -04:00
|
|
|
if (this.promiseOwnMsgReject) {
|
2021-03-29 01:25:05 -04:00
|
|
|
console.log("Timeout!")
|
2021-05-06 00:43:26 -04:00
|
|
|
this._rejectOwnMessage()
|
2021-03-28 03:16:07 -04:00
|
|
|
}
|
2021-03-29 01:25:05 -04:00
|
|
|
}, timeoutLimitMillis)
|
2020-08-28 12:34:13 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-04-27 02:59:16 -04:00
|
|
|
/**
|
|
|
|
* Wait for a user-sent message to finish getting sent.
|
|
|
|
*
|
|
|
|
* @return {Promise<int>} - The ID of the sent message.
|
|
|
|
*/
|
|
|
|
async waitForOwnMessage() {
|
|
|
|
return await this.ownMsgPromise
|
|
|
|
}
|
|
|
|
|
2021-04-23 03:38:13 -04:00
|
|
|
async _tryParseMessages(msgList, chatType) {
|
2020-08-17 15:03:10 -04:00
|
|
|
const messages = []
|
2021-02-20 20:00:32 -05:00
|
|
|
let refDate = null
|
2021-04-23 03:38:13 -04:00
|
|
|
for (const child of msgList) {
|
|
|
|
if (child.classList.contains("mdRGT10Date")) {
|
|
|
|
refDate = await this._tryParseDayDate(child.firstElementChild.innerText)
|
|
|
|
} else if (child.classList.contains("MdRGT07Cont")) {
|
|
|
|
// TODO :not(.MdNonDisp) to exclude not-yet-posted messages,
|
|
|
|
// but that is unlikely to be a problem here.
|
|
|
|
// Also, offscreen times may have .MdNonDisp on them
|
2021-04-27 02:59:16 -04:00
|
|
|
// TODO Explicitly look for the most recent date element,
|
|
|
|
// as it might not have been one of the new items in msgList
|
2021-04-23 03:38:13 -04:00
|
|
|
const timeElement = child.querySelector("time")
|
|
|
|
if (timeElement) {
|
|
|
|
const messageDate = await this._tryParseDate(timeElement.innerText, refDate)
|
|
|
|
messages.push(await this._parseMessage(messageDate, child, chatType))
|
2020-08-28 12:34:13 -04:00
|
|
|
}
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return messages
|
|
|
|
}
|
|
|
|
|
2021-04-23 03:38:13 -04:00
|
|
|
/**
|
|
|
|
* Parse the message list of whatever the currently-viewed chat is.
|
|
|
|
*
|
|
|
|
* @return {[MessageData]} - A list of messages.
|
|
|
|
*/
|
|
|
|
async parseMessageList() {
|
|
|
|
const msgList = Array.from(document.querySelectorAll("#_chat_room_msg_list > div[data-local-id]"))
|
|
|
|
msgList.sort((a,b) => a.getAttribute("data-local-id") - b.getAttribute("data-local-id"))
|
|
|
|
return await this._tryParseMessages(msgList, this.getChatType(this.getCurrentChatID()))
|
|
|
|
}
|
|
|
|
|
2021-03-26 02:27:21 -04:00
|
|
|
/**
|
|
|
|
* @typedef PathImage
|
|
|
|
* @type object
|
|
|
|
* @property {string} path - The virtual path of the image (behaves like an ID)
|
|
|
|
* @property {string} src - The URL of the image
|
|
|
|
*/
|
|
|
|
|
|
|
|
_getPathImage(img) {
|
|
|
|
if (img && img.src.startsWith("blob:")) {
|
|
|
|
// NOTE Having a blob but no path means the image exists,
|
|
|
|
// but in a form that cannot be uniquely identified.
|
|
|
|
// If instead there is no blob, the image is blank.
|
|
|
|
return {
|
|
|
|
path: img.getAttribute("data-picture-path"),
|
|
|
|
url: img.src,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-17 15:03:10 -04:00
|
|
|
/**
|
|
|
|
* @typedef Participant
|
|
|
|
* @type object
|
2021-03-26 02:27:21 -04:00
|
|
|
* @property {string} id - The member ID for the participant
|
|
|
|
* @property {PathImage} avatar - The path and blob URL of the participant's avatar
|
|
|
|
* @property {string} name - The contact list name of the participant
|
2020-08-17 15:03:10 -04:00
|
|
|
*/
|
|
|
|
|
2021-02-25 22:21:11 -05:00
|
|
|
getParticipantListItemName(element) {
|
|
|
|
return element.querySelector(".mdRGT13Ttl").innerText
|
|
|
|
}
|
|
|
|
|
2021-03-26 02:27:21 -04:00
|
|
|
getParticipantListItemAvatar(element) {
|
|
|
|
return this._getPathImage(element.querySelector(".mdRGT13Img img[src]"))
|
2021-03-23 02:37:30 -04:00
|
|
|
}
|
|
|
|
|
2021-04-23 03:38:13 -04:00
|
|
|
getParticipantListItemID(element) {
|
2021-02-25 22:21:11 -05:00
|
|
|
// TODO Cache own ID
|
|
|
|
return element.getAttribute("data-mid")
|
|
|
|
}
|
|
|
|
|
2020-08-17 15:03:10 -04:00
|
|
|
/**
|
2021-02-16 02:49:54 -05:00
|
|
|
* Parse a group participants list.
|
|
|
|
* TODO Find what works for a *room* participants list...!
|
2020-08-17 15:03:10 -04:00
|
|
|
*
|
|
|
|
* @param {Element} element - The participant list element.
|
|
|
|
* @return {[Participant]} - The list of participants.
|
|
|
|
*/
|
|
|
|
parseParticipantList(element) {
|
2021-02-25 22:21:11 -05:00
|
|
|
// TODO Might need to explicitly exclude own user if double-puppeting is enabled.
|
|
|
|
// TODO The first member is always yourself, right?
|
|
|
|
const ownParticipant = {
|
|
|
|
// TODO Find way to make this work with multiple mxids using the bridge.
|
|
|
|
// One idea is to add real ID as suffix if we're in a group, and
|
|
|
|
// put in the puppet DB table somehow.
|
|
|
|
id: this.ownID,
|
2021-03-26 02:27:21 -04:00
|
|
|
avatar: this.getParticipantListItemAvatar(element.children[0]),
|
2021-02-25 22:21:11 -05:00
|
|
|
name: this.getParticipantListItemName(element.children[0]),
|
|
|
|
}
|
|
|
|
|
|
|
|
return [ownParticipant].concat(Array.from(element.children).slice(1).map(child => {
|
|
|
|
const name = this.getParticipantListItemName(child)
|
2021-04-23 03:38:13 -04:00
|
|
|
const id = this.getParticipantListItemID(child) || this.getUserIdFromFriendsList(name)
|
2021-02-16 02:49:54 -05:00
|
|
|
return {
|
2021-02-25 22:21:11 -05:00
|
|
|
id: id, // NOTE Don't want non-own user's ID to ever be null.
|
2021-03-26 02:27:21 -04:00
|
|
|
avatar: this.getParticipantListItemAvatar(child),
|
2021-02-25 22:21:11 -05:00
|
|
|
name: name,
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
2021-02-25 22:21:11 -05:00
|
|
|
}))
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef ChatListInfo
|
|
|
|
* @type object
|
2021-03-26 02:27:21 -04:00
|
|
|
* @property {number} id - The ID of the chat.
|
|
|
|
* @property {string} name - The name of the chat.
|
|
|
|
* @property {PathImage} icon - The path and blob URL of the chat icon.
|
2020-08-18 09:47:06 -04:00
|
|
|
* @property {string} lastMsg - The most recent message in the chat.
|
|
|
|
* May be prefixed by sender name.
|
2020-08-24 16:00:32 -04:00
|
|
|
* @property {string} lastMsgDate - An imprecise date for the most recent message
|
|
|
|
* (e.g. "7:16 PM", "Thu" or "Aug 4")
|
2020-08-17 15:03:10 -04:00
|
|
|
*/
|
|
|
|
|
2021-04-23 03:38:13 -04:00
|
|
|
getChatListItemID(element) {
|
2021-02-16 02:49:54 -05:00
|
|
|
return element.getAttribute("data-chatid")
|
|
|
|
}
|
|
|
|
|
|
|
|
getChatListItemName(element) {
|
|
|
|
return element.querySelector(".mdCMN04Ttl").innerText
|
|
|
|
}
|
|
|
|
|
2021-03-26 02:27:21 -04:00
|
|
|
getChatListItemIcon(element) {
|
|
|
|
return this._getPathImage(element.querySelector(".mdCMN04Img > :not(.mdCMN04ImgInner) > img[src]"))
|
2021-03-23 02:37:30 -04:00
|
|
|
}
|
|
|
|
|
2021-02-16 02:49:54 -05:00
|
|
|
getChatListItemLastMsg(element) {
|
|
|
|
return element.querySelector(".mdCMN04Desc").innerText
|
|
|
|
}
|
|
|
|
|
|
|
|
getChatListItemLastMsgDate(element) {
|
|
|
|
return element.querySelector("time").innerText
|
|
|
|
}
|
|
|
|
|
2020-08-17 15:03:10 -04:00
|
|
|
/**
|
2021-02-16 02:49:54 -05:00
|
|
|
* Parse a conversation list item element.
|
2020-08-17 15:03:10 -04:00
|
|
|
*
|
|
|
|
* @param {Element} element - The element to parse.
|
2021-04-23 03:38:13 -04:00
|
|
|
* @param {?string} knownID - The ID of this element, if it is known.
|
2021-04-06 01:56:46 -04:00
|
|
|
* @return {ChatListInfo} - The info in the element.
|
2020-08-17 15:03:10 -04:00
|
|
|
*/
|
2021-04-23 03:38:13 -04:00
|
|
|
parseChatListItem(element, knownID) {
|
2021-02-25 23:59:25 -05:00
|
|
|
return !element.classList.contains("chatList") ? null : {
|
2021-04-23 03:38:13 -04:00
|
|
|
id: knownID || this.getChatListItemID(element),
|
2021-02-16 02:49:54 -05:00
|
|
|
name: this.getChatListItemName(element),
|
2021-03-26 02:27:21 -04:00
|
|
|
icon: this.getChatListItemIcon(element),
|
2021-02-16 02:49:54 -05:00
|
|
|
lastMsg: this.getChatListItemLastMsg(element),
|
|
|
|
lastMsgDate: this.getChatListItemLastMsgDate(element),
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-02-21 02:07:48 -05:00
|
|
|
* Parse the list of recent/saved chats.
|
2020-08-17 15:03:10 -04:00
|
|
|
* @return {[ChatListInfo]} - The list of chats.
|
|
|
|
*/
|
2021-02-21 02:07:48 -05:00
|
|
|
parseChatList() {
|
|
|
|
const chatList = document.querySelector("#_chat_list_body")
|
|
|
|
return Array.from(chatList.children).map(
|
2021-02-16 02:49:54 -05:00
|
|
|
child => this.parseChatListItem(child.firstElementChild))
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-02-25 23:59:25 -05:00
|
|
|
* TODO
|
2020-08-17 15:03:10 -04:00
|
|
|
* Check if an image has been downloaded.
|
|
|
|
*
|
|
|
|
* @param {number} id - The ID of the message whose image to check.
|
|
|
|
* @return {boolean} - Whether or not the image has been downloaded
|
|
|
|
*/
|
|
|
|
imageExists(id) {
|
2020-08-24 15:24:19 -04:00
|
|
|
const imageElement = document.querySelector(
|
|
|
|
`mws-message-wrapper[msg-id="${id}"] mws-image-message-part .image-msg`)
|
|
|
|
return !imageElement.classList.contains("not-rendered")
|
|
|
|
&& imageElement.getAttribute("src") !== ""
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-03-26 02:27:21 -04:00
|
|
|
* Download an image at a given URL and return it as a data URL.
|
2020-08-17 15:03:10 -04:00
|
|
|
*
|
2021-03-26 02:27:21 -04:00
|
|
|
* @param {string} url - The URL of the image to download.
|
2020-08-17 15:03:10 -04:00
|
|
|
* @return {Promise<string>} - The data URL (containing the mime type and base64 data)
|
|
|
|
*/
|
2021-03-26 02:27:21 -04:00
|
|
|
async readImage(url) {
|
|
|
|
const resp = await fetch(url)
|
2020-08-17 15:03:10 -04:00
|
|
|
const reader = new FileReader()
|
|
|
|
const promise = new Promise((resolve, reject) => {
|
|
|
|
reader.onload = () => resolve(reader.result)
|
|
|
|
reader.onerror = reject
|
|
|
|
})
|
|
|
|
reader.readAsDataURL(await resp.blob())
|
|
|
|
return promise
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {[MutationRecord]} mutations - The mutation records that occurred
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_observeChatListMutations(mutations) {
|
2021-02-25 23:59:25 -05:00
|
|
|
// TODO Observe *added/removed* chats, not just new messages
|
2020-08-17 15:03:10 -04:00
|
|
|
const changedChatIDs = new Set()
|
|
|
|
for (const change of mutations) {
|
|
|
|
console.debug("Chat list mutation:", change)
|
2021-02-25 23:59:25 -05:00
|
|
|
if (change.target.id == "_chat_list_body") {
|
|
|
|
// TODO
|
|
|
|
// These could be new chats, or they're
|
|
|
|
// existing ones that just moved around.
|
|
|
|
/*
|
|
|
|
for (const node of change.addedNodes) {
|
|
|
|
}
|
|
|
|
*/
|
2021-04-23 03:38:13 -04:00
|
|
|
} else if (change.target.tagName == "LI") {
|
2021-04-27 02:59:16 -04:00
|
|
|
if (change.target.classList.contains("ExSelected")) {
|
2021-04-23 03:38:13 -04:00
|
|
|
console.log("Not using chat list mutation response for currently-active chat")
|
|
|
|
continue
|
|
|
|
}
|
2021-02-25 23:59:25 -05:00
|
|
|
for (const node of change.addedNodes) {
|
|
|
|
const chat = this.parseChatListItem(node)
|
|
|
|
if (chat) {
|
2021-04-27 02:59:16 -04:00
|
|
|
console.log("Changed chat list item:", chat)
|
2021-02-25 23:59:25 -05:00
|
|
|
changedChatIDs.add(chat.id)
|
|
|
|
} else {
|
|
|
|
console.debug("Could not parse node as a chat list item:", node)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// change.removedNodes tells you which chats that had notifications are now read.
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
if (changedChatIDs.size > 0) {
|
|
|
|
console.debug("Dispatching chat list mutations:", changedChatIDs)
|
2020-08-24 16:00:32 -04:00
|
|
|
window.__mautrixReceiveChanges(Array.from(changedChatIDs)).then(
|
|
|
|
() => console.debug("Chat list mutations dispatched"),
|
|
|
|
err => console.error("Error dispatching chat list mutations:", err))
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-02-25 23:59:25 -05:00
|
|
|
* Add a mutation observer to the chat list.
|
2020-08-17 15:03:10 -04:00
|
|
|
*/
|
2021-02-25 23:59:25 -05:00
|
|
|
addChatListObserver() {
|
2021-04-23 03:38:13 -04:00
|
|
|
this.removeChatListObserver()
|
2021-04-27 02:59:16 -04:00
|
|
|
this.chatListObserver = new MutationObserver(async (mutations) => {
|
|
|
|
// Wait for pending sent messages to be resolved before responding to mutations
|
|
|
|
try {
|
|
|
|
await this.ownMsgPromise
|
|
|
|
} catch (e) {}
|
|
|
|
|
2020-08-24 16:00:32 -04:00
|
|
|
try {
|
|
|
|
this._observeChatListMutations(mutations)
|
|
|
|
} catch (err) {
|
|
|
|
console.error("Error observing chat list mutations:", err)
|
|
|
|
}
|
|
|
|
})
|
2021-02-25 23:59:25 -05:00
|
|
|
this.chatListObserver.observe(
|
|
|
|
document.querySelector("#_chat_list_body"),
|
|
|
|
{ childList: true, subtree: true })
|
2020-08-17 15:03:10 -04:00
|
|
|
console.debug("Started chat list observer")
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disconnect the most recently added mutation observer.
|
|
|
|
*/
|
|
|
|
removeChatListObserver() {
|
|
|
|
if (this.chatListObserver !== null) {
|
|
|
|
this.chatListObserver.disconnect()
|
|
|
|
this.chatListObserver = null
|
|
|
|
console.debug("Disconnected chat list observer")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-20 20:01:50 -04:00
|
|
|
/**
|
|
|
|
* @param {[MutationRecord]} mutations - The mutation records that occurred
|
2021-04-30 01:55:51 -04:00
|
|
|
* @param {str} chatID - The ID of the chat being observed.
|
2021-04-20 20:01:50 -04:00
|
|
|
* @private
|
|
|
|
*/
|
2021-04-30 01:55:51 -04:00
|
|
|
_observeReceiptsDirect(mutations, chatID) {
|
2021-04-20 20:01:50 -04:00
|
|
|
let receipt_id
|
|
|
|
for (const change of mutations) {
|
|
|
|
if ( change.target.classList.contains("mdRGT07Read") &&
|
|
|
|
!change.target.classList.contains("MdNonDisp")) {
|
|
|
|
const msgElement = change.target.closest(".mdRGT07Own")
|
|
|
|
if (msgElement) {
|
|
|
|
let id = +msgElement.getAttribute("data-local-id")
|
|
|
|
if (!receipt_id || receipt_id < id) {
|
|
|
|
receipt_id = id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (receipt_id) {
|
2021-04-30 01:55:51 -04:00
|
|
|
window.__mautrixReceiveReceiptDirectLatest(chatID, receipt_id).then(
|
2021-04-20 20:01:50 -04:00
|
|
|
() => console.debug(`Receipt sent for message ${receipt_id}`),
|
|
|
|
err => console.error(`Error sending receipt for message ${receipt_id}:`, err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {[MutationRecord]} mutations - The mutation records that occurred
|
2021-04-30 01:55:51 -04:00
|
|
|
* @param {str} chatID - The ID of the chat being observed.
|
2021-04-20 20:01:50 -04:00
|
|
|
* @private
|
|
|
|
*/
|
2021-04-30 01:55:51 -04:00
|
|
|
_observeReceiptsMulti(mutations, chatID) {
|
2021-04-23 03:38:13 -04:00
|
|
|
const ids = new Set()
|
2021-04-20 20:01:50 -04:00
|
|
|
const receipts = []
|
|
|
|
for (const change of mutations) {
|
2021-04-30 01:55:51 -04:00
|
|
|
const target = change.type == "characterData" ? change.target.parentElement : change.target
|
|
|
|
if ( change.target.classList.contains("mdRGT07Read") &&
|
|
|
|
!change.target.classList.contains("MdNonDisp"))
|
|
|
|
{
|
2021-04-20 20:01:50 -04:00
|
|
|
const msgElement = change.target.closest(".mdRGT07Own")
|
|
|
|
if (msgElement) {
|
2021-04-23 03:38:13 -04:00
|
|
|
const id = +msgElement.getAttribute("data-local-id")
|
|
|
|
if (!ids.has(id)) {
|
|
|
|
ids.add(id)
|
|
|
|
receipts.push({
|
|
|
|
id: id,
|
|
|
|
count: this._getReceiptCount(change.target),
|
|
|
|
})
|
|
|
|
}
|
2021-04-20 20:01:50 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (receipts.length > 0) {
|
2021-04-30 01:55:51 -04:00
|
|
|
window.__mautrixReceiveReceiptMulti(chatID, receipts).then(
|
2021-04-23 03:38:13 -04:00
|
|
|
() => console.debug(`Receipts sent for ${receipts.length} messages`),
|
|
|
|
err => console.error(`Error sending receipts for ${receipts.length} messages`, err))
|
2021-04-20 20:01:50 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-04-23 03:38:13 -04:00
|
|
|
* Add a mutation observer to the message list of the current chat.
|
|
|
|
* Used for observing new messages & read receipts.
|
2021-04-20 20:01:50 -04:00
|
|
|
*/
|
2021-04-23 03:38:13 -04:00
|
|
|
addMsgListObserver() {
|
2021-04-20 20:01:50 -04:00
|
|
|
const chat_room_msg_list = document.querySelector("#_chat_room_msg_list")
|
|
|
|
if (!chat_room_msg_list) {
|
|
|
|
console.debug("Could not start msg list observer: no msg list available!")
|
|
|
|
return
|
|
|
|
}
|
2021-04-23 03:38:13 -04:00
|
|
|
this.removeMsgListObserver()
|
|
|
|
|
|
|
|
const chatID = this.getCurrentChatID()
|
|
|
|
const chatType = this.getChatType(chatID)
|
2021-04-20 20:01:50 -04:00
|
|
|
|
2021-04-27 02:59:16 -04:00
|
|
|
let orderedPromises = [Promise.resolve()]
|
|
|
|
this.msgListObserver = new MutationObserver(changes => {
|
|
|
|
let msgList = []
|
2021-04-23 03:38:13 -04:00
|
|
|
for (const change of changes) {
|
2021-04-27 02:59:16 -04:00
|
|
|
change.addedNodes.forEach(child => {
|
|
|
|
if (child.tagName == "DIV" && child.hasAttribute("data-local-id")) {
|
|
|
|
msgList.push(child)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (msgList.length == 0) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
msgList.sort((a,b) => a.getAttribute("data-local-id") - b.getAttribute("data-local-id"))
|
|
|
|
if (!this._observeOwnMessage(msgList)) {
|
|
|
|
let prevPromise = orderedPromises.shift()
|
|
|
|
orderedPromises.push(new Promise(resolve => prevPromise
|
|
|
|
.then(() => this._tryParseMessages(msgList, chatType))
|
|
|
|
.then(msgs => window.__mautrixReceiveMessages(chatID, msgs))
|
|
|
|
.then(() => resolve())
|
|
|
|
))
|
2021-04-23 03:38:13 -04:00
|
|
|
}
|
|
|
|
})
|
2021-04-27 02:59:16 -04:00
|
|
|
this.msgListObserver.observe(
|
|
|
|
chat_room_msg_list,
|
2021-04-23 03:38:13 -04:00
|
|
|
{ childList: true })
|
|
|
|
|
|
|
|
console.debug("Started msg list observer")
|
|
|
|
|
|
|
|
|
|
|
|
const observeReadReceipts = (
|
|
|
|
chatType == ChatTypeEnum.DIRECT ?
|
2021-04-20 20:01:50 -04:00
|
|
|
this._observeReceiptsDirect :
|
|
|
|
this._observeReceiptsMulti
|
2021-04-23 03:38:13 -04:00
|
|
|
).bind(this)
|
2021-04-20 20:01:50 -04:00
|
|
|
|
2021-04-23 03:38:13 -04:00
|
|
|
this.receiptObserver = new MutationObserver(changes => {
|
2021-04-20 20:01:50 -04:00
|
|
|
try {
|
2021-04-23 03:38:13 -04:00
|
|
|
observeReadReceipts(changes, chatID)
|
2021-04-20 20:01:50 -04:00
|
|
|
} catch (err) {
|
|
|
|
console.error("Error observing msg list mutations:", err)
|
|
|
|
}
|
|
|
|
})
|
2021-04-23 03:38:13 -04:00
|
|
|
this.receiptObserver.observe(
|
2021-04-30 01:55:51 -04:00
|
|
|
chat_room_msg_list, {
|
|
|
|
subtree: true,
|
|
|
|
attributes: true,
|
|
|
|
attributeFilter: ["class"],
|
|
|
|
// TODO Consider using the same observer to watch for "ⓘ Decrypting..."
|
|
|
|
characterData: chatType != ChatTypeEnum.DIRECT,
|
|
|
|
})
|
2021-04-23 03:38:13 -04:00
|
|
|
|
|
|
|
console.debug("Started receipt observer")
|
2021-04-20 20:01:50 -04:00
|
|
|
}
|
|
|
|
|
2021-04-27 02:59:16 -04:00
|
|
|
_observeOwnMessage(msgList) {
|
2021-05-06 00:43:26 -04:00
|
|
|
if (!this.promiseOwnMsgSuccessSelector) {
|
2021-04-27 02:59:16 -04:00
|
|
|
// Not waiting for a pending sent message
|
|
|
|
return false
|
|
|
|
}
|
2021-05-06 00:43:26 -04:00
|
|
|
if (this.visibleSuccessObserver) {
|
2021-04-27 02:59:16 -04:00
|
|
|
// Already found a element that we're waiting on becoming visible
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const ownMsg of msgList.filter(msg => msg.classList.contains("mdRGT07Own"))) {
|
|
|
|
const successElement =
|
|
|
|
ownMsg.querySelector(this.promiseOwnMsgSuccessSelector)
|
|
|
|
if (successElement) {
|
|
|
|
if (successElement.classList.contains("MdNonDisp")) {
|
2021-05-06 00:43:26 -04:00
|
|
|
console.log("Invisible success")
|
2021-04-27 02:59:16 -04:00
|
|
|
console.log(successElement)
|
|
|
|
} else {
|
|
|
|
console.debug("Already visible success, must not be it")
|
|
|
|
console.debug(successElement)
|
2021-05-06 00:43:26 -04:00
|
|
|
continue
|
2021-04-27 02:59:16 -04:00
|
|
|
}
|
2021-05-06 00:43:26 -04:00
|
|
|
} else {
|
|
|
|
continue
|
2021-04-27 02:59:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
const failureElement =
|
|
|
|
this.promiseOwnMsgFailureSelector &&
|
|
|
|
ownMsg.querySelector(this.promiseOwnMsgFailureSelector)
|
|
|
|
if (failureElement) {
|
|
|
|
if (failureElement.classList.contains("MdNonDisp")) {
|
2021-05-06 00:43:26 -04:00
|
|
|
console.log("Invisible failure")
|
2021-04-27 02:59:16 -04:00
|
|
|
console.log(failureElement)
|
|
|
|
} else {
|
|
|
|
console.debug("Already visible failure, must not be it")
|
|
|
|
console.log(failureElement)
|
2021-05-06 00:43:26 -04:00
|
|
|
continue
|
2021-04-27 02:59:16 -04:00
|
|
|
}
|
2021-05-06 00:43:26 -04:00
|
|
|
} else if (this.promiseOwnMsgFailureSelector) {
|
|
|
|
continue
|
2021-04-27 02:59:16 -04:00
|
|
|
}
|
|
|
|
|
2021-05-06 00:43:26 -04:00
|
|
|
console.log("Found invisible element, wait")
|
|
|
|
const msgID = +ownMsg.getAttribute("data-local-id")
|
|
|
|
this.visibleSuccessObserver = new MutationObserver(
|
|
|
|
this._getOwnVisibleCallback(msgID))
|
|
|
|
this.visibleSuccessObserver.observe(
|
|
|
|
successElement,
|
|
|
|
{ attributes: true, attributeFilter: ["class"] })
|
|
|
|
|
|
|
|
if (this.promiseOwnMsgFailureSelector) {
|
|
|
|
this.visibleFailureObserver = new MutationObserver(
|
|
|
|
this._getOwnVisibleCallback())
|
|
|
|
this.visibleFailureObserver.observe(
|
|
|
|
failureElement,
|
|
|
|
{ attributes: true, attributeFilter: ["class"] })
|
2021-04-27 02:59:16 -04:00
|
|
|
}
|
2021-05-06 00:43:26 -04:00
|
|
|
|
|
|
|
return true
|
2021-04-27 02:59:16 -04:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
_getOwnVisibleCallback(msgID=null) {
|
|
|
|
const isSuccess = !!msgID
|
|
|
|
return changes => {
|
|
|
|
for (const change of changes) {
|
|
|
|
if (!change.target.classList.contains("MdNonDisp")) {
|
|
|
|
console.log(`Waited for visible ${isSuccess ? "success" : "failure"}`)
|
|
|
|
console.log(change.target)
|
|
|
|
isSuccess ? this._resolveOwnMessage(msgID) : this._rejectOwnMessage(change.target)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_resolveOwnMessage(msgID) {
|
2021-05-06 00:43:26 -04:00
|
|
|
if (!this.promiseOwnMsgResolve) return
|
2021-04-27 02:59:16 -04:00
|
|
|
const resolve = this.promiseOwnMsgResolve
|
|
|
|
this._promiseOwnMsgReset()
|
|
|
|
|
|
|
|
window.__mautrixReceiveMessageID(msgID).then(
|
|
|
|
() => resolve(msgID))
|
|
|
|
}
|
|
|
|
|
2021-05-06 00:43:26 -04:00
|
|
|
_rejectOwnMessage(failureElement = null) {
|
|
|
|
if (!this.promiseOwnMsgReject) return
|
2021-04-27 02:59:16 -04:00
|
|
|
const reject = this.promiseOwnMsgReject
|
|
|
|
this._promiseOwnMsgReset()
|
|
|
|
|
|
|
|
reject(failureElement)
|
|
|
|
}
|
|
|
|
|
|
|
|
_promiseOwnMsgReset() {
|
|
|
|
this.promiseOwnMsgSuccessSelector = null
|
|
|
|
this.promiseOwnMsgFailureSelector = null
|
|
|
|
this.promiseOwnMsgResolve = null
|
|
|
|
this.promiseOwnMsgReject = null
|
|
|
|
|
|
|
|
if (this.visibleSuccessObserver) {
|
|
|
|
this.visibleSuccessObserver.disconnect()
|
|
|
|
}
|
|
|
|
this.visibleSuccessObserver = null
|
|
|
|
if (this.visibleFailureObserver) {
|
|
|
|
this.visibleFailureObserver.disconnect()
|
|
|
|
}
|
|
|
|
this.visibleFailureObserver = null
|
|
|
|
}
|
|
|
|
|
2021-04-20 20:01:50 -04:00
|
|
|
removeMsgListObserver() {
|
2021-04-23 03:38:13 -04:00
|
|
|
let result = false
|
2021-04-20 20:01:50 -04:00
|
|
|
if (this.msgListObserver !== null) {
|
|
|
|
this.msgListObserver.disconnect()
|
|
|
|
this.msgListObserver = null
|
|
|
|
console.debug("Disconnected msg list observer")
|
2021-04-23 03:38:13 -04:00
|
|
|
result = true
|
|
|
|
}
|
|
|
|
if (this.receiptObserver !== null) {
|
|
|
|
this.receiptObserver.disconnect()
|
|
|
|
this.receiptObserver = null
|
|
|
|
console.debug("Disconnected receipt observer")
|
|
|
|
result = true
|
2021-04-20 20:01:50 -04:00
|
|
|
}
|
2021-04-23 03:38:13 -04:00
|
|
|
return result
|
2021-04-20 20:01:50 -04:00
|
|
|
}
|
|
|
|
|
2021-02-10 02:34:19 -05:00
|
|
|
addQRChangeObserver(element) {
|
2021-04-23 03:38:13 -04:00
|
|
|
this.removeQRChangeObserver()
|
2021-02-10 02:34:19 -05:00
|
|
|
this.qrChangeObserver = new MutationObserver(changes => {
|
|
|
|
for (const change of changes) {
|
|
|
|
if (change.attributeName === "title" && change.target instanceof Element) {
|
|
|
|
window.__mautrixReceiveQR(change.target.getAttribute("title"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
this.qrChangeObserver.observe(element, {
|
|
|
|
attributes: true,
|
|
|
|
attributeFilter: ["title"],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
removeQRChangeObserver() {
|
|
|
|
if (this.qrChangeObserver !== null) {
|
|
|
|
this.qrChangeObserver.disconnect()
|
|
|
|
this.qrChangeObserver = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addQRAppearObserver(element) {
|
2021-04-23 03:38:13 -04:00
|
|
|
this.removeQRAppearObserver()
|
2021-02-10 02:34:19 -05:00
|
|
|
this.qrAppearObserver = new MutationObserver(changes => {
|
|
|
|
for (const change of changes) {
|
|
|
|
for (const node of change.addedNodes) {
|
|
|
|
const qrElement = node.querySelector("#login_qrcode_area div[title]")
|
|
|
|
if (qrElement) {
|
|
|
|
window.__mautrixReceiveQR(qrElement.title)
|
|
|
|
window.__mautrixController.addQRChangeObserver(element)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
this.qrAppearObserver.observe(element, {
|
|
|
|
childList: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
removeQRAppearObserver() {
|
|
|
|
if (this.qrAppearObserver !== null) {
|
|
|
|
this.qrAppearObserver.disconnect()
|
|
|
|
this.qrAppearObserver = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-16 02:49:54 -05:00
|
|
|
addEmailAppearObserver(element) {
|
2021-04-23 03:38:13 -04:00
|
|
|
this.removeEmailAppearObserver()
|
2021-02-10 02:34:19 -05:00
|
|
|
this.emailAppearObserver = new MutationObserver(changes => {
|
2020-08-17 15:03:10 -04:00
|
|
|
for (const change of changes) {
|
2021-02-10 02:34:19 -05:00
|
|
|
for (const node of change.addedNodes) {
|
2021-02-11 00:04:25 -05:00
|
|
|
const emailArea = node.querySelector("#login_email_area")
|
2021-02-20 03:48:08 -05:00
|
|
|
if (emailArea && !emailArea.classList.contains("MdNonDisp")) {
|
2021-02-10 02:34:19 -05:00
|
|
|
window.__mautrixSendEmailCredentials()
|
|
|
|
return
|
|
|
|
}
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2021-02-10 02:34:19 -05:00
|
|
|
this.emailAppearObserver.observe(element, {
|
|
|
|
childList: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
removeEmailAppearObserver() {
|
|
|
|
if (this.emailAppearObserver !== null) {
|
|
|
|
this.emailAppearObserver.disconnect()
|
|
|
|
this.emailAppearObserver = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-16 02:49:54 -05:00
|
|
|
addPINAppearObserver(element) {
|
2021-04-23 03:38:13 -04:00
|
|
|
this.removePINAppearObserver()
|
2021-02-10 02:34:19 -05:00
|
|
|
this.pinAppearObserver = new MutationObserver(changes => {
|
|
|
|
for (const change of changes) {
|
|
|
|
for (const node of change.addedNodes) {
|
|
|
|
const pinElement = node.querySelector("div.mdCMN01Code")
|
|
|
|
if (pinElement) {
|
|
|
|
window.__mautrixReceivePIN(pinElement.innerText)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
this.pinAppearObserver.observe(element, {
|
|
|
|
childList: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
removePINAppearObserver() {
|
|
|
|
if (this.pinAppearObserver !== null) {
|
|
|
|
this.pinAppearObserver.disconnect()
|
|
|
|
this.pinAppearObserver = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addExpiryObserver(element) {
|
2021-04-23 03:38:13 -04:00
|
|
|
this.removeExpiryObserver()
|
2021-02-10 02:34:19 -05:00
|
|
|
const button = element.querySelector("dialog button")
|
|
|
|
this.expiryObserver = new MutationObserver(changes => {
|
2021-02-20 03:48:08 -05:00
|
|
|
if (changes.length == 1 && !changes[0].target.classList.contains("MdNonDisp")) {
|
2021-02-10 02:34:19 -05:00
|
|
|
window.__mautrixExpiry(button)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
this.expiryObserver.observe(element, {
|
2020-08-17 15:03:10 -04:00
|
|
|
attributes: true,
|
2021-02-10 02:34:19 -05:00
|
|
|
attributeFilter: ["class"],
|
2020-08-17 15:03:10 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-02-10 02:34:19 -05:00
|
|
|
removeExpiryObserver() {
|
|
|
|
if (this.expiryObserver !== null) {
|
|
|
|
this.expiryObserver.disconnect()
|
|
|
|
this.expiryObserver = null
|
2020-08-17 15:03:10 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
window.__mautrixController = new MautrixController()
|