Outbound replies
This commit is contained in:
parent
3ced968494
commit
2b9c59a2af
|
@ -17,104 +17,66 @@ from __future__ import annotations
|
|||
|
||||
from typing import NamedTuple
|
||||
|
||||
from mautrix.types import Format, MessageEventContent, RelationType, RoomID
|
||||
from mautrix.util.formatter import (
|
||||
EntityString,
|
||||
EntityType,
|
||||
MarkdownString,
|
||||
MatrixParser as BaseMatrixParser,
|
||||
SimpleEntity,
|
||||
)
|
||||
from mautrix.appservice import IntentAPI
|
||||
from mautrix.types import MessageEventContent, RelationType, RoomID
|
||||
from mautrix.util.logging import TraceLogger
|
||||
|
||||
from .. import puppet as pu, user as u
|
||||
from ..kt.types.bson import Long
|
||||
from ..kt.types.chat.attachment.reply import ReplyAttachment
|
||||
|
||||
from ..kt.client.types import TO_MSGTYPE_MAP
|
||||
|
||||
from .. import puppet as pu
|
||||
from ..db import Message as DBMessage
|
||||
|
||||
|
||||
class SendParams(NamedTuple):
|
||||
text: str
|
||||
mentions: list[None]
|
||||
reply_to: str
|
||||
|
||||
|
||||
class FacebookFormatString(EntityString[SimpleEntity, EntityType], MarkdownString):
|
||||
def format(self, entity_type: EntityType, **kwargs) -> FacebookFormatString:
|
||||
prefix = suffix = ""
|
||||
if entity_type == EntityType.USER_MENTION:
|
||||
self.entities.append(
|
||||
SimpleEntity(
|
||||
type=entity_type,
|
||||
offset=0,
|
||||
length=len(self.text),
|
||||
extra_info={"user_id": kwargs["user_id"]},
|
||||
)
|
||||
)
|
||||
return self
|
||||
elif entity_type == EntityType.BOLD:
|
||||
prefix = suffix = "*"
|
||||
elif entity_type == EntityType.ITALIC:
|
||||
prefix = suffix = "_"
|
||||
elif entity_type == EntityType.STRIKETHROUGH:
|
||||
prefix = suffix = "~"
|
||||
elif entity_type == EntityType.URL:
|
||||
if kwargs["url"] != self.text:
|
||||
suffix = f" ({kwargs['url']})"
|
||||
elif entity_type == EntityType.PREFORMATTED:
|
||||
prefix = f"```{kwargs['language']}\n"
|
||||
suffix = "\n```"
|
||||
elif entity_type == EntityType.INLINE_CODE:
|
||||
prefix = suffix = "`"
|
||||
elif entity_type == EntityType.BLOCKQUOTE:
|
||||
children = self.trim().split("\n")
|
||||
children = [child.prepend("> ") for child in children]
|
||||
return self.join(children, "\n")
|
||||
elif entity_type == EntityType.HEADER:
|
||||
prefix = "#" * kwargs["size"] + " "
|
||||
else:
|
||||
return self
|
||||
|
||||
self._offset_entities(len(prefix))
|
||||
self.text = f"{prefix}{self.text}{suffix}"
|
||||
return self
|
||||
|
||||
|
||||
class MatrixParser(BaseMatrixParser[FacebookFormatString]):
|
||||
fs = FacebookFormatString
|
||||
# TODO Mentions
|
||||
reply_to: ReplyAttachment
|
||||
|
||||
|
||||
async def matrix_to_kakaotalk(
|
||||
content: MessageEventContent, room_id: RoomID, log: TraceLogger
|
||||
content: MessageEventContent, room_id: RoomID, log: TraceLogger, intent: IntentAPI
|
||||
) -> SendParams:
|
||||
mentions = []
|
||||
reply_to = None
|
||||
# NOTE By design, this *throws* if user intent can't be matched (i.e. if a reply can't be created)
|
||||
# TODO Mentions
|
||||
if content.relates_to.rel_type == RelationType.REPLY:
|
||||
message = await DBMessage.get_by_mxid(content.relates_to.event_id, room_id)
|
||||
if message:
|
||||
content.trim_reply_fallback()
|
||||
reply_to = message.ktid
|
||||
else:
|
||||
log.warning(
|
||||
if not message:
|
||||
raise ValueError(
|
||||
f"Couldn't find reply target {content.relates_to.event_id}"
|
||||
" to bridge text message reply metadata to Facebook"
|
||||
" to bridge text message reply metadata to KakaoTalk"
|
||||
)
|
||||
try:
|
||||
mx_event = await intent.get_event(room_id, message.mxid)
|
||||
except:
|
||||
log.exception(f"Failed to find Matrix event for reply target {message.mxid}")
|
||||
raise
|
||||
kt_sender = pu.Puppet.get_id_from_mxid(mx_event.sender)
|
||||
if kt_sender is None:
|
||||
raise ValueError(
|
||||
f"Found no KakaoTalk user ID for reply target sender {mx_event.sender}"
|
||||
)
|
||||
content.trim_reply_fallback()
|
||||
reply_to = ReplyAttachment(
|
||||
# TODO
|
||||
#mentions=[],
|
||||
# TODO What are reply URLs for?
|
||||
#urls=[],
|
||||
# TODO Set this for emoticon reply, but must first support those
|
||||
attach_only=False,
|
||||
# TODO If replying with media works, must set type AND all attachment properties
|
||||
# But then, the reply object must be an intersection of a ReplyAttachment and something else
|
||||
#attach_type=TO_MSGTYPE_MAP.get(content.msgtype),
|
||||
src_logId=message.ktid,
|
||||
# TODO
|
||||
src_mentions=[],
|
||||
# TODO Check if source message needs to be formatted
|
||||
src_message=mx_event.content.body,
|
||||
src_type=TO_MSGTYPE_MAP[mx_event.content.msgtype],
|
||||
src_userId=Long(kt_sender),
|
||||
)
|
||||
if content.get("format", None) == Format.HTML and content["formatted_body"]:
|
||||
parsed = await MatrixParser().parse(content["formatted_body"])
|
||||
text = parsed.text
|
||||
mentions = []
|
||||
for mention in parsed.entities:
|
||||
mxid = mention.extra_info["user_id"]
|
||||
user = await u.User.get_by_mxid(mxid, create=False)
|
||||
if user and user.ktid:
|
||||
ktid = user.ktid
|
||||
else:
|
||||
puppet = await pu.Puppet.get_by_mxid(mxid, create=False)
|
||||
if puppet:
|
||||
ktid = puppet.ktid
|
||||
else:
|
||||
continue
|
||||
#mentions.append(
|
||||
# Mention(user_id=str(ktid), offset=mention.offset, length=mention.length)
|
||||
#)
|
||||
else:
|
||||
text = content.body
|
||||
return SendParams(text=text, mentions=mentions, reply_to=reply_to)
|
||||
reply_to = None
|
||||
return SendParams(text=content.body, reply_to=reply_to)
|
||||
|
|
|
@ -41,6 +41,7 @@ from ..types.api.struct import FriendListStruct
|
|||
from ..types.bson import Long
|
||||
from ..types.client.client_session import LoginResult
|
||||
from ..types.chat import Chatlog, KnownChatType
|
||||
from ..types.chat.attachment import ReplyAttachment
|
||||
from ..types.oauth import OAuthCredential, OAuthInfo
|
||||
from ..types.packet.chat.kickout import KnownKickoutType, KickoutRes
|
||||
from ..types.request import (
|
||||
|
@ -273,12 +274,18 @@ class Client:
|
|||
await self._rpc_client.request("get_memo_ids", mxid=self.user.mxid)
|
||||
)
|
||||
|
||||
async def send_message(self, channel_props: ChannelProps, text: str) -> Chatlog:
|
||||
async def send_message(
|
||||
self,
|
||||
channel_props: ChannelProps,
|
||||
text: str,
|
||||
reply_to: ReplyAttachment | None,
|
||||
) -> Chatlog:
|
||||
return await self._api_user_request_result(
|
||||
Chatlog,
|
||||
"send_chat",
|
||||
channel_props=channel_props.serialize(),
|
||||
text=text,
|
||||
reply_to=reply_to.serialize() if reply_to is not None else None,
|
||||
)
|
||||
|
||||
async def send_media(
|
||||
|
|
|
@ -26,7 +26,7 @@ from .mention import MentionStruct
|
|||
@dataclass(kw_only=True)
|
||||
class ReplyAttachment(Attachment):
|
||||
attach_only: bool
|
||||
attach_type: int
|
||||
attach_type: Optional[ChatType] = None # NOTE Changed from int for outgoing typeless replies
|
||||
src_linkId: Optional[Long] = None
|
||||
src_logId: Long
|
||||
src_mentions: list[MentionStruct]
|
||||
|
|
|
@ -46,6 +46,7 @@ from mautrix.types import (
|
|||
Membership,
|
||||
MessageEventContent,
|
||||
MessageType,
|
||||
RelationType,
|
||||
RoomID,
|
||||
TextMessageEventContent,
|
||||
UserID,
|
||||
|
@ -770,6 +771,8 @@ class Portal(DBPortal, BasePortal):
|
|||
) -> None:
|
||||
if message.get_edit():
|
||||
raise NotImplementedError("Edits are not supported by the KakaoTalk bridge.")
|
||||
if message.relates_to.rel_type == RelationType.REPLY and not message.msgtype.is_text:
|
||||
raise NotImplementedError("Replying with non-text content is not supported by the KakaoTalk bridge.")
|
||||
sender, is_relay = await self.get_relay_sender(orig_sender, f"message {event_id}")
|
||||
if not sender:
|
||||
raise Exception("not logged in")
|
||||
|
@ -802,14 +805,14 @@ class Portal(DBPortal, BasePortal):
|
|||
async def _handle_matrix_text(
|
||||
self, event_id: EventID, sender: u.User, message: TextMessageEventContent
|
||||
) -> None:
|
||||
converted = await matrix_to_kakaotalk(message, self.mxid, self.log)
|
||||
converted = await matrix_to_kakaotalk(message, self.mxid, self.log, self.main_intent)
|
||||
try:
|
||||
chatlog = await sender.client.send_message(
|
||||
self.channel_props,
|
||||
text=converted.text,
|
||||
# TODO
|
||||
#mentions=converted.mentions,
|
||||
#reply_to=converted.reply_to,
|
||||
reply_to=converted.reply_to,
|
||||
)
|
||||
except CommandException as e:
|
||||
self.log.debug(f"Error handling Matrix message {event_id}: {e!s}")
|
||||
|
@ -837,18 +840,6 @@ class Portal(DBPortal, BasePortal):
|
|||
else:
|
||||
raise NotImplementedError("No file or URL specified")
|
||||
mimetype = 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.ktid
|
||||
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
|
||||
if message.info in (MessageType.IMAGE, MessageType.STICKER, MessageType.VIDEO):
|
||||
|
@ -863,8 +854,6 @@ class Portal(DBPortal, BasePortal):
|
|||
width=width,
|
||||
height=height,
|
||||
ext=guess_extension(mimetype)[1:],
|
||||
# TODO
|
||||
#reply_to=reply_to,
|
||||
)
|
||||
except CommandException as e:
|
||||
self.log.debug(f"Error uploading media for Matrix message {event_id}: {e!s}")
|
||||
|
|
|
@ -24,8 +24,9 @@ import {
|
|||
util,
|
||||
} from "node-kakao"
|
||||
/** @typedef {import("node-kakao").OAuthCredential} OAuthCredential */
|
||||
/** @typedef {import("node-kakao/dist/talk").TalkChannelList} TalkChannelList */
|
||||
/** @typedef {import("node-kakao").ChannelType} ChannelType */
|
||||
/** @typedef {import("node-kakao").ReplyAttachment} ReplyAttachment */
|
||||
/** @typedef {import("node-kakao/dist/talk").TalkChannelList} TalkChannelList */
|
||||
|
||||
import chat from "node-kakao/chat"
|
||||
const { KnownChatType } = chat
|
||||
|
@ -502,17 +503,20 @@ export default class PeerClient {
|
|||
}
|
||||
|
||||
/**
|
||||
* TODO Mentions
|
||||
* @param {Object} req
|
||||
* @param {string} req.mxid
|
||||
* @param {Object} req.channel_props
|
||||
* @param {string} req.text
|
||||
* @param {?ReplyAttachment} req.reply_to
|
||||
*/
|
||||
sendChat = async (req) => {
|
||||
const talkChannel = await this.#getUserChannel(req.mxid, req.channel_props)
|
||||
|
||||
return await talkChannel.sendChat({
|
||||
type: KnownChatType.TEXT,
|
||||
text: req.text,
|
||||
type: !!req.reply_to ? KnownChatType.REPLY : KnownChatType.TEXT,
|
||||
attachment: req.reply_to,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue