Compare commits

..

No commits in common. "8c2775056d97eccb43ad8777c1b74dda1dfadf26" and "db41292be7a98117d0cc44ab3a1260d757e1a166" have entirely different histories.

3 changed files with 35 additions and 101 deletions

View File

@ -24,7 +24,7 @@ from mautrix.util.signed_token import sign_token
from ..kt.client import Client as KakaoTalkClient from ..kt.client import Client as KakaoTalkClient
from ..kt.client.errors import DeviceVerificationRequired, IncorrectPasscode, IncorrectPassword, CommandException from ..kt.client.errors import DeviceVerificationRequired, IncorrectPasscode, IncorrectPassword, CommandException
from .. import puppet as pu #from .. import puppet as pu
from .typehint import CommandEvent from .typehint import CommandEvent
SECTION_AUTH = HelpSection("Authentication", 10, "") SECTION_AUTH = HelpSection("Authentication", 10, "")
@ -53,7 +53,7 @@ try_again_or_cancel = "Try again, or say `$cmdprefix+sp cancel` to give up."
help_args="[_email_]", help_args="[_email_]",
) )
async def login(evt: CommandEvent) -> None: async def login(evt: CommandEvent) -> None:
if await evt.sender.is_logged_in(): if evt.sender.client:
await evt.reply("You're already logged in") await evt.reply("You're already logged in")
return return
@ -156,43 +156,10 @@ async def _handle_login_failure(evt: CommandEvent, e: Exception) -> None:
await evt.reply(f"{message}: {e}") await evt.reply(f"{message}: {e}")
@command_handler( @command_handler(needs_auth=True, help_section=SECTION_AUTH, help_text="Log out of KakaoTalk")
needs_auth=True,
help_section=SECTION_AUTH,
help_text="Log out of KakaoTalk (and optionally change your virtual device ID for next login)",
help_args="[--reset-device]",
)
async def logout(evt: CommandEvent) -> None: async def logout(evt: CommandEvent) -> None:
if len(evt.args) >= 1: #puppet = await pu.Puppet.get_by_ktid(evt.sender.ktid)
if evt.args[0] == "--reset-device": await evt.sender.logout()
reset_device = True #if puppet.is_real_user:
else: # await puppet.switch_mxid(None, None)
await evt.reply("**Usage:** `$cmdprefix+sp logout [--reset-device]`") await evt.reply("Successfully logged out")
return
else:
reset_device = False
puppet = await pu.Puppet.get_by_ktid(evt.sender.ktid)
await evt.sender.logout(reset_device=reset_device)
if puppet.is_real_user:
await puppet.switch_mxid(None, None)
message = "Successfully logged out"
if reset_device:
message += (
", and your next login will use a different device ID.\n\n"
"The old device must be manually de-registered from the KakaoTalk app."
)
await evt.reply(message)
@command_handler(needs_auth=False, help_section=SECTION_AUTH, help_text="Change your virtual device ID for next login")
async def reset_device(evt: CommandEvent) -> None:
if await evt.sender.is_logged_in():
await evt.reply("This command requires you to be logged out.")
else:
await evt.sender.logout(reset_device=True)
await evt.reply(
"Your next login will use a different device ID.\n\n"
"The old device must be manually de-registered from the KakaoTalk app."
)

View File

@ -172,7 +172,7 @@ class User(DBUser, BaseUser):
@property @property
def has_state(self) -> bool: def has_state(self) -> bool:
return bool(self.uuid and self.ktid and self.access_token and self.refresh_token) return self.uuid and self.ktid and self.access_token and self.refresh_token
# region Database getters # region Database getters
@ -233,13 +233,10 @@ class User(DBUser, BaseUser):
async def get_uuid(self, force: bool = False) -> str: async def get_uuid(self, force: bool = False) -> str:
if self.uuid is None or force: if self.uuid is None or force:
self.uuid = await self._generate_uuid() self.uuid = await Client.generate_uuid(await self.get_all_uuids())
await self.save() await self.save()
return self.uuid return self.uuid
async def _generate_uuid(self) -> str:
return await Client.generate_uuid(await self.get_all_uuids())
# endregion # endregion
@property @property
@ -372,7 +369,8 @@ class User(DBUser, BaseUser):
finally: finally:
self._is_refreshing = False self._is_refreshing = False
async def logout(self, *, remove_ktid: bool = True, reset_device: bool = False) -> bool: async def logout(self, remove_ktid: bool = True) -> bool:
# TODO Remove tokens too?
ok = True ok = True
self.stop_listen() self.stop_listen()
if self.has_state: if self.has_state:
@ -387,15 +385,12 @@ class User(DBUser, BaseUser):
await self.client.stop() await self.client.stop()
self.client = None self.client = None
if self.ktid and remove_ktid: if remove_ktid:
if self.ktid:
#await UserPortal.delete_all(self.ktid) #await UserPortal.delete_all(self.ktid)
del self.by_ktid[self.ktid] del self.by_ktid[self.ktid]
self.ktid = None self.ktid = None
self.uuid = None
if reset_device:
self.uuid = await self._generate_uuid()
self.access_token = None
self.refresh_token = None
await self.save() await self.save()
return ok return ok
@ -588,11 +583,10 @@ class User(DBUser, BaseUser):
self.log.info("TODO: stop_listen") self.log.info("TODO: stop_listen")
async def on_logged_in(self, oauth_credential: OAuthCredential) -> None: async def on_logged_in(self, oauth_credential: OAuthCredential) -> None:
self.log.debug(f"Successfully logged in as {oauth_credential.userId}") self.log.debug(f"Successfully logged in as {oauth_credential.uuid}")
self.oauth_credential = oauth_credential self.oauth_credential = oauth_credential
self.client = Client(self, log=self.log.getChild("ktclient")) self.client = Client(self, log=self.log.getChild("ktclient"))
await self.save() await self.save()
self._is_logged_in = True
try: try:
self._logged_in_info = await self.client.fetch_logged_in_user(post_login=True) self._logged_in_info = await self.client.fetch_logged_in_user(post_login=True)
self._logged_in_info_time = time.monotonic() self._logged_in_info_time = time.monotonic()

View File

@ -28,7 +28,6 @@ import {
class UserClient { class UserClient {
static #initializing = false
#talkClient = new TalkClient() #talkClient = new TalkClient()
get talkClient() { return this.#talkClient } get talkClient() { return this.#talkClient }
@ -38,28 +37,16 @@ class UserClient {
get serviceClient() { return this.#serviceClient } get serviceClient() { return this.#serviceClient }
/** /**
* DO NOT CONSTRUCT DIRECTLY. Callers should use {@link UserClient#create} instead. * @param {string} mxid The ID of the associated Matrix user
* @param {string} mxid * @param {OAuthCredential} credential The tokens that API calls may use
* @param {OAuthCredential} credential
*/ */
constructor(mxid, credential) { constructor(mxid, credential) {
if (!UserClient.#initializing) {
throw new Error("Private constructor")
}
UserClient.#initializing = false
this.mxid = mxid this.mxid = mxid
this.credential = credential this.credential = credential
} }
/**
* @param {string} mxid The ID of the associated Matrix user
* @param {OAuthCredential} credential The tokens that API calls may use
*/
static async create(mxid, credential) { static async create(mxid, credential) {
this.#initializing = true
const userClient = new UserClient(mxid, credential) const userClient = new UserClient(mxid, credential)
userClient.#serviceClient = await ServiceApiClient.create(this.credential) userClient.#serviceClient = await ServiceApiClient.create(this.credential)
return userClient return userClient
} }
@ -207,9 +194,9 @@ export default class PeerClient {
/** /**
* Checked lookup of a UserClient for a given mxid. * Checked lookup of a UserClient for a given mxid.
* @param {string} mxid * @param {string} mxid
* @returns {UserClient}
*/ */
#getUser(mxid) { #getUser(mxid) {
/** @type {UserClient} */
const userClient = this.userClients.get(mxid) const userClient = this.userClients.get(mxid)
if (userClient === undefined) { if (userClient === undefined) {
throw new Error(`Could not find user ${mxid}`) throw new Error(`Could not find user ${mxid}`)
@ -217,17 +204,6 @@ export default class PeerClient {
return userClient return userClient
} }
/**
* Get the service client for the specified user ID, or create
* and return a new service client if no user ID is provided.
* @param {string} mxid
* @param {OAuthCredential} oauth_credential
*/
async #getServiceClient(mxid, oauth_credential) {
return this.userClients.get(mxid)?.serviceClient ||
await ServiceApiClient.create(oauth_credential)
}
/** /**
* @param {Object} req * @param {Object} req
* @param {OAuthCredential} req.oauth_credential * @param {OAuthCredential} req.oauth_credential
@ -294,7 +270,9 @@ export default class PeerClient {
* @param {OAuthCredential} req.oauth_credential * @param {OAuthCredential} req.oauth_credential
*/ */
getOwnProfile = async (req) => { getOwnProfile = async (req) => {
const serviceClient = await this.#getServiceClient(req.mxid, req.oauth_credential) const serviceClient =
this.userClients.get(req.mxid)?.serviceClient ||
await ServiceApiClient.create(req.oauth_credential)
return await serviceClient.requestMyProfile() return await serviceClient.requestMyProfile()
} }
@ -305,8 +283,10 @@ export default class PeerClient {
* @param {Long} req.user_id * @param {Long} req.user_id
*/ */
getProfile = async (req) => { getProfile = async (req) => {
const serviceClient = await this.#getServiceClient(req.mxid, req.oauth_credential) const serviceClient =
return await serviceClient.requestProfile(req.user_id) this.userClients.get(mxid)?.serviceClient ||
await ServiceApiClient.create(req.oauth_credential)
return await serviceClient.requestProfile(user_id)
} }
/** /**
@ -330,16 +310,6 @@ export default class PeerClient {
}) })
} }
/**
* @param {Object} req
* @param {string} req.mxid
*/
handleStop = async (req) => {
this.#getUser(req.mxid).close()
this.userClients.delete(req.mxid)
return this.#voidCommandResult
}
#makeCommandResult(result) { #makeCommandResult(result) {
return { return {
success: true, success: true,
@ -348,9 +318,12 @@ export default class PeerClient {
} }
} }
#voidCommandResult = { /**
success: true, * @param {Object} req
status: 0, * @param {string} req.mxid
*/
handleStop = async (req) => {
this.#getUser(req.mxid).close()
} }
handleUnknownCommand = () => { handleUnknownCommand = () => {