如何使用 Telethon 强制通过 SMS 接收验证码,而非 Telegram 应用通知?
结论
Telethon 没有保证强制发送 SMS 验证码的通用方法,发送渠道由 Telegram 服务端决定。
ResendCodeRequest用于请求切换到下一种验证渠道。调用前应检查上一次响应中的next_type,下一种渠道未必是 SMS。- 即使服务端允许重发 SMS,也可能受到请求限制或遇到短信投递失败,手机不一定能收到验证码。
- 首次调用可能直接返回
SentCodeTypeSms,但客户端没有通用参数可以保证首次就发送 SMS。
Telegram 将 auth.resendCode 定义为按前一次响应指定的渠道重发。Telethon 的 force_sms 已被弃用,当前实现会忽略其强制短信含义,因此不能用它来强制发送短信。Telethon 文档
如何判断能否重发 SMS
send_code_request() 通常返回 types.auth.SentCode,需要关注以下字段:
| 字段 | 含义 |
|---|---|
type | 本次使用的验证码渠道 |
next_type | 未收到验证码时,下一次重发使用的渠道 |
timeout | 等待本次验证码的时间,单位为秒 |
phone_code_hash | 后续重发和提交验证码时使用的标识 |
以下判断用于检查本次验证码是否通过 Telegram 应用发送:
isinstance(sent.type, types.auth.SentCodeTypeApp)
要判断下一次重发渠道是否为 SMS,则应检查:
isinstance(sent.next_type, types.auth.CodeTypeSms)
这里分别使用 SentCodeTypeApp 和 CodeTypeSms,要注意 type 与 next_type 的类型区别。
如果 next_type 是 None,说明响应没有提供下一种重发渠道;如果是其他类型,客户端也无法通过调用重发接口将其改成 SMS。timeout 到期后仍需主动调用重发接口,等待本身不会触发短信发送。auth.sentCode 字段说明
按服务端允许的渠道请求短信
按以下顺序处理:
- 调用
send_code_request()。 - 如果本次已经使用 SMS,直接等待验证码。
- 如果
next_type是CodeTypeSms,等待返回的timeout后调用ResendCodeRequest。 - 检查重发结果,并保存最新的
phone_code_hash。
以下示例使用 Telethon 1.x 接口,处理普通数字短信验证码。传入的 client 应已连接且尚未登录,phone 使用带国家代码的国际格式。
import asyncio
from telethon import functions, types
async def request_sms_code(client, phone):
sent = await client.send_code_request(phone)
if not isinstance(sent, types.auth.SentCode):
raise RuntimeError("返回结果需要其他授权流程,不能继续普通短信验证")
if isinstance(sent.type, types.auth.SentCodeTypeSms):
return sent
if not isinstance(sent.next_type, types.auth.CodeTypeSms):
next_name = (
type(sent.next_type).__name__
if sent.next_type is not None
else "None"
)
raise RuntimeError(
f"服务端没有提供下一步 SMS 渠道:next_type={next_name}"
)
if sent.timeout is not None and sent.timeout > 0:
await asyncio.sleep(sent.timeout)
sent = await client(
functions.auth.ResendCodeRequest(
phone_number=phone,
phone_code_hash=sent.phone_code_hash,
)
)
if not isinstance(sent, types.auth.SentCode):
raise RuntimeError("重发结果需要其他授权流程")
if not isinstance(sent.type, types.auth.SentCodeTypeSms):
raise RuntimeError(
f"重发后渠道为 {type(sent.type).__name__},请按实际结果处理"
)
return sent
async def login_with_sms(client, phone):
sent = await request_sms_code(client, phone)
code = input("请输入收到的短信验证码:").strip()
return await client.sign_in(
phone=phone,
code=code,
phone_code_hash=sent.phone_code_hash,
)
直接调用原始 ResendCodeRequest 后,不应依赖高层封装先前缓存的 phone_code_hash,因此这里显式传入最新值。Telethon 授权实现
示例没有实现自动重试,也没有覆盖 SentCodeTypeSmsWord、SentCodeTypeSmsPhrase 等短信变体。遇到其他返回类型时,需要补充相应处理,不能一律按普通数字验证码处理。
哪些情况下重发仍然无效
Telegram 没有开放可用的短信渠道
Telegram 官方说明,在某些登录或注册条件下,短信或电话验证只能使用 Firebase SMS 流程,而目前只有官方移动客户端可以完成该流程。普通 Telethon 脚本无法通过再次调用重发接口解除这一限制。其他号码曾经重发成功,也不代表所有账号状态下都能获得 SMS。Telegram 用户授权文档
请求受到限制,或验证流程已经失效
重发接口可能返回 PHONE_CODE_EXPIRED、SEND_CODE_UNAVAILABLE 等错误,其中 SEND_CODE_UNAVAILABLE 表示该号码当前可用的发送选项已经用尽。auth.resendCode 错误说明
发送验证码时还可能遇到 PHONE_NUMBER_FLOOD、PHONE_NUMBER_BANNED 或 SMS_CODE_CREATE_FAILED。这些情况需要根据具体错误处理,连续重发不能保证恢复。auth.sendCode 错误说明
如果 Telethon 抛出 FloodWaitError,应按异常给出的时间等待,不要在异常处理中立即循环请求。
服务端报告发送 SMS,但手机没有收到
SentCodeTypeSms 表示服务端报告验证码已通过 SMS 发送,不能证明短信已送达手机。运营商延迟、短信拦截或号码的接收能力都可能影响实际收件。
此时应检查手机和运营商侧的接收情况,并继续遵守服务端返回的重发条件。
尚未注册的号码需要单独处理
号码尚未注册 Telegram,与账号已经注册但无法访问现有会话,需要分开处理。
SentCodeTypeApp 的协议含义是验证码已作为 Telegram 服务通知发送到其他已登录会话,不能据此判断号码未注册。如果结果与预期不符,应先核对号码和账号状态。Telegram 验证码类型说明
对于确实未注册的号码,收到验证码也不等于完成注册。Telethon 的 sign_up() 已被弃用,当前实现会直接抛出 ValueError。应先通过官方移动客户端按提示完成注册,再使用 Telethon 登录。Telethon sign_up() 实现
多个号码应各自使用独立的 session。成功登录后,可以复用对应的 session,减少重复请求验证码。若账号启用了两步验证,提交验证码后还需要处理 SessionPasswordNeededError 并输入账号密码。Telethon 登录文档
首次请求能否直接指定 SMS
首次请求的 CodeSettings 没有通用的 force_sms 字段。allow_flashcall、allow_missed_call 等参数描述客户端支持的能力,关闭它们也不能保证服务端发送 SMS。allow_app_hash 用于短信自动读取相关功能,不能用来禁止应用内验证码。CodeSettings 参数说明
如果服务端首次选择 SMS,就无需重发。首次返回应用内验证码时,只能根据 next_type 使用服务端允许的后续渠道。没有 SMS 选项时,需要通过官方客户端完成可用的验证流程。
备注:内容仅供参考。
