Update mautrix-python & copy latest goodies from mautrix-facebook
This commit is contained in:
parent
c8803bc8cc
commit
3900e666ff
|
@ -110,9 +110,9 @@ class Message:
|
|||
event_ids: list[EventID],
|
||||
timestamp: int,
|
||||
mx_room: RoomID,
|
||||
) -> None:
|
||||
) -> list[Message]:
|
||||
if not event_ids:
|
||||
return
|
||||
return []
|
||||
columns = [col.strip('"') for col in cls.columns.split(", ")]
|
||||
records = [
|
||||
(mxid, mx_room, ktid, index, kt_chat, kt_receiver, timestamp)
|
||||
|
@ -123,6 +123,7 @@ class Message:
|
|||
await conn.copy_records_to_table("message", records=records, columns=columns)
|
||||
else:
|
||||
await conn.executemany(cls._insert_query, records)
|
||||
return [Message(*record) for record in records]
|
||||
|
||||
|
||||
async def insert(self) -> None:
|
||||
|
|
|
@ -43,6 +43,16 @@ class Reaction:
|
|||
return None
|
||||
return cls(**row)
|
||||
|
||||
@classmethod
|
||||
async def get_by_message_ktid(cls, kt_msgid: str, kt_receiver: int) -> dict[int, Reaction]:
|
||||
q = (
|
||||
"SELECT mxid, mx_room, kt_msgid, kt_receiver, kt_sender, reaction "
|
||||
"FROM reaction WHERE kt_msgid=$1 AND kt_receiver=$2"
|
||||
)
|
||||
rows = await cls.db.fetch(q, kt_msgid, kt_receiver)
|
||||
row_gen = (cls._from_row(row) for row in rows)
|
||||
return {react.kt_sender: react for react in row_gen}
|
||||
|
||||
@classmethod
|
||||
async def get_by_ktid(cls, kt_msgid: str, kt_receiver: int, kt_sender: int) -> Reaction | None:
|
||||
q = (
|
||||
|
|
|
@ -19,6 +19,9 @@ homeserver:
|
|||
status_endpoint: null
|
||||
# Endpoint for reporting per-message status.
|
||||
message_send_checkpoint_endpoint: null
|
||||
# Whether asynchronous uploads via MSC2246 should be enabled for media.
|
||||
# Requires a media repo that supports MSC2246.
|
||||
async_media: false
|
||||
|
||||
# Application service host/registration related details
|
||||
# Changing these values requires regeneration of the registration.
|
||||
|
|
|
@ -64,80 +64,6 @@ class MatrixHandler(BaseMatrixHandler):
|
|||
room_id, "This room has been marked as your KakaoTalk bridge notice room."
|
||||
)
|
||||
|
||||
async def handle_puppet_invite(
|
||||
self, room_id: RoomID, puppet: pu.Puppet, invited_by: u.User, event_id: EventID
|
||||
) -> None:
|
||||
intent = puppet.default_mxid_intent
|
||||
self.log.debug(f"{invited_by.mxid} invited puppet for {puppet.ktid} to {room_id}")
|
||||
if not await invited_by.is_logged_in():
|
||||
await intent.error_and_leave(
|
||||
room_id,
|
||||
text="Please log in before inviting KakaoTalk puppets to private chats.",
|
||||
)
|
||||
return
|
||||
|
||||
portal = await po.Portal.get_by_mxid(room_id)
|
||||
if portal:
|
||||
if portal.is_direct:
|
||||
await intent.error_and_leave(
|
||||
room_id, text="You can not invite additional users to private chats."
|
||||
)
|
||||
return
|
||||
# TODO add KakaoTalk inviting
|
||||
# await portal.invite_kakaotalk(inviter, puppet)
|
||||
# await intent.join_room(room_id)
|
||||
return
|
||||
await intent.join_room(room_id)
|
||||
try:
|
||||
members = await intent.get_room_members(room_id)
|
||||
except MatrixError:
|
||||
self.log.exception(f"Failed to get member list after joining {room_id}")
|
||||
await intent.leave_room(room_id)
|
||||
return
|
||||
if len(members) > 2:
|
||||
# TODO add KakaoTalk group creating
|
||||
await intent.send_notice(
|
||||
room_id, "You can not invite KakaoTalk puppets to multi-user rooms."
|
||||
)
|
||||
await intent.leave_room(room_id)
|
||||
return
|
||||
portal = await po.Portal.get_by_ktid(
|
||||
puppet.ktid, fb_receiver=invited_by.ktid # TODO kt_type=??
|
||||
)
|
||||
if portal.mxid:
|
||||
try:
|
||||
await intent.invite_user(portal.mxid, invited_by.mxid, check_cache=False)
|
||||
await intent.send_notice(
|
||||
room_id,
|
||||
text=f"You already have a private chat with me in room {portal.mxid}",
|
||||
html=(
|
||||
"You already have a private chat with me: "
|
||||
f"<a href='https://matrix.to/#/{portal.mxid}'>Link to room</a>"
|
||||
),
|
||||
)
|
||||
await intent.leave_room(room_id)
|
||||
return
|
||||
except MatrixError:
|
||||
pass
|
||||
portal.mxid = room_id
|
||||
e2be_ok = await portal.check_dm_encryption()
|
||||
await portal.save()
|
||||
if e2be_ok is True:
|
||||
evt_type, content = await self.e2ee.encrypt(
|
||||
room_id,
|
||||
EventType.ROOM_MESSAGE,
|
||||
TextMessageEventContent(
|
||||
msgtype=MessageType.NOTICE,
|
||||
body="Portal to private chat created and end-to-bridge encryption enabled.",
|
||||
),
|
||||
)
|
||||
await intent.send_message_event(room_id, evt_type, content)
|
||||
else:
|
||||
message = "Portal to private chat created."
|
||||
if e2be_ok is False:
|
||||
message += "\n\nWarning: Failed to enable end-to-bridge encryption"
|
||||
await intent.send_notice(room_id, message)
|
||||
|
||||
async def handle_invite(
|
||||
self, room_id: RoomID, user_id: UserID, invited_by: u.User, event_id: EventID
|
||||
) -> None:
|
||||
|
|
|
@ -217,9 +217,14 @@ class Portal(DBPortal, BasePortal):
|
|||
async def delete(self) -> None:
|
||||
if self.mxid:
|
||||
await DBMessage.delete_all_by_room(self.mxid)
|
||||
self.by_ktid.pop(self._ktid_full, None)
|
||||
self.by_mxid.pop(self.mxid, None)
|
||||
await super().delete()
|
||||
self.by_ktid.pop(self._ktid_full, None)
|
||||
self.mxid = None
|
||||
self.name_set = False
|
||||
self.avatar_set = False
|
||||
self.relay_user_id = None
|
||||
self.encrypted = False
|
||||
await super().save()
|
||||
|
||||
# endregion
|
||||
# region Properties
|
||||
|
@ -265,6 +270,11 @@ class Portal(DBPortal, BasePortal):
|
|||
)
|
||||
return self._main_intent
|
||||
|
||||
async def get_dm_puppet(self) -> p.Puppet | None:
|
||||
if not self.is_direct:
|
||||
return None
|
||||
return await p.Puppet.get_by_ktid(self.kt_sender)
|
||||
|
||||
# endregion
|
||||
# region Chat info updating
|
||||
|
||||
|
@ -362,7 +372,12 @@ class Portal(DBPortal, BasePortal):
|
|||
data, decryption_info = encrypt_attachment(data)
|
||||
upload_mime_type = "application/octet-stream"
|
||||
filename = None
|
||||
url = await intent.upload_media(data, mime_type=upload_mime_type, filename=filename)
|
||||
url = await intent.upload_media(
|
||||
data,
|
||||
mime_type=upload_mime_type,
|
||||
filename=filename,
|
||||
async_upload=cls.config["homeserver.async_media"],
|
||||
)
|
||||
if decryption_info:
|
||||
decryption_info.url = url
|
||||
return url, info, decryption_info
|
||||
|
@ -436,6 +451,15 @@ class Portal(DBPortal, BasePortal):
|
|||
self.avatar_set = False
|
||||
return True
|
||||
|
||||
async def update_info_from_puppet(self, puppet: p.Puppet | None = None) -> bool:
|
||||
if not self.is_direct:
|
||||
return False
|
||||
if not puppet:
|
||||
puppet = await self.get_dm_puppet()
|
||||
changed = await self._update_name(puppet.name)
|
||||
changed = await self._update_photo_from_puppet(puppet) or changed
|
||||
return changed
|
||||
|
||||
"""
|
||||
async def sync_per_room_nick(self, puppet: p.Puppet, name: str) -> None:
|
||||
intent = puppet.intent_for(self)
|
||||
|
@ -457,26 +481,16 @@ class Portal(DBPortal, BasePortal):
|
|||
)
|
||||
"""
|
||||
|
||||
async def _update_participants(self, source: u.User, participants: list[UserInfoUnion] | None = None) -> bool:
|
||||
if participants is None:
|
||||
self.log.debug("Called _update_participants with no participants, fetching them now...")
|
||||
participants = await source.client.get_participants(self.channel_props)
|
||||
changed = False
|
||||
if not self._main_intent:
|
||||
assert self.is_direct, "_main_intent for non-direct chat portal should have been set already"
|
||||
self._kt_sender = participants[
|
||||
0 if self.kt_type == KnownChannelType.MemoChat or participants[0].userId != source.ktid else 1
|
||||
].userId
|
||||
self._main_intent = (await p.Puppet.get_by_ktid(self._kt_sender)).default_mxid_intent
|
||||
else:
|
||||
self._kt_sender = (await p.Puppet.get_by_mxid(self._main_intent.mxid)).ktid if self.is_direct else None
|
||||
# TODO nick_map?
|
||||
for participant in participants:
|
||||
async def _update_participant(
|
||||
self, source: u.User, participant: UserInfoUnion
|
||||
) -> bool:
|
||||
# TODO nick map?
|
||||
self.log.trace("Syncing participant %s", participant.id)
|
||||
puppet = await p.Puppet.get_by_ktid(participant.userId)
|
||||
await puppet.update_info_from_participant(source, participant)
|
||||
changed = False
|
||||
if self.is_direct and self._kt_sender == puppet.ktid and self.encrypted:
|
||||
changed = await self._update_name(puppet.name) or changed
|
||||
changed = await self._update_photo_from_puppet(puppet) or changed
|
||||
changed = await self._update_info_from_puppet(puppet.name) or changed
|
||||
if self.mxid:
|
||||
if puppet.ktid != self.kt_receiver or puppet.is_real_user:
|
||||
await puppet.intent_for(self).ensure_joined(self.mxid, bot=self.main_intent)
|
||||
|
@ -484,6 +498,26 @@ class Portal(DBPortal, BasePortal):
|
|||
# await self.sync_per_room_nick(puppet, nick_map[puppet.ktid])
|
||||
return changed
|
||||
|
||||
|
||||
async def _update_participants(self, source: u.User, participants: list[UserInfoUnion] | None = None) -> bool:
|
||||
# TODO nick map?
|
||||
if participants is None:
|
||||
self.log.debug("Called _update_participants with no participants, fetching them now...")
|
||||
participants = await source.client.get_participants(self.channel_props)
|
||||
if not self._main_intent:
|
||||
assert self.is_direct, "_main_intent for non-direct chat portal should have been set already"
|
||||
self._kt_sender = participants[
|
||||
0 if self.kt_type == KnownChannelType.MemoChat or participants[0].userId != source.ktid else 1
|
||||
].userId
|
||||
self._main_intent = (await self.get_dm_puppet()).default_mxid_intent
|
||||
else:
|
||||
self._kt_sender = (await p.Puppet.get_by_mxid(self._main_intent.mxid)).ktid if self.is_direct else None
|
||||
sync_tasks = [
|
||||
self._update_participant(source, pcp) for pcp in participants
|
||||
]
|
||||
changed = any(await asyncio.gather(*sync_tasks))
|
||||
return changed
|
||||
|
||||
# endregion
|
||||
# region Matrix room creation
|
||||
|
||||
|
@ -833,7 +867,7 @@ class Portal(DBPortal, BasePortal):
|
|||
if message.relates_to.rel_type == RelationType.REPLY:
|
||||
reply_to_msg = await DBMessage.get_by_mxid(message.relates_to.event_id, self.mxid)
|
||||
if reply_to_msg:
|
||||
reply_to = reply_to_msg.fbid
|
||||
reply_to = reply_to_msg.ktid
|
||||
else:
|
||||
self.log.warning(
|
||||
f"Couldn't find reply target {message.relates_to.event_id}"
|
||||
|
@ -928,16 +962,6 @@ class Portal(DBPortal, BasePortal):
|
|||
)
|
||||
self._typing = users
|
||||
|
||||
async def enable_dm_encryption(self) -> bool:
|
||||
ok = await super().enable_dm_encryption()
|
||||
if ok:
|
||||
try:
|
||||
puppet = await p.Puppet.get_by_ktid(self.ktid)
|
||||
await self.main_intent.set_room_name(self.mxid, puppet.name)
|
||||
except Exception:
|
||||
self.log.warning(f"Failed to set room name", exc_info=True)
|
||||
return ok
|
||||
|
||||
# endregion
|
||||
# region KakaoTalk event handling
|
||||
|
||||
|
@ -976,6 +1000,7 @@ class Portal(DBPortal, BasePortal):
|
|||
sender: p.Puppet,
|
||||
message: Chatlog,
|
||||
) -> None:
|
||||
# TODO Backfill!! This avoids timing conflicts on startup sync
|
||||
self.log.debug(f"Handling KakaoTalk event {message.logId}")
|
||||
if not self.mxid:
|
||||
mxid = await self.create_matrix_room(source)
|
||||
|
@ -1015,6 +1040,7 @@ class Portal(DBPortal, BasePortal):
|
|||
self.log.warning(f"Unhandled KakaoTalk message {message.logId}")
|
||||
return
|
||||
self.log.debug(f"Handled KakaoTalk message {message.logId} -> {event_ids}")
|
||||
# TODO Might have to handle remote reactions on messages created by bulk_create
|
||||
await DBMessage.bulk_create(
|
||||
ktid=message.logId,
|
||||
kt_chat=self.ktid,
|
||||
|
@ -1232,7 +1258,7 @@ class Portal(DBPortal, BasePortal):
|
|||
messages = await source.client.get_chats(
|
||||
self.channel_props,
|
||||
after_log_id,
|
||||
limit
|
||||
limit,
|
||||
)
|
||||
if not messages:
|
||||
self.log.debug("Didn't get any messages from server")
|
||||
|
|
|
@ -204,8 +204,8 @@ class Puppet(DBPuppet, BasePuppet):
|
|||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
async def reupload_avatar(
|
||||
self,
|
||||
source: u.User,
|
||||
intent: IntentAPI,
|
||||
url: str,
|
||||
|
@ -214,7 +214,9 @@ class Puppet(DBPuppet, BasePuppet):
|
|||
async with source.client.get(url) as resp:
|
||||
data = await resp.read()
|
||||
mime = magic.mimetype(data)
|
||||
return await intent.upload_media(data, mime_type=mime)
|
||||
return await intent.upload_media(
|
||||
data, mime_type=mime, async_upload=self.config["homeserver.async_media"]
|
||||
)
|
||||
|
||||
async def _update_photo(self, source: u.User, photo_id: str) -> bool:
|
||||
if photo_id != self.photo_id or not self.avatar_set:
|
||||
|
|
|
@ -555,6 +555,20 @@ class User(DBUser, BaseUser):
|
|||
return None
|
||||
return await pu.Puppet.get_by_ktid(self.ktid)
|
||||
|
||||
async def get_portal_with(self, puppet: pu.Puppet, create: bool = True) -> po.Portal | None:
|
||||
# TODO
|
||||
return None
|
||||
"""
|
||||
if not self.ktid or not self.client:
|
||||
return None
|
||||
return await po.Portal.get_by_ktid(
|
||||
await self.client.get_dm_channel_id_for(puppet.ktid),
|
||||
kt_receiver=self.ktid,
|
||||
create=create,
|
||||
kt_type=KnownChannelType.DirectChat if puppet.ktid != self.ktid else KnownChannelType.MemoChat
|
||||
)
|
||||
"""
|
||||
|
||||
# region KakaoTalk event handling
|
||||
|
||||
async def on_connect(self) -> None:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
aiohttp>=3,<4
|
||||
asyncpg>=0.20,<0.26
|
||||
commonmark>=0.8,<0.10
|
||||
mautrix==0.15.0rc4
|
||||
mautrix>=0.15.4,<0.16
|
||||
pycryptodome>=3,<4
|
||||
python-magic>=0.4,<0.5
|
||||
ruamel.yaml>=0.15.94,<0.18
|
||||
|
|
Loading…
Reference in New Issue