From fdd3a1d6b7e38ccb2170f55423ba3b6a7a98e3bd Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Sat, 20 Feb 2021 03:47:26 -0500 Subject: [PATCH] Message sending --- mautrix_line/portal.py | 40 ++++++++++++----------- puppet/src/contentscript.js | 64 +++++++++++++++++++++++++------------ puppet/src/puppet.js | 35 +++++++++++++++----- 3 files changed, 92 insertions(+), 47 deletions(-) diff --git a/mautrix_line/portal.py b/mautrix_line/portal.py index ca4d174..af408e5 100644 --- a/mautrix_line/portal.py +++ b/mautrix_line/portal.py @@ -127,10 +127,14 @@ class Portal(DBPortal, BasePortal): # TODO media return message_id = await sender.client.send(self.chat_id, text) - msg = DBMessage(mxid=event_id, mx_room=self.mxid, mid=message_id, chat_id=self.chat_id) - await msg.insert() - await self._send_delivery_receipt(event_id) - self.log.debug(f"Handled Matrix message {event_id} -> {message_id}") + # TODO Handle message-send timeouts better + if message_id != -1: + msg = DBMessage(mxid=event_id, mx_room=self.mxid, mid=message_id, chat_id=self.chat_id) + await msg.insert() + await self._send_delivery_receipt(event_id) + self.log.debug(f"Handled Matrix message {event_id} -> {message_id}") + else: + self.log.warning(f"Handled Matrix message {event_id} -> {message_id}") async def handle_matrix_leave(self, user: 'u.User') -> None: if self.is_direct: @@ -355,20 +359,20 @@ class Portal(DBPortal, BasePortal): "type": "m.room.related_groups", "content": {"groups": [self.config["appservice.community_id"]]}, }) - initial_state.append({ - "type": str(EventType.ROOM_POWER_LEVELS), - "content": { - "users": { - self.az.bot_mxid: 100, - self.main_intent.mxid: 9001, - }, - "events": {}, - "events_default": 100, - "state_default": 50, - "invite": 50, - "redact": 0 - } - }) + #initial_state.append({ + # "type": str(EventType.ROOM_POWER_LEVELS), + # "content": { + # "users": { + # self.az.bot_mxid: 100, + # self.main_intent.mxid: 100, + # }, + # "events": {}, + # "events_default": 100, + # "state_default": 50, + # "invite": 50, + # "redact": 0 + # } + #}) # We lock backfill lock here so any messages that come between the room being created # and the initial backfill finishing wouldn't be bridged before the backfill messages. diff --git a/puppet/src/contentscript.js b/puppet/src/contentscript.js index 83b470a..5194f76 100644 --- a/puppet/src/contentscript.js +++ b/puppet/src/contentscript.js @@ -135,29 +135,51 @@ class MautrixController { return messageData } - waitForMessage(elem) { - return new Promise(resolve => { - let msgID = null - const observer = new MutationObserver(changes => { - for (const change of changes) { - if (change.type === "attributes" && change.attributeName === "msg-id") { - msgID = +elem.getAttribute("msg-id") - window.__mautrixReceiveMessageID(msgID) - } else if (change.type === "childList" - && change.target.nodeName.toLowerCase() === "mws-relative-timestamp" - && change.addedNodes.length > 0 - && change.addedNodes[0] instanceof Text) { - resolve(msgID) - observer.disconnect() - return + + promiseOwnMessage() { + let observer + let msgID = -1 + let resolve + let reject + + const invisibleTimeCallback = (changes) => { + for (const change of changes) { + for (const addedNode of change.addedNodes) { + if (addedNode.classList.contains("mdRGT07Own")) { + const timeElement = addedNode.querySelector("time.MdNonDisp") + if (timeElement) { + msgID = addedNode.getAttribute("data-local-id") + observer.disconnect() + observer = new MutationObserver(visibleTimeCallback) + observer.observe(timeElement, { attributes: true, attributeFilter: ["class"] }) + return + } } } - }) - observer.observe(elem, { attributes: true, attributeFilter: ["msg-id"] }) - observer.observe(elem.querySelector("mws-message-status"), { - childList: true, - subtree: true, - }) + } + } + + const visibleTimeCallback = (changes) => { + for (const change of changes) { + if (!change.target.classList.contains("MdNonDisp")) { + window.__mautrixReceiveMessageID(msgID) + observer.disconnect() + resolve(msgID) + return + } + } + } + + observer = new MutationObserver(invisibleTimeCallback) + observer.observe( + document.querySelector("#_chat_room_msg_list"), + { childList: true }) + + return new Promise((realResolve, realReject) => { + resolve = realResolve + reject = realReject + // TODO Handle a timeout better than this + setTimeout(() => { observer.disconnect(); reject() }, 10000) }) } diff --git a/puppet/src/puppet.js b/puppet/src/puppet.js index 21a2446..e382226 100644 --- a/puppet/src/puppet.js +++ b/puppet/src/puppet.js @@ -93,9 +93,9 @@ export default class MessagesPuppeteer { await this.page.exposeFunction("__mautrixSendEmailCredentials", this._sendEmailCredentials.bind(this)) await this.page.exposeFunction("__mautrixReceivePIN", this._receivePIN.bind(this)) await this.page.exposeFunction("__mautrixExpiry", this._receiveExpiry.bind(this)) - /* TODO await this.page.exposeFunction("__mautrixReceiveMessageID", id => this.sentMessageIDs.add(id)) + /* TODO await this.page.exposeFunction("__mautrixReceiveChanges", this._receiveChatListChanges.bind(this)) await this.page.exposeFunction("__chronoParseDate", chrono.parseDate) @@ -469,17 +469,36 @@ export default class MessagesPuppeteer { } } + // TODO Catch "An error has occurred" dialog + // Selector is just "dialog", then "button" + // Child of "#layer_contents" + // Always present, just made visible via classes + async _sendMessageUnsafe(chatID, text) { await this._switchChatUnsafe(chatID) - await this.page.focus("mws-message-compose .input-box textarea") - await this.page.keyboard.type(text) - await this.page.click(".compose-container > mws-message-send-button > button") - const id = await this.page.$eval("mws-message-wrapper.outgoing[msg-id^='tmp_']", - elem => window.__mautrixController.waitForMessage(elem)) - this.log("Successfully sent message", id, "to", chatID) - return id + const promise = this.page.evaluate( + () => window.__mautrixController.promiseOwnMessage()) + + const input = await this.page.$("#_chat_room_input") + await input.click() + await input.type(text) + await input.press("Enter") + + try { + this.log("Waiting for message to be sent") + const id = await promise + this.log(`Successfully sent message ${id} to ${chatID}`) + return id + } catch (e) { + // TODO Handle a timeout better than this + this.error(`Timed out waiting for message to ${chatID}`) + return -1 + } } + // TODO Inbound read receipts + // Probably use a MutationObserver mapped to msgID + async _getMessagesUnsafe(id, minID = 0) { /* TODO Also handle "decrypting" state await this._switchChatUnsafe(id)