From 4693719798abbc7e455a19f80bad715fa4898c4a Mon Sep 17 00:00:00 2001 From: gvidonind-commits <45360663+artgas1@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:23:33 +0300 Subject: [PATCH 1/3] feat: Add support for adding contacts by username without phone number - Modified add_contact function to support username-based contact addition - If phone parameter is empty or starts with @, function resolves username first - Uses contacts.AddContactRequest API for username-based contacts - Maintains backward compatibility with phone-based contact addition - Allows adding contacts as done in Telegram UI when searching by username --- main.py | 117 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 31 deletions(-) diff --git a/main.py b/main.py index 454ab2b..adde78a 100644 --- a/main.py +++ b/main.py @@ -1304,52 +1304,107 @@ async def add_contact(phone: str, first_name: str, last_name: str = "") -> str: """ Add a new contact to your Telegram account. Args: - phone: The phone number of the contact (with country code). + phone: The phone number of the contact (with country code) OR username (starting with @) OR empty string for username-based addition. first_name: The contact's first name. last_name: The contact's last name (optional). + + Note: If phone is empty or starts with @, the function will try to resolve it as a username + and add the contact using contacts.addContact API (which supports adding contacts without phone numbers). """ try: - # Try to import the required types first - from telethon.tl.types import InputPhoneContact - - result = await client( - functions.contacts.ImportContactsRequest( - contacts=[ - InputPhoneContact( - client_id=0, - phone=phone, + # Check if phone is empty or is a username (starts with @) + is_username = not phone or phone.startswith("@") + + if is_username: + # Handle username-based contact addition + username = phone.lstrip("@") if phone else "" + if not username: + return "Error: Username cannot be empty when adding contact by username." + + # Resolve username to get user information + try: + resolve_result = await client(functions.contacts.ResolveUsernameRequest(username=username)) + + # Extract user from the result + if not resolve_result.users: + return f"Error: User with username @{username} not found." + + user = resolve_result.users[0] + if not isinstance(user, User): + return f"Error: Resolved entity is not a user." + + user_id = user.id + access_hash = user.access_hash + + # Use contacts.addContact to add the contact by user ID + from telethon.tl.types import InputUser + + result = await client( + functions.contacts.AddContactRequest( + id=InputUser(user_id=user_id, access_hash=access_hash), first_name=first_name, last_name=last_name, + phone="", # Empty phone for username-based contacts ) - ] - ) - ) - if result.imported: - return f"Contact {first_name} {last_name} added successfully." + ) + + if hasattr(result, "updates") and result.updates: + return f"Contact {first_name} {last_name} (@{username}) added successfully." + else: + return f"Contact {first_name} {last_name} (@{username}) added successfully (no updates returned)." + + except Exception as resolve_e: + logger.exception(f"add_contact (username resolve) failed (username={username})") + return log_and_format_error("add_contact", resolve_e, username=username) + else: - return f"Contact not added. Response: {str(result)}" - except (ImportError, AttributeError) as type_err: - # Try alternative approach using raw API - try: + # Original phone-based contact addition + from telethon.tl.types import InputPhoneContact + result = await client( functions.contacts.ImportContactsRequest( contacts=[ - { - "client_id": 0, - "phone": phone, - "first_name": first_name, - "last_name": last_name, - } + InputPhoneContact( + client_id=0, + phone=phone, + first_name=first_name, + last_name=last_name, + ) ] ) ) - if hasattr(result, "imported") and result.imported: - return f"Contact {first_name} {last_name} added successfully (alt method)." + if result.imported: + return f"Contact {first_name} {last_name} added successfully." else: - return f"Contact not added. Alternative method response: {str(result)}" - except Exception as alt_e: - logger.exception(f"add_contact (alt method) failed (phone={phone})") - return log_and_format_error("add_contact", alt_e, phone=phone) + return f"Contact not added. Response: {str(result)}" + except (ImportError, AttributeError) as type_err: + # Try alternative approach using raw API (only for phone-based) + # Check if phone is empty or is a username (starts with @) + is_username_alt = not phone or phone.startswith("@") + if not is_username_alt: + try: + result = await client( + functions.contacts.ImportContactsRequest( + contacts=[ + { + "client_id": 0, + "phone": phone, + "first_name": first_name, + "last_name": last_name, + } + ] + ) + ) + if hasattr(result, "imported") and result.imported: + return f"Contact {first_name} {last_name} added successfully (alt method)." + else: + return f"Contact not added. Alternative method response: {str(result)}" + except Exception as alt_e: + logger.exception(f"add_contact (alt method) failed (phone={phone})") + return log_and_format_error("add_contact", alt_e, phone=phone) + else: + logger.exception(f"add_contact (type error for username) failed") + return log_and_format_error("add_contact", type_err) except Exception as e: logger.exception(f"add_contact failed (phone={phone})") return log_and_format_error("add_contact", e, phone=phone) From afb02be510f78e664f6b1989132aa9828f13a577 Mon Sep 17 00:00:00 2001 From: gvidonind-commits <45360663+artgas1@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:32:08 +0300 Subject: [PATCH 2/3] refactor: Add separate username parameter to add_contact function - Changed phone parameter to Optional[str] = None - Added separate username parameter (Optional[str] = None) - Improved function signature clarity - username is now explicit parameter - Updated validation logic to check for either phone or username - Maintains backward compatibility with phone-based contacts - Better API design - no longer need to pass username via phone parameter --- main.py | 53 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/main.py b/main.py index adde78a..cee48d4 100644 --- a/main.py +++ b/main.py @@ -1300,34 +1300,41 @@ async def get_message_context( title="Add Contact", openWorldHint=True, destructiveHint=True, idempotentHint=True ) ) -async def add_contact(phone: str, first_name: str, last_name: str = "") -> str: +async def add_contact(phone: Optional[str] = None, first_name: str = "", last_name: str = "", username: Optional[str] = None) -> str: """ Add a new contact to your Telegram account. Args: - phone: The phone number of the contact (with country code) OR username (starting with @) OR empty string for username-based addition. + phone: The phone number of the contact (with country code). Required if username is not provided. first_name: The contact's first name. last_name: The contact's last name (optional). + username: The Telegram username (without @). Use this for adding contacts without phone numbers. - Note: If phone is empty or starts with @, the function will try to resolve it as a username + Note: Either phone or username must be provided. If username is provided, the function will resolve it and add the contact using contacts.addContact API (which supports adding contacts without phone numbers). """ try: - # Check if phone is empty or is a username (starts with @) - is_username = not phone or phone.startswith("@") + # Normalize None to empty string for easier checking + phone = phone or "" + username = username or "" - if is_username: - # Handle username-based contact addition - username = phone.lstrip("@") if phone else "" - if not username: - return "Error: Username cannot be empty when adding contact by username." + # Validate that at least one identifier is provided + if not phone and not username: + return "Error: Either phone or username must be provided." + + # If username is provided, use it for username-based contact addition + if username: + # Remove @ if present + username_clean = username.lstrip("@") + if not username_clean: + return "Error: Username cannot be empty." # Resolve username to get user information try: - resolve_result = await client(functions.contacts.ResolveUsernameRequest(username=username)) + resolve_result = await client(functions.contacts.ResolveUsernameRequest(username=username_clean)) # Extract user from the result if not resolve_result.users: - return f"Error: User with username @{username} not found." + return f"Error: User with username @{username_clean} not found." user = resolve_result.users[0] if not isinstance(user, User): @@ -1349,15 +1356,15 @@ async def add_contact(phone: str, first_name: str, last_name: str = "") -> str: ) if hasattr(result, "updates") and result.updates: - return f"Contact {first_name} {last_name} (@{username}) added successfully." + return f"Contact {first_name} {last_name} (@{username_clean}) added successfully." else: - return f"Contact {first_name} {last_name} (@{username}) added successfully (no updates returned)." + return f"Contact {first_name} {last_name} (@{username_clean}) added successfully (no updates returned)." except Exception as resolve_e: - logger.exception(f"add_contact (username resolve) failed (username={username})") - return log_and_format_error("add_contact", resolve_e, username=username) + logger.exception(f"add_contact (username resolve) failed (username={username_clean})") + return log_and_format_error("add_contact", resolve_e, username=username_clean) - else: + elif phone: # Original phone-based contact addition from telethon.tl.types import InputPhoneContact @@ -1377,11 +1384,11 @@ async def add_contact(phone: str, first_name: str, last_name: str = "") -> str: return f"Contact {first_name} {last_name} added successfully." else: return f"Contact not added. Response: {str(result)}" + else: + return "Error: Phone number is required when username is not provided." except (ImportError, AttributeError) as type_err: # Try alternative approach using raw API (only for phone-based) - # Check if phone is empty or is a username (starts with @) - is_username_alt = not phone or phone.startswith("@") - if not is_username_alt: + if phone and not username: try: result = await client( functions.contacts.ImportContactsRequest( @@ -1403,11 +1410,11 @@ async def add_contact(phone: str, first_name: str, last_name: str = "") -> str: logger.exception(f"add_contact (alt method) failed (phone={phone})") return log_and_format_error("add_contact", alt_e, phone=phone) else: - logger.exception(f"add_contact (type error for username) failed") + logger.exception(f"add_contact (type error) failed") return log_and_format_error("add_contact", type_err) except Exception as e: - logger.exception(f"add_contact failed (phone={phone})") - return log_and_format_error("add_contact", e, phone=phone) + logger.exception(f"add_contact failed (phone={phone}, username={username})") + return log_and_format_error("add_contact", e, phone=phone, username=username) @mcp.tool( From 359b86315ea4a24b7d26a6e72ee1fc83a967f716 Mon Sep 17 00:00:00 2001 From: artgas1 <45360663+artgas1@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:21:25 +0300 Subject: [PATCH 3/3] style: Apply black formatting to add_contact function Co-Authored-By: Claude Opus 4.6 --- main.py | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/main.py b/main.py index cee48d4..03e0ef9 100644 --- a/main.py +++ b/main.py @@ -1300,7 +1300,12 @@ async def get_message_context( title="Add Contact", openWorldHint=True, destructiveHint=True, idempotentHint=True ) ) -async def add_contact(phone: Optional[str] = None, first_name: str = "", last_name: str = "", username: Optional[str] = None) -> str: +async def add_contact( + phone: Optional[str] = None, + first_name: str = "", + last_name: str = "", + username: Optional[str] = None, +) -> str: """ Add a new contact to your Telegram account. Args: @@ -1308,7 +1313,7 @@ async def add_contact(phone: Optional[str] = None, first_name: str = "", last_na first_name: The contact's first name. last_name: The contact's last name (optional). username: The Telegram username (without @). Use this for adding contacts without phone numbers. - + Note: Either phone or username must be provided. If username is provided, the function will resolve it and add the contact using contacts.addContact API (which supports adding contacts without phone numbers). """ @@ -1316,36 +1321,38 @@ async def add_contact(phone: Optional[str] = None, first_name: str = "", last_na # Normalize None to empty string for easier checking phone = phone or "" username = username or "" - + # Validate that at least one identifier is provided if not phone and not username: return "Error: Either phone or username must be provided." - + # If username is provided, use it for username-based contact addition if username: # Remove @ if present username_clean = username.lstrip("@") if not username_clean: return "Error: Username cannot be empty." - + # Resolve username to get user information try: - resolve_result = await client(functions.contacts.ResolveUsernameRequest(username=username_clean)) - + resolve_result = await client( + functions.contacts.ResolveUsernameRequest(username=username_clean) + ) + # Extract user from the result if not resolve_result.users: return f"Error: User with username @{username_clean} not found." - + user = resolve_result.users[0] if not isinstance(user, User): return f"Error: Resolved entity is not a user." - + user_id = user.id access_hash = user.access_hash - + # Use contacts.addContact to add the contact by user ID from telethon.tl.types import InputUser - + result = await client( functions.contacts.AddContactRequest( id=InputUser(user_id=user_id, access_hash=access_hash), @@ -1354,16 +1361,20 @@ async def add_contact(phone: Optional[str] = None, first_name: str = "", last_na phone="", # Empty phone for username-based contacts ) ) - + if hasattr(result, "updates") and result.updates: - return f"Contact {first_name} {last_name} (@{username_clean}) added successfully." + return ( + f"Contact {first_name} {last_name} (@{username_clean}) added successfully." + ) else: return f"Contact {first_name} {last_name} (@{username_clean}) added successfully (no updates returned)." - + except Exception as resolve_e: - logger.exception(f"add_contact (username resolve) failed (username={username_clean})") + logger.exception( + f"add_contact (username resolve) failed (username={username_clean})" + ) return log_and_format_error("add_contact", resolve_e, username=username_clean) - + elif phone: # Original phone-based contact addition from telethon.tl.types import InputPhoneContact