forked from fair/matrix-puppeteer-line
Contact syncing and invites
* Add ability to sync all LINE contacts, which is relevant because the list of recent chats excludes users you haven't spoken to lately. * Add bot command to list all contacts. * Allow inviting a puppet to a DM to create a portal for that LINE user, instead of having to wait for that user to message you first.
This commit is contained in:
parent
ce31caa034
commit
57c448e0c3
|
@ -67,6 +67,7 @@
|
||||||
* [x] At startup
|
* [x] At startup
|
||||||
* [x] When receiving invite or message
|
* [x] When receiving invite or message
|
||||||
* [ ] When sending message in new chat from LINE app
|
* [ ] When sending message in new chat from LINE app
|
||||||
|
* [x] Private chat creation by inviting Matrix puppet of LINE user to new room
|
||||||
* [x] Notification for message send failure
|
* [x] Notification for message send failure
|
||||||
* [ ] Provisioning API for logging in
|
* [ ] Provisioning API for logging in
|
||||||
* [x] Use bridge bot for messages sent from LINE app (when double-puppeting is disabled and `bridge.invite_own_puppet_to_pm` is enabled)
|
* [x] Use bridge bot for messages sent from LINE app (when double-puppeting is disabled and `bridge.invite_own_puppet_to_pm` is enabled)
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
from .auth import SECTION_AUTH
|
from .auth import SECTION_AUTH
|
||||||
from .conn import SECTION_CONNECTION
|
from .conn import SECTION_CONNECTION
|
||||||
|
from .line import SECTION_CHATS
|
||||||
|
|
|
@ -41,6 +41,12 @@ async def ping(evt: CommandEvent) -> None:
|
||||||
|
|
||||||
|
|
||||||
@command_handler(needs_auth=True, management_only=False, help_section=SECTION_CONNECTION,
|
@command_handler(needs_auth=True, management_only=False, help_section=SECTION_CONNECTION,
|
||||||
help_text="Synchronize portals")
|
help_text="Synchronize contacts and portals")
|
||||||
async def sync(evt: CommandEvent) -> None:
|
async def sync(evt: CommandEvent) -> None:
|
||||||
await evt.sender.sync()
|
await evt.sender.sync()
|
||||||
|
|
||||||
|
|
||||||
|
@command_handler(needs_auth=True, management_only=False, help_section=SECTION_CONNECTION,
|
||||||
|
help_text="Synchronize contacts")
|
||||||
|
async def sync_contacts(evt: CommandEvent) -> None:
|
||||||
|
await evt.sender.sync_contacts()
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
# matrix-puppeteer-line - A very hacky Matrix-LINE bridge based on running LINE's Chrome extension in Puppeteer
|
||||||
|
# Copyright (C) 2020-2021 Tulir Asokan, Andrew Ferrazzutti
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
from mautrix.bridge.commands import HelpSection, command_handler
|
||||||
|
|
||||||
|
from .. import puppet as pu
|
||||||
|
from .typehint import CommandEvent
|
||||||
|
|
||||||
|
SECTION_CHATS = HelpSection("Contacts & Chats", 40, "")
|
||||||
|
|
||||||
|
|
||||||
|
@command_handler(needs_auth=True, management_only=False, help_section=SECTION_CHATS,
|
||||||
|
help_text="List all LINE contacts")
|
||||||
|
async def list_contacts(evt: CommandEvent) -> None:
|
||||||
|
# TODO Use a generator if it's worth it
|
||||||
|
puppets = await pu.Puppet.get_all()
|
||||||
|
results = "".join(f"* [{puppet.name}](https://matrix.to/#/{puppet.default_mxid})\n"
|
||||||
|
for puppet in puppets)
|
||||||
|
if results:
|
||||||
|
await evt.reply(f"Contacts:\n\n{results}")
|
||||||
|
else:
|
||||||
|
await evt.reply("No contacts found.")
|
|
@ -13,7 +13,7 @@
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from typing import Optional, ClassVar, TYPE_CHECKING
|
from typing import Optional, ClassVar, List, TYPE_CHECKING
|
||||||
|
|
||||||
from attr import dataclass
|
from attr import dataclass
|
||||||
|
|
||||||
|
@ -61,3 +61,10 @@ class Puppet:
|
||||||
if not row:
|
if not row:
|
||||||
return None
|
return None
|
||||||
return cls(**row)
|
return cls(**row)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_all(cls) -> List['Puppet']:
|
||||||
|
q = ("SELECT mid, name, avatar_path, avatar_mxc, name_set, avatar_set, is_registered "
|
||||||
|
"FROM puppet")
|
||||||
|
rows = await cls.db.fetch(q)
|
||||||
|
return [cls(**row) for row in rows]
|
||||||
|
|
|
@ -52,6 +52,23 @@ class MatrixHandler(BaseMatrixHandler):
|
||||||
await self.az.intent.send_notice(room_id, "This room has been marked as your "
|
await self.az.intent.send_notice(room_id, "This room has been marked as your "
|
||||||
"LINE bridge notice room.")
|
"LINE bridge notice room.")
|
||||||
|
|
||||||
|
async def handle_puppet_invite(self, room_id: RoomID, puppet: 'pu.Puppet',
|
||||||
|
invited_by: 'u.User', _: EventID) -> None:
|
||||||
|
chat_id = puppet.mid
|
||||||
|
portal = await po.Portal.get_by_chat_id(chat_id, create=True)
|
||||||
|
if portal.mxid:
|
||||||
|
# TODO Allow creating a LINE group/room from a Matrix invite
|
||||||
|
await portal.main_intent.error_and_leave(room_id, "You already have an existing chat with me!")
|
||||||
|
return
|
||||||
|
portal.mxid = room_id
|
||||||
|
# TODO Put pause/resume in portal methods, with a lock or something
|
||||||
|
await invited_by.client.pause()
|
||||||
|
try:
|
||||||
|
chat_info = await invited_by.client.get_chat(chat_id)
|
||||||
|
await portal.update_matrix_room(invited_by, chat_info)
|
||||||
|
finally:
|
||||||
|
await invited_by.client.resume()
|
||||||
|
|
||||||
async def handle_leave(self, room_id: RoomID, user_id: UserID, event_id: EventID) -> None:
|
async def handle_leave(self, room_id: RoomID, user_id: UserID, event_id: EventID) -> None:
|
||||||
portal = await po.Portal.get_by_mxid(room_id)
|
portal = await po.Portal.get_by_mxid(room_id)
|
||||||
if not portal:
|
if not portal:
|
||||||
|
|
|
@ -189,6 +189,7 @@ class Portal(DBPortal, BasePortal):
|
||||||
async def handle_matrix_leave(self, user: 'u.User') -> None:
|
async def handle_matrix_leave(self, user: 'u.User') -> None:
|
||||||
self.log.info(f"{user.mxid} left portal to {self.chat_id}, "
|
self.log.info(f"{user.mxid} left portal to {self.chat_id}, "
|
||||||
f"cleaning up and deleting...")
|
f"cleaning up and deleting...")
|
||||||
|
# TODO Delete room history in LINE to prevent a re-sync from happening
|
||||||
await self.cleanup_and_delete()
|
await self.cleanup_and_delete()
|
||||||
|
|
||||||
async def _bridge_own_message_pm(self, source: 'u.User', puppet: Optional['p.Puppet'], mid: str,
|
async def _bridge_own_message_pm(self, source: 'u.User', puppet: Optional['p.Puppet'], mid: str,
|
||||||
|
@ -711,12 +712,13 @@ class Portal(DBPortal, BasePortal):
|
||||||
return await self._create_matrix_room(source, info)
|
return await self._create_matrix_room(source, info)
|
||||||
|
|
||||||
async def _update_matrix_room(self, source: 'u.User', info: ChatInfo) -> None:
|
async def _update_matrix_room(self, source: 'u.User', info: ChatInfo) -> None:
|
||||||
|
await self.update_info(info, source.client)
|
||||||
|
|
||||||
await self.main_intent.invite_user(self.mxid, source.mxid, check_cache=True)
|
await self.main_intent.invite_user(self.mxid, source.mxid, check_cache=True)
|
||||||
puppet = await p.Puppet.get_by_custom_mxid(source.mxid)
|
puppet = await p.Puppet.get_by_custom_mxid(source.mxid)
|
||||||
if puppet and puppet.intent:
|
if puppet and puppet.intent:
|
||||||
await puppet.intent.ensure_joined(self.mxid)
|
await puppet.intent.ensure_joined(self.mxid)
|
||||||
|
|
||||||
await self.update_info(info, source.client)
|
|
||||||
await self.backfill(source, info)
|
await self.backfill(source, info)
|
||||||
|
|
||||||
async def _create_matrix_room(self, source: 'u.User', info: ChatInfo) -> Optional[RoomID]:
|
async def _create_matrix_room(self, source: 'u.User', info: ChatInfo) -> Optional[RoomID]:
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from typing import Optional, Dict, TYPE_CHECKING, cast
|
from typing import Optional, Dict, List, TYPE_CHECKING, cast
|
||||||
|
|
||||||
from mautrix.bridge import BasePuppet
|
from mautrix.bridge import BasePuppet
|
||||||
from mautrix.types import UserID, ContentURI
|
from mautrix.types import UserID, ContentURI
|
||||||
|
@ -196,8 +196,16 @@ class Puppet(DBPuppet, BasePuppet):
|
||||||
def is_mid_for_own_puppet(cls, mid) -> bool:
|
def is_mid_for_own_puppet(cls, mid) -> bool:
|
||||||
return mid and mid.startswith("_OWN_")
|
return mid and mid.startswith("_OWN_")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_own_puppet(self) -> bool:
|
||||||
|
return self.mid.startswith("_OWN_")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_by_custom_mxid(cls, mxid: UserID) -> Optional['u.User']:
|
async def get_by_custom_mxid(cls, mxid: UserID) -> Optional['u.User']:
|
||||||
if mxid == cls.config["bridge.user"]:
|
if mxid == cls.config["bridge.user"]:
|
||||||
return await cls.bridge.get_user(mxid)
|
return await cls.bridge.get_user(mxid)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_all(cls) -> List['Puppet']:
|
||||||
|
return [p for p in await super().get_all() if not p.is_own_puppet]
|
||||||
|
|
|
@ -40,6 +40,10 @@ class Client(RPCClient):
|
||||||
async def get_own_profile(self) -> Participant:
|
async def get_own_profile(self) -> Participant:
|
||||||
return Participant.deserialize(await self.request("get_own_profile"))
|
return Participant.deserialize(await self.request("get_own_profile"))
|
||||||
|
|
||||||
|
async def get_contacts(self) -> List[Participant]:
|
||||||
|
resp = await self.request("get_contacts")
|
||||||
|
return [Participant.deserialize(data) for data in resp]
|
||||||
|
|
||||||
async def get_chats(self) -> List[ChatListInfo]:
|
async def get_chats(self) -> List[ChatListInfo]:
|
||||||
resp = await self.request("get_chats")
|
resp = await self.request("get_chats")
|
||||||
return [ChatListInfo.deserialize(data) for data in resp]
|
return [ChatListInfo.deserialize(data) for data in resp]
|
||||||
|
|
|
@ -35,8 +35,8 @@ class ChatListInfo(SerializableAttrs['ChatListInfo']):
|
||||||
id: int
|
id: int
|
||||||
name: str
|
name: str
|
||||||
icon: Optional[PathImage]
|
icon: Optional[PathImage]
|
||||||
lastMsg: str
|
lastMsg: Optional[str]
|
||||||
lastMsgDate: str
|
lastMsgDate: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
|
@ -132,6 +132,7 @@ class User(DBUser, BaseUser):
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
async def sync(self) -> None:
|
async def sync(self) -> None:
|
||||||
|
await self.sync_contacts()
|
||||||
# TODO Use some kind of async lock / event to queue syncing actions
|
# TODO Use some kind of async lock / event to queue syncing actions
|
||||||
self.is_syncing = True
|
self.is_syncing = True
|
||||||
if self._connection_check_task:
|
if self._connection_check_task:
|
||||||
|
@ -146,21 +147,25 @@ class User(DBUser, BaseUser):
|
||||||
limit = self.config["bridge.initial_conversation_sync"]
|
limit = self.config["bridge.initial_conversation_sync"]
|
||||||
self.log.info("Syncing chats")
|
self.log.info("Syncing chats")
|
||||||
await self.send_bridge_notice("Synchronizing chats...")
|
await self.send_bridge_notice("Synchronizing chats...")
|
||||||
chats = await self.client.get_chats()
|
|
||||||
num_created = 0
|
# TODO Since only chat ID is used, retrieve only that
|
||||||
for index, chat in enumerate(chats):
|
chat_infos = await self.client.get_chats()
|
||||||
portal = await po.Portal.get_by_chat_id(chat.id, create=True)
|
for chat_info in chat_infos[:limit]:
|
||||||
if portal.mxid or num_created < limit:
|
portal = await po.Portal.get_by_chat_id(chat_info.id, create=True)
|
||||||
chat = await self.client.get_chat(chat.id)
|
chat_info_full = await self.client.get_chat(chat_info.id)
|
||||||
if portal.mxid:
|
await portal.create_matrix_room(self, chat_info_full)
|
||||||
await portal.update_matrix_room(self, chat)
|
await self.send_bridge_notice("Chat synchronization complete")
|
||||||
else:
|
|
||||||
await portal.create_matrix_room(self, chat)
|
|
||||||
num_created += 1
|
|
||||||
await self.send_bridge_notice("Synchronization complete")
|
|
||||||
await self.client.resume()
|
await self.client.resume()
|
||||||
self.is_syncing = False
|
self.is_syncing = False
|
||||||
|
|
||||||
|
async def sync_contacts(self) -> None:
|
||||||
|
await self.send_bridge_notice("Synchronizing contacts...")
|
||||||
|
contacts = await self.client.get_contacts()
|
||||||
|
for contact in contacts:
|
||||||
|
puppet = await pu.Puppet.get_by_mid(contact.id)
|
||||||
|
await puppet.update_info(contact, self.client)
|
||||||
|
await self.send_bridge_notice("Contact synchronization complete")
|
||||||
|
|
||||||
async def sync_portal(self, portal: 'po.Portal') -> None:
|
async def sync_portal(self, portal: 'po.Portal') -> None:
|
||||||
chat_id = portal.chat_id
|
chat_id = portal.chat_id
|
||||||
self.log.info(f"Viewing (and syncing) chat {chat_id}")
|
self.log.info(f"Viewing (and syncing) chat {chat_id}")
|
||||||
|
|
|
@ -261,6 +261,7 @@ export default class Client {
|
||||||
pause: () => this.puppet.stopObserving(),
|
pause: () => this.puppet.stopObserving(),
|
||||||
resume: () => this.puppet.startObserving(),
|
resume: () => this.puppet.startObserving(),
|
||||||
get_own_profile: () => this.puppet.getOwnProfile(),
|
get_own_profile: () => this.puppet.getOwnProfile(),
|
||||||
|
get_contacts: () => this.puppet.getContacts(),
|
||||||
get_chats: () => this.puppet.getRecentChats(),
|
get_chats: () => this.puppet.getRecentChats(),
|
||||||
get_chat: req => this.puppet.getChatInfo(req.chat_id, req.force_view),
|
get_chat: req => this.puppet.getChatInfo(req.chat_id, req.force_view),
|
||||||
get_messages: req => this.puppet.getMessages(req.chat_id),
|
get_messages: req => this.puppet.getMessages(req.chat_id),
|
||||||
|
|
|
@ -845,6 +845,33 @@ class MautrixController {
|
||||||
return element.getAttribute("data-mid")
|
return element.getAttribute("data-mid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a friends list item element.
|
||||||
|
*
|
||||||
|
* @param {Element} element - The element to parse.
|
||||||
|
* @param {?string} knownID - The ID of this element, if it is known.
|
||||||
|
* @return {Participant} - The info in the element.
|
||||||
|
*/
|
||||||
|
parseFriendsListItem(element, knownID) {
|
||||||
|
return {
|
||||||
|
id: knownID || this.getFriendsListItemID(element),
|
||||||
|
avatar: this.getFriendsListItemAvatar(element),
|
||||||
|
name: this.getFriendsListItemName(element),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the friends list.
|
||||||
|
*
|
||||||
|
* @return {Participant[]}
|
||||||
|
*/
|
||||||
|
parseFriendsList() {
|
||||||
|
const friends = []
|
||||||
|
document.querySelectorAll("#contact_wrap_friends > ul > li[data-mid]")
|
||||||
|
.forEach(e => friends.push(this.parseFriendsListItem(e)))
|
||||||
|
return friends
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a group participants list.
|
* Parse a group participants list.
|
||||||
* TODO Find what works for a *room* participants list...!
|
* TODO Find what works for a *room* participants list...!
|
||||||
|
|
|
@ -412,10 +412,20 @@ export default class MessagesPuppeteer {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all contacts in the Friends list.
|
||||||
|
*
|
||||||
|
* @return {Promise<Participant[]>}
|
||||||
|
*/
|
||||||
|
async getContacts() {
|
||||||
|
return await this.taskQueue.push(() =>
|
||||||
|
this.page.evaluate(() => window.__mautrixController.parseFriendsList()))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the IDs of the most recent chats.
|
* Get the IDs of the most recent chats.
|
||||||
*
|
*
|
||||||
* @return {Promise<[ChatListInfo]>} - List of chat IDs in order of most recent message.
|
* @return {Promise<ChatListInfo[]>} - List of chat IDs in order of most recent message.
|
||||||
*/
|
*/
|
||||||
async getRecentChats() {
|
async getRecentChats() {
|
||||||
return await this.taskQueue.push(() =>
|
return await this.taskQueue.push(() =>
|
||||||
|
@ -425,7 +435,7 @@ export default class MessagesPuppeteer {
|
||||||
/**
|
/**
|
||||||
* @typedef ChatInfo
|
* @typedef ChatInfo
|
||||||
* @type object
|
* @type object
|
||||||
* @property {[Participant]} participants
|
* @property {Participant[]} participants
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -649,14 +659,32 @@ export default class MessagesPuppeteer {
|
||||||
return ownProfile
|
return ownProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
_listItemSelector(id) {
|
_chatItemSelector(id) {
|
||||||
return `#_chat_list_body div[data-chatid="${id}"]`
|
return `#_chat_list_body div[data-chatid="${id}"]`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_friendItemSelector(id) {
|
||||||
|
return `#contact_wrap_friends > ul > li[data-mid="${id}"]`
|
||||||
|
}
|
||||||
|
|
||||||
async _switchChat(chatID, forceView = false) {
|
async _switchChat(chatID, forceView = false) {
|
||||||
// TODO Allow passing in an element directly
|
// TODO Allow passing in an element directly
|
||||||
this.log(`Switching to chat ${chatID}`)
|
this.log(`Switching to chat ${chatID}`)
|
||||||
const chatListItem = await this.page.$(this._listItemSelector(chatID))
|
let chatListItem = await this.page.$(this._chatItemSelector(chatID))
|
||||||
|
if (!chatListItem) {
|
||||||
|
this.log(`Chat ${chatID} not in recents list`)
|
||||||
|
if (chatID.charAt(0) == 'u') {
|
||||||
|
const friendsListItem = await this.page.$(this._friendItemSelector(chatID))
|
||||||
|
if (!friendsListItem) {
|
||||||
|
throw `Cannot find friend with ID ${chatID}`
|
||||||
|
}
|
||||||
|
friendsListItem.evaluate(e => e.click()) // Evaluate in browser context to avoid having to view tab
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
throw "Can't yet get info of new groups/rooms"
|
||||||
|
}
|
||||||
|
chatListItem = await this.page.waitForSelector(this._chatItemSelector(chatID))
|
||||||
|
}
|
||||||
|
|
||||||
const chatName = await chatListItem.evaluate(
|
const chatName = await chatListItem.evaluate(
|
||||||
element => window.__mautrixController.getChatListItemName(element))
|
element => window.__mautrixController.getChatListItemName(element))
|
||||||
|
@ -739,10 +767,7 @@ export default class MessagesPuppeteer {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getChatInfoUnsafe(chatID, forceView) {
|
async _getChatInfoUnsafe(chatID, forceView) {
|
||||||
const chatListInfo = await this.page.$eval(this._listItemSelector(chatID),
|
// TODO Commonize this
|
||||||
(element, chatID) => window.__mautrixController.parseChatListItem(element, chatID),
|
|
||||||
chatID)
|
|
||||||
|
|
||||||
let [isDirect, isGroup, isRoom] = [false,false,false]
|
let [isDirect, isGroup, isRoom] = [false,false,false]
|
||||||
switch (chatID.charAt(0)) {
|
switch (chatID.charAt(0)) {
|
||||||
case "u":
|
case "u":
|
||||||
|
@ -756,6 +781,36 @@ export default class MessagesPuppeteer {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chatListItem = await this.page.$(this._chatItemSelector(chatID))
|
||||||
|
if (!chatListItem) {
|
||||||
|
if (isDirect) {
|
||||||
|
const friendsListItem = await this.page.$(this._friendItemSelector(chatID))
|
||||||
|
if (!friendsListItem) {
|
||||||
|
throw `Cannot find friend with ID ${chatID}`
|
||||||
|
}
|
||||||
|
const friendsListInfo = await friendsListItem.evaluate(
|
||||||
|
(element, chatID) => window.__mautrixController.parseFriendsListItem(element, chatID),
|
||||||
|
chatID)
|
||||||
|
|
||||||
|
this.log(`Found NEW direct chat with ${chatID}`)
|
||||||
|
return {
|
||||||
|
participants: [friendsListInfo],
|
||||||
|
id: chatID,
|
||||||
|
name: friendsListInfo.name,
|
||||||
|
icon: friendsListInfo.avatar,
|
||||||
|
lastMsg: null,
|
||||||
|
lastMsgDate: null,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
throw "Can't yet get info of new groups/rooms"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatListInfo = await chatListItem.evaluate(
|
||||||
|
(element, chatID) => window.__mautrixController.parseChatListItem(element, chatID),
|
||||||
|
chatID)
|
||||||
|
|
||||||
let participants
|
let participants
|
||||||
if (!isDirect) {
|
if (!isDirect) {
|
||||||
this.log("Found multi-user chat, so viewing it to get participants")
|
this.log("Found multi-user chat, so viewing it to get participants")
|
||||||
|
|
Loading…
Reference in New Issue