diff --git a/matrix_appservice_kakaotalk/kt/client/client.py b/matrix_appservice_kakaotalk/kt/client/client.py index c404bce..17b7e2b 100644 --- a/matrix_appservice_kakaotalk/kt/client/client.py +++ b/matrix_appservice_kakaotalk/kt/client/client.py @@ -49,7 +49,7 @@ from ..types.request import ( CommandResultDoneValue ) -from .types import PortalChannelInfo, UserInfoUnion +from .types import PortalChannelInfo, UserInfoUnion, ChannelProps from .errors import InvalidAccessToken from .error_helper import raise_unsuccessful_response @@ -207,34 +207,34 @@ class Client: ) return profile_req_struct.profile - async def get_portal_channel_info(self, channel_id: Long) -> PortalChannelInfo: + async def get_portal_channel_info(self, channel_props: ChannelProps) -> PortalChannelInfo: return await self._api_user_request_result( PortalChannelInfo, "get_portal_channel_info", - channel_id=channel_id.serialize() + channel_props=channel_props.serialize(), ) - async def get_participants(self, channel_id: Long) -> list[UserInfoUnion]: + async def get_participants(self, channel_props: ChannelProps) -> list[UserInfoUnion]: return await self._api_user_request_result( ResultListType(UserInfoUnion), "get_participants", - channel_id=channel_id.serialize() + channel_props=channel_props.serialize() ) - async def get_chats(self, channel_id: Long, sync_from: Long | None, limit: int | None) -> list[Chatlog]: + async def get_chats(self, channel_props: ChannelProps, sync_from: Long | None, limit: int | None) -> list[Chatlog]: return await self._api_user_request_result( ResultListType(Chatlog), "get_chats", - channel_id=channel_id.serialize(), + channel_props=channel_props.serialize(), sync_from=sync_from.serialize() if sync_from else None, limit=limit ) - async def send_message(self, channel_id: Long, text: str) -> Chatlog: + async def send_message(self, channel_props: ChannelProps, text: str) -> Chatlog: return await self._api_user_request_result( Chatlog, "send_message", - channel_id=channel_id.serialize(), + channel_props=channel_props.serialize(), text=text ) diff --git a/matrix_appservice_kakaotalk/kt/client/types.py b/matrix_appservice_kakaotalk/kt/client/types.py index f44b9e8..aa8d0b1 100644 --- a/matrix_appservice_kakaotalk/kt/client/types.py +++ b/matrix_appservice_kakaotalk/kt/client/types.py @@ -21,7 +21,9 @@ from attr import dataclass from mautrix.types import SerializableAttrs, JSON, deserializer +from ..types.bson import Long from ..types.channel.channel_info import NormalChannelInfo +from ..types.channel.channel_type import ChannelType from ..types.openlink.open_channel_info import OpenChannelInfo from ..types.user.channel_user_info import NormalChannelUserInfo, OpenChannelUserInfo @@ -56,3 +58,9 @@ class PortalChannelInfo(SerializableAttrs): participants: list[UserInfoUnion] # TODO Image channel_info: Optional[ChannelInfoUnion] = None # Should be set manually by caller + + +@dataclass +class ChannelProps(SerializableAttrs): + id: Long + type: ChannelType diff --git a/matrix_appservice_kakaotalk/portal.py b/matrix_appservice_kakaotalk/portal.py index bbb928a..2b4bb94 100644 --- a/matrix_appservice_kakaotalk/portal.py +++ b/matrix_appservice_kakaotalk/portal.py @@ -52,7 +52,7 @@ from .kt.types.channel.channel_info import ChannelInfo from .kt.types.channel.channel_type import KnownChannelType, ChannelType from .kt.types.chat.chat import Chatlog -from .kt.client.types import UserInfoUnion, PortalChannelInfo +from .kt.client.types import UserInfoUnion, PortalChannelInfo, ChannelProps from .kt.client.errors import CommandException if TYPE_CHECKING: @@ -194,6 +194,13 @@ class Portal(DBPortal, BasePortal): raise ValueError(f"Non-direct chat portal should have no sender, but has sender {self._kt_sender}") return self._kt_sender + @property + def channel_props(self) -> ChannelProps: + return ChannelProps( + id=self.ktid, + type=self.kt_type + ) + @property def main_intent(self) -> IntentAPI: if not self._main_intent: @@ -245,7 +252,7 @@ class Portal(DBPortal, BasePortal): ) -> PortalChannelInfo: if not info: self.log.debug("Called update_info with no info, fetching channel info...") - info = await source.client.get_portal_channel_info(self.ktid) + info = await source.client.get_portal_channel_info(self.channel_props) changed = False if not self.is_direct: changed = any( @@ -400,7 +407,7 @@ class Portal(DBPortal, BasePortal): async def _update_participants(self, source: u.User, participants: list[UserInfoUnion] | None = None) -> bool: if participants is None: self.log.debug("Called _update_participants with no participants, fetching them now...") - participants = await source.client.get_participants(self.ktid) + participants = await source.client.get_participants(self.channel_props) changed = False if not self._main_intent: assert self.is_direct, "_main_intent for non-direct chat portal should have been set already" @@ -736,7 +743,7 @@ class Portal(DBPortal, BasePortal): converted = await matrix_to_kakaotalk(message, self.mxid, self.log) try: chatlog = await sender.client.send_message( - self.ktid, + self.channel_props, text=converted.text, # TODO #mentions=converted.mentions, @@ -959,7 +966,7 @@ class Portal(DBPortal, BasePortal): self.log.debug(f"Backfilling history through {source.mxid}") self.log.debug(f"Fetching {f'up to {limit}' if limit else 'all'} messages through {source.ktid}") messages = await source.client.get_chats( - channel_info.channelId, + self.channel_props, after_log_id, limit ) @@ -989,7 +996,6 @@ class Portal(DBPortal, BasePortal): # TODO Save kt_sender in DB instead? Depends on if DM channels are shared... user = await u.User.get_by_ktid(self.kt_receiver) assert user, f"Found no user for this portal's receiver of {self.kt_receiver}" - # TODO Should this backfill? Useful for forgotten channels await self._update_participants(user) else: self.log.debug("Not setting _main_intent of new direct chat until after checking participant list") diff --git a/matrix_appservice_kakaotalk/user.py b/matrix_appservice_kakaotalk/user.py index d184211..feca078 100644 --- a/matrix_appservice_kakaotalk/user.py +++ b/matrix_appservice_kakaotalk/user.py @@ -499,7 +499,7 @@ class User(DBUser, BaseUser): kt_receiver=self.ktid, kt_type=channel_info.type ) - portal_info = await self.client.get_portal_channel_info(channel_info.channelId) + portal_info = await self.client.get_portal_channel_info(portal.channel_props) portal_info.channel_info = channel_info if not portal.mxid: await portal.create_matrix_room(self, portal_info) diff --git a/node/src/client.js b/node/src/client.js index efaa17a..ff300ea 100644 --- a/node/src/client.js +++ b/node/src/client.js @@ -24,6 +24,8 @@ import { util, } from "node-kakao" /** @typedef {import("node-kakao").OAuthCredential} OAuthCredential */ +/** @typedef {import("node-kakao/dist/talk").TalkChannelList} TalkChannelList */ +/** @typedef {import("node-kakao").ChannelType} ChannelType */ import chat from "node-kakao/chat" const { KnownChatType } = chat @@ -68,6 +70,30 @@ class UserClient { return userClient } + /** + * @param {Object} channel_props + * @param {Long} channel_props.id + * @param {ChannelType} channel_props.type + */ + async getChannel(channel_props) { + let channel = this.#talkClient.channelList.get(channel_props.id) + if (channel) { + return channel + } else { + const channelList = getChannelListForType( + this.#talkClient.channelList, + channel_props.type + ) + const res = await channelList.addChannel({ + channelId: channel_props.id, + }) + if (!res.success) { + throw new Error(`Unable to add ${channel_props.type} channel ${channel_props.id}`) + } + return res.result + } + } + close() { this.#talkClient.close() } @@ -337,11 +363,11 @@ export default class PeerClient { /** * @param {Object} req * @param {string} req.mxid - * @param {Long} req.channel_id + * @param {Object} req.channel_props */ getPortalChannelInfo = async (req) => { const userClient = this.#getUser(req.mxid) - const talkChannel = userClient.talkClient.channelList.get(req.channel_id) + const talkChannel = await userClient.getChannel(req.channel_props) const res = await talkChannel.updateAll() if (!res.success) return res @@ -356,24 +382,24 @@ export default class PeerClient { /** * @param {Object} req * @param {string} req.mxid - * @param {Long} req.channel_id + * @param {Object} req.channel_props */ getParticipants = async (req) => { const userClient = this.#getUser(req.mxid) - const talkChannel = userClient.getChannel(req.channel_id) + const talkChannel = await userClient.getChannel(req.channel_props) return await talkChannel.getAllLatestUserInfo() } /** * @param {Object} req * @param {string} req.mxid - * @param {Long} req.channel_id + * @param {Object} req.channel_props * @param {Long?} req.sync_from * @param {Number?} req.limit */ getChats = async (req) => { const userClient = this.#getUser(req.mxid) - const talkChannel = userClient.talkClient.channelList.get(req.channel_id) + const talkChannel = await userClient.getChannel(req.channel_props) const res = await talkChannel.getChatListFrom(req.sync_from) if (res.success && 0 < req.limit && req.limit < res.result.length) { @@ -385,12 +411,12 @@ export default class PeerClient { /** * @param {Object} req * @param {string} req.mxid - * @param {Long} req.channel_id + * @param {Object} req.channel_props * @param {string} req.text */ sendMessage = async (req) => { const userClient = this.#getUser(req.mxid) - const talkChannel = userClient.talkClient.channelList.get(req.channel_id) + const talkChannel = await userClient.getChannel(req.channel_props) return await talkChannel.sendChat({ type: KnownChatType.TEXT, @@ -517,3 +543,18 @@ export default class PeerClient { return value } } + + +/** + * @param {TalkChannelList} channelList + * @param {ChannelType} channelType + */ +function getChannelListForType(channelList, channelType) { + switch (channelType) { + case "OM": + case "OD": + return channelList.open + default: + return channelList.normal + } +}