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],
|
event_ids: list[EventID],
|
||||||
timestamp: int,
|
timestamp: int,
|
||||||
mx_room: RoomID,
|
mx_room: RoomID,
|
||||||
) -> None:
|
) -> list[Message]:
|
||||||
if not event_ids:
|
if not event_ids:
|
||||||
return
|
return []
|
||||||
columns = [col.strip('"') for col in cls.columns.split(", ")]
|
columns = [col.strip('"') for col in cls.columns.split(", ")]
|
||||||
records = [
|
records = [
|
||||||
(mxid, mx_room, ktid, index, kt_chat, kt_receiver, timestamp)
|
(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)
|
await conn.copy_records_to_table("message", records=records, columns=columns)
|
||||||
else:
|
else:
|
||||||
await conn.executemany(cls._insert_query, records)
|
await conn.executemany(cls._insert_query, records)
|
||||||
|
return [Message(*record) for record in records]
|
||||||
|
|
||||||
|
|
||||||
async def insert(self) -> None:
|
async def insert(self) -> None:
|
||||||
|
|
|
@ -43,6 +43,16 @@ class Reaction:
|
||||||
return None
|
return None
|
||||||
return cls(**row)
|
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
|
@classmethod
|
||||||
async def get_by_ktid(cls, kt_msgid: str, kt_receiver: int, kt_sender: int) -> Reaction | None:
|
async def get_by_ktid(cls, kt_msgid: str, kt_receiver: int, kt_sender: int) -> Reaction | None:
|
||||||
q = (
|
q = (
|
||||||
|
|
|
@ -19,6 +19,9 @@ homeserver:
|
||||||
status_endpoint: null
|
status_endpoint: null
|
||||||
# Endpoint for reporting per-message status.
|
# Endpoint for reporting per-message status.
|
||||||
message_send_checkpoint_endpoint: null
|
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
|
# Application service host/registration related details
|
||||||
# Changing these values requires regeneration of the registration.
|
# 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."
|
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(
|
async def handle_invite(
|
||||||
self, room_id: RoomID, user_id: UserID, invited_by: u.User, event_id: EventID
|
self, room_id: RoomID, user_id: UserID, invited_by: u.User, event_id: EventID
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
@ -217,9 +217,14 @@ class Portal(DBPortal, BasePortal):
|
||||||
async def delete(self) -> None:
|
async def delete(self) -> None:
|
||||||
if self.mxid:
|
if self.mxid:
|
||||||
await DBMessage.delete_all_by_room(self.mxid)
|
await DBMessage.delete_all_by_room(self.mxid)
|
||||||
|
self.by_mxid.pop(self.mxid, None)
|
||||||
self.by_ktid.pop(self._ktid_full, None)
|
self.by_ktid.pop(self._ktid_full, None)
|
||||||
self.by_mxid.pop(self.mxid, None)
|
self.mxid = None
|
||||||
await super().delete()
|
self.name_set = False
|
||||||
|
self.avatar_set = False
|
||||||
|
self.relay_user_id = None
|
||||||
|
self.encrypted = False
|
||||||
|
await super().save()
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
# region Properties
|
# region Properties
|
||||||
|
@ -265,6 +270,11 @@ class Portal(DBPortal, BasePortal):
|
||||||
)
|
)
|
||||||
return self._main_intent
|
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
|
# endregion
|
||||||
# region Chat info updating
|
# region Chat info updating
|
||||||
|
|
||||||
|
@ -362,7 +372,12 @@ class Portal(DBPortal, BasePortal):
|
||||||
data, decryption_info = encrypt_attachment(data)
|
data, decryption_info = encrypt_attachment(data)
|
||||||
upload_mime_type = "application/octet-stream"
|
upload_mime_type = "application/octet-stream"
|
||||||
filename = None
|
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:
|
if decryption_info:
|
||||||
decryption_info.url = url
|
decryption_info.url = url
|
||||||
return url, info, decryption_info
|
return url, info, decryption_info
|
||||||
|
@ -436,6 +451,15 @@ class Portal(DBPortal, BasePortal):
|
||||||
self.avatar_set = False
|
self.avatar_set = False
|
||||||
return True
|
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:
|
async def sync_per_room_nick(self, puppet: p.Puppet, name: str) -> None:
|
||||||
intent = puppet.intent_for(self)
|
intent = puppet.intent_for(self)
|
||||||
|
@ -457,31 +481,41 @@ class Portal(DBPortal, BasePortal):
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
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_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)
|
||||||
|
#if puppet.ktid in nick_map:
|
||||||
|
# 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:
|
async def _update_participants(self, source: u.User, participants: list[UserInfoUnion] | None = None) -> bool:
|
||||||
|
# TODO nick map?
|
||||||
if participants is None:
|
if participants is None:
|
||||||
self.log.debug("Called _update_participants with no participants, fetching them now...")
|
self.log.debug("Called _update_participants with no participants, fetching them now...")
|
||||||
participants = await source.client.get_participants(self.channel_props)
|
participants = await source.client.get_participants(self.channel_props)
|
||||||
changed = False
|
|
||||||
if not self._main_intent:
|
if not self._main_intent:
|
||||||
assert self.is_direct, "_main_intent for non-direct chat portal should have been set already"
|
assert self.is_direct, "_main_intent for non-direct chat portal should have been set already"
|
||||||
self._kt_sender = participants[
|
self._kt_sender = participants[
|
||||||
0 if self.kt_type == KnownChannelType.MemoChat or participants[0].userId != source.ktid else 1
|
0 if self.kt_type == KnownChannelType.MemoChat or participants[0].userId != source.ktid else 1
|
||||||
].userId
|
].userId
|
||||||
self._main_intent = (await p.Puppet.get_by_ktid(self._kt_sender)).default_mxid_intent
|
self._main_intent = (await self.get_dm_puppet()).default_mxid_intent
|
||||||
else:
|
else:
|
||||||
self._kt_sender = (await p.Puppet.get_by_mxid(self._main_intent.mxid)).ktid if self.is_direct else None
|
self._kt_sender = (await p.Puppet.get_by_mxid(self._main_intent.mxid)).ktid if self.is_direct else None
|
||||||
# TODO nick_map?
|
sync_tasks = [
|
||||||
for participant in participants:
|
self._update_participant(source, pcp) for pcp in participants
|
||||||
puppet = await p.Puppet.get_by_ktid(participant.userId)
|
]
|
||||||
await puppet.update_info_from_participant(source, participant)
|
changed = any(await asyncio.gather(*sync_tasks))
|
||||||
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
|
|
||||||
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)
|
|
||||||
#if puppet.ktid in nick_map:
|
|
||||||
# await self.sync_per_room_nick(puppet, nick_map[puppet.ktid])
|
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
@ -833,7 +867,7 @@ class Portal(DBPortal, BasePortal):
|
||||||
if message.relates_to.rel_type == RelationType.REPLY:
|
if message.relates_to.rel_type == RelationType.REPLY:
|
||||||
reply_to_msg = await DBMessage.get_by_mxid(message.relates_to.event_id, self.mxid)
|
reply_to_msg = await DBMessage.get_by_mxid(message.relates_to.event_id, self.mxid)
|
||||||
if reply_to_msg:
|
if reply_to_msg:
|
||||||
reply_to = reply_to_msg.fbid
|
reply_to = reply_to_msg.ktid
|
||||||
else:
|
else:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
f"Couldn't find reply target {message.relates_to.event_id}"
|
f"Couldn't find reply target {message.relates_to.event_id}"
|
||||||
|
@ -928,16 +962,6 @@ class Portal(DBPortal, BasePortal):
|
||||||
)
|
)
|
||||||
self._typing = users
|
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
|
# endregion
|
||||||
# region KakaoTalk event handling
|
# region KakaoTalk event handling
|
||||||
|
|
||||||
|
@ -976,6 +1000,7 @@ class Portal(DBPortal, BasePortal):
|
||||||
sender: p.Puppet,
|
sender: p.Puppet,
|
||||||
message: Chatlog,
|
message: Chatlog,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
# TODO Backfill!! This avoids timing conflicts on startup sync
|
||||||
self.log.debug(f"Handling KakaoTalk event {message.logId}")
|
self.log.debug(f"Handling KakaoTalk event {message.logId}")
|
||||||
if not self.mxid:
|
if not self.mxid:
|
||||||
mxid = await self.create_matrix_room(source)
|
mxid = await self.create_matrix_room(source)
|
||||||
|
@ -1015,6 +1040,7 @@ class Portal(DBPortal, BasePortal):
|
||||||
self.log.warning(f"Unhandled KakaoTalk message {message.logId}")
|
self.log.warning(f"Unhandled KakaoTalk message {message.logId}")
|
||||||
return
|
return
|
||||||
self.log.debug(f"Handled KakaoTalk message {message.logId} -> {event_ids}")
|
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(
|
await DBMessage.bulk_create(
|
||||||
ktid=message.logId,
|
ktid=message.logId,
|
||||||
kt_chat=self.ktid,
|
kt_chat=self.ktid,
|
||||||
|
@ -1232,7 +1258,7 @@ class Portal(DBPortal, BasePortal):
|
||||||
messages = await source.client.get_chats(
|
messages = await source.client.get_chats(
|
||||||
self.channel_props,
|
self.channel_props,
|
||||||
after_log_id,
|
after_log_id,
|
||||||
limit
|
limit,
|
||||||
)
|
)
|
||||||
if not messages:
|
if not messages:
|
||||||
self.log.debug("Didn't get any messages from server")
|
self.log.debug("Didn't get any messages from server")
|
||||||
|
|
|
@ -204,8 +204,8 @@ class Puppet(DBPuppet, BasePuppet):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def reupload_avatar(
|
async def reupload_avatar(
|
||||||
|
self,
|
||||||
source: u.User,
|
source: u.User,
|
||||||
intent: IntentAPI,
|
intent: IntentAPI,
|
||||||
url: str,
|
url: str,
|
||||||
|
@ -214,7 +214,9 @@ class Puppet(DBPuppet, BasePuppet):
|
||||||
async with source.client.get(url) as resp:
|
async with source.client.get(url) as resp:
|
||||||
data = await resp.read()
|
data = await resp.read()
|
||||||
mime = magic.mimetype(data)
|
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:
|
async def _update_photo(self, source: u.User, photo_id: str) -> bool:
|
||||||
if photo_id != self.photo_id or not self.avatar_set:
|
if photo_id != self.photo_id or not self.avatar_set:
|
||||||
|
|
|
@ -555,6 +555,20 @@ class User(DBUser, BaseUser):
|
||||||
return None
|
return None
|
||||||
return await pu.Puppet.get_by_ktid(self.ktid)
|
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
|
# region KakaoTalk event handling
|
||||||
|
|
||||||
async def on_connect(self) -> None:
|
async def on_connect(self) -> None:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
aiohttp>=3,<4
|
aiohttp>=3,<4
|
||||||
asyncpg>=0.20,<0.26
|
asyncpg>=0.20,<0.26
|
||||||
commonmark>=0.8,<0.10
|
commonmark>=0.8,<0.10
|
||||||
mautrix==0.15.0rc4
|
mautrix>=0.15.4,<0.16
|
||||||
pycryptodome>=3,<4
|
pycryptodome>=3,<4
|
||||||
python-magic>=0.4,<0.5
|
python-magic>=0.4,<0.5
|
||||||
ruamel.yaml>=0.15.94,<0.18
|
ruamel.yaml>=0.15.94,<0.18
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -48,7 +48,7 @@ setuptools.setup(
|
||||||
|
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
extras_require=extras_require,
|
extras_require=extras_require,
|
||||||
python_requires="~=3.7",
|
python_requires="~=3.8",
|
||||||
|
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 1 - Planning",
|
"Development Status :: 1 - Planning",
|
||||||
|
|
Loading…
Reference in New Issue