diff --git a/mautrix_telegram/abstract_user.py b/mautrix_telegram/abstract_user.py index e70f0ba7..b545f72a 100644 --- a/mautrix_telegram/abstract_user.py +++ b/mautrix_telegram/abstract_user.py @@ -271,6 +271,7 @@ class AbstractUser(ABC): # TODO duplication not checked puppet = pu.Puppet.get(TelegramID(update.user_id)) if isinstance(update, UpdateUserName): + puppet.username = update.username if await puppet.update_displayname(self, update): puppet.save() elif isinstance(update, UpdateUserPhoto): diff --git a/mautrix_telegram/commands/auth.py b/mautrix_telegram/commands/auth.py index 1f37f13c..f8e7401c 100644 --- a/mautrix_telegram/commands/auth.py +++ b/mautrix_telegram/commands/auth.py @@ -21,7 +21,9 @@ from telethon.errors import ( AccessTokenExpiredError, AccessTokenInvalidError, FirstNameInvalidError, FloodWaitError, PasswordHashInvalidError, PhoneCodeExpiredError, PhoneCodeInvalidError, PhoneNumberAppSignupForbiddenError, PhoneNumberBannedError, PhoneNumberFloodError, - PhoneNumberOccupiedError, PhoneNumberUnoccupiedError, SessionPasswordNeededError) + PhoneNumberOccupiedError, PhoneNumberUnoccupiedError, SessionPasswordNeededError, + UsernameInvalidError, UsernameNotModifiedError, UsernameOccupiedError) +from telethon.tl.functions.account import UpdateUsernameRequest from . import command_handler, CommandEvent, SECTION_AUTH from .. import puppet as pu, user as u @@ -82,7 +84,8 @@ async def login_matrix(evt: CommandEvent) -> Optional[Dict]: } if evt.config["appservice.public.enabled"]: prefix = evt.config["appservice.public.external"] - url = f"{prefix}/matrix-login?token={evt.public_website.make_token(evt.sender.mxid, '/matrix-login')}" + token = evt.public_website.make_token(evt.sender.mxid, "/matrix-login") + url = f"{prefix}/matrix-login?token={token}" if allow_matrix_login: return await evt.reply( "This bridge instance allows you to log in inside or outside Matrix.\n\n" @@ -330,3 +333,27 @@ async def logout(evt: CommandEvent) -> Optional[Dict]: if await evt.sender.log_out(): return await evt.reply("Logged out successfully.") return await evt.reply("Failed to log out.") + + +@command_handler(needs_auth=True, + help_section=SECTION_AUTH, + help_text="Change your Telegram username") +async def username(evt: CommandEvent) -> Optional[Dict]: + if len(evt.args) == 0: + return await evt.reply("**Usage:** `$cmdprefix+sp username `") + if evt.sender.is_bot: + return await evt.reply("Bots can't set their own username.") + try: + await evt.sender.client(UpdateUsernameRequest(username=evt.args[0])) + except UsernameInvalidError: + return await evt.reply("Invalid username. Usernames must be between 5 and 30 alphanumeric " + "characters.") + except UsernameNotModifiedError: + return await evt.reply("That is your current username.") + except UsernameOccupiedError: + return await evt.reply("That username is already in use.") + await evt.sender.update_info() + if len(evt.sender.username) == 0: + await evt.reply("Username removed") + else: + await evt.reply(f"Username changed to {evt.sender.username}") diff --git a/mautrix_telegram/puppet.py b/mautrix_telegram/puppet.py index 5d6f76d5..900dbea2 100644 --- a/mautrix_telegram/puppet.py +++ b/mautrix_telegram/puppet.py @@ -14,7 +14,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from typing import Awaitable, Coroutine, Dict, List, Optional, Pattern, TYPE_CHECKING +from typing import Awaitable, Coroutine, Dict, List, Optional, Pattern, Union, TYPE_CHECKING from difflib import SequenceMatcher from enum import Enum from aiohttp import ServerDisconnectedError @@ -24,7 +24,7 @@ import re from sqlalchemy import orm -from telethon.tl.types import UserProfilePhoto, User, FileLocation +from telethon.tl.types import UserProfilePhoto, User, FileLocation, UpdateUserName, PeerUser from mautrix_appservice import AppService, IntentAPI, IntentError, MatrixRequestError from .types import MatrixUserID, TelegramID @@ -320,7 +320,7 @@ class Puppet: if name: break - if info.deleted: + if isinstance(info, User) and info.deleted: name = f"Deleted account {info.id}" elif not name: name = info.id @@ -345,12 +345,15 @@ class Puppet: if changed: self.save() - async def update_displayname(self, source: 'AbstractUser', info: User) -> bool: + async def update_displayname(self, source: 'AbstractUser', info: Union[User, UpdateUserName] + ) -> bool: ignore_source = (not source.is_relaybot and self.displayname_source is not None and self.displayname_source != source.tgid) if ignore_source: return False + if isinstance(info, UpdateUserName): + info = await source.client.get_entity(PeerUser(self.tgid)) displayname = self.get_displayname(info) if displayname != self.displayname: