Support LINE users with no discoverable ID
AKA "strangers". Should only happen to non-friends in rooms (not groups!)
This commit is contained in:
parent
3c5c8cd610
commit
8613ad1256
|
@ -3,6 +3,7 @@ from mautrix.util.async_db import Database
|
||||||
from .upgrade import upgrade_table
|
from .upgrade import upgrade_table
|
||||||
from .user import User
|
from .user import User
|
||||||
from .puppet import Puppet
|
from .puppet import Puppet
|
||||||
|
from .stranger import Stranger
|
||||||
from .portal import Portal
|
from .portal import Portal
|
||||||
from .message import Message
|
from .message import Message
|
||||||
from .media import Media
|
from .media import Media
|
||||||
|
@ -10,8 +11,8 @@ from .receipt_reaction import ReceiptReaction
|
||||||
|
|
||||||
|
|
||||||
def init(db: Database) -> None:
|
def init(db: Database) -> None:
|
||||||
for table in (User, Puppet, Portal, Message, Media, ReceiptReaction):
|
for table in (User, Puppet, Stranger, Portal, Message, Media, ReceiptReaction):
|
||||||
table.db = db
|
table.db = db
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["upgrade_table", "User", "Puppet", "Portal", "Message", "Media", "ReceiptReaction"]
|
__all__ = ["upgrade_table", "User", "Puppet", "Stranger", "Portal", "Message", "Media", "ReceiptReaction"]
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
# 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 typing import Optional, ClassVar, TYPE_CHECKING
|
||||||
|
|
||||||
|
from attr import dataclass
|
||||||
|
from random import randint, seed
|
||||||
|
|
||||||
|
from mautrix.util.async_db import Database
|
||||||
|
|
||||||
|
fake_db = Database("") if TYPE_CHECKING else None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Stranger:
|
||||||
|
db: ClassVar[Database] = fake_db
|
||||||
|
|
||||||
|
# Optional properties are ones that should be set by Puppet
|
||||||
|
fake_mid: str
|
||||||
|
name: Optional[str] = None
|
||||||
|
avatar_path: Optional[str] = None
|
||||||
|
available: bool = False
|
||||||
|
|
||||||
|
async def insert(self) -> None:
|
||||||
|
q = ("INSERT INTO stranger (fake_mid, name, avatar_path, available) "
|
||||||
|
"VALUES ($1, $2, $3, $4)")
|
||||||
|
await self.db.execute(q, self.fake_mid, self.name, self.avatar_path, self.available)
|
||||||
|
|
||||||
|
async def update_profile_info(self) -> None:
|
||||||
|
q = ("UPDATE stranger SET name=$2, avatar_path=$3 "
|
||||||
|
"WHERE fake_mid=$1")
|
||||||
|
await self.db.execute(q, self.fake_mid, self.name, self.avatar_path)
|
||||||
|
|
||||||
|
async def make_available(self) -> None:
|
||||||
|
q = ("UPDATE stranger SET available=true "
|
||||||
|
"WHERE name=$1 AND avatar_path=$2")
|
||||||
|
await self.db.execute(q, self.name, self.avatar_path)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_by_mid(cls, mid: str) -> Optional['Stranger']:
|
||||||
|
q = ("SELECT fake_mid, name, avatar_path, available "
|
||||||
|
"FROM stranger WHERE fake_mid=$1")
|
||||||
|
row = await cls.db.fetchrow(q, mid)
|
||||||
|
if not row:
|
||||||
|
return None
|
||||||
|
return cls(**row)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_by_profile(cls, info: 'Participant') -> Optional['Stranger']:
|
||||||
|
q = ("SELECT fake_mid, name, avatar_path, available "
|
||||||
|
"FROM stranger WHERE name=$1 AND avatar_path=$2")
|
||||||
|
row = await cls.db.fetchrow(q, info.name, info.avatar.path)
|
||||||
|
if not row:
|
||||||
|
return None
|
||||||
|
return cls(**row)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_any_available(cls) -> Optional['Stranger']:
|
||||||
|
q = ("SELECT fake_mid, name, avatar_path, available "
|
||||||
|
"FROM stranger WHERE available=true")
|
||||||
|
row = await cls.db.fetchrow(q)
|
||||||
|
if not row:
|
||||||
|
return None
|
||||||
|
return cls(**row)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def init_available_or_new(cls) -> 'Stranger':
|
||||||
|
stranger = await cls.get_any_available()
|
||||||
|
if not stranger:
|
||||||
|
while True:
|
||||||
|
fake_mid = "_STRANGER_"
|
||||||
|
for _ in range(32):
|
||||||
|
fake_mid += f"{randint(0,15):x}"
|
||||||
|
if await cls.get_by_mid(fake_mid) != None:
|
||||||
|
# Extremely unlikely event of a randomly-generated ID colliding with another.
|
||||||
|
# If it happens, must be not that unlikely after all, so pick a new seed.
|
||||||
|
seed()
|
||||||
|
else:
|
||||||
|
stranger = cls(fake_mid)
|
||||||
|
break
|
||||||
|
return stranger
|
|
@ -109,7 +109,23 @@ async def upgrade_read_receipts(conn: Connection) -> None:
|
||||||
@upgrade_table.register(description="Media metadata")
|
@upgrade_table.register(description="Media metadata")
|
||||||
async def upgrade_deduplicate_blob(conn: Connection) -> None:
|
async def upgrade_deduplicate_blob(conn: Connection) -> None:
|
||||||
await conn.execute("""ALTER TABLE media
|
await conn.execute("""ALTER TABLE media
|
||||||
ADD COLUMN IF NOT EXISTS mime_type TEXT,
|
ADD COLUMN IF NOT EXISTS mime_type TEXT,
|
||||||
ADD COLUMN IF NOT EXISTS file_name TEXT,
|
ADD COLUMN IF NOT EXISTS file_name TEXT,
|
||||||
ADD COLUMN IF NOT EXISTS size INTEGER
|
ADD COLUMN IF NOT EXISTS size INTEGER
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
@upgrade_table.register(description="Strangers")
|
||||||
|
async def upgrade_strangers(conn: Connection) -> None:
|
||||||
|
await conn.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS stranger (
|
||||||
|
fake_mid TEXT NOT NULL UNIQUE,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
avatar_path TEXT NOT NULL,
|
||||||
|
available BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
|
||||||
|
PRIMARY KEY (name, avatar_path),
|
||||||
|
FOREIGN KEY (fake_mid)
|
||||||
|
REFERENCES puppet (mid)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
)""")
|
|
@ -205,29 +205,34 @@ class Portal(DBPortal, BasePortal):
|
||||||
intent = None
|
intent = None
|
||||||
return intent
|
return intent
|
||||||
|
|
||||||
async def handle_remote_message(self, source: 'u.User', sender: Optional['p.Puppet'],
|
async def handle_remote_message(self, source: 'u.User', evt: Message) -> None:
|
||||||
evt: Message) -> None:
|
if await DBMessage.get_by_mid(evt.id):
|
||||||
|
self.log.debug(f"Ignoring duplicate message {evt.id}")
|
||||||
|
return
|
||||||
|
|
||||||
if evt.is_outgoing:
|
if evt.is_outgoing:
|
||||||
if source.intent:
|
if source.intent:
|
||||||
|
sender = None
|
||||||
intent = source.intent
|
intent = source.intent
|
||||||
else:
|
else:
|
||||||
if not self.invite_own_puppet_to_pm:
|
if not self.invite_own_puppet_to_pm:
|
||||||
self.log.warning(f"Ignoring message {evt.id}: double puppeting isn't enabled")
|
self.log.warning(f"Ignoring message {evt.id}: double puppeting isn't enabled")
|
||||||
return
|
return
|
||||||
|
sender = p.Puppet.get_by_mid(evt.sender.id) if not self.is_direct else None
|
||||||
intent = await self._bridge_own_message_pm(source, sender, f"message {evt.id}")
|
intent = await self._bridge_own_message_pm(source, sender, f"message {evt.id}")
|
||||||
if not intent:
|
if not intent:
|
||||||
return
|
return
|
||||||
elif self.other_user:
|
|
||||||
intent = (await p.Puppet.get_by_mid(self.other_user)).intent
|
|
||||||
elif sender:
|
|
||||||
intent = sender.intent
|
|
||||||
else:
|
else:
|
||||||
self.log.warning(f"Ignoring message {evt.id}: sender puppet is unavailable")
|
sender = await p.Puppet.get_by_mid(self.other_user if self.is_direct else evt.sender.id)
|
||||||
return
|
# TODO Respond to name/avatar changes of users in a DM
|
||||||
|
if not self.is_direct:
|
||||||
if await DBMessage.get_by_mid(evt.id):
|
if sender:
|
||||||
self.log.debug(f"Ignoring duplicate message {evt.id}")
|
await sender.update_info(evt.sender, source.client)
|
||||||
return
|
else:
|
||||||
|
self.log.warning(f"Could not find ID of LINE user who sent event {evt.id}")
|
||||||
|
sender = await p.Puppet.get_by_profile(evt.sender, source.client)
|
||||||
|
intent = sender.intent
|
||||||
|
intent.ensure_joined(self.mxid)
|
||||||
|
|
||||||
if evt.image and evt.image.url:
|
if evt.image and evt.image.url:
|
||||||
if not evt.image.is_sticker or self.config["bridge.receive_stickers"]:
|
if not evt.image.is_sticker or self.config["bridge.receive_stickers"]:
|
||||||
|
@ -318,6 +323,9 @@ class Portal(DBPortal, BasePortal):
|
||||||
format=Format.HTML if msg_html else None,
|
format=Format.HTML if msg_html else None,
|
||||||
body=msg_text, formatted_body=msg_html)
|
body=msg_text, formatted_body=msg_html)
|
||||||
event_id = await self._send_message(intent, content, timestamp=evt.timestamp)
|
event_id = await self._send_message(intent, content, timestamp=evt.timestamp)
|
||||||
|
# TODO Joins/leaves/invites/rejects, which are sent as LINE message events after all!
|
||||||
|
# Also keep track of strangers who leave / get blocked / become friends
|
||||||
|
# (maybe not here for all of that)
|
||||||
else:
|
else:
|
||||||
content = TextMessageEventContent(
|
content = TextMessageEventContent(
|
||||||
msgtype=MessageType.NOTICE,
|
msgtype=MessageType.NOTICE,
|
||||||
|
@ -349,11 +357,13 @@ class Portal(DBPortal, BasePortal):
|
||||||
if reaction:
|
if reaction:
|
||||||
await self.main_intent.redact(self.mxid, reaction.mxid)
|
await self.main_intent.redact(self.mxid, reaction.mxid)
|
||||||
await reaction.delete()
|
await reaction.delete()
|
||||||
|
# If there are as many receipts as there are chat participants, then everyone
|
||||||
|
# must have read the message, so send real read receipts from each puppet.
|
||||||
# TODO Not just -1 if there are multiple _OWN_ puppets...
|
# TODO Not just -1 if there are multiple _OWN_ puppets...
|
||||||
if receipt_count == len(self._last_participant_update) - 1:
|
if receipt_count == len(self._last_participant_update) - 1:
|
||||||
for participant in filter(lambda participant: not p.Puppet.is_mid_for_own_puppet(participant), self._last_participant_update):
|
for mid in filter(lambda mid: not p.Puppet.is_mid_for_own_puppet(mid), self._last_participant_update):
|
||||||
puppet = await p.Puppet.get_by_mid(participant)
|
intent = (await p.Puppet.get_by_mid(mid)).intent
|
||||||
await puppet.intent.send_receipt(self.mxid, event_id)
|
await intent.send_receipt(self.mxid, event_id)
|
||||||
else:
|
else:
|
||||||
# TODO Translatable string for "Read by"
|
# TODO Translatable string for "Read by"
|
||||||
reaction_mxid = await self.main_intent.react(self.mxid, event_id, f"(Read by {receipt_count})")
|
reaction_mxid = await self.main_intent.react(self.mxid, event_id, f"(Read by {receipt_count})")
|
||||||
|
@ -418,8 +428,13 @@ class Portal(DBPortal, BasePortal):
|
||||||
if self._main_intent is self.az.intent:
|
if self._main_intent is self.az.intent:
|
||||||
self._main_intent = (await p.Puppet.get_by_mid(self.other_user)).intent
|
self._main_intent = (await p.Puppet.get_by_mid(self.other_user)).intent
|
||||||
for participant in conv.participants:
|
for participant in conv.participants:
|
||||||
puppet = await p.Puppet.get_by_mid(participant.id)
|
# REMINDER: multi-user chats include your own LINE user in the participant list
|
||||||
await puppet.update_info(participant, client)
|
if participant.id != None:
|
||||||
|
puppet = await p.Puppet.get_by_mid(participant.id, client)
|
||||||
|
await puppet.update_info(participant, client)
|
||||||
|
else:
|
||||||
|
self.log.warning(f"Could not find ID of LINE user {participant.name}")
|
||||||
|
puppet = await p.Puppet.get_by_profile(participant, client)
|
||||||
# TODO Consider setting no room name for non-group chats.
|
# TODO Consider setting no room name for non-group chats.
|
||||||
# But then the LINE bot itself may appear in the title...
|
# But then the LINE bot itself may appear in the title...
|
||||||
changed = await self._update_name(f"{conv.name} (LINE)")
|
changed = await self._update_name(f"{conv.name} (LINE)")
|
||||||
|
@ -480,7 +495,12 @@ class Portal(DBPortal, BasePortal):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Store the current member list to prevent unnecessary updates
|
# Store the current member list to prevent unnecessary updates
|
||||||
current_members = {participant.id for participant in participants}
|
current_members = set()
|
||||||
|
for participant in participants:
|
||||||
|
current_members.add(
|
||||||
|
participant.id if participant.id != None else \
|
||||||
|
(await p.Puppet.get_by_profile(participant)).mid)
|
||||||
|
|
||||||
if current_members == self._last_participant_update:
|
if current_members == self._last_participant_update:
|
||||||
self.log.trace("Not updating participants: list matches cached list")
|
self.log.trace("Not updating participants: list matches cached list")
|
||||||
return
|
return
|
||||||
|
@ -495,7 +515,7 @@ class Portal(DBPortal, BasePortal):
|
||||||
for participant in participants:
|
for participant in participants:
|
||||||
if forbid_own_puppets and p.Puppet.is_mid_for_own_puppet(participant.id):
|
if forbid_own_puppets and p.Puppet.is_mid_for_own_puppet(participant.id):
|
||||||
continue
|
continue
|
||||||
intent = (await p.Puppet.get_by_mid(participant.id)).intent
|
intent = (await p.Puppet.get_by_sender(participant)).intent
|
||||||
await intent.ensure_joined(self.mxid)
|
await intent.ensure_joined(self.mxid)
|
||||||
|
|
||||||
print(current_members)
|
print(current_members)
|
||||||
|
@ -536,15 +556,8 @@ class Portal(DBPortal, BasePortal):
|
||||||
|
|
||||||
self.log.debug("Got %d messages from server", len(messages))
|
self.log.debug("Got %d messages from server", len(messages))
|
||||||
async with NotificationDisabler(self.mxid, source):
|
async with NotificationDisabler(self.mxid, source):
|
||||||
# Member joins/leaves are not shown in chat history.
|
|
||||||
# Best we can do is have a puppet join if its user had sent a message.
|
|
||||||
members_known = set(await self.main_intent.get_room_members(self.mxid)) if not self.is_direct else None
|
|
||||||
for evt in messages:
|
for evt in messages:
|
||||||
puppet = await p.Puppet.get_by_mid(evt.sender.id) if not self.is_direct else None
|
await self.handle_remote_message(source, evt)
|
||||||
if puppet and evt.sender.id not in members_known:
|
|
||||||
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)
|
self.log.info("Backfilled %d messages through %s", len(messages), source.mxid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -680,10 +693,8 @@ class Portal(DBPortal, BasePortal):
|
||||||
self.by_mxid[self.mxid] = self
|
self.by_mxid[self.mxid] = self
|
||||||
await self.backfill(source)
|
await self.backfill(source)
|
||||||
if not self.is_direct:
|
if not self.is_direct:
|
||||||
# For multi-user chats, backfill before updating participants,
|
# TODO Joins and leaves are (usually) shown after all, so track them properly.
|
||||||
# to act as as a best guess of when users actually joined.
|
# In the meantime, just check the participants list after backfilling.
|
||||||
# No way to tell when a user actually left, so just check the
|
|
||||||
# participants list after backfilling.
|
|
||||||
await self._update_participants(info.participants)
|
await self._update_participants(info.participants)
|
||||||
|
|
||||||
return self.mxid
|
return self.mxid
|
||||||
|
|
|
@ -19,7 +19,7 @@ from mautrix.bridge import BasePuppet
|
||||||
from mautrix.types import UserID, ContentURI
|
from mautrix.types import UserID, ContentURI
|
||||||
from mautrix.util.simple_template import SimpleTemplate
|
from mautrix.util.simple_template import SimpleTemplate
|
||||||
|
|
||||||
from .db import Puppet as DBPuppet
|
from .db import Puppet as DBPuppet, Stranger
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .rpc import Participant, Client, PathImage
|
from .rpc import Participant, Client, PathImage
|
||||||
from . import user as u
|
from . import user as u
|
||||||
|
@ -141,6 +141,9 @@ class Puppet(DBPuppet, BasePuppet):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_by_mid(cls, mid: str, create: bool = True) -> Optional['Puppet']:
|
async def get_by_mid(cls, mid: str, create: bool = True) -> Optional['Puppet']:
|
||||||
|
if mid is None:
|
||||||
|
return None
|
||||||
|
|
||||||
# TODO Might need to parse a real id from "_OWN"
|
# TODO Might need to parse a real id from "_OWN"
|
||||||
try:
|
try:
|
||||||
return cls.by_mid[mid]
|
return cls.by_mid[mid]
|
||||||
|
@ -160,10 +163,38 @@ class Puppet(DBPuppet, BasePuppet):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_by_profile(cls, info: Participant, client: Optional[Client] = None) -> 'Puppet':
|
||||||
|
stranger = await Stranger.get_by_profile(info)
|
||||||
|
if not stranger:
|
||||||
|
stranger = await Stranger.init_available_or_new()
|
||||||
|
|
||||||
|
puppet = cls(stranger.fake_mid)
|
||||||
|
# NOTE An update will insert anyways, so just do it now
|
||||||
|
await puppet.insert()
|
||||||
|
await puppet.update_info(info, client)
|
||||||
|
puppet._add_to_cache()
|
||||||
|
|
||||||
|
# Get path from puppet in case it uses the URL as the path.
|
||||||
|
# But that should never happen in practice for strangers,
|
||||||
|
# which should only occur in rooms, where avatars have paths.
|
||||||
|
stranger.avatar_path = puppet.avatar_path
|
||||||
|
stranger.name = info.name
|
||||||
|
await stranger.insert()
|
||||||
|
# TODO Need a way to keep stranger name/avatar up to date,
|
||||||
|
# lest name/avatar changes get seen as another stranger.
|
||||||
|
# Also need to detect when a stranger becomes a friend.
|
||||||
|
return await cls.get_by_mid(stranger.fake_mid)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_by_sender(cls, info: Participant, client: Optional[Client] = None) -> 'Puppet':
|
||||||
|
puppet = await cls.get_by_mid(info.id)
|
||||||
|
return puppet if puppet else await cls.get_by_profile(info, client)
|
||||||
|
|
||||||
# TODO When supporting multiple bridge users, this should return the user whose puppet this is
|
# TODO When supporting multiple bridge users, this should return the user whose puppet this is
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_mid_for_own_puppet(cls, mid) -> bool:
|
def is_mid_for_own_puppet(cls, mid) -> bool:
|
||||||
return mid.startswith("_OWN_") if mid else False
|
return mid and 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']:
|
||||||
|
|
|
@ -41,9 +41,9 @@ class ChatListInfo(SerializableAttrs['ChatListInfo']):
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Participant(SerializableAttrs['Participant']):
|
class Participant(SerializableAttrs['Participant']):
|
||||||
id: str
|
|
||||||
avatar: Optional[PathImage]
|
|
||||||
name: str
|
name: str
|
||||||
|
avatar: Optional[PathImage]
|
||||||
|
id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
|
@ -162,12 +162,11 @@ class User(DBUser, BaseUser):
|
||||||
async def handle_message(self, evt: Message) -> None:
|
async def handle_message(self, evt: Message) -> None:
|
||||||
self.log.trace("Received message %s", evt)
|
self.log.trace("Received message %s", evt)
|
||||||
portal = await po.Portal.get_by_chat_id(evt.chat_id, create=True)
|
portal = await po.Portal.get_by_chat_id(evt.chat_id, create=True)
|
||||||
puppet = await pu.Puppet.get_by_mid(evt.sender.id) if not portal.is_direct else None
|
|
||||||
if not portal.mxid:
|
if not portal.mxid:
|
||||||
await self.client.set_last_message_ids(await DBMessage.get_max_mids())
|
await self.client.set_last_message_ids(await DBMessage.get_max_mids())
|
||||||
chat_info = await self.client.get_chat(evt.chat_id)
|
chat_info = await self.client.get_chat(evt.chat_id)
|
||||||
await portal.create_matrix_room(self, chat_info)
|
await portal.create_matrix_room(self, chat_info)
|
||||||
await portal.handle_remote_message(self, puppet, evt)
|
await portal.handle_remote_message(self, evt)
|
||||||
|
|
||||||
async def handle_receipt(self, receipt: Receipt) -> None:
|
async def handle_receipt(self, receipt: Receipt) -> None:
|
||||||
self.log.trace(f"Received receipt for chat {receipt.chat_id}")
|
self.log.trace(f"Received receipt for chat {receipt.chat_id}")
|
||||||
|
|
|
@ -238,10 +238,18 @@ class MautrixController {
|
||||||
sender.id = this.getUserIdFromFriendsList(sender.name)
|
sender.id = this.getUserIdFromFriendsList(sender.name)
|
||||||
// Group members aren't necessarily friends,
|
// Group members aren't necessarily friends,
|
||||||
// but the participant list includes their ID.
|
// but the participant list includes their ID.
|
||||||
|
// ROOMS DO NOT!! Ugh.
|
||||||
if (!sender.id) {
|
if (!sender.id) {
|
||||||
const participantsList = document.querySelector(participantsListSelector)
|
const participantsList = document.querySelector(participantsListSelector)
|
||||||
imgElement = participantsList.querySelector(`img[alt='${sender.name}'`)
|
// Groups use a participant's name as the alt text of their avatar image,
|
||||||
sender.id = imgElement.parentElement.parentElement.getAttribute("data-mid")
|
// but rooms do not...ARGH! But they both use a dedicated element for it.
|
||||||
|
const participantNameElement =
|
||||||
|
Array.from(participantsList.querySelectorAll(`.mdRGT13Ttl`))
|
||||||
|
.find(e => e.innerText == "EN-MY translator")
|
||||||
|
if (participantNameElement) {
|
||||||
|
imgElement = participantNameElement.previousElementSibling.firstElementChild
|
||||||
|
sender.id = imgElement.parentElement.parentElement.getAttribute("data-mid")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
imgElement = element.querySelector(".mdRGT07Img > img")
|
imgElement = element.querySelector(".mdRGT07Img > img")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue