Compare commits

..

2 Commits

Author SHA1 Message Date
dela
a3675dacaf Merge branch 'main' of https://github.carrydelahaye.work/sar/gptVeteran
加入了bot,加入了代理池适配
2026-01-07 16:42:47 +08:00
dela
2d509c3807 加入代理 机器人 2026-01-07 16:40:28 +08:00
7 changed files with 887 additions and 359 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.venv/
__pycache__/

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.12

77
README.md Normal file
View 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 会返回使用的邮箱地址。
## ⚠️ 免责声明
本项目仅供学习和研究使用。请勿用于任何非法用途。使用本工具产生的任何后果由使用者自行承担。

View File

@@ -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
def extract_verification_link(content):
"""从邮件内容提取验证链接""" # --- 代理池管理器 ---
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_TOKENAuthorization 为空)")
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: 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('&amp;', '&') return match.group(1).replace('&amp;', '&')
# 备用:直接匹配 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 查询字符串 self.http_client = httpx.Client(timeout=30.0)
logger.debug(f"发送 GET 请求至: {endpoint}")
response = requests.get(endpoint, headers=headers, params=params) def __del__(self):
if hasattr(self, "http_client"):
# 2. 关联请求与响应 self.http_client.close()
logger.info(f"操作 '{operation}' 收到响应,状态码: {response.status_code}")
# 检查 HTTP 错误状态码 def generate_email(self, length: int = 10, domain_index: int = 0) -> str | None:
response.raise_for_status() """生成一个新的临时邮箱地址"""
try:
# 尝试解析 JSON response = self.http_client.get(
data = response.json() f"{self.base_url}/generate",
logger.debug(f"成功解析 JSON 响应: \n{json.dumps(data, indent=2)}") headers=self.headers,
params={'length': length, 'domainIndex': domain_index}
email = data.get('email') )
if email: if response.status_code != 200:
# 3. 增加成功日志 logger.error(f"生成邮箱失败 ({response.status_code}): {response.text}")
logger.info(f"成功 {operation},获取到邮箱: {email}") return None
return email return response.json().get('email')
else: except Exception as e:
logger.error(f"操作 '{operation}' 失败: 响应 JSON 中缺少 'email' 键。") logger.error(f"生成邮箱请求失败: {e}")
return None return None
except requests.exceptions.HTTPError as err:
# 4. 丰富错误上下文 def get_emails(self, mailbox: str) -> list | None:
logger.error(f"操作 '{operation}' 失败,发生 HTTP 错误: {err}") """获取指定邮箱的邮件列表"""
# 在 debug 级别记录完整的服务器响应,避免在 INFO/ERROR 级别刷屏 try:
logger.debug(f"服务器错误响应全文: {err.response.text}") response = self.http_client.get(
return None f"{self.base_url}/emails",
headers=self.headers,
except requests.exceptions.RequestException as err: params={'mailbox': mailbox}
logger.critical(f"操作 '{operation}' 失败,发生严重请求错误 (如网络问题): {err}") )
return None if response.status_code != 200:
logger.error(f"获取邮件列表失败 ({response.status_code}): {response.text}")
except json.JSONDecodeError: return None
logger.error(f"操作 '{operation}' 失败: 无法解析服务器响应为 JSON。") return response.json()
logger.debug(f"服务器返回的非 JSON 内容: '{response.text}'") except Exception as e:
return None logger.error(f"获取邮件列表请求失败: {e}")
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 return None
id = data[0].get('id')
logger.debug(id) def get_email_detail(self, email_id: int) -> str | None:
content = get_mail_detail_from_mailbox(email_address, id) """获取指定邮件的详细内容"""
url = extract_verification_link(content) try:
return url response = self.http_client.get(
f"{self.base_url}/email/{email_id}",
except requests.exceptions.HTTPError as err: headers=self.headers
logger.error(f"操作 '{operation}' 失败: 发生 HTTP 错误: {err}") )
logger.debug(f"服务器错误响应全文: {err.response.text}") if response.status_code != 200:
return None # 发生错误时返回空列表 logger.error(f"获取邮件详情失败 ({response.status_code}): {response.text}")
return None
except requests.exceptions.RequestException as err: data = response.json()
logger.critical(f"操作 '{operation}' 失败: 发生严重请求错误 (如网络问题): {err}") return data.get('html_content') or data.get('content')
return None except Exception as e:
logger.error(f"获取邮件详情请求失败: {e}")
except json.JSONDecodeError: return None
logger.error(f"操作 '{operation}' 失败: 无法解析服务器响应为 JSON。")
logger.debug(f"服务器返回的非 JSON 内容: '{response.text}'") def delete_mailbox(self, address: str) -> bool:
return None """删除指定的邮箱"""
if not address:
return False
def delete_mailbox(email_address: str) -> bool: try:
""" response = self.http_client.delete(
通过 API 删除一个临时邮箱地址(邮箱)。 f"{self.base_url}/mailboxes",
Args: headers=self.headers,
email_address (str): 要删除的邮箱地址。 params={'address': address}
auth_token (str): 用于 API 认证的 Bearer Token。 )
Returns: if response.status_code not in [200, 204]:
bool: 如果成功删除则返回 True否则返回 False。 logger.error(f"删除邮箱失败 ({response.status_code}): {response.text}")
""" return 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 return True
except Exception as e:
# 如果返回 200 OK 并带有 JSON 体 logger.error(f"删除邮箱请求失败: {e}")
data = response.json() return False
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: class SheerIDVerifier:
def __init__(self, verification_id: str,program_id:str) -> None: def __init__(self, verification_id: str, program_id: str, proxy_pool: ProxyPool = None, log_callback=None) -> None:
self.program_id = program_id self.program_id = program_id
self.verification_id = verification_id self.verification_id = verification_id
self.device_fingerprint = self._generate_device_fingerprint() self.fingerprint = RandomFingerprint()
self.http_client = httpx.Client(timeout=30.0) 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): def __del__(self):
if hasattr(self, "http_client"): if hasattr(self, "http_client"):
self.http_client.close() self.http_client.close()
@staticmethod def log(self, msg):
def _generate_device_fingerprint() -> str: logger.info(msg)
chars = '0123456789abcdef' if self.log_callback:
return ''.join(random.choice(chars) for _ in range(32)) try:
self.log_callback(msg)
@staticmethod except Exception:
def normalize_url(url: str) -> str: pass
"""规范化 URL保留原样"""
return url
@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}"
init_headers = {
'host': 'services.sheerid.com',
'upgrade-insecure-requests': '1',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'sec-fetch-user': '?1',
}
init_headers.update(self.fingerprint.get_headers())
self.http_client.get(init_url, headers=init_headers)
time.sleep(random.uniform(0.5, 1.5))
email_address = generate_new_email(10,0) # --- 步骤 1: 提交 Status ---
time.sleep(30) self.log(">>> 步骤 1: 提交 Status...")
submit_data, submit_status = self._sheerid_request( step1_url = f"https://services.sheerid.com/rest/v2/verification/{self.verification_id}/step/collectMilitaryStatus"
"POST",
f"https://services.sheerid.com/rest/v2/verification/{self.verification_id}/step/collectMilitaryStatus", resp1 = self.http_client.post(step1_url, json={'status': 'VETERAN'}, headers=self._get_common_headers())
{'status': 'VETERAN'}, if resp1.status_code != 200:
) raise Exception(f"步骤 1 失败 ({resp1.status_code}): {resp1.text}")
if submit_status != 200: data1 = resp1.json()
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}")
submissionUrl = submit_data.get("submissionUrl")
verificationId = re.search(r"verificationId=([a-f0-9]+)", submissionUrl, re.IGNORECASE)
random_string = ''.join(random.choices(string.ascii_uppercase, k=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..."
} }
} }
self.log(f">>> 步骤 2: 提交个人信息 ({self.identity.first_name} {self.identity.last_name})...")
resp2 = self.http_client.post(submissionUrl, json=verify_body, headers=self._get_common_headers(current_vid))
if resp2.status_code == 429:
self.log("🛑 触发 429 风控! 建议更换 IP 或暂停运行。")
raise Exception("Rate Limit / Verification Limit Exceeded",resp2.text)
if resp2.status_code != 200:
self.log(f"步骤 2 响应: {resp2.text}")
raise Exception(f"步骤 2 失败 ({resp2.status_code})")
data2 = resp2.json()
if data2.get("currentStep") == "error":
raise Exception(f"逻辑错误: {data2.get('errorIds')} - {data2.get('systemErrorMessage')}")
self.log(f"步骤 2 成功! Current Step: {data2.get('currentStep')}")
self.log(f"\n✅ 验证邮件已发送到: {email_address}")
self.log("请检查邮箱并点击验证链接完成验证!")
info_data, info_status = self._sheerid_request(
"POST",
submissionUrl,
verify_body,
)
if info_status != 200:
raise Exception(f"提交信息 失败 (状态码 {info_status}): {info_data}")
if info_data.get("currentStep") == "error":
error_msg = ", ".join(info_data.get("errorIds", ["Unknown error"]))
raise Exception(f"提交信息 错误: {error_msg}")
time.sleep(120)
finish_verifying_url = get_sheerid_links_from_mailbox(email_address)
while True:
if finish_verifying_url:
break
time.sleep(120)
finish_verifying_url = get_sheerid_links_from_mailbox(email_address)
logger.debug("完成验证 " + finish_verifying_url)
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: except Exception as e:
logger.error(f"验证失败: {e}") logger.error(f"❌ 失败: {e}")
return {"success": False, "message": str(e), "verification_id": self.verification_id} # 删除邮箱
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:
logger.error(f"验证过程发生异常: {e}")
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("\n" + "="*50)
print("❌ 错误: 无效的验证 ID 格式") print(f"最终结果: {'成功' if result['success'] else '失败'}")
sys.exit(1) print(result.get('message'))
print("="*50)
print(f"✅ 解析到验证 ID: {verification_id}\n✅ 解析到验证 ID: {program_id}", end="\n")
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
View 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
View 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
View 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" },
]