完成bot基础开发

This commit is contained in:
dela
2026-01-06 12:06:12 +08:00
parent b1b2b4827b
commit 919a9fff83
11 changed files with 643 additions and 4 deletions

View File

@@ -2,7 +2,7 @@
# 复制此文件为 .env 并填写实际值 # 复制此文件为 .env 并填写实际值
# Telegram 通知配置(可选) # Telegram 通知配置(可选)
TELEGRAM_TOKEN= TELEGRAM_TOKEN=8388442374:AAF5v5jZIMn2kvGmWMBfPtPUYuHdC5T59Y8
TELEGRAM_CHAT_ID= TELEGRAM_CHAT_ID=
# 线程配置 # 线程配置

View File

@@ -28,6 +28,7 @@ dependencies = [
"pycryptodome>=3.23.0", "pycryptodome>=3.23.0",
"v-jstools>=0.0.8", "v-jstools>=0.0.8",
"python-telegram-bot>=20.0", "python-telegram-bot>=20.0",
"dotenv>=0.9.9",
] ]
[project.optional-dependencies] [project.optional-dependencies]

154
checker/scripts/gencard.py Normal file
View 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
View File

11
checker/scripts/start_bot.sh Executable file
View 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

View File

@@ -0,0 +1,3 @@
from .main import main
__all__ = ["main"]

View 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)

View 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()

View File

@@ -1,9 +1,12 @@
"""卡片处理模块""" """卡片处理模块"""
from .parser import parse_card_file, deduplicate_cards from .parser import parse_card_file, deduplicate_cards
from .bin_lookup import lookup_bin from .bin_lookup import lookup_bin
from .generator import generator, CardGenerator
__all__ = [ __all__ = [
'parse_card_file', "parse_card_file",
'deduplicate_cards', "deduplicate_cards",
'lookup_bin', "lookup_bin",
"generator",
"CardGenerator"
] ]

View 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
View File

@@ -2,6 +2,20 @@ version = 1
revision = 3 revision = 3
requires-python = ">=3.10" 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]] [[package]]
name = "black" name = "black"
version = "25.12.0" version = "25.12.0"
@@ -150,9 +164,11 @@ version = "0.2.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "colorama" }, { name = "colorama" },
{ name = "dotenv" },
{ name = "faker" }, { name = "faker" },
{ name = "pycryptodome" }, { name = "pycryptodome" },
{ name = "pyfiglet" }, { name = "pyfiglet" },
{ name = "python-telegram-bot" },
{ name = "requests" }, { name = "requests" },
{ name = "urllib3" }, { name = "urllib3" },
{ name = "v-jstools" }, { name = "v-jstools" },
@@ -171,12 +187,14 @@ dev = [
requires-dist = [ requires-dist = [
{ name = "black", marker = "extra == 'dev'", specifier = ">=23.0.0" }, { name = "black", marker = "extra == 'dev'", specifier = ">=23.0.0" },
{ name = "colorama", specifier = ">=0.4.6" }, { name = "colorama", specifier = ">=0.4.6" },
{ name = "dotenv", specifier = ">=0.9.9" },
{ name = "faker", specifier = ">=20.0.0" }, { name = "faker", specifier = ">=20.0.0" },
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.7.0" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.7.0" },
{ name = "pycryptodome", specifier = ">=3.23.0" }, { name = "pycryptodome", specifier = ">=3.23.0" },
{ name = "pyfiglet", specifier = ">=1.0.2" }, { name = "pyfiglet", specifier = ">=1.0.2" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" },
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.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 = "requests", specifier = ">=2.31.0" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" },
{ name = "urllib3", specifier = ">=2.0.0" }, { name = "urllib3", specifier = ">=2.0.0" },
@@ -309,6 +327,17 @@ toml = [
{ name = "tomli", marker = "python_full_version <= '3.11'" }, { 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]] [[package]]
name = "exceptiongroup" name = "exceptiongroup"
version = "1.3.1" 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" }, { 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]] [[package]]
name = "idna" name = "idna"
version = "3.11" 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" }, { 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]] [[package]]
name = "pytokens" name = "pytokens"
version = "0.3.0" version = "0.3.0"