You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Telethon开发Telegram机器人:验证码过期问题解决方案咨询

解决Telethon机器人验证码过期问题

问题描述

基于Telethon开发的Telegram用户认证与会话存储机器人,持续运行并存储大量会话,但出现验证码过期错误,导致用户无法完成认证。

错误信息

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

核心代码

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.")
        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
    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()

解决方案

1. 配置会话超时自动结束

在ConversationHandler中添加timeout参数,设置为Telegram验证码实际有效期(5分钟=300秒),用户长时间无操作时自动终止会话,避免过期验证码残留:

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)],
    timeout=300  # 5分钟无操作自动终止会话
)

2. 添加验证码过期自动清理任务

用户获取验证码后启动定时任务,5分钟后自动清除用户数据中的验证码哈希,并主动提示用户重新请求:

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
        # 启动5分钟后清理验证码哈希的任务
        context.job_queue.run_once(clear_code_hash, 300, context=update.effective_chat.id)
        # 修改提示为实际有效期5分钟
        update.message.reply_text("We've sent a login code to your phone. Please enter it within 5 minutes.")
        return MASUKKAN_KODE
    else:
        update.message.reply_text("Failed to send login code or invalid number entered. Please try again.")
        return MEMILIH_AKSI

# 新增清理验证码哈希的函数
def clear_code_hash(context: CallbackContext):
    chat_id = context.job.context
    user_data = context.dispatcher.user_data.get(chat_id, {})
    if 'phone_code_hash' in user_data:
        del user_data['phone_code_hash']
        context.bot.send_message(chat_id, "Your verification code has expired. Please request a new code by entering your phone number again.")

3. 优化验证码验证前置检查

在masukkan_kode函数中先校验验证码哈希是否存在,避免用户输入过期验证码时的无效请求:

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("Your verification code has expired. Please request a new code by entering your phone number again.")
        return MASUKKAN_NOMOR
    
    hasil = loop.run_until_complete(async_verifikasi_kode(nomor_telepon, kode, phone_code_hash))
    
    # 原逻辑保持不变...

4. 复用Telethon客户端实例

避免每次请求新建客户端,将实例暂存到用户数据中,减少连接开销并保持会话一致性:

async def async_kirim_kode_login(nomor_telepon: str, user_data):
    # 复用用户数据中的客户端实例,不存在则新建
    if 'client' not in user_data:
        user_data['client'] = TelegramClient(StringSession(), API_ID, API_HASH)
        await user_data['client'].connect()
    
    client = user_data['client']
    try:
        if not await client.is_user_authorized():
            result = await client.send_code_request(nomor_telepon)
            logger.info(f"Sent code request to {nomor_telepon}")
            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

# 修改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, context.user_data))
    # 原逻辑保持不变...

内容的提问来源于stack exchange,提问作者ruhkan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 03:37:02