完成bot基础开发
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
# 复制此文件为 .env 并填写实际值
|
||||
|
||||
# Telegram 通知配置(可选)
|
||||
TELEGRAM_TOKEN=
|
||||
TELEGRAM_TOKEN=8388442374:AAF5v5jZIMn2kvGmWMBfPtPUYuHdC5T59Y8
|
||||
TELEGRAM_CHAT_ID=
|
||||
|
||||
# 线程配置
|
||||
|
||||
@@ -28,6 +28,7 @@ dependencies = [
|
||||
"pycryptodome>=3.23.0",
|
||||
"v-jstools>=0.0.8",
|
||||
"python-telegram-bot>=20.0",
|
||||
"dotenv>=0.9.9",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
154
checker/scripts/gencard.py
Normal file
154
checker/scripts/gencard.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import random
|
||||
import datetime
|
||||
import re
|
||||
|
||||
class CardGenerator:
|
||||
def __init__(self):
|
||||
self.current_year = datetime.datetime.now().year
|
||||
|
||||
def luhn_checksum(self, card_number_str):
|
||||
"""计算 Luhn 校验和"""
|
||||
digits = [int(d) for d in card_number_str]
|
||||
checksum = 0
|
||||
is_second = False
|
||||
for digit in reversed(digits):
|
||||
if is_second:
|
||||
digit *= 2
|
||||
if digit > 9:
|
||||
digit -= 9
|
||||
checksum += digit
|
||||
is_second = not is_second
|
||||
return checksum
|
||||
|
||||
def calculate_check_digit(self, card_number_without_check):
|
||||
"""计算最后一位校验位"""
|
||||
temp_number = card_number_without_check + "0"
|
||||
checksum = self.luhn_checksum(temp_number)
|
||||
if checksum % 10 == 0:
|
||||
return "0"
|
||||
else:
|
||||
return str(10 - (checksum % 10))
|
||||
|
||||
def generate_card_number(self, pattern):
|
||||
"""
|
||||
核心生成逻辑:支持任意位置的 'x'
|
||||
"""
|
||||
# 1. 确定卡号长度标准 (Amex 15位,其他 16位)
|
||||
# 如果 pattern 已经很长,就用 pattern 的长度,否则根据卡头判断
|
||||
target_len = 16
|
||||
if pattern.startswith(('34', '37')):
|
||||
target_len = 15
|
||||
|
||||
# 2. 补全 pattern 到目标长度 (减去最后一位校验位)
|
||||
# 如果输入的 pattern 比如是 "547958" (长度6),需要补 x 到 15位 (留一位给校验)
|
||||
# 如果输入已经包含了 x 且长度足够,则保留原样
|
||||
|
||||
working_pattern = pattern.lower()
|
||||
|
||||
# 移除可能存在的非数字/x字符
|
||||
working_pattern = re.sub(r'[^0-9x]', '', working_pattern)
|
||||
|
||||
# 自动填充 x
|
||||
if len(working_pattern) < target_len:
|
||||
working_pattern += 'x' * (target_len - len(working_pattern))
|
||||
|
||||
# 截断到 倒数第二位 (最后一位是校验位)
|
||||
# 注意:如果用户输入了完整的 16位 pattern 且最后一位不是 x,我们会覆盖它,
|
||||
# 因为 Luhn 算法要求最后一位必须是计算出来的。
|
||||
# 除非用户就是想校验?但在生成逻辑里,我们通常假设最后一位是计算的。
|
||||
base_pattern = working_pattern[:target_len-1]
|
||||
|
||||
generated_prefix = ""
|
||||
for char in base_pattern:
|
||||
if char == 'x':
|
||||
generated_prefix += str(random.randint(0, 9))
|
||||
else:
|
||||
generated_prefix += char
|
||||
|
||||
# 计算校验位
|
||||
check_digit = self.calculate_check_digit(generated_prefix)
|
||||
return generated_prefix + check_digit
|
||||
|
||||
def parse_date_cvv(self, card_num, m_req, y_req, c_req):
|
||||
"""
|
||||
处理日期和CVV的占位符逻辑
|
||||
"""
|
||||
# --- Month ---
|
||||
if m_req in ['(m)', 'rnd', '', None]:
|
||||
month = str(random.randint(1, 12)).zfill(2)
|
||||
else:
|
||||
month = m_req.strip() # 使用用户指定的静态值,如 '05'
|
||||
|
||||
# --- Year ---
|
||||
if y_req in ['(y)', 'rnd', '', None]:
|
||||
year = str(self.current_year + random.randint(2, 5))
|
||||
else:
|
||||
year = y_req.strip() # 使用用户指定的静态值,如 '2023' 或 '23'
|
||||
|
||||
# --- CVV ---
|
||||
if c_req in ['(cvv)', 'rnd', '', None]:
|
||||
if card_num.startswith(('34', '37')):
|
||||
cvv = str(random.randint(1102, 9999))
|
||||
else:
|
||||
cvv = str(random.randint(112, 999))
|
||||
else:
|
||||
cvv = c_req.strip()
|
||||
|
||||
return month, year, cvv
|
||||
|
||||
def process_line(self, raw_input, count=1):
|
||||
"""
|
||||
智能解析输入字符串并生成
|
||||
支持格式:
|
||||
- BIN only: 547958
|
||||
- Pattern: 460723xxxxxxxxxx
|
||||
- Complex: 5319719xxx5xxx87
|
||||
- Full: 434256|11|2022|212
|
||||
- Templates: 434256|(m)|(y)|(cvv)
|
||||
"""
|
||||
# 默认值
|
||||
parts = raw_input.split('|')
|
||||
|
||||
# 提取各个部分,没有的就设为 None,交给 parse_date_cvv 处理默认逻辑
|
||||
pattern_part = parts[0] if len(parts) > 0 else ""
|
||||
month_part = parts[1] if len(parts) > 1 else None
|
||||
year_part = parts[2] if len(parts) > 2 else None
|
||||
cvv_part = parts[3] if len(parts) > 3 else None
|
||||
|
||||
results = []
|
||||
for _ in range(count):
|
||||
# 1. 生成卡号
|
||||
card_num = self.generate_card_number(pattern_part)
|
||||
|
||||
# 2. 处理附属信息
|
||||
m, y, c = self.parse_date_cvv(card_num, month_part, year_part, cvv_part)
|
||||
|
||||
results.append(f"{card_num}|{m}|{y}|{c}")
|
||||
|
||||
return results
|
||||
|
||||
# --- 测试与演示 ---
|
||||
|
||||
if __name__ == "__main__":
|
||||
gen = CardGenerator()
|
||||
|
||||
# 你列出的所有测试用例
|
||||
test_cases = [
|
||||
"547958", # 仅 BIN
|
||||
"460723xxxxxxxxxx", # 带图案
|
||||
"5319719xxx5xxx87", # 专业用户 (混合 pattern)
|
||||
"434256|11|2022|212", # 你自己的固定模式
|
||||
"559917|11|2022|(cvv)", # 随机 CVV
|
||||
"434256|(m)|(y)|(cvv)", # 全随机占位符
|
||||
"434256240669|(m)|(y)|(cvv)", # 短卡号 (没有最后4位) -> 会自动补全
|
||||
"53673712123xxxxx|05|2023|", # 指定长年份
|
||||
"53673712123xxxxx|05|23|" # 指定短年份
|
||||
]
|
||||
|
||||
print(f"{'INPUT FORMAT':<35} | {'GENERATED OUTPUT'}")
|
||||
print("-" * 80)
|
||||
|
||||
for case in test_cases:
|
||||
# 每个案例生成 1 条作为演示
|
||||
output = gen.process_line(case, count=1)[0]
|
||||
print(f"{case:<35} -> {output}")
|
||||
0
checker/scripts/setup_with_uv.sh
Normal file → Executable file
0
checker/scripts/setup_with_uv.sh
Normal file → Executable file
11
checker/scripts/start_bot.sh
Executable file
11
checker/scripts/start_bot.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
# 启动 Telegram Bot
|
||||
|
||||
# 检查是否安装了 python-telegram-bot
|
||||
if ! python -c "import telegram" &> /dev/null; then
|
||||
echo "📦 安装 Bot 依赖..."
|
||||
uv pip install "python-telegram-bot>=20.0"
|
||||
fi
|
||||
|
||||
echo "🚀 启动 Checker Bot..."
|
||||
python -m checker.bot.main
|
||||
3
checker/src/checker/bot/__init__.py
Normal file
3
checker/src/checker/bot/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .main import main
|
||||
|
||||
__all__ = ["main"]
|
||||
221
checker/src/checker/bot/handlers.py
Normal file
221
checker/src/checker/bot/handlers.py
Normal file
@@ -0,0 +1,221 @@
|
||||
import logging
|
||||
import asyncio
|
||||
import re
|
||||
import requests
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from telegram import Update
|
||||
from telegram.ext import ContextTypes
|
||||
from telegram.constants import ParseMode
|
||||
|
||||
from ..models import Card, CheckStatus
|
||||
from ..checkers import StripeChecker
|
||||
from ..cards import generator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 全局线程池,避免每个请求都创建
|
||||
# 可以根据服务器性能调整 max_workers
|
||||
executor = ThreadPoolExecutor(max_workers=5)
|
||||
|
||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""响应 /start 命令"""
|
||||
user = update.effective_user
|
||||
await update.message.reply_html(
|
||||
f"Hi {user.mention_html()}! 👋\n\n"
|
||||
"我是 Gemini Checker Bot。\n\n"
|
||||
"<b>可用命令:</b>\n"
|
||||
"1. <b>/chk</b> - 检测卡片\n"
|
||||
" <code>/chk cc|mm|yy|cvv</code>\n\n"
|
||||
"2. <b>/gen</b> - 生成卡片\n"
|
||||
" <code>/gen 10 536737xxxx</code> (生成10张)\n"
|
||||
" <code>/gen 10 536737xxxx /chk</code> (生成并检测)"
|
||||
)
|
||||
|
||||
async def gen_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""响应 /gen 命令"""
|
||||
# 格式: /gen <count> <pattern> [/chk]
|
||||
if not context.args or len(context.args) < 2:
|
||||
await update.message.reply_html(
|
||||
"❌ <b>格式错误</b>\n\n"
|
||||
"用法:\n"
|
||||
"<code>/gen 10 536737xxxxxxxxxx</code>\n"
|
||||
"<code>/gen 10 536737xxxxxxxxxx /chk</code>"
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
count = int(context.args[0])
|
||||
pattern = context.args[1]
|
||||
except ValueError:
|
||||
await update.message.reply_text("❌ 数量必须是数字。")
|
||||
return
|
||||
|
||||
# 检查是否有 /chk
|
||||
do_check = False
|
||||
if len(context.args) > 2 and '/chk' in context.args[2:]:
|
||||
do_check = True
|
||||
|
||||
# 限制生成数量
|
||||
if count > 1000:
|
||||
await update.message.reply_text("❌ 一次最多生成 1000 张卡片。")
|
||||
return
|
||||
|
||||
# 1. 生成卡片
|
||||
try:
|
||||
cards_list = generator.process_line(pattern, count=count)
|
||||
except Exception as e:
|
||||
await update.message.reply_text(f"❌ 生成失败: {e}")
|
||||
return
|
||||
|
||||
# 2. 如果不需要检测,直接返回文本
|
||||
if not do_check:
|
||||
response_text = "\n".join(cards_list)
|
||||
# 避免消息过长 (Telegram 限制 4096 字符)
|
||||
if len(response_text) > 4000:
|
||||
# 如果太长,保存为文件发送 (简化处理,这里只截断)
|
||||
response_text = response_text[:4000] + "\n... (truncated)"
|
||||
|
||||
await update.message.reply_text(
|
||||
f"✅ 生成了 {count} 张卡片:\n\n<code>{response_text}</code>",
|
||||
parse_mode=ParseMode.HTML
|
||||
)
|
||||
return
|
||||
|
||||
# 3. 如果需要检测,复用检测逻辑
|
||||
# 将生成的卡片列表拼接成字符串,传给 process_input_text
|
||||
input_text = "\n".join(cards_list)
|
||||
|
||||
await update.message.reply_text(
|
||||
f"✅ 已生成 {count} 张卡片,即将开始检测..."
|
||||
)
|
||||
|
||||
await process_input_text(update, context, input_text)
|
||||
|
||||
|
||||
async def chk_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""响应 /chk 命令"""
|
||||
text = update.message.text
|
||||
# 移除命令部分 (例如 /chk 或 /chk@botname)
|
||||
# 使用 regex 替换开头的命令
|
||||
content = re.sub(r'^/\w+(@\w+)?\s*', '', text, count=1, flags=re.MULTILINE)
|
||||
|
||||
if not content.strip():
|
||||
await update.message.reply_text("❌ 请在命令后提供卡片列表。")
|
||||
return
|
||||
|
||||
await process_input_text(update, context, content)
|
||||
|
||||
async def process_input_text(update: Update, context: ContextTypes.DEFAULT_TYPE, text: str) -> None:
|
||||
"""统一处理输入文本"""
|
||||
# 1. 解析卡片
|
||||
cards = []
|
||||
lines = text.splitlines()
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
card = Card.parse(line)
|
||||
if card:
|
||||
cards.append(card)
|
||||
|
||||
if not cards:
|
||||
# 如果是直接消息,可能用户只是发了聊天内容,不是卡片
|
||||
# 如果是 /chk 命令进来的,已经在 chk_command 判空了,但这里是解析后的
|
||||
# 我们只在明确是命令或者看起来像卡片时才报错
|
||||
# 为简单起见,如果解析不到卡片,提示格式错误
|
||||
await update.message.reply_text("❌ 未找到有效卡片格式。请检查格式: cc|mm|yy|cvv")
|
||||
return
|
||||
|
||||
# 2. 发送开始消息
|
||||
status_msg = await update.message.reply_text(
|
||||
f"🔍 收到 {len(cards)} 张卡片,开始检测..."
|
||||
)
|
||||
|
||||
# 3. 初始化检测器
|
||||
# 注意: 这里不传入 telegram token,因为我们要自己控制回复
|
||||
checker = StripeChecker(
|
||||
timeout=20,
|
||||
max_retries=3
|
||||
)
|
||||
|
||||
stats = {
|
||||
"live": 0,
|
||||
"dead": 0,
|
||||
"unknown": 0,
|
||||
"total": len(cards),
|
||||
"checked": 0
|
||||
}
|
||||
|
||||
# 4. 创建并发任务
|
||||
tasks = []
|
||||
# 如果卡片太多,可能需要分批处理,这里简单起见直接全部提交
|
||||
for card in cards:
|
||||
tasks.append(process_card(card, checker, update, stats))
|
||||
|
||||
# 等待所有任务完成
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
# 5. 最终报告
|
||||
report = (
|
||||
f"<b>🏁 检测完成</b>\n\n"
|
||||
f"Total: {stats['total']}\n"
|
||||
f"Live: {stats['live']} ✅\n"
|
||||
f"Dead: {stats['dead']} ❌\n"
|
||||
f"Unknown: {stats['unknown']} ⚠️"
|
||||
)
|
||||
await status_msg.edit_text(report, parse_mode=ParseMode.HTML)
|
||||
|
||||
async def process_card(
|
||||
card: Card,
|
||||
checker: StripeChecker,
|
||||
update: Update,
|
||||
stats: dict
|
||||
):
|
||||
"""异步处理单张卡片"""
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
# 每个任务独立的 Session
|
||||
session = requests.Session()
|
||||
|
||||
try:
|
||||
# 在线程池中运行阻塞的检测逻辑
|
||||
result = await loop.run_in_executor(
|
||||
executor,
|
||||
checker.check,
|
||||
card,
|
||||
session,
|
||||
None # 暂时不使用代理,后续可扩展
|
||||
)
|
||||
|
||||
stats["checked"] += 1
|
||||
|
||||
if result.status == CheckStatus.LIVE:
|
||||
stats["live"] += 1
|
||||
await send_live_alert(update, result)
|
||||
elif result.status == CheckStatus.DEAD:
|
||||
stats["dead"] += 1
|
||||
else:
|
||||
stats["unknown"] += 1
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking card {card.formatted}: {e}")
|
||||
stats["unknown"] += 1
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
async def send_live_alert(update: Update, result):
|
||||
"""发送存活卡片通知"""
|
||||
card = result.card
|
||||
bin_info = result.bin_info
|
||||
|
||||
msg = (
|
||||
f"<b>Approved ✅</b>\n\n"
|
||||
f"<b>CC:</b> <code>{card.formatted}</code>\n"
|
||||
f"<b>Status:</b> {result.message}\n"
|
||||
f"<b>Info:</b> {bin_info.brand} - {bin_info.card_type}\n"
|
||||
f"<b>Country:</b> {bin_info.country} {bin_info.country_flag}\n"
|
||||
f"<b>Bank:</b> {bin_info.bank}"
|
||||
)
|
||||
# 使用 reply_html 回复原始消息
|
||||
await update.message.reply_html(msg)
|
||||
|
||||
45
checker/src/checker/bot/main.py
Normal file
45
checker/src/checker/bot/main.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import os
|
||||
import logging
|
||||
from telegram.ext import Application, CommandHandler, MessageHandler, filters
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from .handlers import start, chk_command, gen_command
|
||||
|
||||
# 加载 .env
|
||||
load_dotenv()
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def main():
|
||||
"""启动 Telegram Bot"""
|
||||
token = os.getenv("TELEGRAM_TOKEN")
|
||||
if not token:
|
||||
logger.error("❌ 错误: 未在环境变量中找到 TELEGRAM_TOKEN")
|
||||
print("请在 .env 文件中配置 TELEGRAM_TOKEN")
|
||||
return
|
||||
|
||||
# 检查是否配置了限制用户
|
||||
# 这里简单实现:如果有配置 ALLOWED_IDS,则在 handler 中过滤
|
||||
# 目前暂未在 handlers.py 中实现过滤逻辑,默认允许所有知道 bot 的人使用
|
||||
|
||||
print("🚀 正在启动 Checker Bot...")
|
||||
|
||||
application = Application.builder().token(token).build()
|
||||
|
||||
# 注册处理器
|
||||
application.add_handler(CommandHandler("start", start))
|
||||
application.add_handler(CommandHandler("chk", chk_command))
|
||||
application.add_handler(CommandHandler("gen", gen_command))
|
||||
|
||||
print("✅ Bot 已启动,正在等待消息...")
|
||||
|
||||
# 运行 polling
|
||||
application.run_polling()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,9 +1,12 @@
|
||||
"""卡片处理模块"""
|
||||
from .parser import parse_card_file, deduplicate_cards
|
||||
from .bin_lookup import lookup_bin
|
||||
from .generator import generator, CardGenerator
|
||||
|
||||
__all__ = [
|
||||
'parse_card_file',
|
||||
'deduplicate_cards',
|
||||
'lookup_bin',
|
||||
"parse_card_file",
|
||||
"deduplicate_cards",
|
||||
"lookup_bin",
|
||||
"generator",
|
||||
"CardGenerator"
|
||||
]
|
||||
|
||||
114
checker/src/checker/cards/generator.py
Normal file
114
checker/src/checker/cards/generator.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import random
|
||||
import datetime
|
||||
import re
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
class CardGenerator:
|
||||
"""信用卡生成器"""
|
||||
|
||||
def __init__(self):
|
||||
self.current_year = datetime.datetime.now().year
|
||||
|
||||
def luhn_checksum(self, card_number_str: str) -> int:
|
||||
"""计算 Luhn 校验和"""
|
||||
digits = [int(d) for d in card_number_str]
|
||||
checksum = 0
|
||||
is_second = False
|
||||
for digit in reversed(digits):
|
||||
if is_second:
|
||||
digit *= 2
|
||||
if digit > 9:
|
||||
digit -= 9
|
||||
checksum += digit
|
||||
is_second = not is_second
|
||||
return checksum
|
||||
|
||||
def calculate_check_digit(self, card_number_without_check: str) -> str:
|
||||
"""计算最后一位校验位"""
|
||||
temp_number = card_number_without_check + "0"
|
||||
checksum = self.luhn_checksum(temp_number)
|
||||
if checksum % 10 == 0:
|
||||
return "0"
|
||||
else:
|
||||
return str(10 - (checksum % 10))
|
||||
|
||||
def generate_card_number(self, pattern: str) -> str:
|
||||
"""
|
||||
核心生成逻辑:支持任意位置的 'x'
|
||||
"""
|
||||
# 1. 确定卡号长度标准 (Amex 15位,其他 16位)
|
||||
target_len = 16
|
||||
if pattern.startswith(('34', '37')):
|
||||
target_len = 15
|
||||
|
||||
working_pattern = pattern.lower()
|
||||
|
||||
# 移除可能存在的非数字/x字符
|
||||
working_pattern = re.sub(r'[^0-9x]', '', working_pattern)
|
||||
|
||||
# 自动填充 x
|
||||
if len(working_pattern) < target_len:
|
||||
working_pattern += 'x' * (target_len - len(working_pattern))
|
||||
|
||||
# 截断到 倒数第二位 (最后一位是校验位)
|
||||
base_pattern = working_pattern[:target_len-1]
|
||||
|
||||
generated_prefix = ""
|
||||
for char in base_pattern:
|
||||
if char == 'x':
|
||||
generated_prefix += str(random.randint(0, 9))
|
||||
else:
|
||||
generated_prefix += char
|
||||
|
||||
# 计算校验位
|
||||
check_digit = self.calculate_check_digit(generated_prefix)
|
||||
return generated_prefix + check_digit
|
||||
|
||||
def parse_date_cvv(self, card_num: str, m_req: Optional[str], y_req: Optional[str], c_req: Optional[str]) -> Tuple[str, str, str]:
|
||||
"""
|
||||
处理日期和CVV的占位符逻辑
|
||||
"""
|
||||
# --- Month ---
|
||||
if m_req in ['(m)', 'rnd', '', None]:
|
||||
month = str(random.randint(1, 12)).zfill(2)
|
||||
else:
|
||||
month = m_req.strip()
|
||||
|
||||
# --- Year ---
|
||||
if y_req in ['(y)', 'rnd', '', None]:
|
||||
year = str(self.current_year + random.randint(2, 5))
|
||||
else:
|
||||
year = y_req.strip()
|
||||
|
||||
# --- CVV ---
|
||||
if c_req in ['(cvv)', 'rnd', '', None]:
|
||||
if card_num.startswith(('34', '37')):
|
||||
cvv = str(random.randint(1102, 9999))
|
||||
else:
|
||||
cvv = str(random.randint(112, 999))
|
||||
else:
|
||||
cvv = c_req.strip()
|
||||
|
||||
return month, year, cvv
|
||||
|
||||
def process_line(self, raw_input: str, count: int = 1) -> List[str]:
|
||||
"""
|
||||
生成指定数量的卡片
|
||||
"""
|
||||
parts = raw_input.split('|')
|
||||
|
||||
pattern_part = parts[0] if len(parts) > 0 else ""
|
||||
month_part = parts[1] if len(parts) > 1 else None
|
||||
year_part = parts[2] if len(parts) > 2 else None
|
||||
cvv_part = parts[3] if len(parts) > 3 else None
|
||||
|
||||
results = []
|
||||
for _ in range(count):
|
||||
card_num = self.generate_card_number(pattern_part)
|
||||
m, y, c = self.parse_date_cvv(card_num, month_part, year_part, cvv_part)
|
||||
results.append(f"{card_num}|{m}|{y}|{c}")
|
||||
|
||||
return results
|
||||
|
||||
# 全局实例
|
||||
generator = CardGenerator()
|
||||
87
checker/uv.lock
generated
87
checker/uv.lock
generated
@@ -2,6 +2,20 @@ version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.10"
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.12.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||
{ 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 = "black"
|
||||
version = "25.12.0"
|
||||
@@ -150,9 +164,11 @@ version = "0.2.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "colorama" },
|
||||
{ name = "dotenv" },
|
||||
{ name = "faker" },
|
||||
{ name = "pycryptodome" },
|
||||
{ name = "pyfiglet" },
|
||||
{ name = "python-telegram-bot" },
|
||||
{ name = "requests" },
|
||||
{ name = "urllib3" },
|
||||
{ name = "v-jstools" },
|
||||
@@ -171,12 +187,14 @@ dev = [
|
||||
requires-dist = [
|
||||
{ name = "black", marker = "extra == 'dev'", specifier = ">=23.0.0" },
|
||||
{ name = "colorama", specifier = ">=0.4.6" },
|
||||
{ name = "dotenv", specifier = ">=0.9.9" },
|
||||
{ name = "faker", specifier = ">=20.0.0" },
|
||||
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.7.0" },
|
||||
{ name = "pycryptodome", specifier = ">=3.23.0" },
|
||||
{ name = "pyfiglet", specifier = ">=1.0.2" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" },
|
||||
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" },
|
||||
{ name = "python-telegram-bot", specifier = ">=20.0" },
|
||||
{ name = "requests", specifier = ">=2.31.0" },
|
||||
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" },
|
||||
{ name = "urllib3", specifier = ">=2.0.0" },
|
||||
@@ -309,6 +327,17 @@ toml = [
|
||||
{ name = "tomli", marker = "python_full_version <= '3.11'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.9.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "python-dotenv" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.3.1"
|
||||
@@ -333,6 +362,43 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/5a/26cdb1b10a55ac6eb11a738cea14865fa753606c4897d7be0f5dc230df00/faker-39.0.0-py3-none-any.whl", hash = "sha256:c72f1fca8f1a24b8da10fcaa45739135a19772218ddd61b86b7ea1b8c790dce7", size = 1980775, upload-time = "2025-12-17T19:19:02.926Z" },
|
||||
]
|
||||
|
||||
[[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"
|
||||
@@ -600,6 +666,27 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
|
||||
]
|
||||
|
||||
[[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 = "pytokens"
|
||||
version = "0.3.0"
|
||||
|
||||
Reference in New Issue
Block a user