diff --git a/matrix_appservice_kakaotalk/db/message.py b/matrix_appservice_kakaotalk/db/message.py
index 30ebb0c..ed01c36 100644
--- a/matrix_appservice_kakaotalk/db/message.py
+++ b/matrix_appservice_kakaotalk/db/message.py
@@ -21,7 +21,7 @@ from asyncpg import Record
from attr import dataclass, field
from mautrix.types import EventID, RoomID
-from mautrix.util.async_db import Database
+from mautrix.util.async_db import Database, Scheme
from ..kt.types.bson import Long
@@ -101,6 +101,30 @@ class Message:
"VALUES ($1, $2, $3, $4, $5, $6, $7)"
)
+ @classmethod
+ async def bulk_create(
+ cls,
+ ktid: str,
+ kt_chat: int,
+ kt_receiver: int,
+ event_ids: list[EventID],
+ timestamp: int,
+ mx_room: RoomID,
+ ) -> None:
+ if not event_ids:
+ return
+ columns = [col.strip('"') for col in cls.columns.split(", ")]
+ records = [
+ (mxid, mx_room, ktid, index, kt_chat, kt_receiver, timestamp)
+ for index, mxid in enumerate(event_ids)
+ ]
+ async with cls.db.acquire() as conn, conn.transaction():
+ if cls.db.scheme == Scheme.POSTGRES:
+ await conn.copy_records_to_table("message", records=records, columns=columns)
+ else:
+ await conn.executemany(cls._insert_query, records)
+
+
async def insert(self) -> None:
q = self._insert_query
await self.db.execute(
diff --git a/matrix_appservice_kakaotalk/kt/client/client.py b/matrix_appservice_kakaotalk/kt/client/client.py
index 0e374c9..c93800c 100644
--- a/matrix_appservice_kakaotalk/kt/client/client.py
+++ b/matrix_appservice_kakaotalk/kt/client/client.py
@@ -23,7 +23,7 @@ with any other potential backend.
from __future__ import annotations
from typing import TYPE_CHECKING, cast, Type, Optional, Union
-
+from contextlib import asynccontextmanager
import logging
import urllib.request
@@ -65,6 +65,12 @@ if TYPE_CHECKING:
from ...user import User
+@asynccontextmanager
+async def sandboxed_get(url: URL) -> _RequestContextManager:
+ async with ClientSession() as sess, sess.get(url) as resp:
+ yield resp
+
+
# TODO Consider defining an interface for this, with node/native backend as swappable implementations
# TODO If no state is stored, consider using free functions instead of classmethods
class Client:
@@ -154,6 +160,7 @@ class Client:
self,
url: Union[str, URL],
headers: Optional[dict[str, str]] = None,
+ sandbox: bool = False,
**kwargs,
) -> _RequestContextManager:
# TODO Is auth ever needed?
@@ -163,6 +170,8 @@ class Client:
**(headers or {}),
}
url = URL(url)
+ if sandbox:
+ return sandboxed_get(url)
return self.http.get(url, headers=headers, **kwargs)
# endregion
diff --git a/matrix_appservice_kakaotalk/kt/types/chat/__init__.py b/matrix_appservice_kakaotalk/kt/types/chat/__init__.py
index c0eff5d..b3fa5a2 100644
--- a/matrix_appservice_kakaotalk/kt/types/chat/__init__.py
+++ b/matrix_appservice_kakaotalk/kt/types/chat/__init__.py
@@ -13,7 +13,6 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-"""
+from .attachment import *
from .chat_type import *
from .chat import *
-"""
diff --git a/matrix_appservice_kakaotalk/kt/types/attachment/__init__.py b/matrix_appservice_kakaotalk/kt/types/chat/attachment/__init__.py
similarity index 74%
rename from matrix_appservice_kakaotalk/kt/types/attachment/__init__.py
rename to matrix_appservice_kakaotalk/kt/types/chat/attachment/__init__.py
index 0b590cb..f034f85 100644
--- a/matrix_appservice_kakaotalk/kt/types/attachment/__init__.py
+++ b/matrix_appservice_kakaotalk/kt/types/chat/attachment/__init__.py
@@ -19,8 +19,7 @@ from attr import dataclass
from mautrix.types import SerializableAttrs
-from ..bson import Long
-from .mention import MentionStruct
+from .mention import *
@dataclass(kw_only=True)
@@ -28,9 +27,28 @@ class Attachment(SerializableAttrs):
shout: Optional[bool] = None
mentions: Optional[list[MentionStruct]] = None
urls: Optional[list[str]] = None
+ url: Optional[str] = None # NOTE Added since this may have replaced urls
@dataclass
class PathAttachment(Attachment):
path: str
s: int
+
+
+from .media import *
+from .reply import *
+from .media import *
+from .emoticon import *
+#from .voip import *
+from .contact import *
+from .map import *
+from .post import *
+from .openlink import *
+
+
+__all__ = [
+ "Attachment",
+ "PathAttachment",
+]
+# TODO What about the import *s?
diff --git a/matrix_appservice_kakaotalk/kt/types/chat/attachment/contact.py b/matrix_appservice_kakaotalk/kt/types/chat/attachment/contact.py
new file mode 100644
index 0000000..66db832
--- /dev/null
+++ b/matrix_appservice_kakaotalk/kt/types/chat/attachment/contact.py
@@ -0,0 +1,42 @@
+# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
+# Copyright (C) 2022 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 .
+from typing import Union
+
+from attr import dataclass
+
+from ...bson import Long
+from . import Attachment
+
+
+@dataclass
+class ProfileAttachment(Attachment):
+ userId: Union[int, Long]
+ nickName: str
+ fullProfileImageUrl: str
+ profileImageUrl: str
+ statusMessage: str
+
+
+@dataclass
+class ContactAttachment(Attachment):
+ name: str
+ url: str
+
+
+__all__ = [
+ "ProfileAttachment",
+ "ContactAttachment",
+]
diff --git a/matrix_appservice_kakaotalk/kt/types/chat/attachment/emoticon.py b/matrix_appservice_kakaotalk/kt/types/chat/attachment/emoticon.py
new file mode 100644
index 0000000..5a81518
--- /dev/null
+++ b/matrix_appservice_kakaotalk/kt/types/chat/attachment/emoticon.py
@@ -0,0 +1,37 @@
+# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
+# Copyright (C) 2022 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 .
+from typing import Optional
+
+from attr import dataclass
+
+from . import Attachment
+
+
+@dataclass
+class EmoticonAttachment(Attachment):
+ path: str
+ name: str
+ type: str
+ alt: str
+ s: Optional[int] = None
+ sound: Optional[str] = None
+ width: Optional[int] = None
+ height: Optional[int] = None
+
+
+__all__ = [
+ "EmoticonAttachment",
+]
diff --git a/matrix_appservice_kakaotalk/kt/types/chat/attachment/map.py b/matrix_appservice_kakaotalk/kt/types/chat/attachment/map.py
new file mode 100644
index 0000000..6084936
--- /dev/null
+++ b/matrix_appservice_kakaotalk/kt/types/chat/attachment/map.py
@@ -0,0 +1,31 @@
+# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
+# Copyright (C) 2022 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 .
+from attr import dataclass
+
+from . import Attachment
+
+
+@dataclass
+class MapAttachment(Attachment):
+ lat: int
+ lng: int
+ a: str
+ c: bool
+
+
+__all__ = [
+ "MapAttachment",
+]
diff --git a/matrix_appservice_kakaotalk/kt/types/chat/attachment/media.py b/matrix_appservice_kakaotalk/kt/types/chat/attachment/media.py
new file mode 100644
index 0000000..0bf831d
--- /dev/null
+++ b/matrix_appservice_kakaotalk/kt/types/chat/attachment/media.py
@@ -0,0 +1,93 @@
+# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
+# Copyright (C) 2022 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 .
+from typing import Optional
+
+from attr import dataclass
+
+from . import Attachment
+
+
+@dataclass
+class MediaKeyAttachment(Attachment):
+ k: str
+ url: str
+ s: int
+
+
+@dataclass
+class PhotoAttachment(Attachment):
+ w: int
+ h: int
+ thumbnailUrl: str
+ thumbnailWidth: int
+ thumbnailHeight: int
+ cs: str
+ mt: str
+
+
+@dataclass
+class MultiPhotoAttachment(Attachment):
+ kl: list[str]
+ wl: list[int]
+ hl: list[int]
+ csl: list[str]
+ imageUrls: list[str]
+ thumbnailUrls: list[str]
+ thumbnailWidths: list[int]
+ thumbnailHeights: list[int]
+ sl: int
+
+
+@dataclass
+class VideoAttachment(Attachment):
+ tk: str
+ w: int
+ h: int
+ cs: str
+ d: int
+
+
+@dataclass
+class FileAttachment(Attachment):
+ name: str
+ size: int
+ expire: int
+ cs: str
+
+
+@dataclass
+class AudioAttachment(Attachment):
+ d: int
+ expire: Optional[int] = None
+
+
+@dataclass
+class LongTextAttachment(Attachment):
+ path: str
+ k: str
+ s: int
+ sd: bool
+
+
+__all__ = [
+ "MediaKeyAttachment",
+ "PhotoAttachment",
+ "MultiPhotoAttachment",
+ "VideoAttachment",
+ "FileAttachment",
+ "AudioAttachment",
+ "LongTextAttachment",
+]
diff --git a/matrix_appservice_kakaotalk/kt/types/attachment/mention.py b/matrix_appservice_kakaotalk/kt/types/chat/attachment/mention.py
similarity index 94%
rename from matrix_appservice_kakaotalk/kt/types/attachment/mention.py
rename to matrix_appservice_kakaotalk/kt/types/chat/attachment/mention.py
index c151b1c..1006891 100644
--- a/matrix_appservice_kakaotalk/kt/types/attachment/mention.py
+++ b/matrix_appservice_kakaotalk/kt/types/chat/attachment/mention.py
@@ -19,7 +19,7 @@ from attr import dataclass
from mautrix.types import SerializableAttrs
-from ..bson import Long
+from ...bson import Long
@dataclass
@@ -27,3 +27,8 @@ class MentionStruct(SerializableAttrs):
at: list[int]
len: int
user_id: Union[Long, int]
+
+
+__all__ = [
+ "MentionStruct",
+]
diff --git a/matrix_appservice_kakaotalk/kt/types/chat/attachment/openlink.py b/matrix_appservice_kakaotalk/kt/types/chat/attachment/openlink.py
new file mode 100644
index 0000000..cead03a
--- /dev/null
+++ b/matrix_appservice_kakaotalk/kt/types/chat/attachment/openlink.py
@@ -0,0 +1,30 @@
+# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
+# Copyright (C) 2022 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 .
+from attr import dataclass
+
+from .post import PostAttachment
+
+
+@dataclass
+class OpenScheduleAttachment(PostAttachment):
+ scheduleId: int
+ title: str
+ eventAt: int
+
+
+__all__ = [
+ "OpenScheduleAttachment",
+]
diff --git a/matrix_appservice_kakaotalk/kt/types/chat/attachment/post.py b/matrix_appservice_kakaotalk/kt/types/chat/attachment/post.py
new file mode 100644
index 0000000..2ea9c83
--- /dev/null
+++ b/matrix_appservice_kakaotalk/kt/types/chat/attachment/post.py
@@ -0,0 +1,141 @@
+# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
+# Copyright (C) 2022 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 .
+from typing import Optional, Union
+from enum import IntEnum
+
+from attr import dataclass
+
+from mautrix.types import SerializableAttrs
+
+from ...bson import Long
+from . import Attachment
+from .emoticon import EmoticonAttachment
+
+
+class KnownPostItemType(IntEnum):
+ TEXT = 1
+ FOOTER = 2
+ HEADER = 3
+ EMOTICON = 4
+ IMAGE = 5
+ VIDEO = 6
+ FILE = 7
+ SCHEDULE = 8
+ VOTE = 9
+ SCRAP = 10
+
+PostItemType = Union[KnownPostItemType, int]
+
+
+class KnownPostSubItemType(IntEnum):
+ pass
+
+PostSubItemType = Union[KnownPostSubItemType, int]
+
+
+class KnownPostFooterStyle(IntEnum):
+ ARTICLE = 1
+ SCHEDULE = 2
+ SCHEDULE_ANSWER = 3
+ VOTE = 4
+ VOTE_RESULT = 5
+
+PostFooterStyle = Union[KnownPostFooterStyle, int]
+
+
+class PostItem:
+ @dataclass
+ class Unknown(SerializableAttrs):
+ t: PostItemType
+
+ @dataclass(kw_only=True)
+ class Text(Unknown):
+ t = KnownPostItemType.TEXT
+ ct: str
+ jct: str
+
+ @dataclass(kw_only=True)
+ class Header(Unknown):
+ t = KnownPostItemType.HEADER
+ st: int
+
+ @dataclass
+ class UDict(SerializableAttrs):
+ id: Union[int, Long]
+
+ u: Optional[UDict]
+
+ @dataclass(kw_only=True)
+ class Image(Unknown):
+ t = KnownPostItemType.IMAGE
+ tt: Optional[str] = None
+ th: list[str]
+ g: Optional[bool] = None
+
+ @dataclass(kw_only=True)
+ class Emoticon(Unknown):
+ t = KnownPostItemType.EMOTICON
+ ct: EmoticonAttachment
+
+ @dataclass(kw_only=True)
+ class Vote(Unknown):
+ t = KnownPostItemType.VOTE
+ st: int
+ tt: str
+ ittpe: Optional[str] = None
+ its: list[dict]
+
+ @dataclass(kw_only=True)
+ class Video(Unknown):
+ t = KnownPostItemType.VIDEO
+ th: str
+
+ @dataclass(kw_only=True)
+ class File(Unknown):
+ t = KnownPostItemType.FILE
+ tt: str
+ c: int
+
+ @dataclass(kw_only=True)
+ class Footer(Unknown):
+ t = KnownPostItemType.FOOTER
+ st: PostFooterStyle
+ url: str
+
+
+@dataclass(kw_only=True)
+class PostAttachment(Attachment):
+ subtype: Optional[PostSubItemType] = None
+ os: list[PostItem.Unknown]
+
+
+@dataclass
+class VoteAttachment(PostAttachment):
+ voteId: int
+ title: str
+
+
+__all__ = [
+ "KnownPostItemType",
+ "PostItemType",
+ "KnownPostSubItemType",
+ "PostSubItemType",
+ "KnownPostFooterStyle",
+ "PostFooterStyle",
+ "PostItem",
+ "PostAttachment",
+ "VoteAttachment",
+]
diff --git a/matrix_appservice_kakaotalk/kt/types/chat/attachment/reply.py b/matrix_appservice_kakaotalk/kt/types/chat/attachment/reply.py
new file mode 100644
index 0000000..dbb9993
--- /dev/null
+++ b/matrix_appservice_kakaotalk/kt/types/chat/attachment/reply.py
@@ -0,0 +1,40 @@
+# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
+# Copyright (C) 2022 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 .
+from typing import Optional
+
+from attr import dataclass
+
+from ...bson import Long
+from . import Attachment
+from ..chat_type import ChatType
+from .mention import MentionStruct
+
+
+@dataclass(kw_only=True)
+class ReplyAttachment(Attachment):
+ attach_only: bool
+ attach_type: int
+ src_linkId: Optional[Long] = None
+ src_logId: Long
+ src_mentions: list[MentionStruct]
+ src_message: str
+ src_type: ChatType
+ src_userId: Long
+
+
+__all__ = [
+ "ReplyAttachment",
+]
diff --git a/matrix_appservice_kakaotalk/kt/types/chat/chat.py b/matrix_appservice_kakaotalk/kt/types/chat/chat.py
index 0f454a4..62201ed 100644
--- a/matrix_appservice_kakaotalk/kt/types/chat/chat.py
+++ b/matrix_appservice_kakaotalk/kt/types/chat/chat.py
@@ -13,16 +13,30 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from typing import Optional, Union
+from typing import Optional, Union, Type
from attr import dataclass
-from mautrix.types import SerializableAttrs
+from mautrix.types import SerializableAttrs, JSON
from ..bson import Long
from ..user.channel_user import ChannelUser
-from ..attachment import Attachment
-from .chat_type import ChatType
+from .attachment import (
+ Attachment,
+ PhotoAttachment,
+ VideoAttachment,
+ ContactAttachment,
+ AudioAttachment,
+ EmoticonAttachment,
+ OpenScheduleAttachment,
+ VoteAttachment,
+ MapAttachment,
+ ProfileAttachment,
+ FileAttachment,
+ PostAttachment,
+ ReplyAttachment,
+)
+from .chat_type import ChatType, KnownChatType
@dataclass
@@ -35,6 +49,36 @@ class Chat(ChatTypeComponent):
attachment: Optional[Attachment] = None
supplement: Optional[dict] = None
+ @classmethod
+ def deserialize(cls, data: JSON) -> "Chat":
+ if "attachment" in data:
+ attachment = _attachment_type_map.get(int(data["type"]), Attachment).deserialize(data["attachment"])
+ del data["attachment"]
+ else:
+ attachment = None
+ obj = super().deserialize(data)
+ obj.attachment = attachment
+ return obj
+
+# TODO More
+_attachment_type_map: dict[KnownChatType, Type[Attachment]] = {
+ KnownChatType.PHOTO: PhotoAttachment,
+ KnownChatType.VIDEO: VideoAttachment,
+ KnownChatType.CONTACT: ContactAttachment,
+ KnownChatType.AUDIO: AudioAttachment,
+ KnownChatType.DITEMEMOTICON: EmoticonAttachment,
+ KnownChatType.SCHEDULE: OpenScheduleAttachment,
+ KnownChatType.VOTE: VoteAttachment,
+ KnownChatType.MAP: MapAttachment,
+ KnownChatType.PROFILE: ProfileAttachment,
+ KnownChatType.FILE: FileAttachment,
+ KnownChatType.POST: PostAttachment,
+ KnownChatType.REPLY: ReplyAttachment,
+ KnownChatType.OPEN_SCHEDULE: OpenScheduleAttachment,
+ KnownChatType.OPEN_VOTE: VoteAttachment,
+ KnownChatType.OPEN_POST: PostAttachment,
+}
+
@dataclass
class TypedChat(Chat, ChatTypeComponent):
diff --git a/matrix_appservice_kakaotalk/portal.py b/matrix_appservice_kakaotalk/portal.py
index 249a756..e012fc2 100644
--- a/matrix_appservice_kakaotalk/portal.py
+++ b/matrix_appservice_kakaotalk/portal.py
@@ -15,7 +15,16 @@
# along with this program. If not, see .
from __future__ import annotations
-from typing import TYPE_CHECKING, Any, AsyncGenerator, Pattern, cast
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ AsyncGenerator,
+ Awaitable,
+ Callable,
+ Pattern,
+ cast,
+)
+from io import BytesIO
import asyncio
import re
import time
@@ -24,9 +33,13 @@ from mautrix.appservice import IntentAPI
from mautrix.bridge import BasePortal, NotificationDisabler, async_getter_lock
from mautrix.errors import MatrixError
from mautrix.types import (
+ AudioInfo,
ContentURI,
+ EncryptedFile,
EventID,
EventType,
+ FileInfo,
+ ImageInfo,
LocationMessageEventContent,
MediaMessageEventContent,
Membership,
@@ -35,7 +48,9 @@ from mautrix.types import (
RoomID,
TextMessageEventContent,
UserID,
+ VideoInfo,
)
+from mautrix.util import ffmpeg, magic, variation_selector
from mautrix.util.message_send_checkpoint import MessageSendCheckpointStatus
from mautrix.util.simple_lock import SimpleLock
@@ -50,7 +65,10 @@ from .formatter import kakaotalk_to_matrix, matrix_to_kakaotalk
from .kt.types.bson import Long
from .kt.types.channel.channel_info import ChannelInfo
from .kt.types.channel.channel_type import KnownChannelType, ChannelType
-from .kt.types.chat.chat import Chatlog
+from .kt.types.chat import Chatlog, KnownChatType
+from .kt.types.chat.attachment import (
+ PhotoAttachment,
+)
from .kt.client.types import UserInfoUnion, PortalChannelInfo, ChannelProps
from .kt.client.errors import CommandException
@@ -158,6 +176,18 @@ class Portal(DBPortal, BasePortal):
NotificationDisabler.puppet_cls = p.Puppet
NotificationDisabler.config_enabled = cls.config["bridge.backfill.disable_notifications"]
+ # TODO More
+ cls._message_handler_type_map: dict[
+ KnownChatType,
+ Callable[[Portal, u.User, IntentAPI, Chatlog], Awaitable[list[EventID]]]
+ ] = {
+ KnownChatType.TEXT: cls._handle_remote_text,
+ KnownChatType.PHOTO: cls._handle_remote_photo,
+ #KnownChatType.VIDEO: cls._handle_remote_video,
+ #KnownChatType.AUDIO: cls._handle_remote_audio,
+ #KnownChatType.FILE: cls._handle_remote_file,
+ }
+
# region DB conversion
async def delete(self) -> None:
@@ -268,9 +298,8 @@ class Portal(DBPortal, BasePortal):
await self.save()
return info
- """
@classmethod
- async def _reupload_kt_file(
+ async def _reupload_remote_file(
cls,
url: str,
source: u.User,
@@ -278,20 +307,19 @@ class Portal(DBPortal, BasePortal):
*,
filename: str | None = None,
encrypt: bool = False,
- referer: str = "messenger_thread_photo",
find_size: bool = False,
convert_audio: bool = False,
) -> tuple[ContentURI, FileInfo | VideoInfo | AudioInfo | ImageInfo, EncryptedFile | None]:
if not url:
raise ValueError("URL not provided")
- headers = {"referer": f"fbapp://{source.state.application.client_id}/{referer}"}
sandbox = cls.config["bridge.sandbox_media_download"]
- async with source.client.get(url, headers=headers, sandbox=sandbox) as resp:
+ # TODO Referer header?
+ async with source.client.get(url, sandbox=sandbox) as resp:
length = int(resp.headers["Content-Length"])
if length > cls.matrix.media_config.upload_size:
raise ValueError("File not available: too large")
data = await resp.read()
- mime = magic.from_buffer(data, mime=True)
+ mime = magic.mimetype(data)
if convert_audio and mime != "audio/ogg":
data = await ffmpeg.convert_bytes(
data, ".ogg", output_args=("-c:a", "libopus"), input_mime=mime
@@ -312,7 +340,6 @@ class Portal(DBPortal, BasePortal):
if decryption_info:
decryption_info.url = url
return url, info, decryption_info
- """
async def _update_name(self, name: str) -> bool:
if not name:
@@ -859,10 +886,9 @@ class Portal(DBPortal, BasePortal):
source: u.User,
sender: p.Puppet,
message: Chatlog,
- reply_to: Chatlog | None = None,
) -> None:
try:
- await self._handle_remote_message(source, sender, message, reply_to)
+ await self._handle_remote_message(source, sender, message)
except Exception:
self.log.exception(
"Error handling KakaoTalk message %s",
@@ -874,7 +900,6 @@ class Portal(DBPortal, BasePortal):
source: u.User,
sender: p.Puppet,
message: Chatlog,
- reply_to: Chatlog | None = None,
) -> None:
self.log.debug(f"Handling KakaoTalk event {message.logId}")
if not self.mxid:
@@ -896,25 +921,59 @@ class Portal(DBPortal, BasePortal):
await intent.ensure_joined(self.mxid)
self._backfill_leave.add(intent)
- if message.attachment:
- self.log.info("TODO: _handle_remote_message attachments")
- if message.supplement:
- self.log.info("TODO: _handle_remote_message supplements")
- if message.text:
- content = await kakaotalk_to_matrix(message.text)
- event_id = await self._send_message(intent, content, timestamp=message.sendAt)
- await DBMessage(
- mxid=event_id,
- mx_room=self.mxid,
- ktid=message.logId,
- index=0,
- kt_chat=self.ktid,
- kt_receiver=self.kt_receiver,
- timestamp=message.sendAt,
- ).insert()
- await self._send_delivery_receipt(event_id)
- else:
+ event_ids = []
+ handler = self._message_handler_type_map.get(message.type)
+ if not handler:
+ self.log.warning(f"No handler for message type {message.type}, falling back to text")
+ handler = Portal._handle_remote_text
+ event_ids += await handler(self, source, intent, message)
+ if not event_ids:
self.log.warning(f"Unhandled KakaoTalk message {message.logId}")
+ return
+ event_ids = [event_id for event_id in event_ids if event_id]
+ self.log.debug(f"Handled KakaoTalk message {message.logId} -> {event_ids}")
+ await DBMessage.bulk_create(
+ ktid=message.logId,
+ kt_chat=self.ktid,
+ kt_receiver=self.kt_receiver,
+ mx_room=self.mxid,
+ timestamp=message.sendAt,
+ event_ids=event_ids,
+ )
+ await self._send_delivery_receipt(event_ids[-1])
+
+ async def _handle_remote_text(self, source: u.User, intent: IntentAPI, message: Chatlog) -> list[EventID]:
+ # TODO Handle mentions properly
+ content = await kakaotalk_to_matrix(message.text)
+ # TODO Replies
+ return [await self._send_message(intent, content, timestamp=message.sendAt)]
+
+ async def _handle_remote_photo(self, source: u.User, intent: IntentAPI, message: Chatlog) -> list[EventID]:
+ assert message.attachment
+ assert message.attachment.url or message.attachment.urls
+ url = message.attachment.url or message.attachment.urls[0]
+ assert isinstance(message.attachment, PhotoAttachment)
+ info = ImageInfo(
+ width=message.attachment.w,
+ height=message.attachment.h,
+ )
+ # TODO Animated images?
+ mxc, additional_info, decryption_info = await self._reupload_remote_file(
+ url,
+ source,
+ intent,
+ encrypt=self.encrypted,
+ find_size=False,
+ )
+ info.size = additional_info.size
+ info.mimetype = additional_info.mimetype
+ content = MediaMessageEventContent(
+ url=mxc, file=decryption_info, msgtype=MessageType.IMAGE, body=message.text, info=info
+ )
+ if not content:
+ return []
+ # TODO Replies
+ return [await self._send_message(intent, content, timestamp=message.sendAt)]
# TODO Many more remote handlers
diff --git a/matrix_appservice_kakaotalk/user.py b/matrix_appservice_kakaotalk/user.py
index feca078..0003769 100644
--- a/matrix_appservice_kakaotalk/user.py
+++ b/matrix_appservice_kakaotalk/user.py
@@ -606,7 +606,6 @@ class User(DBUser, BaseUser):
await portal.backfill_lock.wait(evt.logId)
if not puppet.name:
portal.schedule_resync(self, puppet)
- # TODO reply_to
await portal.handle_remote_message(self, puppet, evt)
# TODO Many more handlers