Allow syncing (existing) direct chat channel by inviting puppet to DM

This commit is contained in:
Andrew Ferrazzutti 2022-04-04 20:07:30 -04:00
parent 3900e666ff
commit a9633118c5
4 changed files with 87 additions and 39 deletions

View File

@ -53,7 +53,7 @@ from ..types.request import (
from .types import PortalChannelInfo, UserInfoUnion, ChannelProps from .types import PortalChannelInfo, UserInfoUnion, ChannelProps
from .errors import InvalidAccessToken from .errors import InvalidAccessToken, CommandException
from .error_helper import raise_unsuccessful_response from .error_helper import raise_unsuccessful_response
try: try:
@ -257,6 +257,22 @@ class Client:
"list_friends", "list_friends",
) )
async def get_friend_dm_id(self, friend_id: Long) -> Long | None:
try:
return await self._api_user_request_result(
Long,
"get_friend_dm_id",
friend_id=friend_id.serialize(),
)
except CommandException:
self.log.exception(f"Could not find friend with ID {friend_id}")
return None
async def get_memo_ids(self) -> list[Long]:
return ResultListType(Long).deserialize(
await self._rpc_client.request("get_memo_ids", mxid=self.user.mxid)
)
async def send_message(self, channel_props: ChannelProps, text: str) -> Chatlog: async def send_message(self, channel_props: ChannelProps, text: str) -> Chatlog:
return await self._api_user_request_result( return await self._api_user_request_result(
Chatlog, Chatlog,

View File

@ -122,7 +122,7 @@ class Portal(DBPortal, BasePortal):
config: Config config: Config
_main_intent: IntentAPI | None _main_intent: IntentAPI | None
_kt_sender: int | None _kt_sender: Long | None
_create_room_lock: asyncio.Lock _create_room_lock: asyncio.Lock
_send_locks: dict[int, asyncio.Lock] _send_locks: dict[int, asyncio.Lock]
_noop_lock: FakeLock = FakeLock() _noop_lock: FakeLock = FakeLock()
@ -218,7 +218,7 @@ class Portal(DBPortal, BasePortal):
if self.mxid: if self.mxid:
await DBMessage.delete_all_by_room(self.mxid) await DBMessage.delete_all_by_room(self.mxid)
self.by_mxid.pop(self.mxid, None) self.by_mxid.pop(self.mxid, None)
self.by_ktid.pop(self._ktid_full, None) self.by_ktid.pop(self.ktid_full, None)
self.mxid = None self.mxid = None
self.name_set = False self.name_set = False
self.avatar_set = False self.avatar_set = False
@ -230,7 +230,7 @@ class Portal(DBPortal, BasePortal):
# region Properties # region Properties
@property @property
def _ktid_full(self) -> tuple[int, int]: def ktid_full(self) -> tuple[Long, Long]:
return self.ktid, self.kt_receiver return self.ktid, self.kt_receiver
@property @property
@ -263,11 +263,7 @@ class Portal(DBPortal, BasePortal):
@property @property
def main_intent(self) -> IntentAPI: def main_intent(self) -> IntentAPI:
if not self._main_intent: if not self._main_intent:
raise ValueError( raise ValueError("Portal must be postinit()ed before main_intent can be used")
"Portal must be postinit()ed before main_intent can be used"
if not self.is_direct else
"Direct chat portal must call postinit and _update_participants before main_intent can be used"
)
return self._main_intent return self._main_intent
async def get_dm_puppet(self) -> p.Puppet | None: async def get_dm_puppet(self) -> p.Puppet | None:
@ -485,7 +481,7 @@ class Portal(DBPortal, BasePortal):
self, source: u.User, participant: UserInfoUnion self, source: u.User, participant: UserInfoUnion
) -> bool: ) -> bool:
# TODO nick map? # TODO nick map?
self.log.trace("Syncing participant %s", participant.id) self.log.trace(f"Syncing participant {participant.userId}")
puppet = await p.Puppet.get_by_ktid(participant.userId) puppet = await p.Puppet.get_by_ktid(participant.userId)
await puppet.update_info_from_participant(source, participant) await puppet.update_info_from_participant(source, participant)
changed = False changed = False
@ -504,14 +500,6 @@ class Portal(DBPortal, BasePortal):
if participants is None: if participants is None:
self.log.debug("Called _update_participants with no participants, fetching them now...") self.log.debug("Called _update_participants with no participants, fetching them now...")
participants = await source.client.get_participants(self.channel_props) participants = await source.client.get_participants(self.channel_props)
if not self._main_intent:
assert self.is_direct, "_main_intent for non-direct chat portal should have been set already"
self._kt_sender = participants[
0 if self.kt_type == KnownChannelType.MemoChat or participants[0].userId != source.ktid else 1
].userId
self._main_intent = (await self.get_dm_puppet()).default_mxid_intent
else:
self._kt_sender = (await p.Puppet.get_by_mxid(self._main_intent.mxid)).ktid if self.is_direct else None
sync_tasks = [ sync_tasks = [
self._update_participant(source, pcp) for pcp in participants self._update_participant(source, pcp) for pcp in participants
] ]
@ -1277,21 +1265,25 @@ class Portal(DBPortal, BasePortal):
# region Database getters # region Database getters
async def postinit(self) -> None: async def postinit(self) -> None:
self.by_ktid[self._ktid_full] = self self.by_ktid[self.ktid_full] = self
if self.mxid: if self.mxid:
self.by_mxid[self.mxid] = self self.by_mxid[self.mxid] = self
if not self.is_direct: if not self.is_direct:
self._main_intent = self.az.intent self._main_intent = self.az.intent
elif self.mxid: else:
# TODO Save kt_sender in DB instead? Depends on if DM channels are shared... # TODO Save kt_sender in DB instead? Depends on if DM channels are shared...
user = await u.User.get_by_ktid(self.kt_receiver) 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}" assert user, f"Found no user for this portal's receiver of {self.kt_receiver}"
if user.is_connected: if self.kt_type == KnownChannelType.MemoChat:
await self._update_participants(user) self._kt_sender = user.ktid
else: else:
self.log.debug(f"Not setting _main_intent of new direct chat for disconnected user {user.ktid}") # NOTE This throws if the user isn't connected--good!
else: # Nothing should init a portal for a disconnected user.
self.log.debug("Not setting _main_intent of new direct chat until after checking participant list") participants = await user.client.get_participants(self.channel_props)
self._kt_sender = participants[
0 if participants[0].userId != user.ktid else 1
].userId
self._main_intent = (await p.Puppet.get_by_ktid(self._kt_sender)).default_mxid_intent
@classmethod @classmethod
@async_getter_lock @async_getter_lock

View File

@ -40,7 +40,7 @@ from .kt.client.errors import AuthenticationRequired, ResponseError
from .kt.types.api.struct.profile import ProfileStruct from .kt.types.api.struct.profile import ProfileStruct
from .kt.types.bson import Long from .kt.types.bson import Long
from .kt.types.channel.channel_info import ChannelInfo, NormalChannelInfo, NormalChannelData from .kt.types.channel.channel_info import ChannelInfo, NormalChannelInfo, NormalChannelData
from .kt.types.channel.channel_type import ChannelType from .kt.types.channel.channel_type import ChannelType, KnownChannelType
from .kt.types.chat.chat import Chatlog from .kt.types.chat.chat import Chatlog
from .kt.types.client.client_session import LoginDataItem, LoginResult from .kt.types.client.client_session import LoginDataItem, LoginResult
from .kt.types.oauth import OAuthCredential from .kt.types.oauth import OAuthCredential
@ -556,18 +556,28 @@ class User(DBUser, BaseUser):
return await pu.Puppet.get_by_ktid(self.ktid) return await pu.Puppet.get_by_ktid(self.ktid)
async def get_portal_with(self, puppet: pu.Puppet, create: bool = True) -> po.Portal | None: async def get_portal_with(self, puppet: pu.Puppet, create: bool = True) -> po.Portal | None:
# TODO # TODO Make upstream request to return custom failure message
return None if not self.ktid or not self.is_connected:
""" return None
if not self.ktid or not self.client: if puppet.ktid != self.ktid:
kt_type = KnownChannelType.DirectChat
ktid = await self.client.get_friend_dm_id(puppet.ktid)
else:
kt_type = KnownChannelType.MemoChat
memo_ids = await self.client.get_memo_ids()
if not memo_ids:
ktid = Long(0)
else:
ktid = memo_ids[0]
if len(memo_ids) > 1:
self.log.info("Found multiple memo chats, so using the first one as a fallback")
if ktid:
return await po.Portal.get_by_ktid(
ktid, kt_receiver=self.ktid, create=create, kt_type=kt_type
)
else:
self.log.warning(f"Didn't find an existing DM channel with KakaoTalk user {puppet.ktid}, so not creating one")
return None return None
return await po.Portal.get_by_ktid(
await self.client.get_dm_channel_id_for(puppet.ktid),
kt_receiver=self.ktid,
create=create,
kt_type=KnownChannelType.DirectChat if puppet.ktid != self.ktid else KnownChannelType.MemoChat
)
"""
# region KakaoTalk event handling # region KakaoTalk event handling

View File

@ -464,13 +464,41 @@ export default class PeerClient {
/** /**
* @param {Object} req * @param {Object} req
* @param {?string} req.mxid * @param {string} req.mxid
* @param {?OAuthCredential} req.oauth_credential
*/ */
listFriends = async (req) => { listFriends = async (req) => {
return await this.#getUser(req.mxid).serviceClient.requestFriendList() return await this.#getUser(req.mxid).serviceClient.requestFriendList()
} }
/**
* @param {Object} req
* @param {string} req.mxid The user whose friend is being looked up.
* @param {string} req.friend_id The friend to search for.
* @param {string} propertyName The property to retrieve from the specified friend.
*/
getFriendProperty = async (req, propertyName) => {
const res = await this.#getUser(req.mxid).serviceClient.findFriendById(req.friend_id)
if (!res.success) return res
return this.#makeCommandResult(res.result.friend[propertyName])
}
/**
* @param {Object} req
* @param {string} req.mxid
*/
getMemoIds = (req) => {
/** @type Long[] */
const channelIds = []
const channelList = this.#getUser(req.mxid).talkClient.channelList
for (const channel of channelList.all()) {
if (channel.info.type == "MemoChat") {
channelIds.push(channel.channelId)
}
}
return channelIds
}
/** /**
* @param {Object} req * @param {Object} req
* @param {string} req.mxid * @param {string} req.mxid
@ -590,6 +618,8 @@ export default class PeerClient {
get_participants: this.getParticipants, get_participants: this.getParticipants,
get_chats: this.getChats, get_chats: this.getChats,
list_friends: this.listFriends, list_friends: this.listFriends,
get_friend_dm_id: req => this.getFriendProperty(req, "directChatId"),
get_memo_ids: this.getMemoIds,
send_message: this.sendMessage, send_message: this.sendMessage,
send_media: this.sendMedia, send_media: this.sendMedia,
}[req.command] || this.handleUnknownCommand }[req.command] || this.handleUnknownCommand