diff --git a/matrix_puppeteer_line/db/portal.py b/matrix_puppeteer_line/db/portal.py index 8a35130..b5d764c 100644 --- a/matrix_puppeteer_line/db/portal.py +++ b/matrix_puppeteer_line/db/portal.py @@ -17,7 +17,7 @@ from typing import Optional, ClassVar, List, TYPE_CHECKING from attr import dataclass -from mautrix.types import RoomID +from mautrix.types import RoomID, ContentURI from mautrix.util.async_db import Database fake_db = Database("") if TYPE_CHECKING else None @@ -28,27 +28,31 @@ class Portal: db: ClassVar[Database] = fake_db chat_id: str - other_user: str + other_user: str # TODO Remove, as it's redundant: other_user == chat_id for direct chats mxid: Optional[RoomID] name: Optional[str] - icon_url: Optional[str] + icon_path: Optional[str] + icon_mxc: Optional[ContentURI] encrypted: bool async def insert(self) -> None: - q = ("INSERT INTO portal (chat_id, other_user, mxid, name, icon_url, encrypted) " - "VALUES ($1, $2, $3, $4, $5, $6)") + q = ("INSERT INTO portal (chat_id, other_user, mxid, name, icon_path, icon_mxc, encrypted) " + "VALUES ($1, $2, $3, $4, $5, $6, $7)") await self.db.execute(q, self.chat_id, self.other_user, self.mxid, self.name, - self.icon_url, self.encrypted) + self.icon_path, self.icon_mxc, + self.encrypted) async def update(self) -> None: - q = ("UPDATE portal SET other_user=$2, mxid=$3, name=$4, icon_url=$5, encrypted=$6 " + q = ("UPDATE portal SET other_user=$2, mxid=$3, name=$4, " + " icon_path=$5, icon_mxc=$6, encrypted=$7 " "WHERE chat_id=$1") - await self.db.execute(q, self.chat_id, self.other_user, - self.mxid, self.name, self.icon_url, self.encrypted) + await self.db.execute(q, self.chat_id, self.other_user, self.mxid, self.name, + self.icon_path, self.icon_mxc, + self.encrypted) @classmethod async def get_by_mxid(cls, mxid: RoomID) -> Optional['Portal']: - q = ("SELECT chat_id, other_user, mxid, name, icon_url, encrypted " + q = ("SELECT chat_id, other_user, mxid, name, icon_path, icon_mxc, encrypted " "FROM portal WHERE mxid=$1") row = await cls.db.fetchrow(q, mxid) if not row: @@ -57,7 +61,7 @@ class Portal: @classmethod async def get_by_chat_id(cls, chat_id: int) -> Optional['Portal']: - q = ("SELECT chat_id, other_user, mxid, name, icon_url, encrypted " + q = ("SELECT chat_id, other_user, mxid, name, icon_path, icon_mxc, encrypted " "FROM portal WHERE chat_id=$1") row = await cls.db.fetchrow(q, chat_id) if not row: @@ -66,12 +70,14 @@ class Portal: @classmethod async def find_private_chats(cls) -> List['Portal']: - rows = await cls.db.fetch("SELECT chat_id, other_user, mxid, name, icon_url, encrypted " + rows = await cls.db.fetch("SELECT chat_id, other_user, mxid, name, " + " icon_path, icon_mxc, encrypted " "FROM portal WHERE other_user IS NOT NULL") return [cls(**row) for row in rows] @classmethod async def all_with_room(cls) -> List['Portal']: - rows = await cls.db.fetch("SELECT chat_id, other_user, mxid, name, icon_url, encrypted " + rows = await cls.db.fetch("SELECT chat_id, other_user, mxid, name, " + " icon_path, icon_mxc, encrypted " "FROM portal WHERE mxid IS NOT NULL") return [cls(**row) for row in rows] diff --git a/matrix_puppeteer_line/db/puppet.py b/matrix_puppeteer_line/db/puppet.py index 94258d2..f9346c6 100644 --- a/matrix_puppeteer_line/db/puppet.py +++ b/matrix_puppeteer_line/db/puppet.py @@ -17,6 +17,7 @@ from typing import Optional, ClassVar, TYPE_CHECKING from attr import dataclass +from mautrix.types import ContentURI from mautrix.util.async_db import Database fake_db = Database("") if TYPE_CHECKING else None @@ -28,21 +29,35 @@ class Puppet: mid: str name: Optional[str] - avatar_url: Optional[str] + avatar_path: Optional[str] + avatar_mxc: Optional[ContentURI] + name_set: bool + avatar_set: bool is_registered: bool async def insert(self) -> None: - q = "INSERT INTO puppet (mid, name, avatar_url, is_registered) VALUES ($1, $2, $3, $4)" - await self.db.execute(q, self.mid, self.name, self.avatar_url, self.is_registered) + q = ("INSERT INTO puppet (mid, name, " + " avatar_path, avatar_mxc, name_set, avatar_set, " + " is_registered) " + "VALUES ($1, $2, $3, $4, $5, $6, $7)") + await self.db.execute(q, self.mid, self.name, + self.avatar_path, self.avatar_mxc, self.name_set, self.avatar_set, + self.is_registered) async def update(self) -> None: - q = "UPDATE puppet SET name=$2, avatar_url=$3, is_registered=$4 WHERE mid=$1" - await self.db.execute(q, self.mid, self.name, self.avatar_url, self.is_registered) + q = ("UPDATE puppet SET name=$2, " + " avatar_path=$3, avatar_mxc=$4, name_set=$5, avatar_set=$6, " + " is_registered=$7 " + "WHERE mid=$1") + await self.db.execute(q, self.mid, self.name, + self.avatar_path, self.avatar_mxc, self.name_set, self.avatar_set, + self.is_registered) @classmethod async def get_by_mid(cls, mid: str) -> Optional['Puppet']: - row = await cls.db.fetchrow("SELECT mid, name, avatar_url, is_registered FROM puppet WHERE mid=$1", - mid) + q = ("SELECT mid, name, avatar_path, avatar_mxc, name_set, avatar_set, is_registered " + "FROM puppet WHERE mid=$1") + row = await cls.db.fetchrow(q, mid) if not row: return None return cls(**row) diff --git a/matrix_puppeteer_line/db/upgrade.py b/matrix_puppeteer_line/db/upgrade.py index 80884f2..5f796a0 100644 --- a/matrix_puppeteer_line/db/upgrade.py +++ b/matrix_puppeteer_line/db/upgrade.py @@ -50,9 +50,13 @@ async def upgrade_v1(conn: Connection) -> None: @upgrade_table.register(description="Avatars and icons") async def upgrade_avatars(conn: Connection) -> None: - for (table, column) in [('puppet', 'avatar_url'), ('portal', 'icon_url')]: - column_exists = await conn.fetchval( - "SELECT EXISTS(SELECT FROM information_schema.columns " - f"WHERE table_name='{table}' AND column_name='{column}')") - if not column_exists: - await conn.execute(f'ALTER TABLE "{table}" ADD COLUMN {column} TEXT') \ No newline at end of file + await conn.execute("""ALTER TABLE puppet + ADD COLUMN IF NOT EXISTS avatar_path TEXT, + ADD COLUMN IF NOT EXISTS avatar_mxc TEXT, + ADD COLUMN IF NOT EXISTS name_set BOOLEAN, + ADD COLUMN IF NOT EXISTS avatar_set BOOLEAN + """) + await conn.execute("""ALTER TABLE portal + ADD COLUMN IF NOT EXISTS icon_path TEXT, + ADD COLUMN IF NOT EXISTS icon_mxc TEXT + """) \ No newline at end of file diff --git a/matrix_puppeteer_line/portal.py b/matrix_puppeteer_line/portal.py index 4f1c220..f568b24 100644 --- a/matrix_puppeteer_line/portal.py +++ b/matrix_puppeteer_line/portal.py @@ -30,7 +30,7 @@ from mautrix.util.network_retry import call_with_net_retry from .db import Portal as DBPortal, Message as DBMessage from .config import Config -from .rpc import ChatInfo, Participant, Message +from .rpc import ChatInfo, Participant, Message, Client, PathImage from . import user as u, puppet as p, matrix as m if TYPE_CHECKING: @@ -62,9 +62,10 @@ class Portal(DBPortal, BasePortal): _last_participant_update: Set[str] def __init__(self, chat_id: int, other_user: Optional[str] = None, - mxid: Optional[RoomID] = None, name: Optional[str] = None, icon_url: Optional[str] = None, + mxid: Optional[RoomID] = None, name: Optional[str] = None, + icon_path: Optional[str] = None, icon_mxc: Optional[ContentURI] = None, encrypted: bool = False) -> None: - super().__init__(chat_id, other_user, mxid, name, icon_url, encrypted) + super().__init__(chat_id, other_user, mxid, name, icon_path, icon_mxc, encrypted) self._create_room_lock = asyncio.Lock() self.log = self.log.getChild(str(chat_id)) @@ -229,18 +230,25 @@ class Portal(DBPortal, BasePortal): return ReuploadedMediaInfo(mxc, decryption_info, mime_type, file_name, len(data)) - async def update_info(self, conv: ChatInfo) -> None: + async def update_info(self, conv: ChatInfo, client: Optional[Client]) -> None: if self.is_direct: self.other_user = conv.participants[0].id if self._main_intent is self.az.intent: self._main_intent = (await p.Puppet.get_by_mid(self.other_user)).intent for participant in conv.participants: puppet = await p.Puppet.get_by_mid(participant.id) - await puppet.update_info(participant) + await puppet.update_info(participant, client) # TODO Consider setting no room name for non-group chats. # But then the LINE bot itself may appear in the title... changed = await self._update_name(f"{conv.name} (LINE)") - changed = await self._update_icon(conv.iconURL) or changed + if client: + if not self.is_direct: + changed = await self._update_icon(conv.icon, client) or changed + elif puppet and puppet.avatar_mxc != self.icon_mxc: + changed = True + self.icon_mxc = puppet.avatar_mxc + if self.mxid: + await self.main_intent.set_room_avatar(self.mxid, self.icon_mxc) if changed: await self.update_bridge_info() await self.update() @@ -256,12 +264,17 @@ class Portal(DBPortal, BasePortal): return True return False - async def _update_icon(self, icon_url: Optional[str]) -> bool: - if self.icon_url != icon_url: - self.icon_url = icon_url - if icon_url: - # TODO set icon from bytes - pass + async def _update_icon(self, icon: Optional[PathImage], client: Client) -> bool: + icon_path = icon.path if icon else None + if icon_path != self.icon_path: + self.icon_path = icon_path + if icon and icon.url: + resp = await client.read_image(icon.url) + self.icon_mxc = await self.main_intent.upload_media(resp.data, mime_type=resp.mime) + else: + self.icon_mxc = ContentURI("") + if self.mxid: + await self.main_intent.set_room_avatar(self.mxid, self.icon_mxc) return True return False @@ -319,7 +332,7 @@ class Portal(DBPortal, BasePortal): for evt in messages: puppet = await p.Puppet.get_by_mid(evt.sender.id) if not self.is_direct else None if puppet and evt.sender.id not in members_known: - await puppet.update_info(evt.sender) + await puppet.update_info(evt.sender, source.client) members_known.add(evt.sender.id) await self.handle_remote_message(source, puppet, evt) self.log.info("Backfilled %d messages through %s", len(messages), source.mxid) @@ -377,7 +390,7 @@ class Portal(DBPortal, BasePortal): if puppet: await puppet.az.intent.ensure_joined(self.mxid) - await self.update_info(info) + await self.update_info(info, source.client) await self.backfill(source) await self._update_participants(info.participants) @@ -385,7 +398,7 @@ class Portal(DBPortal, BasePortal): if self.mxid: await self._update_matrix_room(source, info) return self.mxid - await self.update_info(info) + await self.update_info(info, source.client) self.log.debug("Creating Matrix room") name: Optional[str] = None initial_state = [{ @@ -483,7 +496,10 @@ class Portal(DBPortal, BasePortal): self.by_mxid.pop(self.mxid, None) self.mxid = None self.name = None - self.icon_url = None + self.icon_path = None + self.icon_mxc = None + self.name_set = False + self.icon_set = False self.encrypted = False await self.update() diff --git a/matrix_puppeteer_line/puppet.py b/matrix_puppeteer_line/puppet.py index e684177..d9af8eb 100644 --- a/matrix_puppeteer_line/puppet.py +++ b/matrix_puppeteer_line/puppet.py @@ -16,12 +16,12 @@ from typing import Optional, Dict, TYPE_CHECKING, cast from mautrix.bridge import BasePuppet -from mautrix.types import UserID +from mautrix.types import UserID, ContentURI from mautrix.util.simple_template import SimpleTemplate from .db import Puppet as DBPuppet from .config import Config -from .rpc import Participant +from .rpc import Participant, Client, PathImage from . import user as u if TYPE_CHECKING: @@ -38,9 +38,11 @@ class Puppet(DBPuppet, BasePuppet): default_mxid: UserID - def __init__(self, mid: str, name: Optional[str] = None, avatar_url: Optional[str] = None, + def __init__(self, mid: str, name: Optional[str] = None, + avatar_path: Optional[str] = None, avatar_mxc: Optional[ContentURI] = None, + name_set: bool = False, avatar_set: bool = False, is_registered: bool = False) -> None: - super().__init__(mid=mid, name=name, avatar_url=avatar_url, is_registered=is_registered) + super().__init__(mid, name, avatar_path, avatar_mxc, name_set, avatar_set, is_registered) self.log = self.log.getChild(mid) self.default_mxid = self.get_mxid_from_id(mid) @@ -61,27 +63,46 @@ class Puppet(DBPuppet, BasePuppet): cls.login_shared_secret_map[cls.hs_domain] = secret.encode("utf-8") cls.login_device_name = "LINE Bridge" - async def update_info(self, info: Participant) -> None: + async def update_info(self, info: Participant, client: Optional[Client]) -> None: update = False update = await self._update_name(info.name) or update - update = await self._update_avatar(info.avatarURL) or update + if client: + update = await self._update_avatar(info.avatar, client) or update if update: await self.update() async def _update_name(self, name: str) -> bool: name = self.config["bridge.displayname_template"].format(displayname=name) - if name != self.name: + if name != self.name or not self.name_set: self.name = name - await self.intent.set_displayname(self.name) + try: + await self.intent.set_displayname(self.name) + self.name_set = True + except Exception: + self.log.exception("Failed to set displayname") + self.name_set = False return True return False - async def _update_avatar(self, avatar_url: Optional[str]) -> bool: - if avatar_url != self.avatar_url: - self.avatar_url = avatar_url - if avatar_url: - # TODO set the avatar from bytes - pass + async def _update_avatar(self, avatar: Optional[PathImage], client: Client) -> bool: + if avatar and avatar.url and not avatar.path: + # Avatar exists, but in a form that cannot be uniquely identified. + # Skip it for now. + return False + avatar_path = avatar.path if avatar else None + if avatar_path != self.avatar_path or not self.avatar_set: + self.avatar_path = avatar_path + if avatar and avatar.url: + resp = await client.read_image(avatar.url) + self.avatar_mxc = await self.intent.upload_media(resp.data, mime_type=resp.mime) + else: + self.avatar_mxc = ContentURI("") + try: + await self.intent.set_avatar_url(self.avatar_mxc) + self.avatar_set = True + except Exception: + self.log.exception("Failed to set user avatar") + self.avatar_set = False return True return False diff --git a/matrix_puppeteer_line/rpc/__init__.py b/matrix_puppeteer_line/rpc/__init__.py index cc7201f..09a3d7a 100644 --- a/matrix_puppeteer_line/rpc/__init__.py +++ b/matrix_puppeteer_line/rpc/__init__.py @@ -1,2 +1,2 @@ from .client import Client -from .types import RPCError, ChatListInfo, ChatInfo, Participant, Message, StartStatus +from .types import RPCError, PathImage, ChatListInfo, ChatInfo, Participant, Message, StartStatus diff --git a/matrix_puppeteer_line/rpc/client.py b/matrix_puppeteer_line/rpc/client.py index c8a616f..aa58f0c 100644 --- a/matrix_puppeteer_line/rpc/client.py +++ b/matrix_puppeteer_line/rpc/client.py @@ -15,10 +15,11 @@ # along with this program. If not, see . from typing import AsyncGenerator, TypedDict, List, Tuple, Dict, Callable, Awaitable, Any from collections import deque +from base64 import b64decode import asyncio from .rpc import RPCClient -from .types import ChatListInfo, ChatInfo, Message, StartStatus +from .types import ChatListInfo, ChatInfo, Message, ImageData, StartStatus class LoginCommand(TypedDict): @@ -45,6 +46,25 @@ class Client(RPCClient): resp = await self.request("get_messages", chat_id=chat_id) return [Message.deserialize(data) for data in resp] + async def read_image(self, image_url: str) -> ImageData: + resp = await self.request("read_image", image_url=image_url) + if not resp.startswith("data:"): + raise TypeError("Image data is not in the form of a Data URL") + + typestart = 5 + typeend = resp.find(",", typestart) + data = bytes(resp[typeend+1:], "utf-8") + + paramstart = resp.rfind(";", typestart, typeend) + if paramstart == -1: + mime = resp[typestart:typeend] + else: + mime = resp[typestart:paramstart] + if resp[paramstart+1:typeend] == "base64": + data = b64decode(data) + + return ImageData(mime=mime, data=data) + async def is_connected(self) -> bool: resp = await self.request("is_connected") return resp["is_connected"] diff --git a/matrix_puppeteer_line/rpc/rpc.py b/matrix_puppeteer_line/rpc/rpc.py index d29189a..1cd287f 100644 --- a/matrix_puppeteer_line/rpc/rpc.py +++ b/matrix_puppeteer_line/rpc/rpc.py @@ -161,6 +161,6 @@ class RPCClient: await self._writer.drain() return future - async def request(self, command: str, **data: Any) -> Dict[str, Any]: + async def request(self, command: str, **data: Any) -> Any: future = await self._raw_request(command, **data) return await future diff --git a/matrix_puppeteer_line/rpc/types.py b/matrix_puppeteer_line/rpc/types.py index 05533cf..bfaf2ad 100644 --- a/matrix_puppeteer_line/rpc/types.py +++ b/matrix_puppeteer_line/rpc/types.py @@ -24,11 +24,17 @@ class RPCError(Exception): pass +@dataclass +class PathImage(SerializableAttrs['PathImage']): + path: str + url: str + + @dataclass class ChatListInfo(SerializableAttrs['ChatListInfo']): id: int name: str - iconURL: Optional[str] + icon: Optional[PathImage] lastMsg: str lastMsgDate: str @@ -36,7 +42,7 @@ class ChatListInfo(SerializableAttrs['ChatListInfo']): @dataclass class Participant(SerializableAttrs['Participant']): id: str - avatarURL: Optional[str] + avatar: Optional[PathImage] name: str @@ -56,6 +62,12 @@ class Message(SerializableAttrs['Message']): image: Optional[str] = None +@dataclass +class ImageData: + mime: str + data: bytes + + @dataclass class StartStatus(SerializableAttrs['StartStatus']): started: bool diff --git a/puppet/src/client.js b/puppet/src/client.js index e4d9a1f..b20223b 100644 --- a/puppet/src/client.js +++ b/puppet/src/client.js @@ -232,6 +232,7 @@ export default class Client { get_chats: () => this.puppet.getRecentChats(), get_chat: req => this.puppet.getChatInfo(req.chat_id), get_messages: req => this.puppet.getMessages(req.chat_id), + read_image: req => this.puppet.readImage(req.image_url), is_connected: async () => ({ is_connected: !await this.puppet.isDisconnected() }), }[req.command] || this.handleUnknownCommand } diff --git a/puppet/src/contentscript.js b/puppet/src/contentscript.js index 1fc0fce..729c3f8 100644 --- a/puppet/src/contentscript.js +++ b/puppet/src/contentscript.js @@ -182,7 +182,7 @@ class MautrixController { const participantsList = document.querySelector(participantsListSelector) sender.id = participantsList.querySelector(`img[alt='${senderName}'`).parentElement.parentElement.getAttribute("data-mid") } - sender.avatarURL = this.getParticipantListItemAvatarURL(element) + sender.avatar = this.getParticipantListItemAvatar(element) } else { // TODO Get own ID and store it somewhere appropriate. // Unable to get own ID from a room chat... @@ -196,7 +196,7 @@ class MautrixController { await window.__mautrixShowParticipantsList() const participantsList = document.querySelector(participantsListSelector) sender.name = this.getParticipantListItemName(participantsList.children[0]) - sender.avatarURL = this.getParticipantListItemAvatarURL(participantsList.children[0]) + sender.avatar = this.getParticipantListItemAvatar(participantsList.children[0]) sender.id = this.ownID } @@ -299,25 +299,41 @@ class MautrixController { return messages } + /** + * @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 + } + } + /** * @typedef Participant * @type object - * @property {string} id - The member ID for the participant - * @property {string} avatarURL - The URL of the participant's avatar - * @property {string} name - The contact list name of the participant + * @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 */ getParticipantListItemName(element) { return element.querySelector(".mdRGT13Ttl").innerText } - getParticipantListItemAvatarURL(element) { - const img = element.querySelector(".mdRGT13Img img[src]") - if (img && img.getAttribute("data-picture-path") != "" && img.src.startsWith("blob:")) { - return img.src - } else { - return "" - } + getParticipantListItemAvatar(element) { + return this._getPathImage(element.querySelector(".mdRGT13Img img[src]")) } getParticipantListItemId(element) { @@ -340,7 +356,7 @@ class MautrixController { // 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, - avatarURL: this.getParticipantListItemAvatarURL(element.children[0]), + avatar: this.getParticipantListItemAvatar(element.children[0]), name: this.getParticipantListItemName(element.children[0]), } @@ -349,7 +365,7 @@ class MautrixController { const id = this.getParticipantListItemId(child) || this.getUserIdFromFriendsList(name) return { id: id, // NOTE Don't want non-own user's ID to ever be null. - avatarURL: this.getParticipantListItemAvatarURL(child), + avatar: this.getParticipantListItemAvatar(child), name: name, } })) @@ -358,9 +374,9 @@ class MautrixController { /** * @typedef ChatListInfo * @type object - * @property {number} id - The ID of the chat. - * @property {string} name - The name of the chat. - * @property {string} iconURL - The URL of the chat icon. + * @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. * @property {string} lastMsg - The most recent message in the chat. * May be prefixed by sender name. * @property {string} lastMsgDate - An imprecise date for the most recent message @@ -375,13 +391,8 @@ class MautrixController { return element.querySelector(".mdCMN04Ttl").innerText } - getChatListItemIconURL(element) { - const img = element.querySelector(".mdCMN04Img > :not(.mdCMN04ImgInner) > img[src]") - if (img && img.getAttribute("data-picture-path") != "" && img.src.startsWith("blob:")) { - return img.src - } else { - return "" - } + getChatListItemIcon(element) { + return this._getPathImage(element.querySelector(".mdCMN04Img > :not(.mdCMN04ImgInner) > img[src]")) } getChatListItemLastMsg(element) { @@ -403,7 +414,7 @@ class MautrixController { return !element.classList.contains("chatList") ? null : { id: knownId || this.getChatListItemId(element), name: this.getChatListItemName(element), - iconURL: this.getChatListItemIconURL(element), + icon: this.getChatListItemIcon(element), lastMsg: this.getChatListItemLastMsg(element), lastMsgDate: this.getChatListItemLastMsgDate(element), } @@ -434,17 +445,13 @@ class MautrixController { } /** - * TODO - * Download an image and return it as a data URL. - * Used for downloading the blob: URLs in image messages. + * Download an image at a given URL and return it as a data URL. * - * @param {number} id - The ID of the message whose image to download. + * @param {string} url - The URL of the image to download. * @return {Promise} - The data URL (containing the mime type and base64 data) */ - async readImage(id) { - const imageElement = document.querySelector( - `mws-message-wrapper[msg-id="${id}"] mws-image-message-part .image-msg`) - const resp = await fetch(imageElement.getAttribute("src")) + async readImage(url) { + const resp = await fetch(url) const reader = new FileReader() const promise = new Promise((resolve, reject) => { reader.onload = () => resolve(reader.result) diff --git a/puppet/src/puppet.js b/puppet/src/puppet.js index f86ff91..8aa3733 100644 --- a/puppet/src/puppet.js +++ b/puppet/src/puppet.js @@ -396,6 +396,13 @@ export default class MessagesPuppeteer { this.log("Updated most recent message ID map:", this.mostRecentMessages) } + async readImage(imageUrl) { + return await this.taskQueue.push(() => + this.page.evaluate( + url => window.__mautrixController.readImage(url), + imageUrl)) + } + async startObserving() { this.log("Adding chat list observer") await this.page.evaluate( @@ -496,7 +503,7 @@ export default class MessagesPuppeteer { //await chatDetailArea.$(".MdTxtDesc02") || // 1:1 chat with custom title - get participant's real name participants = [{ id: id, - avatarURL: chatListInfo.iconURL, + avatar: chatListInfo.icon, name: chatListInfo.name, }] }