Compare commits

..

No commits in common. "d7b0b9013f30beab723dacbf75e1dd5fc8183eaa" and "ace4eefec7efeab3cf19336fd023dfd4dd46dca1" have entirely different histories.

5 changed files with 51 additions and 217 deletions

View File

@ -4,11 +4,11 @@
* [ ] Message content * [ ] Message content
* [x] Text * [x] Text
* [ ] Media * [ ] Media
* [x] Stickers * [ ] Stickers
* [x] Files * [ ] Files
* [x] Voice messages * [ ] Voice messages
* [x] Videos * [ ] Videos
* [x] Images * [ ] Images
* [ ] Locations * [ ] Locations
* [ ] Formatting * [ ] Formatting
* [ ] Replies * [ ] Replies
@ -32,11 +32,8 @@
* [x] Text * [x] Text
* [ ] Media * [ ] Media
* [ ] Stickers * [ ] Stickers
* [ ] Files * [ ] Videos
* [x] Voice messages * [ ] Images
* [x] Videos
* [x] Images
* [ ] Locations
* [ ] Formatting * [ ] Formatting
* [ ] Replies * [ ] Replies
* [ ] Mentions * [ ] Mentions

View File

@ -27,6 +27,7 @@ class Attachment(SerializableAttrs):
shout: Optional[bool] = None shout: Optional[bool] = None
mentions: Optional[list[MentionStruct]] = None mentions: Optional[list[MentionStruct]] = None
urls: Optional[list[str]] = None urls: Optional[list[str]] = None
url: Optional[str] = None # NOTE Added since this may have replaced urls
@dataclass @dataclass

View File

@ -21,20 +21,14 @@ from . import Attachment
@dataclass @dataclass
class MediaAttachment(Attachment): class MediaKeyAttachment(Attachment):
# NOTE Added to cover Attachments that need a url but might not have a key k: str
url: str url: str
s: int s: int
@dataclass @dataclass
class MediaKeyAttachment(MediaAttachment): class PhotoAttachment(Attachment):
k: str
@dataclass
class PhotoAttachment(MediaKeyAttachment):
# NOTE Changed superclass from Attachment
w: int w: int
h: int h: int
thumbnailUrl: str thumbnailUrl: str
@ -54,13 +48,11 @@ class MultiPhotoAttachment(Attachment):
thumbnailUrls: list[str] thumbnailUrls: list[str]
thumbnailWidths: list[int] thumbnailWidths: list[int]
thumbnailHeights: list[int] thumbnailHeights: list[int]
sl: list[int] # NOTE Changed to a list sl: int
mtl: list[str] # NOTE Added
@dataclass @dataclass
class VideoAttachment(MediaAttachment): class VideoAttachment(Attachment):
# NOTE Changed superclass from Attachment
tk: str tk: str
w: int w: int
h: int h: int
@ -69,8 +61,7 @@ class VideoAttachment(MediaAttachment):
@dataclass @dataclass
class FileAttachment(MediaKeyAttachment): class FileAttachment(Attachment):
# NOTE Changed superclass from Attachment
name: str name: str
size: int size: int
expire: int expire: int
@ -78,8 +69,7 @@ class FileAttachment(MediaKeyAttachment):
@dataclass @dataclass
class AudioAttachment(MediaKeyAttachment): class AudioAttachment(Attachment):
# NOTE Changed superclass from Attachment
d: int d: int
expire: Optional[int] = None expire: Optional[int] = None
@ -93,7 +83,6 @@ class LongTextAttachment(Attachment):
__all__ = [ __all__ = [
"MediaAttachment",
"MediaKeyAttachment", "MediaKeyAttachment",
"PhotoAttachment", "PhotoAttachment",
"MultiPhotoAttachment", "MultiPhotoAttachment",

View File

@ -35,7 +35,6 @@ from .attachment import (
FileAttachment, FileAttachment,
PostAttachment, PostAttachment,
ReplyAttachment, ReplyAttachment,
MultiPhotoAttachment,
) )
from .chat_type import ChatType, KnownChatType from .chat_type import ChatType, KnownChatType
@ -75,7 +74,6 @@ _attachment_type_map: dict[KnownChatType, Type[Attachment]] = {
KnownChatType.FILE: FileAttachment, KnownChatType.FILE: FileAttachment,
KnownChatType.POST: PostAttachment, KnownChatType.POST: PostAttachment,
KnownChatType.REPLY: ReplyAttachment, KnownChatType.REPLY: ReplyAttachment,
KnownChatType.MULTIPHOTO: MultiPhotoAttachment,
KnownChatType.OPEN_SCHEDULE: OpenScheduleAttachment, KnownChatType.OPEN_SCHEDULE: OpenScheduleAttachment,
KnownChatType.OPEN_VOTE: VoteAttachment, KnownChatType.OPEN_VOTE: VoteAttachment,
KnownChatType.OPEN_POST: PostAttachment, KnownChatType.OPEN_POST: PostAttachment,

View File

@ -42,7 +42,6 @@ from mautrix.types import (
FileInfo, FileInfo,
ImageInfo, ImageInfo,
LocationMessageEventContent, LocationMessageEventContent,
MediaInfo,
MediaMessageEventContent, MediaMessageEventContent,
Membership, Membership,
MessageEventContent, MessageEventContent,
@ -69,13 +68,7 @@ from .kt.types.channel.channel_info import ChannelInfo
from .kt.types.channel.channel_type import KnownChannelType, ChannelType from .kt.types.channel.channel_type import KnownChannelType, ChannelType
from .kt.types.chat import Chatlog, KnownChatType from .kt.types.chat import Chatlog, KnownChatType
from .kt.types.chat.attachment import ( from .kt.types.chat.attachment import (
Attachment,
AudioAttachment,
#FileAttachment,
MediaAttachment,
MultiPhotoAttachment,
PhotoAttachment, PhotoAttachment,
VideoAttachment,
) )
from .kt.client.types import ( from .kt.client.types import (
@ -190,25 +183,14 @@ class Portal(DBPortal, BasePortal):
NotificationDisabler.config_enabled = cls.config["bridge.backfill.disable_notifications"] NotificationDisabler.config_enabled = cls.config["bridge.backfill.disable_notifications"]
# TODO More # TODO More
cls._message_type_handler_map: dict[ cls._message_handler_type_map: dict[
KnownChatType, KnownChatType,
Callable[ Callable[[Portal, u.User, IntentAPI, Chatlog], Awaitable[list[EventID]]]
[
Portal,
u.User,
IntentAPI,
Attachment | None,
int,
str | None
],
Awaitable[list[EventID]]
]
] = { ] = {
KnownChatType.TEXT: cls._handle_remote_text, KnownChatType.TEXT: cls._handle_remote_text,
KnownChatType.PHOTO: cls._handle_remote_photo, KnownChatType.PHOTO: cls._handle_remote_photo,
KnownChatType.MULTIPHOTO: cls._handle_remote_multiphoto, #KnownChatType.VIDEO: cls._handle_remote_video,
KnownChatType.VIDEO: cls._handle_remote_video, #KnownChatType.AUDIO: cls._handle_remote_audio,
KnownChatType.AUDIO: cls._handle_remote_audio,
#KnownChatType.FILE: cls._handle_remote_file, #KnownChatType.FILE: cls._handle_remote_file,
} }
@ -330,7 +312,6 @@ class Portal(DBPortal, BasePortal):
intent: IntentAPI, intent: IntentAPI,
*, *,
filename: str | None = None, filename: str | None = None,
mimetype: str | None,
encrypt: bool = False, encrypt: bool = False,
find_size: bool = False, find_size: bool = False,
convert_audio: bool = False, convert_audio: bool = False,
@ -344,19 +325,18 @@ class Portal(DBPortal, BasePortal):
if length > cls.matrix.media_config.upload_size: if length > cls.matrix.media_config.upload_size:
raise ValueError("File not available: too large") raise ValueError("File not available: too large")
data = await resp.read() data = await resp.read()
if not mimetype: mime = magic.mimetype(data)
mimetype = magic.mimetype(data) if convert_audio and mime != "audio/ogg":
if convert_audio and mimetype != "audio/ogg":
data = await ffmpeg.convert_bytes( data = await ffmpeg.convert_bytes(
data, ".ogg", output_args=("-c:a", "libopus"), input_mime=mimetype data, ".ogg", output_args=("-c:a", "libopus"), input_mime=mime
) )
mimetype = "audio/ogg" mime = "audio/ogg"
info = FileInfo(mimetype=mimetype, size=len(data)) info = FileInfo(mimetype=mime, size=len(data))
if Image and mimetype.startswith("image/") and find_size: if Image and mime.startswith("image/") and find_size:
with Image.open(BytesIO(data)) as img: with Image.open(BytesIO(data)) as img:
width, height = img.size width, height = img.size
info = ImageInfo(mimetype=mimetype, size=len(data), width=width, height=height) info = ImageInfo(mimetype=mime, size=len(data), width=width, height=height)
upload_mime_type = mimetype upload_mime_type = mime
decryption_info = None decryption_info = None
if encrypt and encrypt_attachment: if encrypt and encrypt_attachment:
data, decryption_info = encrypt_attachment(data) data, decryption_info = encrypt_attachment(data)
@ -827,7 +807,7 @@ class Portal(DBPortal, BasePortal):
data = await self.main_intent.download_media(message.url) data = await self.main_intent.download_media(message.url)
else: else:
raise NotImplementedError("No file or URL specified") raise NotImplementedError("No file or URL specified")
mimetype = message.info.mimetype or magic.mimetype(data) mime = message.info.mimetype or magic.mimetype(data)
""" TODO Replies """ TODO Replies
reply_to = None reply_to = None
if message.relates_to.rel_type == RelationType.REPLY: if message.relates_to.rel_type == RelationType.REPLY:
@ -842,6 +822,7 @@ class Portal(DBPortal, BasePortal):
""" """
filename = message.body filename = message.body
width, height = None, None width, height = None, None
# TODO Find out why/if stickers are always blank
if message.info in (MessageType.IMAGE, MessageType.STICKER, MessageType.VIDEO): if message.info in (MessageType.IMAGE, MessageType.STICKER, MessageType.VIDEO):
width = message.info.width width = message.info.width
height = message.info.height height = message.info.height
@ -853,7 +834,7 @@ class Portal(DBPortal, BasePortal):
filename, filename,
width=width, width=width,
height=height, height=height,
ext=guess_extension(mimetype)[1:], ext=guess_extension(mime)[1:],
# TODO # TODO
#reply_to=reply_to, #reply_to=reply_to,
) )
@ -996,24 +977,16 @@ class Portal(DBPortal, BasePortal):
await intent.ensure_joined(self.mxid) await intent.ensure_joined(self.mxid)
self._backfill_leave.add(intent) self._backfill_leave.add(intent)
handler = self._message_type_handler_map.get(message.type) event_ids = []
handler = self._message_handler_type_map.get(message.type)
if not handler: if not handler:
self.log.warning(f"No handler for message type {message.type}, falling back to text") self.log.warning(f"No handler for message type {message.type}, falling back to text")
handler = Portal._handle_remote_text handler = Portal._handle_remote_text
event_ids = [ event_ids += await handler(self, source, intent, message)
event_id for event_id in
await handler(
self,
source,
intent,
message.attachment,
message.sendAt,
message.text)
if event_id
]
if not event_ids: if not event_ids:
self.log.warning(f"Unhandled KakaoTalk message {message.logId}") self.log.warning(f"Unhandled KakaoTalk message {message.logId}")
return 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}") self.log.debug(f"Handled KakaoTalk message {message.logId} -> {event_ids}")
await DBMessage.bulk_create( await DBMessage.bulk_create(
ktid=message.logId, ktid=message.logId,
@ -1025,162 +998,38 @@ class Portal(DBPortal, BasePortal):
) )
await self._send_delivery_receipt(event_ids[-1]) await self._send_delivery_receipt(event_ids[-1])
async def _handle_remote_text( async def _handle_remote_text(self, source: u.User, intent: IntentAPI, message: Chatlog) -> list[EventID]:
self,
source: u.User,
intent: IntentAPI,
attachment: None,
timestamp: int,
message_text: str | None,
) -> list[EventID]:
# TODO Handle mentions properly # TODO Handle mentions properly
content = await kakaotalk_to_matrix(message_text) content = await kakaotalk_to_matrix(message.text)
# TODO Replies # TODO Replies
return [await self._send_message(intent, content, timestamp=timestamp)] return [await self._send_message(intent, content, timestamp=message.sendAt)]
def _handle_remote_photo( async def _handle_remote_photo(self, source: u.User, intent: IntentAPI, message: Chatlog) -> list[EventID]:
self, assert message.attachment
source: u.User, assert message.attachment.url or message.attachment.urls
intent: IntentAPI, url = message.attachment.url or message.attachment.urls[0]
attachment: PhotoAttachment, assert isinstance(message.attachment, PhotoAttachment)
timestamp: int, info = ImageInfo(
message_text: str | None, width=message.attachment.w,
) -> Awaitable[list[EventID]]: height=message.attachment.h,
return asyncio.gather(self._handle_remote_uniphoto(
source, intent, attachment, timestamp, message_text
))
def _handle_remote_multiphoto(
self,
source: u.User,
intent: IntentAPI,
attachment: MultiPhotoAttachment,
timestamp: int,
message_text: str | None,
) -> Awaitable[list[EventID]]:
# TODO Upload media concurrently, but post messages sequentially
return asyncio.gather(
*[
self._handle_remote_uniphoto(
source, intent,
PhotoAttachment(
shout=attachment.shout,
mentions=attachment.mentions,
urls=attachment.urls,
url=attachment.imageUrls[i],
s=attachment.sl[i],
k=attachment.kl[i],
w=attachment.wl[i],
h=attachment.hl[i],
thumbnailUrl=attachment.thumbnailUrls[i],
thumbnailWidth=attachment.thumbnailWidths[i],
thumbnailHeight=attachment.thumbnailHeights[i],
cs=attachment.csl[i],
mt=attachment.mtl[i],
),
timestamp, message_text,
) )
for i in range(len(attachment.imageUrls)) # TODO Animated images?
]
)
def _handle_remote_uniphoto(
self,
source: u.User,
intent: IntentAPI,
attachment: PhotoAttachment,
timestamp: int,
message_text: str | None,
) -> Awaitable[EventID]:
return self._handle_remote_media(
source, intent, attachment, timestamp, message_text,
ImageInfo(
mimetype=attachment.mt,
size=attachment.s,
width=attachment.w,
height=attachment.h,
),
MessageType.IMAGE,
)
def _handle_remote_video(
self,
source: u.User,
intent: IntentAPI,
attachment: VideoAttachment,
timestamp: int,
message_text: str | None,
) -> Awaitable[list[EventID]]:
return asyncio.gather(self._handle_remote_media(
source, intent, attachment, timestamp, message_text,
VideoInfo(
duration=attachment.d,
width=attachment.w,
height=attachment.h,
),
MessageType.VIDEO,
))
def _handle_remote_audio(
self,
source: u.User,
intent: IntentAPI,
attachment: AudioAttachment,
timestamp: int,
message_text: str | None,
) -> Awaitable[list[EventID]]:
return asyncio.gather(self._handle_remote_media(
source, intent, attachment, timestamp, message_text,
AudioInfo(
size=attachment.s,
duration=attachment.d,
),
MessageType.AUDIO,
))
""" TODO Find what auth is required for reading file contents
def _handle_remote_file(
self,
source: u.User,
intent: IntentAPI,
attachment: FileAttachment,
timestamp: int,
message_text: str | None,
) -> Awaitable[list[EventID]]:
return asyncio.gather(self._handle_remote_media(
source, intent, attachment, timestamp, message_text,
FileInfo(
size=attachment.size,
),
MessageType.FILE,
))
"""
async def _handle_remote_media(
self,
source: u.User,
intent: IntentAPI,
attachment: MediaAttachment,
timestamp: int,
message_text: str | None,
info: MediaInfo,
msgtype: MessageType,
) -> EventID:
mxc, additional_info, decryption_info = await self._reupload_remote_file( mxc, additional_info, decryption_info = await self._reupload_remote_file(
attachment.url, url,
source, source,
intent, intent,
mimetype=info.mimetype,
encrypt=self.encrypted, encrypt=self.encrypted,
find_size=False, find_size=False,
) )
info.size = additional_info.size info.size = additional_info.size
info.mimetype = additional_info.mimetype info.mimetype = additional_info.mimetype
content = MediaMessageEventContent( content = MediaMessageEventContent(
url=mxc, file=decryption_info, msgtype=msgtype, body=message_text, info=info url=mxc, file=decryption_info, msgtype=MessageType.IMAGE, body=message.text, info=info
) )
if not content:
return []
# TODO Replies # TODO Replies
return await self._send_message(intent, content, timestamp=timestamp) return [await self._send_message(intent, content, timestamp=message.sendAt)]
# TODO Many more remote handlers # TODO Many more remote handlers