diff --git a/matrix_appservice_kakaotalk/commands/kakaotalk.py b/matrix_appservice_kakaotalk/commands/kakaotalk.py index 3f149d4..5e02ab3 100644 --- a/matrix_appservice_kakaotalk/commands/kakaotalk.py +++ b/matrix_appservice_kakaotalk/commands/kakaotalk.py @@ -28,6 +28,7 @@ from .typehint import CommandEvent from ..kt.client.errors import CommandException SECTION_FRIENDS = HelpSection("Friends management", 40, "") +SECTION_CHANNELS = HelpSection("Channel management", 45, "") if TYPE_CHECKING: from ..kt.types.api.struct import FriendStruct @@ -67,3 +68,21 @@ async def list_friends(evt: CommandEvent) -> None: await evt.reply(f"{results}") else: await evt.reply("No friends found.") + + +@command_handler( + needs_auth=True, + management_only=False, + help_section=SECTION_CHANNELS, + help_text="Leave this KakaoTalk channel", +) +async def leave(evt: CommandEvent) -> None: + if not evt.sender.is_connected: + await evt.reply("You are not connected to KakaoTalk chats") + return + if not evt.is_portal: + await evt.reply("This command may only be used in a KakaoTalk channel portal room") + return + await evt.mark_read() + await evt.sender.client.leave_channel(evt.portal.channel_props) + await evt.sender.on_channel_left(evt.portal.ktid, evt.portal.kt_type) diff --git a/matrix_appservice_kakaotalk/kt/client/client.py b/matrix_appservice_kakaotalk/kt/client/client.py index ff9ce1a..ee5c446 100644 --- a/matrix_appservice_kakaotalk/kt/client/client.py +++ b/matrix_appservice_kakaotalk/kt/client/client.py @@ -450,6 +450,15 @@ class Client: photo_url=photo_url, ) + def leave_channel( + self, + channel_props: ChannelProps, + ) -> Awaitable[None]: + return self._api_user_request_void( + "leave_channel", + channel_props=channel_props.serialize(), + ) + # TODO Combine each of these pairs into one diff --git a/matrix_appservice_kakaotalk/portal.py b/matrix_appservice_kakaotalk/portal.py index 075c562..40d2c99 100644 --- a/matrix_appservice_kakaotalk/portal.py +++ b/matrix_appservice_kakaotalk/portal.py @@ -1750,11 +1750,13 @@ class Portal(DBPortal, BasePortal): ) -> None: sender_intent = sender.intent_for(self) if sender else self.main_intent if sender == removed: - await removed.intent_for(self).leave_room(self.mxid) + removed_intent = removed.intent_for(self) + if removed_intent != self.main_intent: + await removed_intent.leave_room(self.mxid) if not removed.is_real_user: user = await u.User.get_by_ktid(removed.ktid) if user: - await sender_intent.kick_user(self.mxid, user.mxid, "Left channel from KakaoTalk") + await self.main_intent.kick_user(self.mxid, user.mxid, "Left channel from KakaoTalk") else: for removed_mxid in (r.mxid for r in ( removed, @@ -1770,6 +1772,7 @@ class Portal(DBPortal, BasePortal): await self.main_intent.ban_user( self.mxid, removed_mxid, reason=f"Kicked by {sender.name}" ) + # TODO Clean and delete if removed is real user and portal is direct / not open # TODO Find when or if there is a listener for this # TODO Confirm whether this can refer to any user that was kicked, or only to the current user diff --git a/node/src/client.js b/node/src/client.js index 7e1b6e0..36b200f 100644 --- a/node/src/client.js +++ b/node/src/client.js @@ -998,6 +998,20 @@ export default class PeerClient { }) } + /** + * @param {Object} req + * @param {string} req.mxid + * @param {ChannelProps} req.channel_props + */ + leaveChannel = async (req) => { + const userClient = this.#getUser(req.mxid) + const channelList = getChannelListForType( + userClient.talkClient.channelList, + req.channel_props.type + ) + return channelList.leaveChannel({ channelId: req.channel_props.id }) + } + handleUnknownCommand = () => { throw new Error("Unknown command") } @@ -1086,6 +1100,7 @@ export default class PeerClient { set_channel_name: this.setChannelName, set_channel_description: this.setChannelDescription, set_channel_photo: this.setChannelPhoto, + leave_channel: this.leaveChannel, }[req.command] || this.handleUnknownCommand } const resp = { id: req.id }