Outgoing mentions
This commit is contained in:
parent
6867e6b349
commit
84e6a5829d
|
@ -18,29 +18,63 @@ from __future__ import annotations
|
|||
from typing import NamedTuple
|
||||
|
||||
from mautrix.appservice import IntentAPI
|
||||
from mautrix.types import MessageEventContent, RelationType, RoomID
|
||||
from mautrix.types import Format, MessageEventContent, RelationType, RoomID, UserID
|
||||
from mautrix.util.formatter import (
|
||||
EntityString,
|
||||
EntityType,
|
||||
MarkdownString,
|
||||
MatrixParser,
|
||||
SimpleEntity,
|
||||
)
|
||||
from mautrix.util.logging import TraceLogger
|
||||
|
||||
from ..kt.types.bson import Long
|
||||
from ..kt.types.chat.attachment.reply import ReplyAttachment
|
||||
from ..kt.types.chat.attachment import ReplyAttachment, MentionStruct
|
||||
|
||||
from ..kt.client.types import TO_MSGTYPE_MAP
|
||||
|
||||
from .. import puppet as pu
|
||||
from .. import puppet as pu, user as u
|
||||
from ..db import Message as DBMessage
|
||||
|
||||
|
||||
class SendParams(NamedTuple):
|
||||
text: str
|
||||
# TODO Mentions
|
||||
mentions: list[MentionStruct] | None
|
||||
reply_to: ReplyAttachment
|
||||
|
||||
|
||||
class KakaoTalkFormatString(EntityString[SimpleEntity, EntityType], MarkdownString):
|
||||
def format(self, entity_type: EntityType, **kwargs) -> KakaoTalkFormatString:
|
||||
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"]},
|
||||
)
|
||||
)
|
||||
self.text = f"@{self.text}"
|
||||
return self
|
||||
|
||||
|
||||
class ToKakaoTalkParser(MatrixParser[KakaoTalkFormatString]):
|
||||
fs = KakaoTalkFormatString
|
||||
|
||||
|
||||
async def _get_id_from_mxid(mxid: UserID) -> Long | None:
|
||||
user = await u.User.get_by_mxid(mxid, create=False)
|
||||
if user and user.ktid:
|
||||
return user.ktid
|
||||
else:
|
||||
puppet = await pu.Puppet.get_by_mxid(mxid, create=False)
|
||||
return puppet.ktid if puppet else None
|
||||
|
||||
|
||||
async def matrix_to_kakaotalk(
|
||||
content: MessageEventContent, room_id: RoomID, log: TraceLogger, intent: IntentAPI
|
||||
) -> SendParams:
|
||||
# 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 not message:
|
||||
|
@ -53,7 +87,7 @@ async def matrix_to_kakaotalk(
|
|||
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)
|
||||
kt_sender = await _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}"
|
||||
|
@ -75,8 +109,36 @@ async def matrix_to_kakaotalk(
|
|||
# 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),
|
||||
src_userId=kt_sender,
|
||||
)
|
||||
else:
|
||||
reply_to = None
|
||||
return SendParams(text=content.body, reply_to=reply_to)
|
||||
if content.get("format", None) == Format.HTML and content["formatted_body"]:
|
||||
parsed = await ToKakaoTalkParser().parse(content["formatted_body"])
|
||||
text = parsed.text
|
||||
mentions_by_user: dict[Long, MentionStruct] = {}
|
||||
# Make sure to not create remote mentions for any remote user not in the room
|
||||
if parsed.entities:
|
||||
joined_members = set(await intent.get_room_members(room_id))
|
||||
last_offset = 0
|
||||
at = 0
|
||||
for mention in sorted(parsed.entities, key=lambda entity: entity.offset):
|
||||
mxid = mention.extra_info["user_id"]
|
||||
if mxid not in joined_members:
|
||||
continue
|
||||
ktid = await _get_id_from_mxid(mxid)
|
||||
if ktid is None:
|
||||
continue
|
||||
at += text[last_offset:mention.offset+1].count("@")
|
||||
last_offset = mention.offset+1
|
||||
mention = mentions_by_user.setdefault(ktid, MentionStruct(
|
||||
at=[],
|
||||
len=mention.length,
|
||||
user_id=ktid,
|
||||
))
|
||||
mention.at.append(at)
|
||||
mentions = list(mentions_by_user.values())
|
||||
else:
|
||||
text = content.body
|
||||
mentions = None
|
||||
return SendParams(text=text, mentions=mentions, reply_to=reply_to)
|
||||
|
|
|
@ -41,7 +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.chat.attachment import MentionStruct, ReplyAttachment
|
||||
from ..types.oauth import OAuthCredential, OAuthInfo
|
||||
from ..types.packet.chat.kickout import KnownKickoutType, KickoutRes
|
||||
from ..types.request import (
|
||||
|
@ -279,6 +279,7 @@ class Client:
|
|||
channel_props: ChannelProps,
|
||||
text: str,
|
||||
reply_to: ReplyAttachment | None,
|
||||
mentions: list[MentionStruct] | None,
|
||||
) -> Chatlog:
|
||||
return await self._api_user_request_result(
|
||||
Chatlog,
|
||||
|
@ -286,6 +287,7 @@ class Client:
|
|||
channel_props=channel_props.serialize(),
|
||||
text=text,
|
||||
reply_to=reply_to.serialize() if reply_to is not None else None,
|
||||
mentions=[m.serialize() for m in mentions] if mentions is not None else None,
|
||||
)
|
||||
|
||||
async def send_media(
|
||||
|
|
|
@ -94,15 +94,12 @@ ResultType = TypeVar("ResultType", bound=Serializable)
|
|||
|
||||
def ResultListType(result_type: Type[ResultType]):
|
||||
class _ResultListType(list[result_type], Serializable):
|
||||
def __init__(self, iterable: Iterable[result_type]=()):
|
||||
list.__init__(self, (result_type.deserialize(x) for x in iterable))
|
||||
|
||||
def serialize(self) -> list[JSON]:
|
||||
return [v.serialize() for v in self]
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls, data: list[JSON]) -> "_ResultListType":
|
||||
return cls(data)
|
||||
return [result_type.deserialize(item) for item in data]
|
||||
|
||||
return _ResultListType
|
||||
|
||||
|
|
|
@ -811,9 +811,8 @@ class Portal(DBPortal, BasePortal):
|
|||
chatlog = await sender.client.send_message(
|
||||
self.channel_props,
|
||||
text=converted.text,
|
||||
# TODO
|
||||
#mentions=converted.mentions,
|
||||
reply_to=converted.reply_to,
|
||||
mentions=converted.mentions,
|
||||
)
|
||||
except CommandException as e:
|
||||
self.log.debug(f"Error handling Matrix message {event_id}: {e!s}")
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
/** @typedef {import("node-kakao").OAuthCredential} OAuthCredential */
|
||||
/** @typedef {import("node-kakao").ChannelType} ChannelType */
|
||||
/** @typedef {import("node-kakao").ReplyAttachment} ReplyAttachment */
|
||||
/** @typedef {import("node-kakao").MentionStruct} MentionStruct */
|
||||
/** @typedef {import("node-kakao/dist/talk").TalkChannelList} TalkChannelList */
|
||||
|
||||
import chat from "node-kakao/chat"
|
||||
|
@ -503,12 +504,12 @@ 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
|
||||
* @param {?MentionStruct[]} req.mentions
|
||||
*/
|
||||
sendChat = async (req) => {
|
||||
const talkChannel = await this.#getUserChannel(req.mxid, req.channel_props)
|
||||
|
@ -516,7 +517,7 @@ export default class PeerClient {
|
|||
return await talkChannel.sendChat({
|
||||
text: req.text,
|
||||
type: !!req.reply_to ? KnownChatType.REPLY : KnownChatType.TEXT,
|
||||
attachment: req.reply_to,
|
||||
attachment: !req.mentions ? req.reply_to : {...req.reply_to, mentions: req.mentions},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -525,7 +526,7 @@ export default class PeerClient {
|
|||
* @param {string} req.mxid
|
||||
* @param {Object} req.channel_props
|
||||
* @param {int} req.type
|
||||
* @param {[number]} req.data
|
||||
* @param {number[]} req.data
|
||||
* @param {string} req.name
|
||||
* @param {?int} req.width
|
||||
* @param {?int} req.height
|
||||
|
|
Loading…
Reference in New Issue