Compare commits
No commits in common. "82282932029ac75f70cb4db8c254fccc557577ca" and "454d1b72cc050f27978837decd38ec76bfe571d0" have entirely different histories.
8228293202
...
454d1b72cc
21
ROADMAP.md
21
ROADMAP.md
@ -20,13 +20,13 @@
|
|||||||
* [x] Power level<sup>[1]</sup>
|
* [x] Power level<sup>[1]</sup>
|
||||||
* [ ] Membership actions
|
* [ ] Membership actions
|
||||||
* [ ] Invite
|
* [ ] Invite
|
||||||
* [x] Join
|
* [ ] Join
|
||||||
* [x] Leave<sup>[3]</sup>
|
* [x] Leave<sup>[3]</sup>
|
||||||
* [ ] Ban<sup>[4]</sup>
|
* [ ] Ban<sup>[4]</sup>
|
||||||
* [ ] Unban<sup>[4]</sup>
|
* [ ] Unban<sup>[4]</sup>
|
||||||
* [ ] Room metadata changes
|
* [ ] Room metadata changes
|
||||||
* [x] Name
|
* [x] Name<sup>[1]</sup>
|
||||||
* [x] Topic
|
* [x] Topic<sup>[1]</sup>
|
||||||
* [ ] Avatar
|
* [ ] Avatar
|
||||||
* [ ] Per-room user nick
|
* [ ] Per-room user nick
|
||||||
* KakaoTalk → Matrix
|
* KakaoTalk → Matrix
|
||||||
@ -74,16 +74,6 @@
|
|||||||
* [x] When added to chat
|
* [x] When added to chat
|
||||||
* [x] When receiving message
|
* [x] When receiving message
|
||||||
* [x] Direct chat creation by inviting Matrix puppet of KakaoTalk user to new room
|
* [x] Direct chat creation by inviting Matrix puppet of KakaoTalk user to new room
|
||||||
* [ ] Open Chat creation by bot command
|
|
||||||
* [ ] Group Chat
|
|
||||||
* [ ] 1:1 Chat
|
|
||||||
* [ ] Open Chat settings management
|
|
||||||
* [ ] Public search
|
|
||||||
* [ ] Max number of participants
|
|
||||||
* [ ] Chatroom code
|
|
||||||
* [x] Display Open Chat public URL
|
|
||||||
* [x] Join Open Chat via public URL
|
|
||||||
* [ ] Join passcode-protected Open Chat
|
|
||||||
* [x] Option to use own Matrix account for messages sent from other KakaoTalk clients
|
* [x] Option to use own Matrix account for messages sent from other KakaoTalk clients
|
||||||
* [ ] KakaoTalk friends list management
|
* [ ] KakaoTalk friends list management
|
||||||
* [x] List friends
|
* [x] List friends
|
||||||
@ -92,9 +82,8 @@
|
|||||||
* [x] By Matrix puppet of KakaoTalk user
|
* [x] By Matrix puppet of KakaoTalk user
|
||||||
* [ ] By phone number
|
* [ ] By phone number
|
||||||
* [x] Remove friend
|
* [x] Remove friend
|
||||||
* [ ] Favourite friends
|
* [ ] Manage favourite friends
|
||||||
* [ ] Hidden friends
|
* [ ] Manage hidden friends
|
||||||
* [ ] Blocked users
|
|
||||||
* [x] KakaoTalk ID management
|
* [x] KakaoTalk ID management
|
||||||
* [x] Set/Change ID
|
* [x] Set/Change ID
|
||||||
* [x] Make ID searchable/hidden
|
* [x] Make ID searchable/hidden
|
||||||
|
@ -63,7 +63,7 @@ async def ping(evt: CommandEvent) -> None:
|
|||||||
if not await evt.sender.is_logged_in():
|
if not await evt.sender.is_logged_in():
|
||||||
await evt.reply("You are **logged out** of KakaoTalk.")
|
await evt.reply("You are **logged out** of KakaoTalk.")
|
||||||
else:
|
else:
|
||||||
is_connected = await evt.sender.is_connected_now()
|
is_connected = evt.sender.is_connected and await evt.sender.client.is_connected()
|
||||||
await evt.reply(
|
await evt.reply(
|
||||||
"You are logged into KakaoTalk.\n\n"
|
"You are logged into KakaoTalk.\n\n"
|
||||||
f"You are {'connected to' if is_connected else '**disconnected** from'} KakaoTalk chats."
|
f"You are {'connected to' if is_connected else '**disconnected** from'} KakaoTalk chats."
|
||||||
|
@ -301,46 +301,11 @@ async def _on_friend_edited(evt: CommandEvent, friend_struct: FriendStruct | Non
|
|||||||
await puppet.update_info_from_friend(evt.sender, friend_struct)
|
await puppet.update_info_from_friend(evt.sender, friend_struct)
|
||||||
|
|
||||||
|
|
||||||
@command_handler(
|
|
||||||
management_only=False,
|
|
||||||
help_section=SECTION_CHANNELS,
|
|
||||||
help_text="If the current KakaoTalk channel is an Open Chat, display its URL",
|
|
||||||
)
|
|
||||||
async def get_url(evt: CommandEvent) -> None:
|
|
||||||
if not evt.is_portal:
|
|
||||||
await evt.reply("This command may only be used in a KakaoTalk channel portal room")
|
|
||||||
return
|
|
||||||
await evt.reply(
|
|
||||||
evt.portal.full_link_url or "This channel has no URL."
|
|
||||||
if evt.portal.is_open else "This channel is not an Open Chat."
|
|
||||||
)
|
|
||||||
|
|
||||||
@command_handler(
|
|
||||||
needs_auth=True,
|
|
||||||
management_only=True,
|
|
||||||
help_section=SECTION_CHANNELS,
|
|
||||||
help_text="Join a KakaoTalk Open Chat",
|
|
||||||
help_args="<_URL_>",
|
|
||||||
)
|
|
||||||
async def join(evt: CommandEvent) -> None:
|
|
||||||
if len(evt.args) != 1:
|
|
||||||
await evt.reply(f"**Usage:** `$cmdprefix+sp {evt.command} <URL>`")
|
|
||||||
return
|
|
||||||
if not evt.sender.is_connected:
|
|
||||||
await evt.reply("You are not connected to KakaoTalk chats")
|
|
||||||
return
|
|
||||||
await evt.mark_read()
|
|
||||||
try:
|
|
||||||
await evt.sender.join_channel(evt.args[0])
|
|
||||||
except CommandException as e:
|
|
||||||
await evt.reply(f"Error from KakaoTalk: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
@command_handler(
|
@command_handler(
|
||||||
needs_auth=True,
|
needs_auth=True,
|
||||||
management_only=False,
|
management_only=False,
|
||||||
help_section=SECTION_CHANNELS,
|
help_section=SECTION_CHANNELS,
|
||||||
help_text="Leave the current KakaoTalk channel",
|
help_text="Leave this KakaoTalk channel",
|
||||||
)
|
)
|
||||||
async def leave(evt: CommandEvent) -> None:
|
async def leave(evt: CommandEvent) -> None:
|
||||||
if not evt.sender.is_connected:
|
if not evt.sender.is_connected:
|
||||||
|
@ -117,6 +117,7 @@ class Config(BaseBridgeConfig):
|
|||||||
else:
|
else:
|
||||||
copy("rpc.connection.host")
|
copy("rpc.connection.host")
|
||||||
copy("rpc.connection.port")
|
copy("rpc.connection.port")
|
||||||
|
copy("rpc.logging_keys")
|
||||||
|
|
||||||
def _get_permissions(self, key: str) -> tuple[bool, bool, bool, str]:
|
def _get_permissions(self, key: str) -> tuple[bool, bool, bool, str]:
|
||||||
level = self["bridge.permissions"].get(key, "")
|
level = self["bridge.permissions"].get(key, "")
|
||||||
|
@ -45,8 +45,6 @@ class Portal:
|
|||||||
name_set: bool
|
name_set: bool
|
||||||
topic_set: bool
|
topic_set: bool
|
||||||
avatar_set: bool
|
avatar_set: bool
|
||||||
link_id: Long | None = field(converter=to_optional_long)
|
|
||||||
link_url: str | None
|
|
||||||
fully_read_kt_chat: Long | None = field(converter=to_optional_long)
|
fully_read_kt_chat: Long | None = field(converter=to_optional_long)
|
||||||
relay_user_id: UserID | None
|
relay_user_id: UserID | None
|
||||||
|
|
||||||
@ -60,7 +58,7 @@ class Portal:
|
|||||||
|
|
||||||
_columns = (
|
_columns = (
|
||||||
"ktid, kt_receiver, kt_type, mxid, name, description, photo_id, avatar_url, encrypted, "
|
"ktid, kt_receiver, kt_type, mxid, name, description, photo_id, avatar_url, encrypted, "
|
||||||
"name_set, avatar_set, link_id, link_url, fully_read_kt_chat, relay_user_id"
|
"name_set, avatar_set, fully_read_kt_chat, relay_user_id"
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -101,8 +99,6 @@ class Portal:
|
|||||||
self.encrypted,
|
self.encrypted,
|
||||||
self.name_set,
|
self.name_set,
|
||||||
self.avatar_set,
|
self.avatar_set,
|
||||||
self.link_id,
|
|
||||||
self.link_url,
|
|
||||||
self.fully_read_kt_chat,
|
self.fully_read_kt_chat,
|
||||||
self.relay_user_id,
|
self.relay_user_id,
|
||||||
)
|
)
|
||||||
|
@ -21,4 +21,3 @@ from . import v01_initial_revision
|
|||||||
from . import v02_channel_meta
|
from . import v02_channel_meta
|
||||||
from . import v03_user_connection
|
from . import v03_user_connection
|
||||||
from . import v04_read_receipt_sync
|
from . import v04_read_receipt_sync
|
||||||
from . import v05_open_link
|
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
# matrix-appservice-kakaotalk - A Matrix-KakaoTalk puppeting bridge.
|
|
||||||
# Copyright (C) 2022 Tulir Asokan, Andrew Ferrazzutti
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
from mautrix.util.async_db import Connection, Scheme
|
|
||||||
|
|
||||||
from . import upgrade_table
|
|
||||||
|
|
||||||
|
|
||||||
@upgrade_table.register(description="Track OpenChannel public link IDs and URLs")
|
|
||||||
async def upgrade_v5(conn: Connection) -> None:
|
|
||||||
await conn.execute("ALTER TABLE portal ADD COLUMN link_id BIGINT")
|
|
||||||
await conn.execute("ALTER TABLE portal ADD COLUMN link_url TEXT")
|
|
@ -252,6 +252,11 @@ rpc:
|
|||||||
# Only for type: tcp
|
# Only for type: tcp
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 29392
|
port: 29392
|
||||||
|
# Command arguments to print in logs. Optional.
|
||||||
|
# TODO Support nested arguments, like channel_props.ktid
|
||||||
|
logging_keys:
|
||||||
|
- mxid
|
||||||
|
#- channel_props
|
||||||
|
|
||||||
# Python logging configuration.
|
# Python logging configuration.
|
||||||
#
|
#
|
||||||
|
@ -19,6 +19,7 @@ Currently a wrapper around a Node backend, but
|
|||||||
the abstraction used here should be compatible
|
the abstraction used here should be compatible
|
||||||
with any other potential backend.
|
with any other potential backend.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, cast, Awaitable, Type, Optional, Union
|
from typing import TYPE_CHECKING, cast, Awaitable, Type, Optional, Union
|
||||||
@ -517,26 +518,6 @@ class Client:
|
|||||||
user_id=ktid.serialize(),
|
user_id=ktid.serialize(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def join_channel_by_url(self, url: str) -> Awaitable[Long]:
|
|
||||||
return self._api_user_request_result(
|
|
||||||
Long,
|
|
||||||
"join_channel_by_url",
|
|
||||||
url=url,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def join_channel(
|
|
||||||
self,
|
|
||||||
channel_id: Long,
|
|
||||||
link_id: Long,
|
|
||||||
) -> None:
|
|
||||||
joined_id = await self._api_user_request_result(
|
|
||||||
Long,
|
|
||||||
"join_channel",
|
|
||||||
channel_id=channel_id.serialize(),
|
|
||||||
link_id=link_id.serialize(),
|
|
||||||
)
|
|
||||||
assert channel_id == joined_id, f"Mismatch of joined channel ID: expected {channel_id}, got {joined_id}"
|
|
||||||
|
|
||||||
def leave_channel(
|
def leave_channel(
|
||||||
self,
|
self,
|
||||||
channel_props: ChannelProps,
|
channel_props: ChannelProps,
|
||||||
@ -706,9 +687,6 @@ class Client:
|
|||||||
def _on_error(self, data: dict[str, JSON]) -> Awaitable[None]:
|
def _on_error(self, data: dict[str, JSON]) -> Awaitable[None]:
|
||||||
return self.user.on_error(data)
|
return self.user.on_error(data)
|
||||||
|
|
||||||
def _on_unexpected_disconnect(self, _: dict[str, JSON]) -> Awaitable[None]:
|
|
||||||
return self.user.on_unexpected_disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
def _start_listen(self) -> None:
|
def _start_listen(self) -> None:
|
||||||
self._add_event_handler("chat", self._on_chat)
|
self._add_event_handler("chat", self._on_chat)
|
||||||
@ -726,7 +704,6 @@ class Client:
|
|||||||
self._add_event_handler("disconnected", self._on_listen_disconnect)
|
self._add_event_handler("disconnected", self._on_listen_disconnect)
|
||||||
self._add_event_handler("switch_server", self._on_switch_server)
|
self._add_event_handler("switch_server", self._on_switch_server)
|
||||||
self._add_event_handler("error", self._on_error)
|
self._add_event_handler("error", self._on_error)
|
||||||
self._add_event_handler("unexpected_disconnect", self._on_unexpected_disconnect)
|
|
||||||
|
|
||||||
def _stop_listen(self) -> None:
|
def _stop_listen(self) -> None:
|
||||||
for method in self._handler_methods:
|
for method in self._handler_methods:
|
||||||
|
@ -81,8 +81,6 @@ class PortalChannelInfo(SerializableAttrs):
|
|||||||
name: str
|
name: str
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
photoURL: Optional[str] = None
|
photoURL: Optional[str] = None
|
||||||
linkId: Optional[Long] = None
|
|
||||||
linkURL: Optional[str] = None
|
|
||||||
participantInfo: Optional[PortalChannelParticipantInfo] = None # May set to None to skip participant update
|
participantInfo: Optional[PortalChannelParticipantInfo] = None # May set to None to skip participant update
|
||||||
channel_info: Optional[ChannelInfoUnion] = None # Should be set manually by caller
|
channel_info: Optional[ChannelInfoUnion] = None # Should be set manually by caller
|
||||||
|
|
||||||
@ -91,7 +89,6 @@ class PortalChannelInfo(SerializableAttrs):
|
|||||||
class ChannelProps(SerializableAttrs):
|
class ChannelProps(SerializableAttrs):
|
||||||
id: Long
|
id: Long
|
||||||
type: ChannelType
|
type: ChannelType
|
||||||
link_id: Optional[Long]
|
|
||||||
|
|
||||||
|
|
||||||
# TODO Add non-media types, like polls & maps
|
# TODO Add non-media types, like polls & maps
|
||||||
|
@ -28,7 +28,7 @@ class KnownChannelType(str, Enum):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
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, cls.OD]
|
return value in [cls.DirectChat, cls.MemoChat]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_open(cls, value: Union["KnownChannelType", str]) -> bool:
|
def is_open(cls, value: Union["KnownChannelType", str]) -> bool:
|
||||||
|
@ -96,16 +96,17 @@ class MatrixHandler(BaseMatrixHandler):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
elif (
|
elif (
|
||||||
not user.is_connected
|
not await user.is_logged_in()
|
||||||
and not portal.has_relay
|
and not portal.has_relay
|
||||||
and not self.config["bridge.allow_invites"]
|
and not self.config["bridge.allow_invites"]
|
||||||
):
|
):
|
||||||
await portal.main_intent.kick_user(
|
await portal.main_intent.kick_user(
|
||||||
room_id, user.mxid, "You are not connected to this KakaoTalk bridge."
|
room_id, user.mxid, "You are not logged in to this KakaoTalk bridge."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await portal.handle_matrix_join(user)
|
self.log.debug(f"{user.mxid} joined {room_id}")
|
||||||
|
# await portal.join_matrix(user, event_id)
|
||||||
|
|
||||||
async def handle_leave(self, room_id: RoomID, user_id: UserID, event_id: EventID) -> None:
|
async def handle_leave(self, room_id: RoomID, user_id: UserID, event_id: EventID) -> None:
|
||||||
portal = await po.Portal.get_by_mxid(room_id)
|
portal = await po.Portal.get_by_mxid(room_id)
|
||||||
|
@ -166,8 +166,6 @@ class Portal(DBPortal, BasePortal):
|
|||||||
_CHAT_TYPE_HANDLER_MAP: dict[ChatType, Callable[..., ACallable[list[EventID]]]]
|
_CHAT_TYPE_HANDLER_MAP: dict[ChatType, Callable[..., ACallable[list[EventID]]]]
|
||||||
_STATE_EVENT_HANDLER_MAP: dict[EventType, StateEventHandler]
|
_STATE_EVENT_HANDLER_MAP: dict[EventType, StateEventHandler]
|
||||||
|
|
||||||
OPEN_LINK_URL_PREFIX = "https://open.kakao.com/o/"
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ktid: Long,
|
ktid: Long,
|
||||||
@ -182,8 +180,6 @@ class Portal(DBPortal, BasePortal):
|
|||||||
name_set: bool = False,
|
name_set: bool = False,
|
||||||
topic_set: bool = False,
|
topic_set: bool = False,
|
||||||
avatar_set: bool = False,
|
avatar_set: bool = False,
|
||||||
link_id: Long | None = None,
|
|
||||||
link_url: str | None = None,
|
|
||||||
fully_read_kt_chat: Long | None = None,
|
fully_read_kt_chat: Long | None = None,
|
||||||
relay_user_id: UserID | None = None,
|
relay_user_id: UserID | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -200,8 +196,6 @@ class Portal(DBPortal, BasePortal):
|
|||||||
name_set,
|
name_set,
|
||||||
topic_set,
|
topic_set,
|
||||||
avatar_set,
|
avatar_set,
|
||||||
link_id,
|
|
||||||
link_url,
|
|
||||||
fully_read_kt_chat,
|
fully_read_kt_chat,
|
||||||
relay_user_id,
|
relay_user_id,
|
||||||
)
|
)
|
||||||
@ -321,14 +315,9 @@ class Portal(DBPortal, BasePortal):
|
|||||||
def channel_props(self) -> ChannelProps:
|
def channel_props(self) -> ChannelProps:
|
||||||
return ChannelProps(
|
return ChannelProps(
|
||||||
id=self.ktid,
|
id=self.ktid,
|
||||||
type=self.kt_type,
|
type=self.kt_type
|
||||||
link_id=self.link_id,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def full_link_url(self) -> str:
|
|
||||||
return self.OPEN_LINK_URL_PREFIX + self.link_url if self.link_url else ""
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def main_intent(self) -> IntentAPI:
|
def main_intent(self) -> IntentAPI:
|
||||||
if not self._main_intent:
|
if not self._main_intent:
|
||||||
@ -389,7 +378,6 @@ class Portal(DBPortal, BasePortal):
|
|||||||
self._update_name(info.name),
|
self._update_name(info.name),
|
||||||
self._update_description(info.description),
|
self._update_description(info.description),
|
||||||
self._update_photo(source, info.photoURL),
|
self._update_photo(source, info.photoURL),
|
||||||
self._update_open_link(info.linkId, info.linkURL),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if info.participantInfo:
|
if info.participantInfo:
|
||||||
@ -562,24 +550,6 @@ class Portal(DBPortal, BasePortal):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _update_open_link(self, link_id: Long | None, link_url: str | None) -> bool:
|
|
||||||
changed = False
|
|
||||||
if self.link_id != link_id:
|
|
||||||
self.log.trace(f"Updating OpenLink ID {self.link_id} -> {link_id}")
|
|
||||||
self.link_id = link_id
|
|
||||||
changed = True
|
|
||||||
if self.link_url != link_url:
|
|
||||||
if link_url:
|
|
||||||
if not link_url.startswith(self.OPEN_LINK_URL_PREFIX):
|
|
||||||
self.log.error(f"Unexpected prefix for OpenLink URL {link_url}")
|
|
||||||
link_url = None
|
|
||||||
else:
|
|
||||||
link_url = link_url.removeprefix(self.OPEN_LINK_URL_PREFIX)
|
|
||||||
self.log.trace(f"Updating OpenLink URL {self.link_url} -> {link_url}")
|
|
||||||
self.link_url = link_url
|
|
||||||
changed = True
|
|
||||||
return changed
|
|
||||||
|
|
||||||
async def _update_photo(self, source: u.User, photo_id: str | None) -> bool:
|
async def _update_photo(self, source: u.User, photo_id: str | None) -> bool:
|
||||||
if self.is_direct and not self.encrypted:
|
if self.is_direct and not self.encrypted:
|
||||||
return False
|
return False
|
||||||
@ -1249,6 +1219,9 @@ class Portal(DBPortal, BasePortal):
|
|||||||
# Misses should be guarded by supports_state_event, but handle this just in case
|
# Misses should be guarded by supports_state_event, but handle this just in case
|
||||||
self.log.error(f"Skipping Matrix state event {evt.event_id} of unsupported type {evt.type}")
|
self.log.error(f"Skipping Matrix state event {evt.event_id} of unsupported type {evt.type}")
|
||||||
return
|
return
|
||||||
|
if not self.is_open:
|
||||||
|
self.log.info(f"Not bridging f{handler.action_name} change of portal for non-open channel")
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
effective_sender, _ = await self.get_relay_sender(sender, f"{handler.action_name} {evt.event_id}")
|
effective_sender, _ = await self.get_relay_sender(sender, f"{handler.action_name} {evt.event_id}")
|
||||||
if effective_sender:
|
if effective_sender:
|
||||||
@ -1365,9 +1338,6 @@ class Portal(DBPortal, BasePortal):
|
|||||||
) -> None:
|
) -> None:
|
||||||
if content.topic == prev_content.topic:
|
if content.topic == prev_content.topic:
|
||||||
return
|
return
|
||||||
if not self.is_open:
|
|
||||||
self.log.info(f"Not bridging topic change of portal for non-open channel")
|
|
||||||
return
|
|
||||||
if not (sender and sender.is_connected):
|
if not (sender and sender.is_connected):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Only users connected to KakaoTalk can set the description of a KakaoTalk channel"
|
"Only users connected to KakaoTalk can set the description of a KakaoTalk channel"
|
||||||
@ -1405,13 +1375,6 @@ class Portal(DBPortal, BasePortal):
|
|||||||
async def _revert_matrix_room_avatar(self, prev_content: RoomAvatarStateEventContent) -> None:
|
async def _revert_matrix_room_avatar(self, prev_content: RoomAvatarStateEventContent) -> None:
|
||||||
await self.main_intent.set_room_avatar(self.mxid, prev_content.url)
|
await self.main_intent.set_room_avatar(self.mxid, prev_content.url)
|
||||||
|
|
||||||
async def handle_matrix_join(self, user: u.User) -> None:
|
|
||||||
if self.link_id:
|
|
||||||
try:
|
|
||||||
await user.client.join_channel(self.ktid, self.link_id)
|
|
||||||
except Exception as e:
|
|
||||||
await self.main_intent.kick_user(self.mxid, user.mxid, str(e))
|
|
||||||
|
|
||||||
async def handle_matrix_leave(self, user: u.User) -> None:
|
async def handle_matrix_leave(self, user: u.User) -> None:
|
||||||
if self.is_direct:
|
if self.is_direct:
|
||||||
self.log.info(f"{user.mxid} left private chat portal with {self.ktid}")
|
self.log.info(f"{user.mxid} left private chat portal with {self.ktid}")
|
||||||
|
@ -86,6 +86,7 @@ class RPCClient:
|
|||||||
_is_connected: CancelableEvent
|
_is_connected: CancelableEvent
|
||||||
_is_disconnected: CancelableEvent
|
_is_disconnected: CancelableEvent
|
||||||
_connection_lock: asyncio.Lock
|
_connection_lock: asyncio.Lock
|
||||||
|
_logging_keys: list[str]
|
||||||
|
|
||||||
def __init__(self, config: Config, register_config_key: str) -> None:
|
def __init__(self, config: Config, register_config_key: str) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
@ -105,6 +106,7 @@ class RPCClient:
|
|||||||
self._is_disconnected = CancelableEvent(self.loop)
|
self._is_disconnected = CancelableEvent(self.loop)
|
||||||
self._is_disconnected.set()
|
self._is_disconnected.set()
|
||||||
self._connection_lock = asyncio.Lock()
|
self._connection_lock = asyncio.Lock()
|
||||||
|
self._logging_keys = config["rpc.logging_keys"]
|
||||||
|
|
||||||
async def connect(self) -> None:
|
async def connect(self) -> None:
|
||||||
async with self._connection_lock:
|
async with self._connection_lock:
|
||||||
@ -147,7 +149,8 @@ class RPCClient:
|
|||||||
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",
|
await self._raw_request("register",
|
||||||
peer_id=self.config["appservice.address"],
|
peer_id=self.config["appservice.address"],
|
||||||
register_config=self.config[self.register_config_key])
|
register_config=self.config[self.register_config_key],
|
||||||
|
logging_keys=self._logging_keys)
|
||||||
self._is_connected.set()
|
self._is_connected.set()
|
||||||
self._is_disconnected.clear()
|
self._is_disconnected.clear()
|
||||||
|
|
||||||
@ -247,7 +250,6 @@ class RPCClient:
|
|||||||
self.log.debug(f"Nobody waiting for response to {req_id}")
|
self.log.debug(f"Nobody waiting for response to {req_id}")
|
||||||
return
|
return
|
||||||
if command == "response":
|
if command == "response":
|
||||||
self.log.debug("Received response %d", req_id)
|
|
||||||
waiter.set_result(req.get("response"))
|
waiter.set_result(req.get("response"))
|
||||||
elif command == "error":
|
elif command == "error":
|
||||||
waiter.set_exception(RPCError(req.get("error", line)))
|
waiter.set_exception(RPCError(req.get("error", line)))
|
||||||
@ -303,7 +305,10 @@ class RPCClient:
|
|||||||
req_id = self._next_req_id
|
req_id = self._next_req_id
|
||||||
future = self._response_waiters[req_id] = self.loop.create_future()
|
future = self._response_waiters[req_id] = self.loop.create_future()
|
||||||
req = {"id": req_id, "command": command, **data}
|
req = {"id": req_id, "command": command, **data}
|
||||||
self.log.debug("Request %d: %s", req_id, command)
|
self.log.debug("Request %d: %s", req_id,
|
||||||
|
', '.join(
|
||||||
|
[command] +
|
||||||
|
[f"{k}: {data[k]}" for k in self._logging_keys if k in data]))
|
||||||
assert self._writer is not None
|
assert self._writer is not None
|
||||||
self._writer.write(json.dumps(req).encode("utf-8"))
|
self._writer.write(json.dumps(req).encode("utf-8"))
|
||||||
self._writer.write(b"\n")
|
self._writer.write(b"\n")
|
||||||
|
@ -177,9 +177,6 @@ class User(DBUser, BaseUser):
|
|||||||
self._is_connected = val
|
self._is_connected = val
|
||||||
self._connection_time = time.monotonic()
|
self._connection_time = time.monotonic()
|
||||||
|
|
||||||
async def is_connected_now(self) -> bool:
|
|
||||||
return self._client is not None and await self._client.is_connected()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def connection_time(self) -> float:
|
def connection_time(self) -> float:
|
||||||
return self._connection_time
|
return self._connection_time
|
||||||
@ -707,10 +704,6 @@ class User(DBUser, BaseUser):
|
|||||||
|
|
||||||
# region Matrix->KakaoTalk commands
|
# region Matrix->KakaoTalk commands
|
||||||
|
|
||||||
async def join_channel(self, url: str) -> None:
|
|
||||||
await self.client.join_channel_by_url(url)
|
|
||||||
# TODO Get channel ID(s) and sync
|
|
||||||
|
|
||||||
async def leave_channel(self, portal: po.Portal) -> None:
|
async def leave_channel(self, portal: po.Portal) -> None:
|
||||||
await self.client.leave_channel(portal.channel_props)
|
await self.client.leave_channel(portal.channel_props)
|
||||||
await self.on_channel_left(portal.ktid, portal.kt_type)
|
await self.on_channel_left(portal.ktid, portal.kt_type)
|
||||||
@ -777,22 +770,6 @@ class User(DBUser, BaseUser):
|
|||||||
error_message=str(error),
|
error_message=str(error),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def on_unexpected_disconnect(self) -> None:
|
|
||||||
self.is_connected = False
|
|
||||||
self._track_metric(METRIC_CONNECTED, False)
|
|
||||||
if self.config["bridge.remain_logged_in_on_disconnect"]:
|
|
||||||
# TODO What bridge state to push?
|
|
||||||
self.was_connected = False
|
|
||||||
await self.save()
|
|
||||||
reason_suffix = "To reconnect, use the `sync` command."
|
|
||||||
else:
|
|
||||||
await self.logout()
|
|
||||||
reason_suffix = "You are now logged out. To log back in, use the `login` command."
|
|
||||||
await self.send_bridge_notice(
|
|
||||||
f"Disconnected from KakaoTalk: unexpected error in backend helper module. {reason_suffix}",
|
|
||||||
important=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def on_client_disconnect(self) -> None:
|
async def on_client_disconnect(self) -> None:
|
||||||
self.is_connected = False
|
self.is_connected = False
|
||||||
self._track_metric(METRIC_CONNECTED, False)
|
self._track_metric(METRIC_CONNECTED, False)
|
||||||
|
@ -3,10 +3,5 @@ If `type` is `unix`, `path` is the path where to create the socket, and `force`
|
|||||||
|
|
||||||
If `type` is `tcp`, `port` and `host` are the host/port where to listen.
|
If `type` is `tcp`, `port` and `host` are the host/port where to listen.
|
||||||
|
|
||||||
### Register timeout config
|
### Register timeout
|
||||||
`register_timeout` is the amount of time (in milliseconds) that a connecting peer must send a "register" command after initiating a connection.
|
`register_timeout` is the amount of time (in milliseconds) that a connecting peer must send a "register" command after initiating a connection.
|
||||||
|
|
||||||
### Logging config
|
|
||||||
`logging_keys` specifies which properties of RPC request & response objects to print in logs. Optional.
|
|
||||||
|
|
||||||
A special-case logging key for responses is `value`, which enables logging responses that are primitives instead of objects.
|
|
||||||
|
@ -3,10 +3,5 @@
|
|||||||
"type": "unix",
|
"type": "unix",
|
||||||
"path": "/data/rpc.sock",
|
"path": "/data/rpc.sock",
|
||||||
"force": true
|
"force": true
|
||||||
},
|
|
||||||
"register_timeout": 3000,
|
|
||||||
"logging_keys": {
|
|
||||||
"request": ["mxid"],
|
|
||||||
"response": ["status"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,5 @@
|
|||||||
"path": "/var/run/matrix-appservice-kakaotalk/rpc.sock",
|
"path": "/var/run/matrix-appservice-kakaotalk/rpc.sock",
|
||||||
"force": false
|
"force": false
|
||||||
},
|
},
|
||||||
"register_timeout": 3000,
|
"register_timeout": 3000
|
||||||
"logging_keys": {
|
|
||||||
"request": ["mxid"],
|
|
||||||
"response": ["status"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,6 @@ import { ReadStreamUtil } from "node-kakao/stream"
|
|||||||
/** @typedef {import("node-kakao").MentionStruct} MentionStruct */
|
/** @typedef {import("node-kakao").MentionStruct} MentionStruct */
|
||||||
/** @typedef {import("node-kakao").TalkNormalChannel} TalkNormalChannel */
|
/** @typedef {import("node-kakao").TalkNormalChannel} TalkNormalChannel */
|
||||||
/** @typedef {import("node-kakao").TalkOpenChannel} TalkOpenChannel */
|
/** @typedef {import("node-kakao").TalkOpenChannel} TalkOpenChannel */
|
||||||
/** @typedef {import("node-kakao").OpenLink} OpenLink */
|
|
||||||
/** @typedef {import("node-kakao/dist/talk").TalkChannelList} TalkChannelList */
|
/** @typedef {import("node-kakao/dist/talk").TalkChannelList} TalkChannelList */
|
||||||
// TODO Remove once/if some helper type hints are upstreamed
|
// TODO Remove once/if some helper type hints are upstreamed
|
||||||
/** @typedef {import("node-kakao").OpenChannelUserInfo} OpenChannelUserInfo */
|
/** @typedef {import("node-kakao").OpenChannelUserInfo} OpenChannelUserInfo */
|
||||||
@ -44,10 +43,9 @@ const { KnownChatType } = chat
|
|||||||
import { emitLines, promisify } from "./util.js"
|
import { emitLines, promisify } from "./util.js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} ChannelProps
|
* @typedef {Object} ChannelProps
|
||||||
* @property {Long} id
|
* @property {Long} id
|
||||||
* @property {ChannelType} type
|
* @property {ChannelType} type
|
||||||
* @property {?Long} link_id
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@ -82,7 +80,6 @@ class PermError extends ProtocolError {
|
|||||||
/**
|
/**
|
||||||
* @param {?OpenChannelUserPerm[]} permNeeded
|
* @param {?OpenChannelUserPerm[]} permNeeded
|
||||||
* @param {?OpenChannelUserPerm} permActual
|
* @param {?OpenChannelUserPerm} permActual
|
||||||
* @param {string} action
|
|
||||||
*/
|
*/
|
||||||
constructor(permNeeded, permActual, action) {
|
constructor(permNeeded, permActual, action) {
|
||||||
const who =
|
const who =
|
||||||
@ -100,7 +97,6 @@ class PermError extends ProtocolError {
|
|||||||
class UserClient {
|
class UserClient {
|
||||||
static #initializing = false
|
static #initializing = false
|
||||||
|
|
||||||
#connected = false
|
|
||||||
#talkClient = new TalkClient()
|
#talkClient = new TalkClient()
|
||||||
get talkClient() { return this.#talkClient }
|
get talkClient() { return this.#talkClient }
|
||||||
|
|
||||||
@ -299,10 +295,8 @@ class UserClient {
|
|||||||
this.write("channel_meta_change", {
|
this.write("channel_meta_change", {
|
||||||
info: {
|
info: {
|
||||||
name: data.ol?.ln,
|
name: data.ol?.ln,
|
||||||
description: data.ol?.desc,
|
description: data.ol?.desc || null,
|
||||||
photoURL: data.ol?.liu || null,
|
photoURL: data.ol?.liu || null,
|
||||||
linkId: data.ol?.linkId,
|
|
||||||
linkURL: data.ol?.linkURL,
|
|
||||||
},
|
},
|
||||||
channelId: channel.channelId,
|
channelId: channel.channelId,
|
||||||
channelType: channel.info.type,
|
channelType: channel.info.type,
|
||||||
@ -370,43 +364,20 @@ class UserClient {
|
|||||||
* @param {ChannelProps} channelProps
|
* @param {ChannelProps} channelProps
|
||||||
*/
|
*/
|
||||||
async getChannel(channelProps) {
|
async getChannel(channelProps) {
|
||||||
const talkChannel = this.#talkClient.channelList.get(channelProps.id)
|
let channel = this.#talkClient.channelList.get(channelProps.id)
|
||||||
if (talkChannel) {
|
if (channel) {
|
||||||
return talkChannel
|
return channel
|
||||||
|
} else {
|
||||||
|
const channelList = getChannelListForType(
|
||||||
|
this.#talkClient.channelList,
|
||||||
|
channelProps.type
|
||||||
|
)
|
||||||
|
const res = await channelList.addChannel({ channelId: channelProps.id })
|
||||||
|
if (!res.success) {
|
||||||
|
throw new Error(`Unable to add ${channelProps.type} channel ${channelProps.id}`)
|
||||||
|
}
|
||||||
|
return res.result
|
||||||
}
|
}
|
||||||
|
|
||||||
const channelList = getChannelListForType(
|
|
||||||
this.#talkClient.channelList,
|
|
||||||
channelProps.type
|
|
||||||
)
|
|
||||||
let res = await channelList.addChannel({ channelId: channelProps.id })
|
|
||||||
if (!res.success && channelProps.link_id) {
|
|
||||||
res = await this.#talkClient.channelList.open.joinChannel({ linkId: channelProps.link_id }, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!res.success) {
|
|
||||||
this.error(`Unable to add ${channelProps.type} channel ${channelProps.id}`)
|
|
||||||
throw res
|
|
||||||
}
|
|
||||||
return res.result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Long} channelId
|
|
||||||
*/
|
|
||||||
async getNormalChannel(channelId) {
|
|
||||||
const channelList = this.#talkClient.channelList.normal
|
|
||||||
const talkChannel = channelList.get(channelId)
|
|
||||||
if (talkChannel) {
|
|
||||||
return talkChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await channelList.addChannel({ channelId: channelId })
|
|
||||||
if (!res.success) {
|
|
||||||
this.error(`Unable to add normal channel ${channelProps.id}`)
|
|
||||||
throw res
|
|
||||||
}
|
|
||||||
return res.result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -418,26 +389,19 @@ class UserClient {
|
|||||||
if (credential && this.#credential != credential) {
|
if (credential && this.#credential != credential) {
|
||||||
await this.setCredential(credential)
|
await this.setCredential(credential)
|
||||||
}
|
}
|
||||||
const res = await this.#talkClient.login(this.#credential)
|
return await this.#talkClient.login(this.#credential)
|
||||||
this.#connected = res.success
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
if (this.isConnected()) {
|
if (this.#talkClient.logon) {
|
||||||
this.#talkClient.close()
|
this.#talkClient.close()
|
||||||
}
|
}
|
||||||
this.#connected = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isConnected() {
|
isConnected() {
|
||||||
return this.#talkClient?.logon || false
|
return this.#talkClient?.logon || false
|
||||||
}
|
}
|
||||||
|
|
||||||
isUnexpectedlyDisconnected() {
|
|
||||||
return this.#connected && !this.isConnected()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a user-specific command with (optional) data to the socket.
|
* Send a user-specific command with (optional) data to the socket.
|
||||||
*
|
*
|
||||||
@ -461,9 +425,6 @@ export default class PeerClient {
|
|||||||
*/
|
*/
|
||||||
constructor(manager, socket, connID) {
|
constructor(manager, socket, connID) {
|
||||||
this.manager = manager
|
this.manager = manager
|
||||||
this.registerTimeout = manager.registerTimeout
|
|
||||||
this.loggingKeys = manager.loggingKeys
|
|
||||||
|
|
||||||
this.socket = socket
|
this.socket = socket
|
||||||
this.connID = connID
|
this.connID = connID
|
||||||
|
|
||||||
@ -472,6 +433,8 @@ export default class PeerClient {
|
|||||||
this.maxCommandID = 0
|
this.maxCommandID = 0
|
||||||
this.peerID = ""
|
this.peerID = ""
|
||||||
this.deviceName = "KakaoTalk Bridge"
|
this.deviceName = "KakaoTalk Bridge"
|
||||||
|
/** @type {[string]} */
|
||||||
|
this.loggingKeys = []
|
||||||
/** @type {Map<string, UserClient>} */
|
/** @type {Map<string, UserClient>} */
|
||||||
this.userClients = new Map()
|
this.userClients = new Map()
|
||||||
}
|
}
|
||||||
@ -502,10 +465,10 @@ export default class PeerClient {
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!this.peerID && !this.stopped) {
|
if (!this.peerID && !this.stopped) {
|
||||||
this.log(`Didn't receive register request within ${this.registerTimeout/1000} seconds, terminating`)
|
this.log(`Didn't receive register request within ${this.manager.registerTimeout/1000} seconds, terminating`)
|
||||||
this.stop("Register request timeout")
|
this.stop("Register request timeout")
|
||||||
}
|
}
|
||||||
}, this.registerTimeout)
|
}, this.manager.registerTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
async stop(error = null) {
|
async stop(error = null) {
|
||||||
@ -551,10 +514,10 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.passcode
|
* @param {string} req.passcode
|
||||||
* @param {string} req.uuid
|
* @param {string} req.uuid
|
||||||
* @param {object} req.form
|
* @param {Object} req.form
|
||||||
*/
|
*/
|
||||||
registerDevice = async (req) => {
|
registerDevice = async (req) => {
|
||||||
// TODO Look for a deregister API call
|
// TODO Look for a deregister API call
|
||||||
@ -564,9 +527,9 @@ export default class PeerClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain login tokens. If this fails due to not having a device, also request a device passcode.
|
* Obtain login tokens. If this fails due to not having a device, also request a device passcode.
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.uuid
|
* @param {string} req.uuid
|
||||||
* @param {object} req.form
|
* @param {Object} req.form
|
||||||
* @param {boolean} req.forced
|
* @param {boolean} req.forced
|
||||||
* @returns The response of the login attempt, including obtained
|
* @returns The response of the login attempt, including obtained
|
||||||
* credentials for subsequent token-based login. If a required device passcode
|
* credentials for subsequent token-based login. If a required device passcode
|
||||||
@ -620,41 +583,23 @@ export default class PeerClient {
|
|||||||
* @param {string} mxid
|
* @param {string} mxid
|
||||||
* @param {ChannelProps} channelProps
|
* @param {ChannelProps} channelProps
|
||||||
* @param {?OpenChannelUserPerm[]} permNeeded If set, throw if the user's permission level matches none of the values in this list.
|
* @param {?OpenChannelUserPerm[]} permNeeded If set, throw if the user's permission level matches none of the values in this list.
|
||||||
* @param {?string} action The action requiring permission, to be used in an error message if throwing.
|
* @param {?string} action The action requiring permission, to be used in an error message if throwing..
|
||||||
* @throws {PermError} if the user does not have the specified permission level.
|
* @throws {PermError} if the user does not have the specified permission level.
|
||||||
*/
|
*/
|
||||||
async #getUserChannel(mxid, channelProps, permNeeded, action) {
|
async #getUserChannel(mxid, channelProps, permNeeded, action) {
|
||||||
const userClient = this.#getUser(mxid)
|
const userClient = this.#getUser(mxid)
|
||||||
const talkChannel = await userClient.getChannel(channelProps)
|
const talkChannel = await userClient.getChannel(channelProps)
|
||||||
if (permNeeded) {
|
if (permNeeded) {
|
||||||
await this.#requireChannelPerm(talkChannel, permNeeded, action)
|
const permActual = talkChannel.getUserInfo({ userId: userClient.userId }).perm
|
||||||
|
if (permNeeded.indexOf(permActual) == -1) {
|
||||||
|
throw new PermError(permNeeded, permActual, action)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return talkChannel
|
return talkChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {TalkOpenChannel} talkChannel
|
* @param {Object} req
|
||||||
* @param {OpenChannelUserPerm[]} permNeeded Throw if the user's permission level matches none of the values in this list.
|
|
||||||
* @param {string} action The action requiring permission
|
|
||||||
* @throws {PermError} if the user does not have the specified permission level.
|
|
||||||
*/
|
|
||||||
async #requireChannelPerm(talkChannel, permNeeded, action) {
|
|
||||||
const permActual = talkChannel.getUserInfo({ userId: talkChannel.clientUser.userId }).perm
|
|
||||||
if (permNeeded.indexOf(permActual) == -1) {
|
|
||||||
throw new PermError(permNeeded, permActual, action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} mxid
|
|
||||||
* @param {Long} channelId
|
|
||||||
*/
|
|
||||||
async #getUserNormalChannel(mxid, channelId) {
|
|
||||||
return await this.#getUser(mxid).getNormalChannel(channelId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} req
|
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {OAuthCredential} req.oauth_credential
|
* @param {OAuthCredential} req.oauth_credential
|
||||||
*/
|
*/
|
||||||
@ -669,7 +614,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {OAuthCredential} req.oauth_credential
|
* @param {OAuthCredential} req.oauth_credential
|
||||||
*/
|
*/
|
||||||
@ -688,7 +633,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
*/
|
*/
|
||||||
userStop = async (req) => {
|
userStop = async (req) => {
|
||||||
@ -697,7 +642,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {?OAuthCredential} req.oauth_credential
|
* @param {?OAuthCredential} req.oauth_credential
|
||||||
*/
|
*/
|
||||||
@ -706,7 +651,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
*/
|
*/
|
||||||
handleDisconnect = (req) => {
|
handleDisconnect = (req) => {
|
||||||
@ -714,7 +659,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
*/
|
*/
|
||||||
isConnected = (req) => {
|
isConnected = (req) => {
|
||||||
@ -722,7 +667,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
*/
|
*/
|
||||||
getSettings = async (req) => {
|
getSettings = async (req) => {
|
||||||
@ -746,7 +691,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
*/
|
*/
|
||||||
getOwnProfile = async (req) => {
|
getOwnProfile = async (req) => {
|
||||||
@ -754,7 +699,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {Long} req.user_id
|
* @param {Long} req.user_id
|
||||||
*/
|
*/
|
||||||
@ -763,7 +708,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
*/
|
*/
|
||||||
@ -773,16 +718,11 @@ export default class PeerClient {
|
|||||||
const res = await talkChannel.updateAll()
|
const res = await talkChannel.updateAll()
|
||||||
if (!res.success) return res
|
if (!res.success) return res
|
||||||
|
|
||||||
/** @type {?OpenLink} */
|
|
||||||
const openLink = talkChannel.info.openLink
|
|
||||||
|
|
||||||
return makeCommandResult({
|
return makeCommandResult({
|
||||||
name: talkChannel.getDisplayName(),
|
name: talkChannel.getDisplayName(),
|
||||||
description: openLink?.description,
|
description: talkChannel.info.openLink?.description,
|
||||||
// TODO Find out why linkCoverURL is blank, despite having updated the channel!
|
// TODO Find out why linkCoverURL is blank, despite having updated the channel!
|
||||||
photoURL: openLink?.linkCoverURL || null,
|
photoURL: talkChannel.info.openLink?.linkCoverURL || null,
|
||||||
linkId: openLink?.linkId,
|
|
||||||
linkURL: openLink?.linkURL,
|
|
||||||
participantInfo: {
|
participantInfo: {
|
||||||
// TODO Get members from chatON?
|
// TODO Get members from chatON?
|
||||||
participants: Array.from(talkChannel.getAllUserInfo()),
|
participants: Array.from(talkChannel.getAllUserInfo()),
|
||||||
@ -792,7 +732,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
*/
|
*/
|
||||||
@ -826,7 +766,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
*/
|
*/
|
||||||
@ -836,7 +776,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
* @param {?Long} req.sync_from
|
* @param {?Long} req.sync_from
|
||||||
@ -863,7 +803,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
* @param {[Long]} req.unread_chat_ids Must be in DECREASING order
|
* @param {[Long]} req.unread_chat_ids Must be in DECREASING order
|
||||||
@ -893,7 +833,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} Receipt
|
* @typedef {Object} Receipt
|
||||||
* @property {Long} userId
|
* @property {Long} userId
|
||||||
* @property {Long} chatId
|
* @property {Long} chatId
|
||||||
*/
|
*/
|
||||||
@ -904,7 +844,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {string} req.uuid
|
* @param {string} req.uuid
|
||||||
*/
|
*/
|
||||||
@ -913,7 +853,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {string} req.uuid
|
* @param {string} req.uuid
|
||||||
*/
|
*/
|
||||||
@ -927,7 +867,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {boolean} req.searchable
|
* @param {boolean} req.searchable
|
||||||
*/
|
*/
|
||||||
@ -949,7 +889,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
*/
|
*/
|
||||||
listFriends = async (req) => {
|
listFriends = async (req) => {
|
||||||
@ -957,7 +897,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {Long} req.user_id
|
* @param {Long} req.user_id
|
||||||
* @param {boolean} req.add
|
* @param {boolean} req.add
|
||||||
@ -971,7 +911,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {string} req.uuid
|
* @param {string} req.uuid
|
||||||
* @param {boolean} req.add
|
* @param {boolean} req.add
|
||||||
@ -1008,7 +948,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid The user whose friend is being looked up.
|
* @param {string} req.mxid The user whose friend is being looked up.
|
||||||
* @param {string} req.friend_id The friend to search for.
|
* @param {string} req.friend_id The friend to search for.
|
||||||
* @param {string} propertyName The property to retrieve from the specified friend.
|
* @param {string} propertyName The property to retrieve from the specified friend.
|
||||||
@ -1021,7 +961,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
*/
|
*/
|
||||||
getMemoIds = (req) => {
|
getMemoIds = (req) => {
|
||||||
@ -1038,7 +978,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
* @param {string} req.key
|
* @param {string} req.key
|
||||||
@ -1053,7 +993,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
* @param {string} req.text
|
* @param {string} req.text
|
||||||
@ -1072,7 +1012,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
* @param {int} req.type
|
* @param {int} req.type
|
||||||
@ -1096,7 +1036,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
* @param {Long} req.chat_id
|
* @param {Long} req.chat_id
|
||||||
@ -1110,7 +1050,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
* @param {Long} req.read_until_chat_id
|
* @param {Long} req.read_until_chat_id
|
||||||
@ -1124,7 +1064,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
* @param {Long} req.user_id
|
* @param {Long} req.user_id
|
||||||
@ -1148,59 +1088,40 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
* @param {string} req.name
|
* @param {string} req.name
|
||||||
*/
|
*/
|
||||||
setChannelName = async (req) => {
|
setChannelName = async (req) => {
|
||||||
if (!isChannelTypeOpen(req.channel_props.type)) {
|
const talkChannel = await this.#getUserChannel(
|
||||||
const talkChannel = await this.#getUserNormalChannel(req.mxid, req.channel_props.id)
|
req.mxid,
|
||||||
return await talkChannel.setTitleMeta(req.name)
|
req.channel_props,
|
||||||
} else {
|
[OpenChannelUserPerm.OWNER],
|
||||||
return await this.#setOpenChannelProperty(req.mxid, req.channel_props, "linkName", req.name)
|
"change channel name"
|
||||||
}
|
)
|
||||||
|
return await talkChannel.setTitleMeta(req.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
* @param {string} req.description
|
* @param {string} req.description
|
||||||
*/
|
*/
|
||||||
setChannelDescription = async (req) => {
|
setChannelDescription = async (req) => {
|
||||||
return await this.#setOpenChannelProperty(req.mxid, req.channel_props, "description", req.description)
|
const talkChannel = await this.#getUserChannel(
|
||||||
}
|
req.mxid,
|
||||||
|
req.channel_props,
|
||||||
/**
|
[OpenChannelUserPerm.OWNER],
|
||||||
* @param {string} mxid
|
"change channel description"
|
||||||
* @param {ChannelProps} channelProps
|
|
||||||
* @param {string} propertyName
|
|
||||||
* @param {any} propertyValue
|
|
||||||
*/
|
|
||||||
async #setOpenChannelProperty(mxid, channelProps, propertyName, propertyValue) {
|
|
||||||
if (isChannelTypeOpen(channelProps)) {
|
|
||||||
throw ProtocolError(`Cannot set ${propertyName} of non-open channel ${channelProps.id} (type = ${channelProps.type})`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const userClient = this.#getUser(mxid)
|
|
||||||
/** @type {TalkOpenChannel} */
|
|
||||||
const talkChannel = await userClient.getChannel(channelProps)
|
|
||||||
this.#requireChannelPerm(talkChannel, [OpenChannelUserPerm.OWNER], `change channel ${propertyName}`)
|
|
||||||
|
|
||||||
const linkRes = await talkChannel.getLatestOpenLink()
|
|
||||||
if (!linkRes.success) throw linkRes
|
|
||||||
|
|
||||||
const link = linkRes.result
|
|
||||||
link[propertyName] = propertyValue
|
|
||||||
return await userClient.talkClient.channelList.open.updateOpenLink(
|
|
||||||
{ linkId: link.linkId }, link
|
|
||||||
)
|
)
|
||||||
|
return await talkChannel.setNoticeMeta(req.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO
|
* TODO
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
* @param {string} req.photo_url
|
* @param {string} req.photo_url
|
||||||
@ -1219,15 +1140,14 @@ export default class PeerClient {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {Long} req.user_id
|
* @param {Long} req.user_id
|
||||||
*/
|
*/
|
||||||
createDirectChat = async (req) => {
|
createDirectChat = async (req) => {
|
||||||
const userClient = this.#getUser(req.mxid)
|
const userClient = this.#getUser(req.mxid)
|
||||||
await this.#requireInFriendsList(userClient.serviceClient, req.user_id)
|
|
||||||
|
|
||||||
const channelList = userClient.talkClient.channelList.normal
|
const channelList = userClient.talkClient.channelList.normal
|
||||||
|
|
||||||
const createChannel =
|
const createChannel =
|
||||||
!req.user_id.equals(userClient.userId)
|
!req.user_id.equals(userClient.userId)
|
||||||
? channelList.createChannel.bind(channelList, {
|
? channelList.createChannel.bind(channelList, {
|
||||||
@ -1252,77 +1172,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ServiceApiClient} serviceClient
|
* @param {Object} req
|
||||||
* @param {Long} id
|
|
||||||
*/
|
|
||||||
async #requireInFriendsList(serviceClient, id) {
|
|
||||||
let listRes = await serviceClient.requestFriendList()
|
|
||||||
if (!listRes.success) {
|
|
||||||
this.error("Failed to check friends list")
|
|
||||||
throw listRes
|
|
||||||
}
|
|
||||||
const isFriend = -1 != listRes.result.friends.findIndex(friend => id.equals(friend.userId))
|
|
||||||
if (!isFriend) {
|
|
||||||
throw new ProtocolError("This user is not in your friends list")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} req
|
|
||||||
* @param {string} req.mxid
|
|
||||||
* @param {string} req.url
|
|
||||||
*/
|
|
||||||
joinChannelByURL = async (req) => {
|
|
||||||
const channelList = this.#getUser(req.mxid).talkClient.channelList.open
|
|
||||||
|
|
||||||
const inviteRes = await channelList.getJoinInfo(req.url)
|
|
||||||
if (!inviteRes.success) throw inviteRes
|
|
||||||
|
|
||||||
const channelIds = channelList.getLinkChannelList(inviteRes.result.openLink.linkId)
|
|
||||||
if (channelIds.length == 0) {
|
|
||||||
throw new ProtocolError(`No channel found for OpenLink URL ${req.url}`)
|
|
||||||
}
|
|
||||||
if (channelIds.length > 1) {
|
|
||||||
this.log(`Multiple channels found for OpenLink URL ${req.url}: ${channelIds.join(", ")}`)
|
|
||||||
}
|
|
||||||
for (const channelId of channelList.getLinkChannelList(inviteRes.result.openLink.linkId)) {
|
|
||||||
if (channelList.get(channelId)) {
|
|
||||||
this.log(`Already joined channel ${channelId}`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const joinRes = await channelList.joinChannel(inviteRes.result.openLink, {})
|
|
||||||
if (!joinRes.success) {
|
|
||||||
this.error(`Failed to join channel ${channelId} via ${inviteRes.result.openLink.linkId}`)
|
|
||||||
} else {
|
|
||||||
this.log(`Joined channel ${channelId} via ${inviteRes.result.openLink.linkId}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO Consider returning ID of each joined channel
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} req
|
|
||||||
* @param {string} req.mxid
|
|
||||||
* @param {Long} req.channel_id
|
|
||||||
* @param {Long} req.link_id
|
|
||||||
*/
|
|
||||||
joinChannel = async (req) => {
|
|
||||||
const channelList = this.#getUser(req.mxid).talkClient.channelList.open
|
|
||||||
let talkChannel = channelList.get(req.channel_id)
|
|
||||||
if (talkChannel) {
|
|
||||||
this.log(`Already joined channel ${channelId}`)
|
|
||||||
} else {
|
|
||||||
const joinRes = await channelList.joinChannel({ linkId: req.link_id }, {})
|
|
||||||
if (!joinRes.success) return joinRes
|
|
||||||
|
|
||||||
this.log(`Joined channel ${channelId} via ${req.link_id}`)
|
|
||||||
talkChannel = joinRes.result
|
|
||||||
}
|
|
||||||
return makeCommandResult(talkChannel.channelId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} req
|
|
||||||
* @param {string} req.mxid
|
* @param {string} req.mxid
|
||||||
* @param {ChannelProps} req.channel_props
|
* @param {ChannelProps} req.channel_props
|
||||||
*/
|
*/
|
||||||
@ -1340,14 +1190,16 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} req
|
* @param {Object} req
|
||||||
* @param {string} req.peer_id
|
* @param {string} req.peer_id
|
||||||
* @param {object} req.register_config
|
* @param {Object} req.register_config
|
||||||
* @param {string} req.register_config.device_name
|
* @param {string} req.register_config.device_name
|
||||||
|
* @param {?[string]} req.logging_keys
|
||||||
*/
|
*/
|
||||||
handleRegister = async (req) => {
|
handleRegister = async (req) => {
|
||||||
this.peerID = req.peer_id
|
this.peerID = req.peer_id
|
||||||
this.deviceName = req.register_config.device_name || this.deviceName
|
this.deviceName = req.register_config.device_name || this.deviceName
|
||||||
|
this.loggingKeys = req.logging_keys || this.loggingKeys
|
||||||
this.log(`Registered socket ${this.connID} -> ${this.peerID}`)
|
this.log(`Registered socket ${this.connID} -> ${this.peerID}`)
|
||||||
if (this.manager.clients.has(this.peerID)) {
|
if (this.manager.clients.has(this.peerID)) {
|
||||||
const oldClient = this.manager.clients.get(this.peerID)
|
const oldClient = this.manager.clients.get(this.peerID)
|
||||||
@ -1378,7 +1230,12 @@ export default class PeerClient {
|
|||||||
this.log("Ignoring old request", req.id)
|
this.log("Ignoring old request", req.id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.log(`Request ${req.id}:`, this.#logObj(req, req.command, this.loggingKeys.request))
|
this.log(
|
||||||
|
`Request ${req.id}:`,
|
||||||
|
[req.command].concat(
|
||||||
|
this.loggingKeys.filter(k => k in req).map(k => `${k}: ${JSON.stringify(req[k], this.#writeReplacer)}`))
|
||||||
|
.join(', ')
|
||||||
|
)
|
||||||
this.maxCommandID = req.id
|
this.maxCommandID = req.id
|
||||||
let handler
|
let handler
|
||||||
if (!this.peerID) {
|
if (!this.peerID) {
|
||||||
@ -1430,8 +1287,6 @@ export default class PeerClient {
|
|||||||
set_channel_description: this.setChannelDescription,
|
set_channel_description: this.setChannelDescription,
|
||||||
//set_channel_photo: this.setChannelPhoto,
|
//set_channel_photo: this.setChannelPhoto,
|
||||||
create_direct_chat: this.createDirectChat,
|
create_direct_chat: this.createDirectChat,
|
||||||
join_channel_by_url: this.joinChannelByURL,
|
|
||||||
join_channel: this.joinChannel,
|
|
||||||
leave_channel: this.leaveChannel,
|
leave_channel: this.leaveChannel,
|
||||||
}[req.command] || this.handleUnknownCommand
|
}[req.command] || this.handleUnknownCommand
|
||||||
}
|
}
|
||||||
@ -1447,59 +1302,21 @@ export default class PeerClient {
|
|||||||
success: false,
|
success: false,
|
||||||
status: err.response.status,
|
status: err.response.status,
|
||||||
}
|
}
|
||||||
} else if ("status" in err) {
|
|
||||||
resp.response = err
|
|
||||||
} else {
|
} else {
|
||||||
resp.command = "error"
|
resp.command = "error"
|
||||||
let errorDetails
|
|
||||||
if (err instanceof ProtocolError) {
|
if (err instanceof ProtocolError) {
|
||||||
resp.error = err.message
|
resp.error = err.message
|
||||||
errorDetails = err.message
|
|
||||||
} else if (err instanceof Error) {
|
|
||||||
resp.error = err.toString()
|
|
||||||
errorDetails = err.stack
|
|
||||||
} else {
|
} else {
|
||||||
resp.error = JSON.stringify(err)
|
resp.error = err.toString()
|
||||||
errorDetails = `throwed ${resp.error}`
|
this.log(`Error handling request ${resp.id} ${err.stack}`)
|
||||||
}
|
}
|
||||||
this.error(`Response ${resp.id}: ${errorDetails}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (resp.response) {
|
// TODO Check if session is broken. If it is, close the PeerClient
|
||||||
const success = resp.response.success !== false
|
|
||||||
const logger = (success ? this.log : this.error).bind(this)
|
|
||||||
logger(
|
|
||||||
`Response ${resp.id}:`,
|
|
||||||
this.#logObj(
|
|
||||||
resp.response instanceof Object ? resp.response : {value: resp.response},
|
|
||||||
success ? "success" : "failure",
|
|
||||||
this.loggingKeys.response
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
await this.write(resp)
|
await this.write(resp)
|
||||||
if ("mxid" in req) {
|
|
||||||
const userClient = this.#tryGetUser(req.mxid)
|
|
||||||
if (userClient && userClient.isUnexpectedlyDisconnected()) {
|
|
||||||
this.error("Unexpected disconnect for user", req.mxid)
|
|
||||||
this.userClients.delete(req.mxid)
|
|
||||||
await userClient.write("unexpected_disconnect")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#writeReplacer = function(key, value) {
|
||||||
* @param {object} obj
|
|
||||||
* @param {string} desc
|
|
||||||
* @param {[string]} keys
|
|
||||||
*/
|
|
||||||
#logObj(obj, desc, keys) {
|
|
||||||
return [desc].concat(
|
|
||||||
keys.filter(key => key in obj).map(key => `${key}: ${JSON.stringify(obj[key], this.#writeReplacer)}`)
|
|
||||||
).join(', ')
|
|
||||||
}
|
|
||||||
|
|
||||||
#writeReplacer(key, value) {
|
|
||||||
if (value instanceof Long) {
|
if (value instanceof Long) {
|
||||||
return value.toString()
|
return value.toString()
|
||||||
} else {
|
} else {
|
||||||
@ -1507,7 +1324,7 @@ export default class PeerClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#readReviver(key, value) {
|
#readReviver = function(key, value) {
|
||||||
if (value instanceof Object) {
|
if (value instanceof Object) {
|
||||||
// TODO Use a type map if there will be many possible types
|
// TODO Use a type map if there will be many possible types
|
||||||
if (value.__type__ == "Long") {
|
if (value.__type__ == "Long") {
|
||||||
|
@ -20,33 +20,11 @@ import path from "path"
|
|||||||
import PeerClient from "./client.js"
|
import PeerClient from "./client.js"
|
||||||
import { promisify } from "./util.js"
|
import { promisify } from "./util.js"
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} ListenConfig
|
|
||||||
* @property {string} type
|
|
||||||
* @property {string} path
|
|
||||||
* @property {boolean} force
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} LoggingKeys
|
|
||||||
* @property {[string]} request
|
|
||||||
* @property {[string]} response
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
export default class ClientManager {
|
export default class ClientManager {
|
||||||
/**
|
constructor(listenConfig, registerTimeout) {
|
||||||
* @param {ListenConfig} listenConfig
|
|
||||||
* @param {number} registerTimeout
|
|
||||||
* @param {?LoggingKeys} loggingKeys
|
|
||||||
*/
|
|
||||||
constructor(listenConfig, registerTimeout, loggingKeys) {
|
|
||||||
if (!listenConfig) {
|
|
||||||
throw new Error("Listen config missing")
|
|
||||||
}
|
|
||||||
this.listenConfig = listenConfig
|
this.listenConfig = listenConfig
|
||||||
this.registerTimeout = registerTimeout || 3000
|
this.registerTimeout = registerTimeout || 3000
|
||||||
this.loggingKeys = loggingKeys || {request: [], response: []}
|
|
||||||
this.server = net.createServer(this.acceptConnection)
|
this.server = net.createServer(this.acceptConnection)
|
||||||
this.connections = []
|
this.connections = []
|
||||||
this.clients = new Map()
|
this.clients = new Map()
|
||||||
@ -65,7 +43,7 @@ export default class ClientManager {
|
|||||||
} else {
|
} else {
|
||||||
const connID = this.connIDSequence++
|
const connID = this.connIDSequence++
|
||||||
this.connections[connID] = sock
|
this.connections[connID] = sock
|
||||||
new PeerClient(this, sock, connID, this.loggingKeys).start()
|
new PeerClient(this, sock, connID).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,11 +32,7 @@ const configPath = args["--config"] || "config.json"
|
|||||||
console.log("[Main] Reading config from", configPath)
|
console.log("[Main] Reading config from", configPath)
|
||||||
const config = JSON.parse(fs.readFileSync(configPath).toString())
|
const config = JSON.parse(fs.readFileSync(configPath).toString())
|
||||||
|
|
||||||
const manager = new ClientManager(
|
const manager = new ClientManager(config.listen, config.register_timeout)
|
||||||
config.listen,
|
|
||||||
config.register_timeout,
|
|
||||||
config.logging_keys
|
|
||||||
)
|
|
||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
manager.stop().then(() => {
|
manager.stop().then(() => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user