Compare commits
2 Commits
7ac432e5a9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3675dacaf | ||
|
|
2d509c3807 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.12
|
||||||
77
README.md
Normal file
77
README.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# GPT Veteran - SheerID Verification Bot
|
||||||
|
|
||||||
|
这是一个基于 Python 和 Telegram Bot 的 SheerID 自动化验证工具,主要用于自动化处理 SheerID 的验证流程(如退伍军人身份验证)。
|
||||||
|
|
||||||
|
## ✨ 功能特点
|
||||||
|
|
||||||
|
* **全自动流程**:自动生成身份信息、指纹,完成表单提交。
|
||||||
|
* **邮箱集成**:内置临时邮箱支持,自动获取验证邮件并提取 Token 完成验证。
|
||||||
|
* **代理池支持**:支持对接外部代理池 API,自动切换 IP,有效避免风控 (429/Rate Limit)。
|
||||||
|
* **指纹模拟**:随机生成 Chrome 版本、User-Agent 和设备指纹,提高成功率。
|
||||||
|
* **Telegram 交互**:通过 Telegram Bot 发送验证链接,实时接收验证进度和结果。
|
||||||
|
|
||||||
|
## 🛠 环境要求
|
||||||
|
|
||||||
|
* Python 3.12+
|
||||||
|
* [uv](https://github.com/astral-sh/uv) (推荐) 或 pip
|
||||||
|
|
||||||
|
## 📦 安装
|
||||||
|
|
||||||
|
1. **克隆项目**
|
||||||
|
```bash
|
||||||
|
git clone <your-repo-url>
|
||||||
|
cd gptVeteran
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **安装依赖**
|
||||||
|
如果您使用 `uv` (推荐):
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
```
|
||||||
|
|
||||||
|
或者使用 pip:
|
||||||
|
```bash
|
||||||
|
pip install httpx python-telegram-bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ 配置
|
||||||
|
|
||||||
|
### 1. 配置 Telegram Bot
|
||||||
|
打开 `bot.py` 文件,找到 `BOT_TOKEN` 变量,填入您从 BotFather 获取的 Token:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# bot.py
|
||||||
|
BOT_TOKEN = "YOUR_BOT_TOKEN_HERE" # 替换为您的真实 Token
|
||||||
|
ALLOWED_USER_IDS = [] # 可选:填入允许使用的 Telegram User ID (整数),留空则不限制
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置代理池 (强烈推荐)
|
||||||
|
打开 `autoDabing.py` 文件,配置您的代理池 API:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# autoDabing.py
|
||||||
|
PROXY_POOL_ENABLED = True # 启用代理池
|
||||||
|
PROXY_POOL_API = "https://your-proxy-api.com/v1/proxies/next" # 代理获取地址
|
||||||
|
PROXY_POOL_AUTH = "Bearer YourToken" # 代理认证 Token
|
||||||
|
```
|
||||||
|
|
||||||
|
> **注意**:如果不使用代理池,请将 `PROXY_POOL_ENABLED` 设置为 `False`。直连很容易触发 SheerID 的风控限制。
|
||||||
|
|
||||||
|
## 🚀 使用方法
|
||||||
|
|
||||||
|
1. **启动 Bot**
|
||||||
|
```bash
|
||||||
|
uv run bot.py
|
||||||
|
# 或者
|
||||||
|
python bot.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **开始验证**
|
||||||
|
* 在 Telegram 中向您的 Bot 发送 `/start`。
|
||||||
|
* 发送包含 SheerID 验证链接的消息 (例如: `https://services.sheerid.com/verify/abcdef123...`)。
|
||||||
|
* Bot 会自动开始运行,并实时回复进度。
|
||||||
|
* 验证成功后,Bot 会返回使用的邮箱地址。
|
||||||
|
|
||||||
|
## ⚠️ 免责声明
|
||||||
|
|
||||||
|
本项目仅供学习和研究使用。请勿用于任何非法用途。使用本工具产生的任何后果由使用者自行承担。
|
||||||
847
autoDabing.py
847
autoDabing.py
@@ -5,446 +5,589 @@ from typing import Optional, Dict, Tuple
|
|||||||
import time
|
import time
|
||||||
import httpx
|
import httpx
|
||||||
import re
|
import re
|
||||||
import requests
|
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
from anyio import sleep
|
# --- 配置日志 ---
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.DEBUG,
|
level=logging.INFO,
|
||||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# --- 配置信息 ---
|
|
||||||
BASE_URL = "https://mail.copy.qzz.io/api"
|
|
||||||
AUTH_TOKEN = 'ks4cPQfAvMQI1iqhFCDJTGpuc5ez6B95Iaf9SzA47MP0LiH5Pv7urHjjvVsHJlCY'
|
|
||||||
|
|
||||||
# --- 公共变量 ---
|
# --- 代理池配置 ---
|
||||||
headers = {
|
PROXY_POOL_ENABLED = os.getenv("PROXY_POOL_ENABLED", "1").strip().lower() not in {"0", "false", "no", "off"} # 是否启用代理池
|
||||||
'Authorization': 'Bearer ' + AUTH_TOKEN
|
PROXY_POOL_API = os.getenv("PROXY_POOL_API", "https://getproxy.mygoband.com/v1/proxies/next").strip()
|
||||||
|
PROXY_POOL_TOKEN = os.getenv("PROXY_POOL_TOKEN", "").strip() # 你的 API key(不需要带 Bearer 前缀)
|
||||||
|
PROXY_POOL_AUTH = f"Bearer {PROXY_POOL_TOKEN}" if PROXY_POOL_TOKEN else "" # Authorization: Bearer <token>
|
||||||
|
|
||||||
|
# 静态代理(当代理池不可用/关闭时作为兜底);留空则直连
|
||||||
|
PROXY_URL = os.getenv("PROXY_URL", "").strip() or None
|
||||||
|
# 例: http://username:password@ip:port
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# --- 代理池管理器 ---
|
||||||
|
class ProxyPool:
|
||||||
|
"""
|
||||||
|
代理池管理器,从代理服务端点获取随机代理
|
||||||
|
"""
|
||||||
|
def __init__(self, api_url: str, auth_token: str):
|
||||||
|
self.api_url = api_url
|
||||||
|
self.auth_token = auth_token
|
||||||
|
self.current_proxy = None
|
||||||
|
|
||||||
|
def get_proxy(self) -> str | None:
|
||||||
|
"""从代理池API获取一个新的代理"""
|
||||||
|
try:
|
||||||
|
# 解析URL
|
||||||
|
if not self.auth_token:
|
||||||
|
logger.error("获取代理失败: 未设置 PROXY_POOL_TOKEN(Authorization 为空)")
|
||||||
|
return None
|
||||||
|
headers = {'Authorization': self.auth_token}
|
||||||
|
|
||||||
|
# 使用 httpx 发起请求
|
||||||
|
with httpx.Client(timeout=10.0) as client:
|
||||||
|
res = client.get(self.api_url, headers=headers)
|
||||||
|
|
||||||
|
if res.status_code != 200:
|
||||||
|
logger.error(f"获取代理失败 ({res.status_code}): {res.text}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 解析响应
|
||||||
|
response_data = res.json()
|
||||||
|
|
||||||
|
# 提取代理信息
|
||||||
|
# 响应格式: {"proxy": {"protocol": "http", "host": "x.x.x.x", "port": 443, "username": "user", "password": "pass"}, "lease_id": "...", "ttl_ms": 59994}
|
||||||
|
if 'proxy' in response_data and isinstance(response_data['proxy'], dict):
|
||||||
|
proxy_info = response_data['proxy']
|
||||||
|
protocol = proxy_info.get('protocol', 'http')
|
||||||
|
proxy_host = proxy_info.get('host')
|
||||||
|
proxy_port = proxy_info.get('port')
|
||||||
|
username = proxy_info.get('username')
|
||||||
|
password = proxy_info.get('password')
|
||||||
|
|
||||||
|
# 强制使用 http 协议,不使用 https
|
||||||
|
protocol = 'http'
|
||||||
|
|
||||||
|
if proxy_host and proxy_port:
|
||||||
|
# 如果有用户名和密码,添加到代理 URL 中
|
||||||
|
if username and password:
|
||||||
|
proxy = f"{protocol}://{username}:{password}@{proxy_host}:{proxy_port}"
|
||||||
|
logger.info(f"代理需要认证: {username}@{proxy_host}:{proxy_port}")
|
||||||
|
else:
|
||||||
|
proxy = f"{protocol}://{proxy_host}:{proxy_port}"
|
||||||
|
else:
|
||||||
|
logger.error(f"代理信息不完整: {proxy_info}")
|
||||||
|
return None
|
||||||
|
elif 'proxy' in response_data and isinstance(response_data['proxy'], str):
|
||||||
|
# 如果是字符串格式(可能已经包含认证信息)
|
||||||
|
proxy = response_data['proxy']
|
||||||
|
# 强制替换 https 为 http
|
||||||
|
if proxy.startswith('https://'):
|
||||||
|
proxy = proxy.replace('https://', 'http://', 1)
|
||||||
|
elif 'host' in response_data:
|
||||||
|
# 兼容旧格式
|
||||||
|
proxy_host = response_data.get('host')
|
||||||
|
proxy_port = response_data.get('port')
|
||||||
|
username = response_data.get('username', '')
|
||||||
|
password = response_data.get('password', '')
|
||||||
|
|
||||||
|
if username and password:
|
||||||
|
proxy = f"http://{username}:{password}@{proxy_host}:{proxy_port}"
|
||||||
|
else:
|
||||||
|
proxy = f"http://{proxy_host}:{proxy_port}"
|
||||||
|
else:
|
||||||
|
# 如果是纯文本格式的代理地址
|
||||||
|
proxy = res.text.strip()
|
||||||
|
# 强制替换 https 为 http
|
||||||
|
if proxy.startswith('https://'):
|
||||||
|
proxy = proxy.replace('https://', 'http://', 1)
|
||||||
|
|
||||||
|
self.current_proxy = proxy
|
||||||
|
logger.info(f"获取到新代理: {proxy}")
|
||||||
|
return proxy
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取代理请求失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def refresh_proxy(self) -> str | None:
|
||||||
|
"""刷新代理(获取新的代理)"""
|
||||||
|
return self.get_proxy()
|
||||||
|
|
||||||
|
# --- 随机身份生成器 ---
|
||||||
|
class RandomIdentity:
|
||||||
|
def __init__(self):
|
||||||
|
self.first_names = ["KELLY","RYAN"]
|
||||||
|
self.last_names = ["BURKHART","Pitts"]
|
||||||
|
|
||||||
|
random_num = random.randint(0, 1)
|
||||||
|
middle_name = ""
|
||||||
|
for i in range(random.randint(5, 10)):
|
||||||
|
middle_name += random.choice(string.ascii_uppercase)
|
||||||
|
self.first_name = self.first_names[random_num] + " " + middle_name
|
||||||
|
self.last_name = self.last_names[random_num]
|
||||||
|
|
||||||
|
# 生日在这两个里面选择1981-05-14 1985-10-01,使用上面的random_num来选择
|
||||||
|
self.birth_date = "1981-05-14" if random_num == 0 else "1985-10-01"
|
||||||
|
|
||||||
|
# 随机退伍日期 (最近3年)
|
||||||
|
d_year = random.randint(2025, 2025)
|
||||||
|
d_month = random.randint(1, 12)
|
||||||
|
d_day = random.randint(1, 28)
|
||||||
|
self.discharge_date = f"{d_year}-{d_month:02d}-{d_day:02d}"
|
||||||
|
|
||||||
|
# --- 随机指纹生成器 ---
|
||||||
|
class RandomFingerprint:
|
||||||
|
def __init__(self):
|
||||||
|
self.chrome_version = random.randint(120, 133)
|
||||||
|
self.build_version = random.randint(0, 5000)
|
||||||
|
self.patch_version = random.randint(0, 150)
|
||||||
|
self.platform = "Windows" if random.random() < 0.8 else "Linux"
|
||||||
|
self.platform_os = '"Windows"' if self.platform == "Windows" else '"Linux"'
|
||||||
|
|
||||||
|
if self.platform == "Windows":
|
||||||
|
self.ua_string = f'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{self.chrome_version}.0.{self.build_version}.{self.patch_version} Safari/537.36'
|
||||||
|
else:
|
||||||
|
self.ua_string = f'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{self.chrome_version}.0.{self.build_version}.{self.patch_version} Safari/537.36'
|
||||||
|
|
||||||
|
# 生成设备指纹哈希 (32位十六进制字符串)
|
||||||
|
self.device_fingerprint_hash = ''.join(random.choice('0123456789abcdef') for _ in range(32))
|
||||||
|
|
||||||
|
def get_headers(self) -> Dict:
|
||||||
|
return {
|
||||||
|
'sec-ch-ua': f'"Chromium";v="{self.chrome_version}", "Not(A:Brand";v="24", "Google Chrome";v="{self.chrome_version}"',
|
||||||
|
'sec-ch-ua-mobile': '?0',
|
||||||
|
'sec-ch-ua-platform': self.platform_os,
|
||||||
|
'user-agent': self.ua_string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- 辅助函数 ---
|
||||||
def extract_verification_link(content):
|
def extract_verification_link(content: str) -> str | None:
|
||||||
"""从邮件内容提取验证链接"""
|
"""从邮件内容提取 SheerID 验证链接"""
|
||||||
|
if not content:
|
||||||
|
return None
|
||||||
|
# 优先匹配 href 属性中的链接
|
||||||
match = re.search(r'href="(https://services\.sheerid\.com/verify/[^"]+emailToken=[^"]+)"', content)
|
match = re.search(r'href="(https://services\.sheerid\.com/verify/[^"]+emailToken=[^"]+)"', content)
|
||||||
if match:
|
if match:
|
||||||
return match.group(1).replace('&', '&')
|
return match.group(1).replace('&', '&')
|
||||||
|
# 备用:直接匹配 URL
|
||||||
match = re.search(r'https://services\.sheerid\.com/verify/[^\s<>"]+emailToken=\d+', content)
|
match = re.search(r'https://services\.sheerid\.com/verify/[^\s<>"]+emailToken=\d+', content)
|
||||||
if match:
|
if match:
|
||||||
return match.group(0)
|
return match.group(0)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def generate_new_email(length: int = 8, domain_index: int = 0) -> str | None:
|
|
||||||
|
def extract_email_token(url: str) -> str | None:
|
||||||
|
"""从验证链接提取 emailToken"""
|
||||||
|
if not url:
|
||||||
|
return None
|
||||||
|
match = re.search(r'emailToken=(\d+)', url)
|
||||||
|
return match.group(1) if match else None
|
||||||
|
|
||||||
|
|
||||||
|
class MailClient:
|
||||||
"""
|
"""
|
||||||
向 API 请求生成一个新的临时邮箱地址。
|
邮件客户端类,用于与临时邮箱 API 交互。
|
||||||
|
邮箱操作使用直连,不使用代理
|
||||||
Args:
|
|
||||||
length (int): 邮箱地址的用户名长度。
|
|
||||||
domain_index (int): 使用的域名索引。
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str | None: 成功时返回邮箱地址字符串,失败时返回 None。
|
|
||||||
"""
|
"""
|
||||||
# 1. 明确操作意图,并记录请求参数
|
|
||||||
operation = "生成新邮箱地址"
|
|
||||||
endpoint = f"{BASE_URL}/generate"
|
|
||||||
params = {'length': length, 'domainIndex': domain_index}
|
|
||||||
|
|
||||||
logger.info(f"开始操作: {operation} (参数: {params})")
|
def __init__(self, base_url: str = "https://mail.copy.qzz.io/api",
|
||||||
|
auth_token: str = 'ks4cPQfAvMQI1iqhFCDJTGpuc5ez6B95Iaf9SzA47MP0LiH5Pv7urHjjvVsHJlCY'):
|
||||||
|
self.base_url = base_url
|
||||||
|
self.headers = {'Authorization': f'Bearer {auth_token}'}
|
||||||
|
|
||||||
try:
|
# 邮箱操作使用直连,不使用代理
|
||||||
# 使用 params 参数,让 requests 自动处理 URL 查询字符串
|
|
||||||
logger.debug(f"发送 GET 请求至: {endpoint}")
|
|
||||||
response = requests.get(endpoint, headers=headers, params=params)
|
|
||||||
|
|
||||||
# 2. 关联请求与响应
|
|
||||||
logger.info(f"操作 '{operation}' 收到响应,状态码: {response.status_code}")
|
|
||||||
# 检查 HTTP 错误状态码
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
# 尝试解析 JSON
|
|
||||||
data = response.json()
|
|
||||||
logger.debug(f"成功解析 JSON 响应: \n{json.dumps(data, indent=2)}")
|
|
||||||
|
|
||||||
email = data.get('email')
|
|
||||||
if email:
|
|
||||||
# 3. 增加成功日志
|
|
||||||
logger.info(f"成功 {operation},获取到邮箱: {email}")
|
|
||||||
return email
|
|
||||||
else:
|
|
||||||
logger.error(f"操作 '{operation}' 失败: 响应 JSON 中缺少 'email' 键。")
|
|
||||||
return None
|
|
||||||
except requests.exceptions.HTTPError as err:
|
|
||||||
# 4. 丰富错误上下文
|
|
||||||
logger.error(f"操作 '{operation}' 失败,发生 HTTP 错误: {err}")
|
|
||||||
# 在 debug 级别记录完整的服务器响应,避免在 INFO/ERROR 级别刷屏
|
|
||||||
logger.debug(f"服务器错误响应全文: {err.response.text}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as err:
|
|
||||||
logger.critical(f"操作 '{operation}' 失败,发生严重请求错误 (如网络问题): {err}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
logger.error(f"操作 '{operation}' 失败: 无法解析服务器响应为 JSON。")
|
|
||||||
logger.debug(f"服务器返回的非 JSON 内容: '{response.text}'")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_mail_detail_from_mailbox(email_address: str, email_id: int) -> str | None:
|
|
||||||
"""
|
|
||||||
从指定的邮箱中获取所有邮件,并提取出 SheerID 验证链接。
|
|
||||||
Args:
|
|
||||||
email_address (str): 要查询的临时邮箱地址。
|
|
||||||
auth_token (str): 用于 API 认证的 Bearer Token。
|
|
||||||
Returns:
|
|
||||||
list[str]: 一个包含所有找到的 SheerID 链接的列表。
|
|
||||||
如果找不到链接或发生错误,则返回一个空列表。
|
|
||||||
"""
|
|
||||||
base_url = "https://mail.copy.qzz.io/api"
|
|
||||||
endpoint = f"{base_url}/email/{email_id}"
|
|
||||||
operation = f"获取邮箱 <{email_address}> 内的 SheerID 链接"
|
|
||||||
logger.info(f"开始操作: {operation}")
|
|
||||||
try:
|
|
||||||
# --- 发送 API 请求 ---
|
|
||||||
response = requests.get(endpoint, headers=headers)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# --- 解析 JSON ---
|
|
||||||
data = response.json()
|
|
||||||
logger.debug(f"成功解析 JSON 响应: \n{json.dumps(data, indent=2)}")
|
|
||||||
extract_verification_link(data.get('html_content'))
|
|
||||||
return data.get('html_content')
|
|
||||||
|
|
||||||
except requests.exceptions.HTTPError as err:
|
|
||||||
logger.error(f"操作 '{operation}' 失败: 发生 HTTP 错误: {err}")
|
|
||||||
logger.debug(f"服务器错误响应全文: {err.response.text}")
|
|
||||||
return None # 发生错误时返回空列表
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as err:
|
|
||||||
logger.critical(f"操作 '{operation}' 失败: 发生严重请求错误 (如网络问题): {err}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
logger.error(f"操作 '{operation}' 失败: 无法解析服务器响应为 JSON。")
|
|
||||||
logger.debug(f"服务器返回的非 JSON 内容: '{response.text}'")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_sheerid_links_from_mailbox(email_address: str) -> str | None:
|
|
||||||
"""
|
|
||||||
从指定的邮箱中获取所有邮件,并提取出 SheerID 验证链接。
|
|
||||||
Args:
|
|
||||||
email_address (str): 要查询的临时邮箱地址。
|
|
||||||
auth_token (str): 用于 API 认证的 Bearer Token。
|
|
||||||
Returns:
|
|
||||||
list[str]: 一个包含所有找到的 SheerID 链接的列表。
|
|
||||||
如果找不到链接或发生错误,则返回一个空列表。
|
|
||||||
"""
|
|
||||||
base_url = "https://mail.copy.qzz.io/api"
|
|
||||||
endpoint = f"{base_url}/emails"
|
|
||||||
|
|
||||||
params = {
|
|
||||||
"mailbox": email_address
|
|
||||||
}
|
|
||||||
|
|
||||||
operation = f"获取邮箱 <{email_address}> 内的 SheerID 链接"
|
|
||||||
logger.info(f"开始操作: {operation}")
|
|
||||||
try:
|
|
||||||
# --- 发送 API 请求 ---
|
|
||||||
response = requests.get(endpoint, headers=headers, params=params)
|
|
||||||
response.raise_for_status()
|
|
||||||
logger.info(f"操作 '{operation}' 收到响应,状态码: {response.status_code}")
|
|
||||||
# --- 解析 JSON ---
|
|
||||||
data = response.json()
|
|
||||||
logger.debug(f"成功解析 JSON 响应: \n{json.dumps(data, indent=2)}")
|
|
||||||
if not data[0].get('sender') == "Verify@SheerID.com":
|
|
||||||
return None
|
|
||||||
id = data[0].get('id')
|
|
||||||
logger.debug(id)
|
|
||||||
content = get_mail_detail_from_mailbox(email_address, id)
|
|
||||||
url = extract_verification_link(content)
|
|
||||||
return url
|
|
||||||
|
|
||||||
except requests.exceptions.HTTPError as err:
|
|
||||||
logger.error(f"操作 '{operation}' 失败: 发生 HTTP 错误: {err}")
|
|
||||||
logger.debug(f"服务器错误响应全文: {err.response.text}")
|
|
||||||
return None # 发生错误时返回空列表
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as err:
|
|
||||||
logger.critical(f"操作 '{operation}' 失败: 发生严重请求错误 (如网络问题): {err}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
logger.error(f"操作 '{operation}' 失败: 无法解析服务器响应为 JSON。")
|
|
||||||
logger.debug(f"服务器返回的非 JSON 内容: '{response.text}'")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def delete_mailbox(email_address: str) -> bool:
|
|
||||||
"""
|
|
||||||
通过 API 删除一个临时邮箱地址(邮箱)。
|
|
||||||
Args:
|
|
||||||
email_address (str): 要删除的邮箱地址。
|
|
||||||
auth_token (str): 用于 API 认证的 Bearer Token。
|
|
||||||
Returns:
|
|
||||||
bool: 如果成功删除则返回 True,否则返回 False。
|
|
||||||
"""
|
|
||||||
# 稍微修改了函数名,因为 API 端点是 /mailboxes,更准确
|
|
||||||
operation = f"删除邮箱 <{email_address}>"
|
|
||||||
logger.info(f"开始操作: {operation}")
|
|
||||||
# 1. 输入验证
|
|
||||||
if not email_address:
|
|
||||||
logger.warning("尝试删除一个空的邮箱地址,操作已取消。")
|
|
||||||
return False
|
|
||||||
endpoint = f"{BASE_URL}/mailboxes"
|
|
||||||
params = {
|
|
||||||
'address': email_address
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
# --- 发送 API 请求 ---
|
|
||||||
response = requests.delete(endpoint, headers=headers, params=params)
|
|
||||||
|
|
||||||
logger.info(f"操作 '{operation}' 收到响应,状态码: {response.status_code}")
|
|
||||||
response.raise_for_status() # 如果状态码是 4xx 或 5xx,将在此处引发 HTTPError
|
|
||||||
# 2. 检查成功响应
|
|
||||||
# 成功的 DELETE 请求通常返回 204 No Content (无响应体)
|
|
||||||
if response.status_code == 204:
|
|
||||||
logger.info(f"成功 {operation} (状态码 204)。")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# 如果返回 200 OK 并带有 JSON 体
|
|
||||||
data = response.json()
|
|
||||||
logger.debug(f"成功 {operation},收到的 JSON 详情: \n{json.dumps(data, indent=2)}")
|
|
||||||
logger.info(f"成功 {operation} (状态码 {response.status_code})。")
|
|
||||||
return True
|
|
||||||
except requests.exceptions.HTTPError as err:
|
|
||||||
logger.error(f"操作 '{operation}' 失败: 发生 HTTP 错误: {err}")
|
|
||||||
# 使用 debug 级别记录详细响应,避免刷屏
|
|
||||||
logger.debug(f"服务器错误响应全文: {err.response.text}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as err:
|
|
||||||
logger.critical(f"操作 '{operation}' 失败: 发生网络等严重请求错误: {err}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
logger.error(f"操作 '{operation}' 失败: 服务器返回了成功的状态码,但响应不是有效的 JSON。")
|
|
||||||
logger.debug(f"服务器返回的非 JSON 内容: '{response.text}'")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class SheerIDVerifier:
|
|
||||||
def __init__(self, verification_id: str,program_id:str) -> None:
|
|
||||||
self.program_id = program_id
|
|
||||||
self.verification_id = verification_id
|
|
||||||
self.device_fingerprint = self._generate_device_fingerprint()
|
|
||||||
self.http_client = httpx.Client(timeout=30.0)
|
self.http_client = httpx.Client(timeout=30.0)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if hasattr(self, "http_client"):
|
if hasattr(self, "http_client"):
|
||||||
self.http_client.close()
|
self.http_client.close()
|
||||||
|
|
||||||
@staticmethod
|
def generate_email(self, length: int = 10, domain_index: int = 0) -> str | None:
|
||||||
def _generate_device_fingerprint() -> str:
|
"""生成一个新的临时邮箱地址"""
|
||||||
chars = '0123456789abcdef'
|
try:
|
||||||
return ''.join(random.choice(chars) for _ in range(32))
|
response = self.http_client.get(
|
||||||
|
f"{self.base_url}/generate",
|
||||||
|
headers=self.headers,
|
||||||
|
params={'length': length, 'domainIndex': domain_index}
|
||||||
|
)
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.error(f"生成邮箱失败 ({response.status_code}): {response.text}")
|
||||||
|
return None
|
||||||
|
return response.json().get('email')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"生成邮箱请求失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
def get_emails(self, mailbox: str) -> list | None:
|
||||||
def normalize_url(url: str) -> str:
|
"""获取指定邮箱的邮件列表"""
|
||||||
"""规范化 URL(保留原样)"""
|
try:
|
||||||
return url
|
response = self.http_client.get(
|
||||||
|
f"{self.base_url}/emails",
|
||||||
|
headers=self.headers,
|
||||||
|
params={'mailbox': mailbox}
|
||||||
|
)
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.error(f"获取邮件列表失败 ({response.status_code}): {response.text}")
|
||||||
|
return None
|
||||||
|
return response.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取邮件列表请求失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_email_detail(self, email_id: int) -> str | None:
|
||||||
|
"""获取指定邮件的详细内容"""
|
||||||
|
try:
|
||||||
|
response = self.http_client.get(
|
||||||
|
f"{self.base_url}/email/{email_id}",
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.error(f"获取邮件详情失败 ({response.status_code}): {response.text}")
|
||||||
|
return None
|
||||||
|
data = response.json()
|
||||||
|
return data.get('html_content') or data.get('content')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取邮件详情请求失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def delete_mailbox(self, address: str) -> bool:
|
||||||
|
"""删除指定的邮箱"""
|
||||||
|
if not address:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
response = self.http_client.delete(
|
||||||
|
f"{self.base_url}/mailboxes",
|
||||||
|
headers=self.headers,
|
||||||
|
params={'address': address}
|
||||||
|
)
|
||||||
|
if response.status_code not in [200, 204]:
|
||||||
|
logger.error(f"删除邮箱失败 ({response.status_code}): {response.text}")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"删除邮箱请求失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# --- 主逻辑类 ---
|
||||||
|
class SheerIDVerifier:
|
||||||
|
def __init__(self, verification_id: str, program_id: str, proxy_pool: ProxyPool = None, log_callback=None) -> None:
|
||||||
|
self.program_id = program_id
|
||||||
|
self.verification_id = verification_id
|
||||||
|
self.fingerprint = RandomFingerprint()
|
||||||
|
self.device_fingerprint_hash = self.fingerprint.device_fingerprint_hash
|
||||||
|
self.identity = RandomIdentity()
|
||||||
|
self.proxy_pool = proxy_pool
|
||||||
|
self.log_callback = log_callback
|
||||||
|
|
||||||
|
self.log(f"指纹初始化: Chrome {self.fingerprint.chrome_version}")
|
||||||
|
self.log(f"身份初始化: {self.identity.first_name} {self.identity.last_name}, DOB: {self.identity.birth_date}")
|
||||||
|
|
||||||
|
# 配置代理 - 获取一个代理并在整个验证过程中使用同一个代理
|
||||||
|
self.proxy_url = None
|
||||||
|
if self.proxy_pool:
|
||||||
|
self.proxy_url = self.proxy_pool.get_proxy()
|
||||||
|
if self.proxy_url:
|
||||||
|
self.log(f"已启用代理池连接: {self.proxy_url}")
|
||||||
|
else:
|
||||||
|
logger.warning("代理池获取失败,使用静态代理或直连")
|
||||||
|
|
||||||
|
if not self.proxy_url and PROXY_URL:
|
||||||
|
self.proxy_url = PROXY_URL
|
||||||
|
self.log("已启用静态代理连接")
|
||||||
|
|
||||||
|
# 修复:httpx 使用 'proxy' 而不是 'proxies'
|
||||||
|
self.http_client = httpx.Client(
|
||||||
|
timeout=30.0,
|
||||||
|
follow_redirects=True,
|
||||||
|
http2=False,
|
||||||
|
proxy=self.proxy_url
|
||||||
|
)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if hasattr(self, "http_client"):
|
||||||
|
self.http_client.close()
|
||||||
|
|
||||||
|
def log(self, msg):
|
||||||
|
logger.info(msg)
|
||||||
|
if self.log_callback:
|
||||||
|
try:
|
||||||
|
self.log_callback(msg)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_verification_id(url: str) -> Optional[str]:
|
def parse_verification_id(url: str) -> Optional[str]:
|
||||||
match = re.search(r"verificationId=([a-f0-9]+)", url, re.IGNORECASE)
|
match = re.search(r"verificationId=([a-f0-9]+)", url, re.IGNORECASE)
|
||||||
if match:
|
return match.group(1) if match else None
|
||||||
return match.group(1)
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_program_id(url: str) -> Optional[str]:
|
def parse_program_id(url: str) -> Optional[str]:
|
||||||
match = re.search(r"verify/([a-f0-9]+)/", url, re.IGNORECASE)
|
match = re.search(r"verify/([a-f0-9]+)/", url, re.IGNORECASE)
|
||||||
if match:
|
return match.group(1) if match else None
|
||||||
return match.group(1)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _sheerid_request(
|
def _get_common_headers(self, referer_vid: str = None) -> Dict:
|
||||||
self, method: str, url: str, body: Optional[Dict] = None
|
vid = referer_vid if referer_vid else self.verification_id
|
||||||
) -> Tuple[Dict, int]:
|
base_headers = {
|
||||||
"""发送 SheerID API 请求"""
|
|
||||||
headers = {
|
|
||||||
'host': 'services.sheerid.com',
|
'host': 'services.sheerid.com',
|
||||||
'sec-ch-ua-platform': '"Windows"',
|
'accept': 'application/json, text/plain, */*',
|
||||||
'sec-ch-ua': f'"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
|
'accept-language': 'en-US,en;q=0.9',
|
||||||
'clientversion': '2.157.0',
|
|
||||||
'sec-ch-ua-mobile': '?0',
|
|
||||||
'clientname': 'jslib',
|
'clientname': 'jslib',
|
||||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36',
|
'clientversion': '2.157.0',
|
||||||
'accept': 'application/json',
|
|
||||||
'content-type': 'application/json',
|
|
||||||
'origin': 'https://services.sheerid.com',
|
'origin': 'https://services.sheerid.com',
|
||||||
'sec-fetch-site': 'same-origin',
|
'referer': f'https://services.sheerid.com/verify/{self.program_id}/?verificationId={vid}',
|
||||||
'sec-fetch-mode': 'cors',
|
|
||||||
'sec-fetch-dest': 'empty',
|
'sec-fetch-dest': 'empty',
|
||||||
'referer': f'https://services.sheerid.com/verify/{self.program_id}/?verificationId={self.verification_id}',
|
'sec-fetch-mode': 'cors',
|
||||||
'accept-encoding': 'gzip, deflate, br, zstd',
|
'sec-fetch-site': 'same-origin',
|
||||||
'accept-language': 'en-US,en-GB;q=0.9,en;q=0.8',
|
|
||||||
'priority': 'u=1, i',
|
'priority': 'u=1, i',
|
||||||
}
|
}
|
||||||
|
base_headers.update(self.fingerprint.get_headers())
|
||||||
|
return base_headers
|
||||||
|
|
||||||
|
def verify(self, email_address: str) -> Dict:
|
||||||
try:
|
try:
|
||||||
response = self.http_client.request(
|
self.log(f"使用邮箱: {email_address}")
|
||||||
method=method, url=url, json=body, headers=headers
|
time.sleep(random.uniform(1.0, 2.5))
|
||||||
)
|
|
||||||
try:
|
|
||||||
data = response.json()
|
|
||||||
except Exception:
|
|
||||||
data = response.text
|
|
||||||
return data, response.status_code
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"SheerID 请求失败: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
def verify(self) -> Dict:
|
# --- 步骤 0: 握手 ---
|
||||||
try:
|
self.log(">>> 步骤 0: 初始化会话...")
|
||||||
|
init_url = f"https://services.sheerid.com/verify/{self.program_id}/?verificationId={self.verification_id}"
|
||||||
|
|
||||||
email_address = generate_new_email(10,0)
|
init_headers = {
|
||||||
time.sleep(30)
|
'host': 'services.sheerid.com',
|
||||||
submit_data, submit_status = self._sheerid_request(
|
'upgrade-insecure-requests': '1',
|
||||||
"POST",
|
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
|
||||||
f"https://services.sheerid.com/rest/v2/verification/{self.verification_id}/step/collectMilitaryStatus",
|
'sec-fetch-dest': 'document',
|
||||||
{'status': 'VETERAN'},
|
'sec-fetch-mode': 'navigate',
|
||||||
)
|
'sec-fetch-site': 'none',
|
||||||
|
'sec-fetch-user': '?1',
|
||||||
|
}
|
||||||
|
init_headers.update(self.fingerprint.get_headers())
|
||||||
|
|
||||||
if submit_status != 200:
|
self.http_client.get(init_url, headers=init_headers)
|
||||||
raise Exception(f"提交状态 失败 (状态码 {submit_status}): {submit_data}")
|
time.sleep(random.uniform(0.5, 1.5))
|
||||||
if submit_data.get("currentStep") != "collectInactiveMilitaryPersonalInfo":
|
|
||||||
error_msg = ", ".join(submit_data.get("errorIds", ["Unknown error"]))
|
|
||||||
raise Exception(f"提交状态 错误: {error_msg}")
|
|
||||||
|
|
||||||
|
# --- 步骤 1: 提交 Status ---
|
||||||
|
self.log(">>> 步骤 1: 提交 Status...")
|
||||||
|
step1_url = f"https://services.sheerid.com/rest/v2/verification/{self.verification_id}/step/collectMilitaryStatus"
|
||||||
|
|
||||||
submissionUrl = submit_data.get("submissionUrl")
|
resp1 = self.http_client.post(step1_url, json={'status': 'VETERAN'}, headers=self._get_common_headers())
|
||||||
verificationId = re.search(r"verificationId=([a-f0-9]+)", submissionUrl, re.IGNORECASE)
|
if resp1.status_code != 200:
|
||||||
|
raise Exception(f"步骤 1 失败 ({resp1.status_code}): {resp1.text}")
|
||||||
|
|
||||||
random_string = ''.join(random.choices(string.ascii_uppercase, k=5))
|
data1 = resp1.json()
|
||||||
|
time.sleep(random.uniform(0.5, 1.5))
|
||||||
|
|
||||||
|
# --- 步骤 2: 提交个人信息 ---
|
||||||
|
submissionUrl = data1.get("submissionUrl")
|
||||||
|
current_vid = self.verification_id
|
||||||
|
|
||||||
|
new_vid_match = re.search(r"verificationId=([a-f0-9]+)", submissionUrl, re.IGNORECASE)
|
||||||
|
if new_vid_match:
|
||||||
|
new_vid = new_vid_match.group(1)
|
||||||
|
if new_vid != self.verification_id:
|
||||||
|
current_vid = new_vid
|
||||||
|
|
||||||
verify_body = {
|
verify_body = {
|
||||||
"firstName": "KELLY " + random_string,
|
"firstName": self.identity.first_name,
|
||||||
"lastName": "BURKHART",
|
"lastName": self.identity.last_name,
|
||||||
"birthDate": "1981-05-14",
|
"birthDate": self.identity.birth_date,
|
||||||
"email": email_address,
|
"email": email_address,
|
||||||
"phoneNumber": "",
|
"phoneNumber": "",
|
||||||
"organization": {
|
"organization": {"id": 4070, "name": "Army"},
|
||||||
"id": 4070,
|
"dischargeDate": self.identity.discharge_date,
|
||||||
"name": "Army"
|
'deviceFingerprintHash': self.device_fingerprint_hash,
|
||||||
},
|
|
||||||
"dischargeDate": "2025-10-29",
|
|
||||||
"locale": "en-US",
|
"locale": "en-US",
|
||||||
"country": "US",
|
"country": "US",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"marketConsentValue": False,
|
"marketConsentValue": False,
|
||||||
"refererUrl": f"https://services.sheerid.com/verify/{self.program_id}/?verificationId={self.verification_id}",
|
"refererUrl": f"https://services.sheerid.com/verify/{self.program_id}/?verificationId={current_vid}",
|
||||||
"verificationId": verificationId,
|
"verificationId": current_vid,
|
||||||
"flags": "{\"doc-upload-considerations\":\"default\",\"doc-upload-may24\":\"default\",\"doc-upload-redesign-use-legacy-message-keys\":false,\"docUpload-assertion-checklist\":\"default\",\"include-cvec-field-france-student\":\"not-labeled-optional\",\"org-search-overlay\":\"default\",\"org-selected-display\":\"default\"}",
|
"flags": "{\"doc-upload-considerations\":\"default\",\"doc-upload-may24\":\"default\",\"doc-upload-redesign-use-legacy-message-keys\":false,\"docUpload-assertion-checklist\":\"default\",\"include-cvec-field-france-student\":\"not-labeled-optional\",\"org-search-overlay\":\"default\",\"org-selected-display\":\"default\"}",
|
||||||
"submissionOptIn": "By submitting the personal information above, I acknowledge that my personal information is being collected under the <a target=\"_blank\" rel=\"noopener noreferrer\" class=\"sid-privacy-policy sid-link\" href=\"https://openai.com/policies/privacy-policy/\">privacy policy</a> of the business from which I am seeking a discount, and I understand that my personal information will be shared with SheerID as a processor/third-party service provider in order for SheerID to confirm my eligibility for a special offer. Contact OpenAI Support for further assistance at support@openai.com"
|
"submissionOptIn": "By submitting..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info_data, info_status = self._sheerid_request(
|
self.log(f">>> 步骤 2: 提交个人信息 ({self.identity.first_name} {self.identity.last_name})...")
|
||||||
"POST",
|
resp2 = self.http_client.post(submissionUrl, json=verify_body, headers=self._get_common_headers(current_vid))
|
||||||
submissionUrl,
|
|
||||||
verify_body,
|
|
||||||
)
|
|
||||||
|
|
||||||
if info_status != 200:
|
if resp2.status_code == 429:
|
||||||
raise Exception(f"提交信息 失败 (状态码 {info_status}): {info_data}")
|
self.log("🛑 触发 429 风控! 建议更换 IP 或暂停运行。")
|
||||||
if info_data.get("currentStep") == "error":
|
raise Exception("Rate Limit / Verification Limit Exceeded",resp2.text)
|
||||||
error_msg = ", ".join(info_data.get("errorIds", ["Unknown error"]))
|
|
||||||
raise Exception(f"提交信息 错误: {error_msg}")
|
|
||||||
|
|
||||||
time.sleep(120)
|
if resp2.status_code != 200:
|
||||||
|
self.log(f"步骤 2 响应: {resp2.text}")
|
||||||
|
raise Exception(f"步骤 2 失败 ({resp2.status_code})")
|
||||||
|
|
||||||
finish_verifying_url = get_sheerid_links_from_mailbox(email_address)
|
data2 = resp2.json()
|
||||||
while True:
|
if data2.get("currentStep") == "error":
|
||||||
if finish_verifying_url:
|
raise Exception(f"逻辑错误: {data2.get('errorIds')} - {data2.get('systemErrorMessage')}")
|
||||||
break
|
|
||||||
time.sleep(120)
|
self.log(f"步骤 2 成功! Current Step: {data2.get('currentStep')}")
|
||||||
finish_verifying_url = get_sheerid_links_from_mailbox(email_address)
|
self.log(f"\n✅ 验证邮件已发送到: {email_address}")
|
||||||
logger.debug("完成验证 " + finish_verifying_url)
|
self.log("请检查邮箱并点击验证链接完成验证!")
|
||||||
|
|
||||||
final_data, final_status = self._sheerid_request(
|
|
||||||
"POST",
|
|
||||||
finish_verifying_url,
|
|
||||||
)
|
|
||||||
if final_status != 200:
|
|
||||||
raise Exception(f"完成验证 失败 (状态码 {submit_status}): {submit_data}")
|
|
||||||
|
|
||||||
logger.debug(f"完成验证 {json.dumps(final_data, indent=2)}")
|
|
||||||
|
|
||||||
time.sleep(30)
|
|
||||||
delete_mailbox(email_address)
|
|
||||||
# 不做状态轮询,直接返回等待审核
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"pending": True,
|
"email": email_address,
|
||||||
"verification_id": self.verification_id,
|
"message": "验证邮件已发送,请检查邮箱"
|
||||||
"redirect_url": final_data.get("redirectUrl"),
|
|
||||||
"status": final_data,
|
|
||||||
}
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ 失败: {e}")
|
||||||
|
# 删除邮箱
|
||||||
|
if hasattr(self, 'mail'):
|
||||||
|
self.mail.delete_mailbox(email_address)
|
||||||
|
self.log(f"邮箱 {email_address} 已删除")
|
||||||
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
|
# 增加一个验证邮箱的token的步骤
|
||||||
|
def verify_email_token(self, email_token):
|
||||||
|
try:
|
||||||
|
self.log(">>> 步骤 3: 验证邮箱 token...")
|
||||||
|
step3_url = f"https://services.sheerid.com/rest/v2/verification/{self.verification_id}/step/emailLoop"
|
||||||
|
|
||||||
|
resp3 = self.http_client.post(step3_url, json={"emailToken": email_token, "deviceFingerprintHash": self.device_fingerprint_hash}, headers=self._get_common_headers())
|
||||||
|
if resp3.status_code != 200:
|
||||||
|
raise Exception(f"步骤 3 失败 ({resp3.status_code}): {resp3.text}")
|
||||||
|
|
||||||
|
data3 = resp3.json()
|
||||||
|
time.sleep(random.uniform(0.5, 1.5))
|
||||||
|
|
||||||
|
if data3.get("currentStep") == "error":
|
||||||
|
raise Exception(f"逻辑错误: {data3.get('errorIds')} - {data3.get('systemErrorMessage')}")
|
||||||
|
self.log(f"步骤 3 成功! Current Step: {data3.get('currentStep')}")
|
||||||
|
return {"success": True, "message": "邮箱 token 验证成功"}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ 失败: {e}")
|
||||||
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
|
def process_verification(verification_url: str, log_callback=None) -> Dict:
|
||||||
|
"""
|
||||||
|
执行完整的验证流程
|
||||||
|
"""
|
||||||
|
def log(msg):
|
||||||
|
logger.info(msg)
|
||||||
|
if log_callback:
|
||||||
|
try:
|
||||||
|
log_callback(msg)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 初始化代理池
|
||||||
|
proxy_pool = None
|
||||||
|
if PROXY_POOL_ENABLED:
|
||||||
|
log("启用代理池模式")
|
||||||
|
if not PROXY_POOL_AUTH:
|
||||||
|
log("⚠️ 代理池已启用但未设置 PROXY_POOL_TOKEN,自动跳过代理池(改用静态代理或直连)")
|
||||||
|
else:
|
||||||
|
proxy_pool = ProxyPool(PROXY_POOL_API, PROXY_POOL_AUTH)
|
||||||
|
|
||||||
|
# 获取邮箱地址
|
||||||
|
mail = MailClient()
|
||||||
|
email_address = mail.generate_email()
|
||||||
|
if not email_address:
|
||||||
|
return {"success": False, "message": "错误: 邮箱地址生成失败"}
|
||||||
|
|
||||||
|
# 解析 URL
|
||||||
|
vid = SheerIDVerifier.parse_verification_id(verification_url)
|
||||||
|
pid = SheerIDVerifier.parse_program_id(verification_url)
|
||||||
|
|
||||||
|
if not vid or not pid:
|
||||||
|
return {"success": False, "message": "URL 解析失败"}
|
||||||
|
|
||||||
|
log("\n开始验证流程...\n")
|
||||||
|
verifier = SheerIDVerifier(vid, pid, proxy_pool=proxy_pool, log_callback=log_callback)
|
||||||
|
res = verifier.verify(email_address)
|
||||||
|
|
||||||
|
if res['success']:
|
||||||
|
log(f"✅ {res.get('message')}")
|
||||||
|
log(f"邮箱: {res.get('email')}")
|
||||||
|
|
||||||
|
# 轮询等待 SheerID 验证邮件
|
||||||
|
emails = None
|
||||||
|
max_retries = 20 # 最大重试次数 (约 100 秒)
|
||||||
|
retry_count = 0
|
||||||
|
|
||||||
|
while retry_count < max_retries:
|
||||||
|
time.sleep(5)
|
||||||
|
retry_count += 1
|
||||||
|
log(f"等待验证邮件... ({retry_count}/{max_retries})")
|
||||||
|
|
||||||
|
emails = mail.get_emails(email_address)
|
||||||
|
if emails and len(emails) > 0:
|
||||||
|
# 检查是否有来自 SheerID 的邮件
|
||||||
|
sheerid_email = next((e for e in emails if e.get('sender') == "Verify@SheerID.com"), None)
|
||||||
|
if sheerid_email:
|
||||||
|
log("收到验证邮件")
|
||||||
|
emails = [sheerid_email] # 只处理这封邮件
|
||||||
|
break
|
||||||
|
|
||||||
|
if not emails or len(emails) == 0:
|
||||||
|
mail.delete_mailbox(email_address)
|
||||||
|
return {"success": False, "message": "超时未收到验证邮件"}
|
||||||
|
|
||||||
|
# 获取邮件内容
|
||||||
|
content = mail.get_email_detail(emails[0].get('id'))
|
||||||
|
|
||||||
|
verification_link = extract_verification_link(content)
|
||||||
|
email_token = extract_email_token(verification_link)
|
||||||
|
|
||||||
|
if not email_token:
|
||||||
|
mail.delete_mailbox(email_address)
|
||||||
|
return {"success": False, "message": "无法提取 email token"}
|
||||||
|
|
||||||
|
# 验证邮箱 token
|
||||||
|
email_result = verifier.verify_email_token(email_token)
|
||||||
|
|
||||||
|
mail.delete_mailbox(email_address)
|
||||||
|
|
||||||
|
if email_result['success']:
|
||||||
|
log("✅ 邮箱 token 验证成功")
|
||||||
|
return {"success": True, "message": "验证流程全部完成", "email": email_address, "details": email_result}
|
||||||
|
else:
|
||||||
|
log("❌ 邮箱 token 验证失败")
|
||||||
|
return {"success": False, "message": "邮箱 token 验证失败", "details": email_result}
|
||||||
|
|
||||||
|
else:
|
||||||
|
return {"success": False, "message": res.get('message')}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ 验证失败: {e}")
|
logger.error(f"验证过程发生异常: {e}")
|
||||||
return {"success": False, "message": str(e), "verification_id": self.verification_id}
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""主函数 - 命令行界面"""
|
print("=" * 50)
|
||||||
import sys
|
print("SheerID 验证工具")
|
||||||
|
print("=" * 50)
|
||||||
print("=" * 60)
|
|
||||||
print("SheerID 学生身份验证工具 (Python版)")
|
|
||||||
print("=" * 60)
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
# 获取验证URL
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
url = sys.argv[1]
|
url = sys.argv[1]
|
||||||
else:
|
else:
|
||||||
url = input("请输入 SheerID 验证 URL: ").strip()
|
url = input("请输入验证 URL: ").strip()
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
print("❌ 错误: 未提供 URL")
|
print("URL 不能为空")
|
||||||
sys.exit(1)
|
return
|
||||||
|
|
||||||
verification_id = SheerIDVerifier.parse_verification_id(url)
|
result = process_verification(url)
|
||||||
program_id = SheerIDVerifier.parse_program_id(url)
|
|
||||||
if not verification_id and not program_id:
|
|
||||||
print("❌ 错误: 无效的验证 ID 格式")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print(f"✅ 解析到验证 ID: {verification_id}\n✅ 解析到验证 ID: {program_id}", end="\n")
|
print("\n" + "="*50)
|
||||||
|
print(f"最终结果: {'成功' if result['success'] else '失败'}")
|
||||||
|
print(result.get('message'))
|
||||||
|
print("="*50)
|
||||||
|
|
||||||
verifier = SheerIDVerifier(verification_id,program_id)
|
|
||||||
result = verifier.verify()
|
|
||||||
|
|
||||||
print()
|
|
||||||
print("=" * 60)
|
|
||||||
print("验证结果:")
|
|
||||||
print("=" * 60)
|
|
||||||
print(f"状态: {'✅ 成功' if result['success'] else '❌ 失败'}")
|
|
||||||
print(f"消息: {result['message']}")
|
|
||||||
if result.get("redirect_url"):
|
|
||||||
print(f"跳转 URL: {result['redirect_url']}")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
return 0 if result["success"] else 1
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
exit(main())
|
main()
|
||||||
|
|||||||
101
bot.py
Normal file
101
bot.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
from telegram import Update
|
||||||
|
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters
|
||||||
|
from autoDabing import process_verification
|
||||||
|
|
||||||
|
# --- 配置 ---
|
||||||
|
BOT_TOKEN = "" # 替换为你的 Bot Token
|
||||||
|
ALLOWED_USER_IDS = [] # 可选:限制允许使用的用户ID,为空则不限制
|
||||||
|
|
||||||
|
# --- 日志 ---
|
||||||
|
logging.basicConfig(
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
level=logging.INFO
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
await update.message.reply_text(
|
||||||
|
"欢迎使用 SheerID 验证 Bot!\n"
|
||||||
|
"请直接发送验证 URL 给我也开始验证。\n"
|
||||||
|
"格式: https://services.sheerid.com/verify/..."
|
||||||
|
)
|
||||||
|
|
||||||
|
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
user_id = update.effective_user.id
|
||||||
|
if ALLOWED_USER_IDS and user_id not in ALLOWED_USER_IDS:
|
||||||
|
await update.message.reply_text("⛔️ 您没有权限使用此 Bot。\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
text = update.message.text.strip()
|
||||||
|
|
||||||
|
# 简单检查是否包含 sheerid URL
|
||||||
|
if "sheerid.com/verify" not in text:
|
||||||
|
await update.message.reply_text("⚠️ 请发送有效的 SheerID 验证链接。\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
await update.message.reply_text("🚀 开始验证,请稍候...")
|
||||||
|
|
||||||
|
# 定义日志回调,发送消息给用户
|
||||||
|
# 注意:在多线程中调用 async 函数比较麻烦,这里我们简化处理,
|
||||||
|
# 只在关键节点发送消息,或者使用 asyncio.run_coroutine_threadsafe (如果需要实时反馈)
|
||||||
|
# 为了避免消息过多触发限制,我们可以收集日志或者只发送重要信息。
|
||||||
|
# 这里演示实时发送,但要注意频率。
|
||||||
|
|
||||||
|
last_msg_time = 0
|
||||||
|
main_loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
|
def log_callback(msg):
|
||||||
|
# 这是一个同步回调,运行在 worker 线程中
|
||||||
|
# 我们通过 asyncio.run_coroutine_threadsafe 将发送消息的任务提交回主循环
|
||||||
|
# 为了防止 flood limit,这里可以做一些过滤或缓冲,这里暂且直接发送
|
||||||
|
try:
|
||||||
|
nonlocal last_msg_time
|
||||||
|
now = time.time()
|
||||||
|
if now - last_msg_time < 0.8:
|
||||||
|
return
|
||||||
|
# 简单的过滤,只发送带 emoji 的或者关键信息
|
||||||
|
if any(char in msg for char in ['✅', '❌', '>>>', '🛑', '邮箱']):
|
||||||
|
last_msg_time = now
|
||||||
|
asyncio.run_coroutine_threadsafe(
|
||||||
|
context.bot.send_message(chat_id=update.effective_chat.id, text=msg),
|
||||||
|
main_loop,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"发送日志消息失败: {e}")
|
||||||
|
|
||||||
|
# 在执行器中运行阻塞的验证函数
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
result = await loop.run_in_executor(None, process_verification, text, log_callback)
|
||||||
|
|
||||||
|
if result['success']:
|
||||||
|
await update.message.reply_text(
|
||||||
|
f"🎉 验证成功!\n"
|
||||||
|
f"邮箱: `{result.get('email')}`\n"
|
||||||
|
f"消息: {result.get('message')}",
|
||||||
|
parse_mode='Markdown'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await update.message.reply_text(f"❌ 验证失败: {result.get('message')}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if BOT_TOKEN == "YOUR_BOT_TOKEN_HERE":
|
||||||
|
print("请在 bot.py 中设置 BOT_TOKEN")
|
||||||
|
return
|
||||||
|
|
||||||
|
application = ApplicationBuilder().token(BOT_TOKEN).build()
|
||||||
|
|
||||||
|
start_handler = CommandHandler('start', start)
|
||||||
|
msg_handler = MessageHandler(filters.TEXT & (~filters.COMMAND), handle_message)
|
||||||
|
|
||||||
|
application.add_handler(start_handler)
|
||||||
|
application.add_handler(msg_handler)
|
||||||
|
|
||||||
|
print("Bot 已启动...")
|
||||||
|
application.run_polling()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
12
pyproject.toml
Normal file
12
pyproject.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[project]
|
||||||
|
name = "gptveteran"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"anyio>=4.12.0",
|
||||||
|
"httpx>=0.28.1",
|
||||||
|
"python-telegram-bot>=22.5",
|
||||||
|
"requests>=2.32.5",
|
||||||
|
]
|
||||||
192
uv.lock
generated
Normal file
192
uv.lock
generated
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "4.12.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2026.1.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "charset-normalizer"
|
||||||
|
version = "3.4.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gptveteran"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
{ name = "httpx" },
|
||||||
|
{ name = "python-telegram-bot" },
|
||||||
|
{ name = "requests" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "anyio", specifier = ">=4.12.0" },
|
||||||
|
{ name = "httpx", specifier = ">=0.28.1" },
|
||||||
|
{ name = "python-telegram-bot", specifier = ">=22.5" },
|
||||||
|
{ name = "requests", specifier = ">=2.32.5" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h11"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpcore"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "h11" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpx"
|
||||||
|
version = "0.28.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "httpcore" },
|
||||||
|
{ name = "idna" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.11"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-telegram-bot"
|
||||||
|
version = "22.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "httpx" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/0b/6b/400f88e5c29a270c1c519a3ca8ad0babc650ec63dbfbd1b73babf625ed54/python_telegram_bot-22.5.tar.gz", hash = "sha256:82d4efd891d04132f308f0369f5b5929e0b96957901f58bcef43911c5f6f92f8", size = 1488269, upload-time = "2025-09-27T13:50:27.879Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/c3/340c7520095a8c79455fcf699cbb207225e5b36490d2b9ee557c16a7b21b/python_telegram_bot-22.5-py3-none-any.whl", hash = "sha256:4b7cd365344a7dce54312cc4520d7fa898b44d1a0e5f8c74b5bd9b540d035d16", size = 730976, upload-time = "2025-09-27T13:50:25.93Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests"
|
||||||
|
version = "2.32.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "charset-normalizer" },
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "urllib3" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.15.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urllib3"
|
||||||
|
version = "2.6.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user