Outbound media messages

This commit is contained in:
Andrew Ferrazzutti 2022-03-26 03:37:53 -04:00
parent 256c4d429a
commit ace4eefec7
6 changed files with 154 additions and 27 deletions

View File

@ -34,7 +34,7 @@ class Message:
mxid: EventID
mx_room: RoomID
ktid: Long = field(converter=Long)
ktid: Long | None = field(converter=lambda ktid: Long(ktid) if ktid is not None else None)
index: int
kt_chat: Long = field(converter=Long)
kt_receiver: Long = field(converter=Long)
@ -104,9 +104,9 @@ class Message:
@classmethod
async def bulk_create(
cls,
ktid: str,
kt_chat: int,
kt_receiver: int,
ktid: Long,
kt_chat: Long,
kt_receiver: Long,
event_ids: list[EventID],
timestamp: int,
mx_room: RoomID,

View File

@ -41,7 +41,7 @@ async def create_v1_tables(conn: Connection) -> None:
"""CREATE TABLE portal (
ktid BIGINT,
kt_receiver BIGINT,
kt_type TEXT,
kt_type TEXT NOT NULL,
mxid TEXT UNIQUE,
name TEXT,
photo_id TEXT,
@ -72,13 +72,13 @@ async def create_v1_tables(conn: Connection) -> None:
)
await conn.execute(
"""CREATE TABLE message (
mxid TEXT,
mx_room TEXT,
mxid TEXT NOT NULL,
mx_room TEXT NOT NULL,
ktid BIGINT,
kt_receiver BIGINT,
"index" SMALLINT,
kt_chat BIGINT,
timestamp BIGINT,
kt_receiver BIGINT NOT NULL,
"index" SMALLINT NOT NULL,
kt_chat BIGINT NOT NULL,
timestamp BIGINT NOT NULL,
PRIMARY KEY (ktid, kt_receiver, "index"),
FOREIGN KEY (kt_chat, kt_receiver) REFERENCES portal(ktid, kt_receiver)
ON UPDATE CASCADE ON DELETE CASCADE,

View File

@ -40,7 +40,7 @@ from ..types.api.struct.profile import ProfileReqStruct, ProfileStruct
from ..types.api.struct import FriendListStruct
from ..types.bson import Long
from ..types.client.client_session import LoginResult
from ..types.chat.chat import Chatlog
from ..types.chat import Chatlog, KnownChatType
from ..types.oauth import OAuthCredential, OAuthInfo
from ..types.request import (
deserialize_result,
@ -254,6 +254,32 @@ class Client:
text=text,
)
async def send_media(
self,
channel_props: ChannelProps,
media_type: KnownChatType,
data: bytes,
filename: str,
*,
width: int | None = None,
height: int | None = None,
ext: str | None = None,
) -> Chatlog:
return await self._api_user_request_result(
Chatlog,
"send_media",
channel_props=channel_props.serialize(),
type=media_type,
data=list(data),
name=filename,
width=width,
height=height,
ext=ext,
# Don't log the bytes
# TODO Disable logging per-argument, not per-command
is_secret=True
)
# TODO Combine these into one

View File

@ -19,11 +19,17 @@ from typing import Optional, NewType, Union
from attr import dataclass
from mautrix.types import SerializableAttrs, JSON, deserializer
from mautrix.types import (
SerializableAttrs,
JSON,
deserializer,
MessageType,
)
from ..types.bson import Long
from ..types.channel.channel_info import NormalChannelInfo
from ..types.channel.channel_type import ChannelType
from ..types.chat import KnownChatType
from ..types.openlink.open_channel_info import OpenChannelInfo
from ..types.user.channel_user_info import NormalChannelUserInfo, OpenChannelUserInfo
@ -64,3 +70,17 @@ class PortalChannelInfo(SerializableAttrs):
class ChannelProps(SerializableAttrs):
id: Long
type: ChannelType
# TODO Add non-media types, like polls & maps
TO_MSGTYPE_MAP: dict[MessageType, KnownChatType] = {
MessageType.TEXT: KnownChatType.TEXT,
MessageType.IMAGE: KnownChatType.PHOTO,
MessageType.STICKER: KnownChatType.PHOTO,
MessageType.VIDEO: KnownChatType.VIDEO,
MessageType.AUDIO: KnownChatType.AUDIO,
MessageType.FILE: KnownChatType.FILE,
}
# https://stackoverflow.com/a/483833
FROM_MSGTYPE_MAP: dict[KnownChatType, MessageType] = {v: k for k, v in TO_MSGTYPE_MAP.items()}

View File

@ -25,6 +25,7 @@ from typing import (
cast,
)
from io import BytesIO
from mimetypes import guess_extension
import asyncio
import re
import time
@ -50,7 +51,7 @@ from mautrix.types import (
UserID,
VideoInfo,
)
from mautrix.util import ffmpeg, magic, variation_selector
from mautrix.util import ffmpeg, magic
from mautrix.util.message_send_checkpoint import MessageSendCheckpointStatus
from mautrix.util.simple_lock import SimpleLock
@ -70,7 +71,12 @@ from .kt.types.chat.attachment import (
PhotoAttachment,
)
from .kt.client.types import UserInfoUnion, PortalChannelInfo, ChannelProps
from .kt.client.types import (
UserInfoUnion,
PortalChannelInfo,
ChannelProps,
TO_MSGTYPE_MAP,
)
from .kt.client.errors import CommandException
if TYPE_CHECKING:
@ -745,24 +751,24 @@ class Portal(DBPortal, BasePortal):
if message.msgtype == MessageType.TEXT or message.msgtype == MessageType.NOTICE:
await self._handle_matrix_text(event_id, sender, message)
elif message.msgtype.is_media:
await self._handle_matrix_media(event_id, sender, message, is_relay)
await self._handle_matrix_media(event_id, sender, message)
# elif message.msgtype == MessageType.LOCATION:
# await self._handle_matrix_location(sender, message)
else:
raise NotImplementedError(f"Unsupported message type {message.msgtype}")
async def _make_dbm(self, sender: u.User, event_id: EventID, ktid: Long) -> None:
await DBMessage(
async def _make_dbm(self, event_id: EventID, ktid: Long | None = None) -> DBMessage:
dbm = DBMessage(
mxid=event_id,
mx_room=self.mxid,
ktid=ktid,
index=0,
kt_chat=self.ktid,
kt_receiver=self.kt_receiver,
# TODO?
#kt_sender=sender.ktid,
timestamp=int(time.time() * 1000),
).insert()
)
await dbm.insert()
return dbm
async def _handle_matrix_text(
self, event_id: EventID, sender: u.User, message: TextMessageEventContent
@ -776,11 +782,10 @@ class Portal(DBPortal, BasePortal):
#mentions=converted.mentions,
#reply_to=converted.reply_to,
)
except CommandException:
self.log.debug(f"Error handling Matrix message {event_id}")
except CommandException as e:
self.log.debug(f"Error handling Matrix message {event_id}: {e!s}")
raise
await self._make_dbm(sender, event_id, chatlog.logId)
await self._make_dbm(event_id, chatlog.logId)
self.log.debug(f"Handled Matrix message {event_id} -> {chatlog.logId}")
sender.send_remote_checkpoint(
MessageSendCheckpointStatus.SUCCESS,
@ -791,9 +796,60 @@ class Portal(DBPortal, BasePortal):
)
async def _handle_matrix_media(
self, event_id: EventID, sender: u.User, message: MediaMessageEventContent, is_relay: bool
self, event_id: EventID, sender: u.User, message: MediaMessageEventContent
) -> None:
self.log.info("TODO: _handle_matrix_media")
if message.file and decrypt_attachment:
data = await self.main_intent.download_media(message.file.url)
data = decrypt_attachment(
data, message.file.key.key, message.file.hashes.get("sha256"), message.file.iv
)
elif message.url:
data = await self.main_intent.download_media(message.url)
else:
raise NotImplementedError("No file or URL specified")
mime = message.info.mimetype or magic.mimetype(data)
""" TODO Replies
reply_to = None
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
else:
self.log.warning(
f"Couldn't find reply target {message.relates_to.event_id}"
" to bridge media message reply metadata to KakaoTalk"
)
"""
filename = message.body
width, height = None, None
# TODO Find out why/if stickers are always blank
if message.info in (MessageType.IMAGE, MessageType.STICKER, MessageType.VIDEO):
width = message.info.width
height = message.info.height
try:
chatlog = await sender.client.send_media(
self.channel_props,
TO_MSGTYPE_MAP[message.msgtype],
data,
filename,
width=width,
height=height,
ext=guess_extension(mime)[1:],
# TODO
#reply_to=reply_to,
)
except CommandException as e:
self.log.debug(f"Error uploading media for Matrix message {event_id}: {e!s}")
raise
await self._make_dbm(event_id, chatlog.logId)
self.log.debug(f"Handled Matrix message {event_id} -> {chatlog.logId}")
sender.send_remote_checkpoint(
MessageSendCheckpointStatus.SUCCESS,
event_id,
self.mxid,
EventType.ROOM_MESSAGE,
message.msgtype,
)
async def _handle_matrix_location(
self, sender: u.User, message: LocationMessageEventContent

View File

@ -451,6 +451,30 @@ export default class PeerClient {
})
}
/**
* @param {Object} req
* @param {string} req.mxid
* @param {Object} req.channel_props
* @param {int} req.type
* @param {[number]} req.data
* @param {string} req.name
* @param {?int} req.width
* @param {?int} req.height
* @param {?string} req.ext
*/
sendMedia = async (req) => {
const userClient = this.#getUser(req.mxid)
const talkChannel = await userClient.getChannel(req.channel_props)
return await talkChannel.sendMedia(req.type, {
data: Uint8Array.from(req.data),
name: req.name,
width: req.width,
height: req.height,
ext: req.ext,
})
}
#makeCommandResult(result) {
return {
success: true,
@ -529,6 +553,7 @@ export default class PeerClient {
get_chats: this.getChats,
list_friends: this.listFriends,
send_message: this.sendMessage,
send_media: this.sendMedia,
}[req.command] || this.handleUnknownCommand
}
const resp = { id: req.id }