From 2602e58734c78352410240acd32d3a71bf5b7191 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 28 Apr 2022 01:36:15 -0400 Subject: [PATCH] Inbound file messages --- .../kt/client/client.py | 13 ++++ .../kt/types/request.py | 13 +++- matrix_appservice_kakaotalk/portal.py | 67 +++++++++++++------ matrix_appservice_kakaotalk/rpc/rpc.py | 2 +- node/src/client.js | 17 +++++ 5 files changed, 90 insertions(+), 22 deletions(-) diff --git a/matrix_appservice_kakaotalk/kt/client/client.py b/matrix_appservice_kakaotalk/kt/client/client.py index 38f6873..78faa79 100644 --- a/matrix_appservice_kakaotalk/kt/client/client.py +++ b/matrix_appservice_kakaotalk/kt/client/client.py @@ -58,6 +58,7 @@ from ..types.request import ( deserialize_result, ResultType, ResultListType, + ResultRawType, RootCommandResult, CommandResultDoneValue ) @@ -327,6 +328,18 @@ class Client: await self._rpc_client.request("get_memo_ids", mxid=self.user.mxid) ) + def download_file( + self, + channel_props: ChannelProps, + key: str, + ) -> Awaitable[bytes]: + return self._api_user_request_result( + ResultRawType(bytes), + "download_file", + channel_props=channel_props.serialize(), + key=key, + ) + def send_chat( self, channel_props: ChannelProps, diff --git a/matrix_appservice_kakaotalk/kt/types/request.py b/matrix_appservice_kakaotalk/kt/types/request.py index 64145c6..7d9c778 100644 --- a/matrix_appservice_kakaotalk/kt/types/request.py +++ b/matrix_appservice_kakaotalk/kt/types/request.py @@ -13,7 +13,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from typing import Generic, Type, TypeVar, Union, Iterable +from typing import Generic, Type, TypeVar, Union from attr import dataclass from enum import IntEnum @@ -103,6 +103,17 @@ def ResultListType(result_type: Type[ResultType]): return _ResultListType +def ResultRawType(result_type: Type): + class _ResultRawType(result_type, Serializable): + def serialize(self) -> result_type: + return self + + @classmethod + def deserialize(cls, data: JSON) -> "_ResultRawType": + return result_type(data) + + return _ResultRawType + @dataclass class CommandResultDoneValue(RootCommandResult, Generic[ResultType]): diff --git a/matrix_appservice_kakaotalk/portal.py b/matrix_appservice_kakaotalk/portal.py index ac760c5..f6e0c9b 100644 --- a/matrix_appservice_kakaotalk/portal.py +++ b/matrix_appservice_kakaotalk/portal.py @@ -81,7 +81,7 @@ from .kt.types.chat import Chatlog, ChatType, KnownChatType from .kt.types.chat.attachment import ( Attachment, AudioAttachment, - #FileAttachment, + FileAttachment, MediaAttachment, MultiPhotoAttachment, PhotoAttachment, @@ -223,7 +223,7 @@ class Portal(DBPortal, BasePortal): KnownChatType.MULTIPHOTO: cls._handle_kakaotalk_multiphoto, KnownChatType.VIDEO: cls._handle_kakaotalk_video, KnownChatType.AUDIO: cls._handle_kakaotalk_audio, - #KnownChatType.FILE: cls._handle_kakaotalk_file, + KnownChatType.FILE: cls._handle_kakaotalk_file, 16385: cls._handle_kakaotalk_deleted, } @@ -436,7 +436,7 @@ class Portal(DBPortal, BasePortal): power_levels.set_user_level(demoter_id, orig_power_levels[demoter_id]) @classmethod - async def _reupload_kakaotalk_file( + async def _reupload_kakaotalk_file_from_url( cls, url: str, source: u.User, @@ -457,6 +457,28 @@ class Portal(DBPortal, BasePortal): if length > cls.matrix.media_config.upload_size: raise ValueError("File not available: too large") data = await resp.read() + return await cls._reupload_kakaotalk_file_from_bytes( + data, + intent, + filename=filename, + mimetype=mimetype, + encrypt=encrypt, + find_size=find_size, + convert_audio=convert_audio, + ) + + @classmethod + async def _reupload_kakaotalk_file_from_bytes( + cls, + data: bytes, + intent: IntentAPI, + *, + filename: str | None = None, + mimetype: str | None = None, + encrypt: bool = False, + find_size: bool = False, + convert_audio: bool = False, + ) -> tuple[ContentURI, FileInfo | VideoInfo | AudioInfo | ImageInfo, EncryptedFile | None]: if not mimetype: mimetype = magic.mimetype(data) if convert_audio and mimetype != "audio/ogg": @@ -1610,22 +1632,6 @@ class Portal(DBPortal, BasePortal): **kwargs )) - """ TODO Find what auth is required for reading file contents - def _handle_kakaotalk_file( - self, - attachment: FileAttachment, - **kwargs - ) -> Awaitable[list[EventID]]: - return asyncio.gather(self._handle_kakaotalk_media( - attachment, - FileInfo( - size=attachment.size, - ), - MessageType.FILE, - **kwargs - )) - """ - async def _handle_kakaotalk_media( self, attachment: MediaAttachment, @@ -1638,7 +1644,7 @@ class Portal(DBPortal, BasePortal): chat_text: str | None, **_ ) -> EventID: - mxc, additional_info, decryption_info = await self._reupload_kakaotalk_file( + mxc, additional_info, decryption_info = await self._reupload_kakaotalk_file_from_url( attachment.url, source, intent, @@ -1653,6 +1659,27 @@ class Portal(DBPortal, BasePortal): ) return await self._send_message(intent, content, timestamp=timestamp) + async def _handle_kakaotalk_file( + self, + source: u.User, + intent: IntentAPI, + attachment: FileAttachment, + timestamp: int, + chat_text: str | None, + **_ + ) -> list[EventID]: + data = await source.client.download_file(self.channel_props, attachment.k) + mxc, info, decryption_info = await self._reupload_kakaotalk_file_from_bytes( + data, + intent, + filename=attachment.name, + encrypt=self.encrypted, + ) + content = MediaMessageEventContent( + url=mxc, file=decryption_info, msgtype=MessageType.FILE, body=chat_text or "", info=info + ) + return [await self._send_message(intent, content, timestamp=timestamp)] + async def handle_kakaotalk_chat_delete( self, sender: p.Puppet, diff --git a/matrix_appservice_kakaotalk/rpc/rpc.py b/matrix_appservice_kakaotalk/rpc/rpc.py index 97c5a62..580fca1 100644 --- a/matrix_appservice_kakaotalk/rpc/rpc.py +++ b/matrix_appservice_kakaotalk/rpc/rpc.py @@ -281,7 +281,7 @@ class RPCClient: break except asyncio.LimitOverrunError as e: self.log.warning(f"Buffer overrun: {e}") - line += await self._reader.read(self._reader._limit) + line += await self._reader.read(e.consumed) except asyncio.CancelledError: raise if not line: diff --git a/node/src/client.js b/node/src/client.js index 079732c..75ca181 100644 --- a/node/src/client.js +++ b/node/src/client.js @@ -23,6 +23,7 @@ import { KnownAuthStatusCode, util, } from "node-kakao" +import { ReadStreamUtil } from "node-kakao/stream" /** @typedef {import("node-kakao").OAuthCredential} OAuthCredential */ /** @typedef {import("node-kakao").ChannelType} ChannelType */ /** @typedef {import("node-kakao").ReplyAttachment} ReplyAttachment */ @@ -824,6 +825,21 @@ export default class PeerClient { return channelIds } + /** + * @param {Object} req + * @param {string} req.mxid + * @param {ChannelProps} req.channel_props + * @param {string} req.key + */ + downloadFile = async (req) => { + const talkChannel = await this.#getUserChannel(req.mxid, req.channel_props) + const res = await talkChannel.downloadMedia({ key: req.key }, KnownChatType.FILE) + if (!res.success) return res + + const data = await ReadStreamUtil.all(res.result) + return makeCommandResult(Array.from(data)) + } + /** * @param {Object} req * @param {string} req.mxid @@ -1048,6 +1064,7 @@ export default class PeerClient { list_friends: this.listFriends, get_friend_dm_id: req => this.getFriendProperty(req, "directChatId"), get_memo_ids: this.getMemoIds, + download_file: this.downloadFile, send_chat: this.sendChat, send_media: this.sendMedia, delete_chat: this.deleteChat,