Compare commits
14 Commits
95d08e5aeb
...
a9c7bfe046
Author | SHA1 | Date | |
---|---|---|---|
a9c7bfe046 | |||
fe6df88a4b | |||
aa5c066552 | |||
587ec98f3e | |||
33a8218eee | |||
59c7f1fd2e | |||
af296510aa | |||
b2f9298817 | |||
38d307c684 | |||
b9cf30e9e6 | |||
73f8792b75 | |||
2be6a761b6 | |||
164bb7ce10 | |||
3bf49123f5 |
10
ROADMAP.md
10
ROADMAP.md
@ -13,7 +13,12 @@
|
|||||||
* [ ] Replies
|
* [ ] Replies
|
||||||
* [x] In DMs
|
* [x] In DMs
|
||||||
* [ ] In multi-user rooms
|
* [ ] In multi-user rooms
|
||||||
* [x] Mentions
|
* [ ] To relay users
|
||||||
|
* [ ] Mentions
|
||||||
|
* [x] In DMs
|
||||||
|
* [ ] In multi-user rooms
|
||||||
|
* [x] To relay users
|
||||||
|
* [ ] Polls
|
||||||
* [x] Message redactions<sup>[1]</sup>
|
* [x] Message redactions<sup>[1]</sup>
|
||||||
* [ ] Message reactions
|
* [ ] Message reactions
|
||||||
* [x] Read receipts
|
* [x] Read receipts
|
||||||
@ -36,8 +41,11 @@
|
|||||||
* [x] Videos
|
* [x] Videos
|
||||||
* [x] Images
|
* [x] Images
|
||||||
* [ ] Locations
|
* [ ] Locations
|
||||||
|
* [ ] Links
|
||||||
* [x] Replies
|
* [x] Replies
|
||||||
* [x] Mentions
|
* [x] Mentions
|
||||||
|
* [ ] Polls
|
||||||
|
* [ ] Posts
|
||||||
* [x] Message deletion/hiding
|
* [x] Message deletion/hiding
|
||||||
* [ ] Message reactions
|
* [ ] Message reactions
|
||||||
* [x] Message history
|
* [x] Message history
|
||||||
|
@ -246,10 +246,10 @@ bridge:
|
|||||||
m.text: '<b>$sender_displayname</b>: $message'
|
m.text: '<b>$sender_displayname</b>: $message'
|
||||||
m.notice: '<b>$sender_displayname<b>: $message'
|
m.notice: '<b>$sender_displayname<b>: $message'
|
||||||
m.emote: '* <b>$sender_displayname<b> $message'
|
m.emote: '* <b>$sender_displayname<b> $message'
|
||||||
m.file: '<b>$sender_displayname</b> sent a file'
|
m.file: 'File from <b>$sender_displayname</b>: $message'
|
||||||
m.image: '<b>$sender_displayname</b> sent an image'
|
m.image: 'Image from <b>$sender_displayname</b>: $message'
|
||||||
m.audio: '<b>$sender_displayname</b> sent an audio file'
|
m.audio: 'Audio from <b>$sender_displayname</b>: $message'
|
||||||
m.video: '<b>$sender_displayname</b> sent a video'
|
m.video: 'Video from <b>$sender_displayname</b>: $message'
|
||||||
m.location: '<b>$sender_displayname</b> sent a location'
|
m.location: '<b>$sender_displayname</b> sent a location'
|
||||||
|
|
||||||
rpc:
|
rpc:
|
||||||
|
@ -24,60 +24,8 @@ from ..kt.types.chat.attachment.mention import MentionStruct
|
|||||||
|
|
||||||
from .. import puppet as pu, user as u
|
from .. import puppet as pu, user as u
|
||||||
|
|
||||||
_START = r"^|\s"
|
|
||||||
_END = r"$|\s"
|
|
||||||
_TEXT_NO_SURROUNDING_SPACE = r"(?:[^\s].*?[^\s])|[^\s]"
|
|
||||||
COMMON_REGEX = re.compile(rf"({_START})([_~*])({_TEXT_NO_SURROUNDING_SPACE})\2({_END})")
|
|
||||||
INLINE_CODE_REGEX = re.compile(rf"({_START})(`)(.+?)`({_END})")
|
|
||||||
MENTION_REGEX = re.compile(r"@([0-9]{1,15})\u2063(.+?)\u2063")
|
|
||||||
|
|
||||||
tags = {"_": "em", "*": "strong", "~": "del", "`": "code"}
|
MENTION_REGEX = re.compile(r"@(\d+)\u2063(.+?)\u2063")
|
||||||
|
|
||||||
|
|
||||||
def _handle_match(html: str, match: Match, nested: bool) -> tuple[str, int]:
|
|
||||||
start, end = match.start(), match.end()
|
|
||||||
prefix, sigil, text, suffix = match.groups()
|
|
||||||
if nested:
|
|
||||||
text = _convert_formatting(text)
|
|
||||||
tag = tags[sigil]
|
|
||||||
# We don't want to include the whitespace suffix length, as that could be used as the
|
|
||||||
# whitespace prefix right after this formatting block.
|
|
||||||
pos = start + len(prefix) + (2 * len(tag) + 5) + len(text)
|
|
||||||
html = f"{html[:start]}{prefix}<{tag}>{text}</{tag}>{suffix}{html[end:]}"
|
|
||||||
return html, pos
|
|
||||||
|
|
||||||
|
|
||||||
def _convert_formatting(html: str) -> str:
|
|
||||||
pos = 0
|
|
||||||
while pos < len(html):
|
|
||||||
i_match = INLINE_CODE_REGEX.search(html, pos)
|
|
||||||
c_match = COMMON_REGEX.search(html, pos)
|
|
||||||
if i_match and c_match:
|
|
||||||
match = min(i_match, c_match, key=lambda match: match.start())
|
|
||||||
else:
|
|
||||||
match = i_match or c_match
|
|
||||||
|
|
||||||
if match:
|
|
||||||
html, pos = _handle_match(html, match, nested=match != i_match)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return html
|
|
||||||
|
|
||||||
|
|
||||||
def _handle_blockquote(output: list[str], blockquote: bool, line: str) -> tuple[bool, str]:
|
|
||||||
if not blockquote and line.startswith("> "):
|
|
||||||
line = line[len("> ") :]
|
|
||||||
output.append("<blockquote>")
|
|
||||||
blockquote = True
|
|
||||||
elif blockquote:
|
|
||||||
if line.startswith(">"):
|
|
||||||
line = line[len(">") :]
|
|
||||||
if line.startswith(" "):
|
|
||||||
line = line[1:]
|
|
||||||
else:
|
|
||||||
output.append("</blockquote>")
|
|
||||||
blockquote = False
|
|
||||||
return blockquote, line
|
|
||||||
|
|
||||||
|
|
||||||
async def kakaotalk_to_matrix(msg: str | None, mentions: list[MentionStruct] | None) -> TextMessageEventContent:
|
async def kakaotalk_to_matrix(msg: str | None, mentions: list[MentionStruct] | None) -> TextMessageEventContent:
|
||||||
|
@ -19,6 +19,7 @@ from typing import NamedTuple
|
|||||||
|
|
||||||
from mautrix.appservice import IntentAPI
|
from mautrix.appservice import IntentAPI
|
||||||
from mautrix.types import Format, MessageEventContent, RelationType, RoomID, UserID
|
from mautrix.types import Format, MessageEventContent, RelationType, RoomID, UserID
|
||||||
|
from mautrix.util import utf16_surrogate
|
||||||
from mautrix.util.formatter import (
|
from mautrix.util.formatter import (
|
||||||
EntityString,
|
EntityString,
|
||||||
EntityType,
|
EntityType,
|
||||||
@ -46,6 +47,7 @@ class SendParams(NamedTuple):
|
|||||||
|
|
||||||
class KakaoTalkFormatString(EntityString[SimpleEntity, EntityType], MarkdownString):
|
class KakaoTalkFormatString(EntityString[SimpleEntity, EntityType], MarkdownString):
|
||||||
def format(self, entity_type: EntityType, **kwargs) -> KakaoTalkFormatString:
|
def format(self, entity_type: EntityType, **kwargs) -> KakaoTalkFormatString:
|
||||||
|
prefix = suffix = ""
|
||||||
if entity_type == EntityType.USER_MENTION:
|
if entity_type == EntityType.USER_MENTION:
|
||||||
self.entities.append(
|
self.entities.append(
|
||||||
SimpleEntity(
|
SimpleEntity(
|
||||||
@ -55,7 +57,32 @@ class KakaoTalkFormatString(EntityString[SimpleEntity, EntityType], MarkdownStri
|
|||||||
extra_info={"user_id": kwargs["user_id"]},
|
extra_info={"user_id": kwargs["user_id"]},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.text = f"@{self.text}"
|
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
|
return self
|
||||||
|
|
||||||
|
|
||||||
@ -126,8 +153,8 @@ async def matrix_to_kakaotalk(
|
|||||||
else:
|
else:
|
||||||
reply_to = None
|
reply_to = None
|
||||||
if content.get("format", None) == Format.HTML and content["formatted_body"] and content.msgtype.is_text:
|
if content.get("format", None) == Format.HTML and content["formatted_body"] and content.msgtype.is_text:
|
||||||
parsed = await ToKakaoTalkParser().parse(content["formatted_body"])
|
parsed = await ToKakaoTalkParser().parse(utf16_surrogate.add(content["formatted_body"]))
|
||||||
text = parsed.text
|
text = utf16_surrogate.remove(parsed.text)
|
||||||
mentions_by_user: dict[Long, MentionStruct] = {}
|
mentions_by_user: dict[Long, MentionStruct] = {}
|
||||||
# Make sure to not create remote mentions for any remote user not in the room
|
# Make sure to not create remote mentions for any remote user not in the room
|
||||||
if parsed.entities:
|
if parsed.entities:
|
||||||
|
@ -240,13 +240,17 @@ class Client:
|
|||||||
self.user.oauth_credential = oauth_info.credential
|
self.user.oauth_credential = oauth_info.credential
|
||||||
await self.user.save()
|
await self.user.save()
|
||||||
|
|
||||||
async def connect(self) -> LoginResult:
|
async def connect(self) -> LoginResult | None:
|
||||||
"""
|
"""
|
||||||
Start a new talk session by providing a token obtained from a prior login.
|
Start a new talk session by providing a token obtained from a prior login.
|
||||||
Receive a snapshot of account state in response.
|
Receive a snapshot of account state in response.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
login_result = await self._api_user_request_result(LoginResult, "connect")
|
login_result = await self._api_user_request_result(LoginResult, "connect")
|
||||||
assert self.user.ktid == login_result.userId, f"User ID mismatch: expected {self.user.ktid}, got {login_result.userId}"
|
assert self.user.ktid == login_result.userId, f"User ID mismatch: expected {self.user.ktid}, got {login_result.userId}"
|
||||||
|
except SerializerError:
|
||||||
|
self.log.exception("Unable to deserialize login result, but connecting anyways")
|
||||||
|
login_result = None
|
||||||
# TODO Skip if handlers are already listening. But this is idempotent and thus probably safe
|
# TODO Skip if handlers are already listening. But this is idempotent and thus probably safe
|
||||||
self._start_listen()
|
self._start_listen()
|
||||||
return login_result
|
return login_result
|
||||||
@ -322,9 +326,9 @@ class Client:
|
|||||||
text: str,
|
text: str,
|
||||||
reply_to: ReplyAttachment | None,
|
reply_to: ReplyAttachment | None,
|
||||||
mentions: list[MentionStruct] | None,
|
mentions: list[MentionStruct] | None,
|
||||||
) -> Chatlog:
|
) -> Long:
|
||||||
return await self._api_user_request_result(
|
return await self._api_user_request_result(
|
||||||
Chatlog,
|
Long,
|
||||||
"send_chat",
|
"send_chat",
|
||||||
channel_props=channel_props.serialize(),
|
channel_props=channel_props.serialize(),
|
||||||
text=text,
|
text=text,
|
||||||
@ -342,9 +346,9 @@ class Client:
|
|||||||
width: int | None = None,
|
width: int | None = None,
|
||||||
height: int | None = None,
|
height: int | None = None,
|
||||||
ext: str | None = None,
|
ext: str | None = None,
|
||||||
) -> Chatlog:
|
) -> Long:
|
||||||
return await self._api_user_request_result(
|
return await self._api_user_request_result(
|
||||||
Chatlog,
|
Long,
|
||||||
"send_media",
|
"send_media",
|
||||||
channel_props=channel_props.serialize(),
|
channel_props=channel_props.serialize(),
|
||||||
type=media_type,
|
type=media_type,
|
||||||
|
@ -30,6 +30,10 @@ class KnownChannelType(str, Enum):
|
|||||||
def is_direct(cls, value: Union["KnownChannelType", str]) -> bool:
|
def is_direct(cls, value: Union["KnownChannelType", str]) -> bool:
|
||||||
return value in [cls.DirectChat, cls.MemoChat]
|
return value in [cls.DirectChat, cls.MemoChat]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_open(cls, value: Union["KnownChannelType", str]) -> bool:
|
||||||
|
return value in [cls.OM, cls.OD]
|
||||||
|
|
||||||
|
|
||||||
ChannelType = Union[KnownChannelType, str] # Substitute for ChannelType = "name1" | ... | "nameN" | str
|
ChannelType = Union[KnownChannelType, str] # Substitute for ChannelType = "name1" | ... | "nameN" | str
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ class EmoticonAttachment(Attachment):
|
|||||||
path: str
|
path: str
|
||||||
name: str
|
name: str
|
||||||
type: str
|
type: str
|
||||||
alt: str
|
alt: Optional[str] = None # NOTE Made optional
|
||||||
s: Optional[int] = None
|
s: Optional[int] = None
|
||||||
sound: Optional[str] = None
|
sound: Optional[str] = None
|
||||||
width: Optional[int] = None
|
width: Optional[int] = None
|
||||||
|
@ -78,10 +78,11 @@ class FileAttachment(MediaKeyAttachment):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AudioAttachment(MediaKeyAttachment):
|
class AudioAttachment(Attachment):
|
||||||
# NOTE Changed superclass from Attachment
|
url: str
|
||||||
d: int
|
d: int
|
||||||
expire: Optional[int] = None
|
expire: Optional[int] = None
|
||||||
|
s: Optional[int] = None # NOTE Optional for inbound
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -157,6 +157,7 @@ class MatrixHandler(BaseMatrixHandler):
|
|||||||
if not user.is_connected:
|
if not user.is_connected:
|
||||||
return
|
return
|
||||||
message = await DBMessage.get_by_mxid(event_id, portal.mxid)
|
message = await DBMessage.get_by_mxid(event_id, portal.mxid)
|
||||||
|
if message:
|
||||||
await user.client.mark_read(portal.channel_props, message.ktid)
|
await user.client.mark_read(portal.channel_props, message.ktid)
|
||||||
|
|
||||||
async def handle_ephemeral_event(
|
async def handle_ephemeral_event(
|
||||||
|
@ -40,6 +40,8 @@ from mautrix.types import (
|
|||||||
EventType,
|
EventType,
|
||||||
FileInfo,
|
FileInfo,
|
||||||
ImageInfo,
|
ImageInfo,
|
||||||
|
JoinRule,
|
||||||
|
JoinRulesStateEventContent,
|
||||||
LocationMessageEventContent,
|
LocationMessageEventContent,
|
||||||
MediaInfo,
|
MediaInfo,
|
||||||
MediaMessageEventContent,
|
MediaMessageEventContent,
|
||||||
@ -198,6 +200,7 @@ class Portal(DBPortal, BasePortal):
|
|||||||
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
# region DB conversion
|
# region DB conversion
|
||||||
@ -629,6 +632,17 @@ class Portal(DBPortal, BasePortal):
|
|||||||
"content": self.bridge_info,
|
"content": self.bridge_info,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
if KnownChannelType.is_open(info.channel_info.type):
|
||||||
|
initial_state.extend((
|
||||||
|
{
|
||||||
|
"type": str(EventType.ROOM_JOIN_RULES),
|
||||||
|
"content": JoinRulesStateEventContent(join_rule=JoinRule.PUBLIC).serialize(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "m.room.guest_access",
|
||||||
|
"content": {"guest_access": "forbidden"},
|
||||||
|
},
|
||||||
|
))
|
||||||
invites = []
|
invites = []
|
||||||
if self.config["bridge.encryption.default"] and self.matrix.e2ee:
|
if self.config["bridge.encryption.default"] and self.matrix.e2ee:
|
||||||
self.encrypted = True
|
self.encrypted = True
|
||||||
@ -777,6 +791,11 @@ class Portal(DBPortal, BasePortal):
|
|||||||
elif not sender.is_connected:
|
elif not sender.is_connected:
|
||||||
raise Exception("not connected to KakaoTalk chats")
|
raise Exception("not connected to KakaoTalk chats")
|
||||||
elif is_relay:
|
elif is_relay:
|
||||||
|
if not message.msgtype.is_text:
|
||||||
|
intro_message = TextMessageEventContent(msgtype=MessageType.TEXT, body=message.body)
|
||||||
|
await self.apply_relay_message_format(orig_sender, intro_message)
|
||||||
|
await self._send_chat(sender, intro_message)
|
||||||
|
else:
|
||||||
await self.apply_relay_message_format(orig_sender, message)
|
await self.apply_relay_message_format(orig_sender, message)
|
||||||
if message.msgtype == MessageType.TEXT or message.msgtype == MessageType.NOTICE:
|
if message.msgtype == MessageType.TEXT or message.msgtype == MessageType.NOTICE:
|
||||||
await self._handle_matrix_text(event_id, sender, message)
|
await self._handle_matrix_text(event_id, sender, message)
|
||||||
@ -787,6 +806,21 @@ class Portal(DBPortal, BasePortal):
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"Unsupported message type {message.msgtype}")
|
raise NotImplementedError(f"Unsupported message type {message.msgtype}")
|
||||||
|
|
||||||
|
async def _send_chat(
|
||||||
|
self, sender: u.User, message: TextMessageEventContent, event_id: EventID | None = None
|
||||||
|
) -> Long:
|
||||||
|
converted = await matrix_to_kakaotalk(message, self.mxid, self.log, self.main_intent)
|
||||||
|
try:
|
||||||
|
return await sender.client.send_chat(
|
||||||
|
self.channel_props,
|
||||||
|
text=converted.text,
|
||||||
|
reply_to=converted.reply_to,
|
||||||
|
mentions=converted.mentions,
|
||||||
|
)
|
||||||
|
except CommandException as e:
|
||||||
|
self.log.debug(f"Error handling Matrix message {event_id if event_id else '<extra>'}: {e!s}")
|
||||||
|
raise
|
||||||
|
|
||||||
async def _make_dbm(self, event_id: EventID, ktid: Long | None = None) -> DBMessage:
|
async def _make_dbm(self, event_id: EventID, ktid: Long | None = None) -> DBMessage:
|
||||||
dbm = DBMessage(
|
dbm = DBMessage(
|
||||||
mxid=event_id,
|
mxid=event_id,
|
||||||
@ -803,19 +837,9 @@ class Portal(DBPortal, BasePortal):
|
|||||||
async def _handle_matrix_text(
|
async def _handle_matrix_text(
|
||||||
self, event_id: EventID, sender: u.User, message: TextMessageEventContent
|
self, event_id: EventID, sender: u.User, message: TextMessageEventContent
|
||||||
) -> None:
|
) -> None:
|
||||||
converted = await matrix_to_kakaotalk(message, self.mxid, self.log, self.main_intent)
|
log_id = await self._send_chat(sender, message, event_id)
|
||||||
try:
|
await self._make_dbm(event_id, log_id)
|
||||||
chatlog = await sender.client.send_chat(
|
self.log.debug(f"Handled Matrix message {event_id} -> {log_id}")
|
||||||
self.channel_props,
|
|
||||||
text=converted.text,
|
|
||||||
reply_to=converted.reply_to,
|
|
||||||
mentions=converted.mentions,
|
|
||||||
)
|
|
||||||
except CommandException as e:
|
|
||||||
self.log.debug(f"Error handling 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(
|
sender.send_remote_checkpoint(
|
||||||
MessageSendCheckpointStatus.SUCCESS,
|
MessageSendCheckpointStatus.SUCCESS,
|
||||||
event_id,
|
event_id,
|
||||||
@ -843,7 +867,7 @@ class Portal(DBPortal, BasePortal):
|
|||||||
width = message.info.width
|
width = message.info.width
|
||||||
height = message.info.height
|
height = message.info.height
|
||||||
try:
|
try:
|
||||||
chatlog = await sender.client.send_media(
|
log_id = await sender.client.send_media(
|
||||||
self.channel_props,
|
self.channel_props,
|
||||||
TO_MSGTYPE_MAP[message.msgtype],
|
TO_MSGTYPE_MAP[message.msgtype],
|
||||||
data,
|
data,
|
||||||
@ -855,8 +879,8 @@ class Portal(DBPortal, BasePortal):
|
|||||||
except CommandException as e:
|
except CommandException as e:
|
||||||
self.log.debug(f"Error uploading media for Matrix message {event_id}: {e!s}")
|
self.log.debug(f"Error uploading media for Matrix message {event_id}: {e!s}")
|
||||||
raise
|
raise
|
||||||
await self._make_dbm(event_id, chatlog.logId)
|
await self._make_dbm(event_id, log_id)
|
||||||
self.log.debug(f"Handled Matrix message {event_id} -> {chatlog.logId}")
|
self.log.debug(f"Handled Matrix message {event_id} -> {log_id}")
|
||||||
sender.send_remote_checkpoint(
|
sender.send_remote_checkpoint(
|
||||||
MessageSendCheckpointStatus.SUCCESS,
|
MessageSendCheckpointStatus.SUCCESS,
|
||||||
event_id,
|
event_id,
|
||||||
@ -1086,7 +1110,7 @@ class Portal(DBPortal, BasePortal):
|
|||||||
type_str = str(chat_type)
|
type_str = str(chat_type)
|
||||||
self.log.warning("No handler for chat type \"%s\" (%s)",
|
self.log.warning("No handler for chat type \"%s\" (%s)",
|
||||||
type_str,
|
type_str,
|
||||||
f"text = {chat_text}" if chat_text is not None else "no text",
|
f"text = \"{chat_text}\"" if chat_text is not None else "no text",
|
||||||
)
|
)
|
||||||
if chat_text:
|
if chat_text:
|
||||||
events = await self._handle_kakaotalk_text(
|
events = await self._handle_kakaotalk_text(
|
||||||
@ -1115,6 +1139,14 @@ class Portal(DBPortal, BasePortal):
|
|||||||
self.log.info("Got feed message at %s: %s", timestamp, chat_text or "none")
|
self.log.info("Got feed message at %s: %s", timestamp, chat_text or "none")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
async def _handle_kakaotalk_deleted(
|
||||||
|
self,
|
||||||
|
timestamp: int,
|
||||||
|
**_
|
||||||
|
) -> list[EventID]:
|
||||||
|
self.log.info(f"Got deleted (?) message at {timestamp}")
|
||||||
|
return []
|
||||||
|
|
||||||
async def _handle_kakaotalk_text(
|
async def _handle_kakaotalk_text(
|
||||||
self,
|
self,
|
||||||
intent: IntentAPI,
|
intent: IntentAPI,
|
||||||
|
@ -144,9 +144,9 @@ class RPCClient:
|
|||||||
self._reader = r
|
self._reader = r
|
||||||
self._writer = w
|
self._writer = w
|
||||||
self._read_task = self.loop.create_task(self._try_read_loop())
|
self._read_task = self.loop.create_task(self._try_read_loop())
|
||||||
|
await self._raw_request("register", peer_id=self.config["appservice.address"])
|
||||||
self._is_connected.set()
|
self._is_connected.set()
|
||||||
self._is_disconnected.clear()
|
self._is_disconnected.clear()
|
||||||
await self.request("register", peer_id=self.config["appservice.address"])
|
|
||||||
|
|
||||||
async def disconnect(self) -> None:
|
async def disconnect(self) -> None:
|
||||||
async with self._connection_lock:
|
async with self._connection_lock:
|
||||||
@ -258,9 +258,11 @@ class RPCClient:
|
|||||||
try:
|
try:
|
||||||
await self._read_loop()
|
await self._read_loop()
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
return
|
||||||
except:
|
except:
|
||||||
self.log.exception("Fatal error in read loop")
|
self.log.exception("Fatal error in read loop")
|
||||||
|
self.log.debug("Reader disconnected")
|
||||||
|
self._on_disconnect()
|
||||||
|
|
||||||
async def _read_loop(self) -> None:
|
async def _read_loop(self) -> None:
|
||||||
while self._reader is not None and not self._reader.at_eof():
|
while self._reader is not None and not self._reader.at_eof():
|
||||||
@ -275,9 +277,6 @@ class RPCClient:
|
|||||||
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(self._reader._limit)
|
||||||
except ConnectionResetError:
|
|
||||||
if self._reader is not None:
|
|
||||||
raise
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
raise
|
raise
|
||||||
if not line:
|
if not line:
|
||||||
@ -293,8 +292,6 @@ class RPCClient:
|
|||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
self.log.exception("Failed to handle incoming request %s", line_str)
|
self.log.exception("Failed to handle incoming request %s", line_str)
|
||||||
self.log.debug("Reader disconnected")
|
|
||||||
self._on_disconnect()
|
|
||||||
|
|
||||||
async def _raw_request(self, command: str, is_secret: bool = False, **data: JSON) -> asyncio.Future[JSON]:
|
async def _raw_request(self, command: str, is_secret: bool = False, **data: JSON) -> asyncio.Future[JSON]:
|
||||||
req_id = self._next_req_id
|
req_id = self._next_req_id
|
||||||
|
@ -412,6 +412,7 @@ class User(DBUser, BaseUser):
|
|||||||
try:
|
try:
|
||||||
login_result = await self.client.connect()
|
login_result = await self.client.connect()
|
||||||
await self.on_connect()
|
await self.on_connect()
|
||||||
|
if login_result:
|
||||||
await self._sync_channels(login_result, sync_count)
|
await self._sync_channels(login_result, sync_count)
|
||||||
return True
|
return True
|
||||||
except AuthenticationRequired as e:
|
except AuthenticationRequired as e:
|
||||||
|
@ -634,12 +634,13 @@ export default class PeerClient {
|
|||||||
*/
|
*/
|
||||||
sendChat = async (req) => {
|
sendChat = async (req) => {
|
||||||
const talkChannel = await this.#getUserChannel(req.mxid, req.channel_props)
|
const talkChannel = await this.#getUserChannel(req.mxid, req.channel_props)
|
||||||
|
const res = await talkChannel.sendChat({
|
||||||
return await talkChannel.sendChat({
|
|
||||||
text: req.text,
|
text: req.text,
|
||||||
type: !!req.reply_to ? KnownChatType.REPLY : KnownChatType.TEXT,
|
type: !!req.reply_to ? KnownChatType.REPLY : KnownChatType.TEXT,
|
||||||
attachment: !req.mentions ? req.reply_to : {...req.reply_to, mentions: req.mentions},
|
attachment: !req.mentions ? req.reply_to : {...req.reply_to, mentions: req.mentions},
|
||||||
})
|
})
|
||||||
|
if (res.success) res.result = res.result.logId
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -655,14 +656,15 @@ export default class PeerClient {
|
|||||||
*/
|
*/
|
||||||
sendMedia = async (req) => {
|
sendMedia = async (req) => {
|
||||||
const talkChannel = await this.#getUserChannel(req.mxid, req.channel_props)
|
const talkChannel = await this.#getUserChannel(req.mxid, req.channel_props)
|
||||||
|
const res = await talkChannel.sendMedia(req.type, {
|
||||||
return await talkChannel.sendMedia(req.type, {
|
|
||||||
data: Uint8Array.from(req.data),
|
data: Uint8Array.from(req.data),
|
||||||
name: req.name,
|
name: req.name,
|
||||||
width: req.width,
|
width: req.width,
|
||||||
height: req.height,
|
height: req.height,
|
||||||
ext: req.ext,
|
ext: req.ext,
|
||||||
})
|
})
|
||||||
|
if (res.success) res.result = res.result.logId
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -770,6 +772,7 @@ export default class PeerClient {
|
|||||||
send_chat: this.sendChat,
|
send_chat: this.sendChat,
|
||||||
send_media: this.sendMedia,
|
send_media: this.sendMedia,
|
||||||
delete_chat: this.deleteChat,
|
delete_chat: this.deleteChat,
|
||||||
|
mark_read: this.markRead,
|
||||||
}[req.command] || this.handleUnknownCommand
|
}[req.command] || this.handleUnknownCommand
|
||||||
}
|
}
|
||||||
const resp = { id: req.id }
|
const resp = { id: req.id }
|
||||||
|
Loading…
Reference in New Issue
Block a user