Inbound file messages

This commit is contained in:
Andrew Ferrazzutti 2022-04-28 01:36:15 -04:00
parent c9961d5078
commit 2602e58734
5 changed files with 90 additions and 22 deletions

View File

@ -58,6 +58,7 @@ from ..types.request import (
deserialize_result, deserialize_result,
ResultType, ResultType,
ResultListType, ResultListType,
ResultRawType,
RootCommandResult, RootCommandResult,
CommandResultDoneValue CommandResultDoneValue
) )
@ -327,6 +328,18 @@ class Client:
await self._rpc_client.request("get_memo_ids", mxid=self.user.mxid) 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( def send_chat(
self, self,
channel_props: ChannelProps, channel_props: ChannelProps,

View File

@ -13,7 +13,7 @@
# #
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Generic, Type, TypeVar, Union, Iterable from typing import Generic, Type, TypeVar, Union
from attr import dataclass from attr import dataclass
from enum import IntEnum from enum import IntEnum
@ -103,6 +103,17 @@ def ResultListType(result_type: Type[ResultType]):
return _ResultListType 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 @dataclass
class CommandResultDoneValue(RootCommandResult, Generic[ResultType]): class CommandResultDoneValue(RootCommandResult, Generic[ResultType]):

View File

@ -81,7 +81,7 @@ from .kt.types.chat import Chatlog, ChatType, KnownChatType
from .kt.types.chat.attachment import ( from .kt.types.chat.attachment import (
Attachment, Attachment,
AudioAttachment, AudioAttachment,
#FileAttachment, FileAttachment,
MediaAttachment, MediaAttachment,
MultiPhotoAttachment, MultiPhotoAttachment,
PhotoAttachment, PhotoAttachment,
@ -223,7 +223,7 @@ class Portal(DBPortal, BasePortal):
KnownChatType.MULTIPHOTO: cls._handle_kakaotalk_multiphoto, KnownChatType.MULTIPHOTO: cls._handle_kakaotalk_multiphoto,
KnownChatType.VIDEO: cls._handle_kakaotalk_video, KnownChatType.VIDEO: cls._handle_kakaotalk_video,
KnownChatType.AUDIO: cls._handle_kakaotalk_audio, KnownChatType.AUDIO: cls._handle_kakaotalk_audio,
#KnownChatType.FILE: cls._handle_kakaotalk_file, KnownChatType.FILE: cls._handle_kakaotalk_file,
16385: cls._handle_kakaotalk_deleted, 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]) power_levels.set_user_level(demoter_id, orig_power_levels[demoter_id])
@classmethod @classmethod
async def _reupload_kakaotalk_file( async def _reupload_kakaotalk_file_from_url(
cls, cls,
url: str, url: str,
source: u.User, source: u.User,
@ -457,6 +457,28 @@ 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()
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: if not mimetype:
mimetype = magic.mimetype(data) mimetype = magic.mimetype(data)
if convert_audio and mimetype != "audio/ogg": if convert_audio and mimetype != "audio/ogg":
@ -1610,22 +1632,6 @@ class Portal(DBPortal, BasePortal):
**kwargs **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( async def _handle_kakaotalk_media(
self, self,
attachment: MediaAttachment, attachment: MediaAttachment,
@ -1638,7 +1644,7 @@ class Portal(DBPortal, BasePortal):
chat_text: str | None, chat_text: str | None,
**_ **_
) -> EventID: ) -> 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, attachment.url,
source, source,
intent, intent,
@ -1653,6 +1659,27 @@ class Portal(DBPortal, BasePortal):
) )
return await self._send_message(intent, content, timestamp=timestamp) 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( async def handle_kakaotalk_chat_delete(
self, self,
sender: p.Puppet, sender: p.Puppet,

View File

@ -281,7 +281,7 @@ class RPCClient:
break break
except asyncio.LimitOverrunError as e: except asyncio.LimitOverrunError as e:
self.log.warning(f"Buffer overrun: {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: except asyncio.CancelledError:
raise raise
if not line: if not line:

View File

@ -23,6 +23,7 @@ import {
KnownAuthStatusCode, KnownAuthStatusCode,
util, util,
} from "node-kakao" } from "node-kakao"
import { ReadStreamUtil } from "node-kakao/stream"
/** @typedef {import("node-kakao").OAuthCredential} OAuthCredential */ /** @typedef {import("node-kakao").OAuthCredential} OAuthCredential */
/** @typedef {import("node-kakao").ChannelType} ChannelType */ /** @typedef {import("node-kakao").ChannelType} ChannelType */
/** @typedef {import("node-kakao").ReplyAttachment} ReplyAttachment */ /** @typedef {import("node-kakao").ReplyAttachment} ReplyAttachment */
@ -824,6 +825,21 @@ export default class PeerClient {
return channelIds 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 {Object} req
* @param {string} req.mxid * @param {string} req.mxid
@ -1048,6 +1064,7 @@ export default class PeerClient {
list_friends: this.listFriends, list_friends: this.listFriends,
get_friend_dm_id: req => this.getFriendProperty(req, "directChatId"), get_friend_dm_id: req => this.getFriendProperty(req, "directChatId"),
get_memo_ids: this.getMemoIds, get_memo_ids: this.getMemoIds,
download_file: this.downloadFile,
send_chat: this.sendChat, send_chat: this.sendChat,
send_media: this.sendMedia, send_media: this.sendMedia,
delete_chat: this.deleteChat, delete_chat: this.deleteChat,