Compare commits
8 Commits
382c345961
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1287fe7ec8 | |||
| a097abf5bc | |||
| 73c838d331 | |||
| 5268ecbf46 | |||
| fa59cccd5a | |||
| f397916816 | |||
| c5d096b19b | |||
| 330cbcecd3 |
@@ -1,3 +1,6 @@
|
||||
# Telegram Bot Configuration
|
||||
# Copy this file to .env and fill in your actual bot token.
|
||||
BOT_TOKEN=123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ
|
||||
GROUP_CHAT_ID=-100123456789
|
||||
ADMIN_IDS=123456789,987654321
|
||||
DISABLE_CAPTCHA=False
|
||||
|
||||
@@ -174,3 +174,6 @@ cython_debug/
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
|
||||
# Database
|
||||
bot.db
|
||||
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.system.id="pyproject.toml" type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="uv (Frankenstein-bot)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/frankenstein-bot.iml" filepath="$PROJECT_DIR$/.idea/frankenstein-bot.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,2 +1,71 @@
|
||||
# Anom-Bot
|
||||
# AWB (Awfully Written Bot)
|
||||
|
||||
A Telegram bot designed for secure message forwarding with captcha verification.
|
||||
|
||||
## Features
|
||||
|
||||
- **Captcha Verification**: Users have to solve an image-based captcha before their messages are forwarded (can be disabled).
|
||||
- **Support for All Media**: Forwarding works for text, photos, videos, documents, audio, and voice messages.
|
||||
- **Message Redirection**: Verified messages are automatically redirected to the admins.
|
||||
- **Anonymized Forwarding**: Messages are forwarded with a custom prefix `Forwarding, ID [MSG_ID]:`, maintaining privacy while allowing admins to track messages.
|
||||
- **Locale Support**: Built-in support for multiple languages (defaulting to English).
|
||||
- **Admin Panel**: Authorized admins can access usage statistics and message logs.
|
||||
|
||||
## Setup
|
||||
|
||||
1. **Clone the repository**:
|
||||
```bash
|
||||
git clone <repository_url>
|
||||
cd AWB
|
||||
```
|
||||
|
||||
2. **Install dependencies**:
|
||||
This project uses `uv` for dependency management.
|
||||
```bash
|
||||
uv sync
|
||||
```
|
||||
Or using pip:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **Configure environment variables**:
|
||||
Copy `.env.dist` to `.env` and fill in your details:
|
||||
```bash
|
||||
cp .env.dist .env
|
||||
```
|
||||
Edit `.env`:
|
||||
- `BOT_TOKEN`: Your Telegram Bot token from @BotFather.
|
||||
- `GROUP_CHAT_ID`: The ID of the group where messages should be redirected.
|
||||
- `ADMIN_IDS`: A comma-separated list of Telegram User IDs who can access admin commands.
|
||||
- `DISABLE_CAPTCHA`: Set to `True` to disable the captcha verification step (optional, defaults to `False`).
|
||||
|
||||
## Usage
|
||||
|
||||
### For Users
|
||||
1. Start the bot with `/start`.
|
||||
2. Send any message or media.
|
||||
3. Solve the image captcha by typing the characters shown.
|
||||
4. Once solved, your message is redirected to the admins.
|
||||
|
||||
### For Admins
|
||||
- `/stats`: View total user count.
|
||||
- `/logs`: See a list of the 10 most recent redirected messages.
|
||||
- `/view <ID>`: View detailed information about a specific message by its ID.
|
||||
- `/ban <USER_ID>`: Block a user from using the bot.
|
||||
- `/unban <USER_ID>`: Unblock a user.
|
||||
|
||||
## Commands
|
||||
|
||||
- `/start`: Initialize the bot and receive a welcome message.
|
||||
- `/id`: Retrieve the ID of the current chat (useful for configuration).
|
||||
|
||||
## Adding Locales
|
||||
|
||||
The bot supports multiple languages. To add a new locale:
|
||||
|
||||
1. Create a new JSON file in the `locales/` directory named after the language code (e.g., `fr.json` for French).
|
||||
2. Copy the keys from `locales/en.json` and provide the translations for your language.
|
||||
3. The bot will automatically load the new locale on startup.
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,13 @@ class Settings(BaseSettings):
|
||||
Application settings loaded from environment variables or a .env file.
|
||||
"""
|
||||
bot_token: SecretStr
|
||||
group_chat_id: int
|
||||
admin_ids: str
|
||||
disable_captcha: bool = False
|
||||
|
||||
@property
|
||||
def admins(self) -> list[int]:
|
||||
return [int(x.strip()) for x in self.admin_ids.split(",") if x.strip().isdigit()]
|
||||
|
||||
# Tell Pydantic to read from a .env file if it exists, ignoring extra fields
|
||||
model_config = SettingsConfigDict(
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from aiogram import Router
|
||||
from . import user, admin
|
||||
|
||||
from . import user, admin, get_chat_id
|
||||
|
||||
def get_handlers_router() -> Router:
|
||||
"""
|
||||
Combines all feature routers (user, admin, etc.) into one main router.
|
||||
"""
|
||||
router = Router()
|
||||
router.include_router(user.router)
|
||||
router.include_router(admin.router)
|
||||
router.include_router(get_chat_id.router)
|
||||
router.include_router(user.router)
|
||||
return router
|
||||
|
||||
+70
-8
@@ -1,14 +1,76 @@
|
||||
from aiogram import Router
|
||||
from aiogram import Router, F
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import Message
|
||||
from bot.config import settings
|
||||
from bot.utils.db import db
|
||||
from bot.utils.i18n import _
|
||||
|
||||
router = Router()
|
||||
|
||||
# Simple middleware-like check for admin
|
||||
def is_admin(message: Message):
|
||||
return message.from_user.id in settings.admins
|
||||
|
||||
@router.message(Command("admin"))
|
||||
async def cmd_admin(message: Message):
|
||||
"""
|
||||
Handles /admin command (placeholder).
|
||||
In production, you'd restrict this using a custom filter or admin ID list.
|
||||
"""
|
||||
await message.answer("Welcome to the Admin panel. Currently, no admin tasks are configured.")
|
||||
@router.message(Command("stats"), F.from_user.id.in_(settings.admins))
|
||||
async def cmd_stats(message: Message, locale: str):
|
||||
stats = db.get_stats()
|
||||
await message.answer(_("stats_title", locale=locale) + "\n" + _("total_users", locale=locale, count=stats["user_count"]))
|
||||
|
||||
@router.message(Command("logs"), F.from_user.id.in_(settings.admins))
|
||||
async def cmd_logs(message: Message, locale: str):
|
||||
logs = db.get_recent_messages()
|
||||
if not logs:
|
||||
await message.answer(_("no_logs", locale=locale))
|
||||
return
|
||||
|
||||
response = _("recent_messages", locale=locale) + "\n"
|
||||
for log in logs:
|
||||
response += f"ID: {log[0]} | {log[1]}: {log[2][:20]}... ({log[3]})\n"
|
||||
|
||||
await message.answer(response)
|
||||
|
||||
@router.message(Command("view"), F.from_user.id.in_(settings.admins))
|
||||
async def cmd_view(message: Message, locale: str):
|
||||
args = message.text.split()
|
||||
if len(args) < 2 or not args[1].isdigit():
|
||||
await message.answer("Usage: /view [msg_id]")
|
||||
return
|
||||
|
||||
msg_id = int(args[1])
|
||||
log = db.get_message_details(msg_id)
|
||||
if not log:
|
||||
await message.answer(_("msg_not_found", locale=locale, id=msg_id))
|
||||
return
|
||||
|
||||
# log: (id, full_name, username, user_id, content_type, timestamp, text)
|
||||
await message.answer(_("msg_details",
|
||||
locale=locale,
|
||||
id=log[0],
|
||||
full_name=log[1],
|
||||
username=log[2],
|
||||
user_id=log[3],
|
||||
content_type=log[4],
|
||||
timestamp=log[5],
|
||||
text=log[6]))
|
||||
|
||||
@router.message(Command("ban"), F.from_user.id.in_(settings.admins))
|
||||
async def cmd_ban(message: Message, locale: str):
|
||||
args = message.text.split()
|
||||
if len(args) < 2 or not args[1].isdigit():
|
||||
await message.answer("Usage: /ban [user_id]")
|
||||
return
|
||||
|
||||
user_id = int(args[1])
|
||||
db.ban_user(user_id)
|
||||
await message.answer(_("user_banned", locale=locale, user_id=user_id))
|
||||
|
||||
@router.message(Command("unban"), F.from_user.id.in_(settings.admins))
|
||||
async def cmd_unban(message: Message, locale: str):
|
||||
args = message.text.split()
|
||||
if len(args) < 2 or not args[1].isdigit():
|
||||
await message.answer("Usage: /unban [user_id]")
|
||||
return
|
||||
|
||||
user_id = int(args[1])
|
||||
db.unban_user(user_id)
|
||||
await message.answer(_("user_unbanned", locale=locale, user_id=user_id))
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
from aiogram import Router
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import Message
|
||||
from bot.utils.i18n import _
|
||||
|
||||
router = Router()
|
||||
|
||||
@router.message(Command("id"))
|
||||
async def cmd_id(message: Message, locale: str):
|
||||
await message.answer(_("id_command", locale=locale, id=message.chat.id))
|
||||
+131
-48
@@ -1,56 +1,139 @@
|
||||
from aiogram import F, Router
|
||||
from aiogram.filters import Command, CommandStart
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiogram import Router, F, Bot, html
|
||||
from aiogram.filters import CommandStart
|
||||
from aiogram.types import Message, BufferedInputFile
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
|
||||
from bot.keyboards.inline import get_links_keyboard
|
||||
from bot.keyboards.reply import get_main_menu_keyboard
|
||||
from bot.config import settings
|
||||
from bot.utils.captcha import generate_captcha
|
||||
from bot.utils.db import db
|
||||
from bot.utils.i18n import _
|
||||
|
||||
router = Router()
|
||||
|
||||
class CaptchaStates(StatesGroup):
|
||||
waiting_for_captcha = State()
|
||||
|
||||
@router.message(CommandStart())
|
||||
async def cmd_start(message: Message):
|
||||
"""
|
||||
Handles /start command. Greets user and sends the main reply menu.
|
||||
"""
|
||||
await message.answer(
|
||||
f"Hello, {message.from_user.full_name}! Welcome to Frankenstein Bot.\n"
|
||||
f"This bot is bootstrapped with a modular and clean structure.",
|
||||
reply_markup=get_main_menu_keyboard(),
|
||||
async def cmd_start(message: Message, locale: str):
|
||||
db.log_user(message.from_user)
|
||||
welcome_key = "welcome_no_captcha" if settings.disable_captcha else "welcome"
|
||||
await message.answer(_(welcome_key, locale=locale))
|
||||
|
||||
@router.message(CaptchaStates.waiting_for_captcha)
|
||||
async def process_captcha(message: Message, state: FSMContext, bot: Bot, locale: str):
|
||||
data = await state.get_data()
|
||||
correct_captcha = data.get("captcha_text")
|
||||
original_msg_id = data.get("original_msg_id")
|
||||
|
||||
if message.text and message.text.upper() == correct_captcha:
|
||||
await state.clear()
|
||||
|
||||
try:
|
||||
db_msg_id = data.get("db_msg_id", "N/A")
|
||||
prefix = _("forwarding_prefix", locale=locale, id=db_msg_id)
|
||||
target_chat_id = settings.group_chat_id
|
||||
|
||||
if data.get('has_media'):
|
||||
new_caption = html.quote(prefix)
|
||||
if data.get('original_caption'):
|
||||
new_caption += f"\n{html.quote(data.get('original_caption'))}"
|
||||
|
||||
await bot.copy_message(
|
||||
chat_id=target_chat_id,
|
||||
from_chat_id=message.chat.id,
|
||||
message_id=original_msg_id,
|
||||
caption=new_caption,
|
||||
parse_mode=None # Use default or specific? Default is HTML.
|
||||
)
|
||||
elif data.get('is_text'):
|
||||
await bot.send_message(
|
||||
chat_id=target_chat_id,
|
||||
text=f"{html.quote(prefix)}\n{html.quote(data.get('original_text'))}"
|
||||
)
|
||||
else:
|
||||
# Other types (stickers, etc.)
|
||||
await bot.copy_message(
|
||||
chat_id=target_chat_id,
|
||||
from_chat_id=message.chat.id,
|
||||
message_id=original_msg_id
|
||||
)
|
||||
await bot.send_message(chat_id=target_chat_id, text=html.quote(prefix))
|
||||
|
||||
await message.answer(_("captcha_solved", locale=locale))
|
||||
|
||||
except Exception as e:
|
||||
await message.answer(_("captcha_error", locale=locale))
|
||||
print(f"Error forwarding: {e}")
|
||||
else:
|
||||
# Generate new captcha
|
||||
captcha_text, captcha_img = generate_captcha()
|
||||
await state.update_data(captcha_text=captcha_text)
|
||||
await message.answer_photo(
|
||||
BufferedInputFile(captcha_img, filename="captcha.png"),
|
||||
caption=_("incorrect_captcha", locale=locale)
|
||||
)
|
||||
|
||||
@router.message(F.chat.type == "private")
|
||||
async def handle_any_message(message: Message, state: FSMContext, bot: Bot, locale: str):
|
||||
# Log everything
|
||||
db_msg_id = db.log_message(message)
|
||||
|
||||
has_media = bool(message.photo or message.video or message.document or message.audio or message.voice)
|
||||
is_text = bool(message.text)
|
||||
|
||||
if settings.disable_captcha:
|
||||
try:
|
||||
wait_message = await message.answer(_("processing_message", locale=locale))
|
||||
prefix = _("forwarding_prefix", locale=locale, id=db_msg_id)
|
||||
target_chat_id = settings.group_chat_id
|
||||
|
||||
if has_media:
|
||||
new_caption = html.quote(prefix)
|
||||
if message.caption:
|
||||
new_caption += f"\n{html.quote(message.caption)}"
|
||||
|
||||
await bot.copy_message(
|
||||
chat_id=target_chat_id,
|
||||
from_chat_id=message.chat.id,
|
||||
message_id=message.message_id,
|
||||
caption=new_caption
|
||||
)
|
||||
elif is_text:
|
||||
await bot.send_message(
|
||||
chat_id=target_chat_id,
|
||||
text=f"{html.quote(prefix)}\n{html.quote(message.text)}"
|
||||
)
|
||||
else:
|
||||
await bot.copy_message(
|
||||
chat_id=target_chat_id,
|
||||
from_chat_id=message.chat.id,
|
||||
message_id=message.message_id
|
||||
)
|
||||
await bot.send_message(chat_id=target_chat_id, text=html.quote(prefix))
|
||||
await wait_message.edit_text(_("message_processed", locale=locale))
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"Error forwarding without captcha: {e}")
|
||||
# Fallback to captcha if something goes wrong?
|
||||
# Or just ignore. Usually, we want to proceed.
|
||||
pass
|
||||
|
||||
# Generate captcha
|
||||
captcha_text, captcha_img = generate_captcha()
|
||||
|
||||
await state.set_state(CaptchaStates.waiting_for_captcha)
|
||||
await state.update_data(
|
||||
captcha_text=captcha_text,
|
||||
db_msg_id=db_msg_id,
|
||||
original_msg_id=message.message_id,
|
||||
original_text=message.text,
|
||||
original_caption=message.caption,
|
||||
has_media=has_media,
|
||||
is_text=is_text
|
||||
)
|
||||
|
||||
|
||||
@router.message(Command("help"))
|
||||
@router.message(F.text == "Help ❓")
|
||||
async def cmd_help(message: Message):
|
||||
"""
|
||||
Handles /help command or 'Help ❓' reply keyboard button click.
|
||||
"""
|
||||
await message.answer(
|
||||
"Here is what I can do:\n"
|
||||
"/start - Start the bot\n"
|
||||
"/help - Get help information\n"
|
||||
"/about - Learn more about the bot",
|
||||
reply_markup=get_links_keyboard(),
|
||||
|
||||
await message.answer_photo(
|
||||
BufferedInputFile(captcha_img, filename="captcha.png"),
|
||||
caption=_("solve_captcha", locale=locale)
|
||||
)
|
||||
|
||||
|
||||
@router.message(Command("about"))
|
||||
@router.message(F.text == "About ℹ️")
|
||||
async def cmd_about(message: Message):
|
||||
"""
|
||||
Handles /about command or 'About ℹ️' reply keyboard button click.
|
||||
"""
|
||||
await message.answer(
|
||||
"Frankenstein Bot is a high-performance Python Telegram bot built on aiogram v3, "
|
||||
"designed to work seamlessly with systemd process management."
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "support_contact")
|
||||
async def process_support_callback(callback: CallbackQuery):
|
||||
"""
|
||||
Processes the inline keyboard callback query 'support_contact'.
|
||||
"""
|
||||
await callback.message.answer("Support: Open an issue or contact our administrator.")
|
||||
await callback.answer()
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
|
||||
def get_links_keyboard() -> InlineKeyboardMarkup:
|
||||
"""
|
||||
Creates an inline keyboard markup with external links and callback query button.
|
||||
"""
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.add(InlineKeyboardButton(text="Google", url="https://google.com"))
|
||||
builder.add(InlineKeyboardButton(text="Contact Support 💬", callback_data="support_contact"))
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
@@ -1,13 +0,0 @@
|
||||
from aiogram.types import KeyboardButton, ReplyKeyboardMarkup
|
||||
from aiogram.utils.keyboard import ReplyKeyboardBuilder
|
||||
|
||||
|
||||
def get_main_menu_keyboard() -> ReplyKeyboardMarkup:
|
||||
"""
|
||||
Creates the main menu reply keyboard markup.
|
||||
"""
|
||||
builder = ReplyKeyboardBuilder()
|
||||
builder.add(KeyboardButton(text="Help ❓"))
|
||||
builder.add(KeyboardButton(text="About ℹ️"))
|
||||
builder.adjust(2)
|
||||
return builder.as_markup(resize_keyboard=True)
|
||||
@@ -24,3 +24,35 @@ class LoggingMiddleware(BaseMiddleware):
|
||||
except Exception as e:
|
||||
logger.error("Error handling event %s: %s", event.__class__.__name__, e, exc_info=True)
|
||||
raise e
|
||||
|
||||
|
||||
class I18nMiddleware(BaseMiddleware):
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any:
|
||||
from_user = data.get("event_from_user")
|
||||
if from_user and from_user.language_code:
|
||||
data["locale"] = from_user.language_code
|
||||
else:
|
||||
data["locale"] = "en"
|
||||
|
||||
return await handler(event, data)
|
||||
|
||||
|
||||
class BanMiddleware(BaseMiddleware):
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any:
|
||||
from_user = data.get("event_from_user")
|
||||
if from_user:
|
||||
from bot.utils.db import db
|
||||
if db.is_user_banned(from_user.id):
|
||||
return
|
||||
|
||||
return await handler(event, data)
|
||||
|
||||
+1
-4
@@ -7,9 +7,6 @@ async def set_bot_commands(bot: Bot):
|
||||
Sets default commands in the Telegram client's command menu.
|
||||
"""
|
||||
commands = [
|
||||
BotCommand(command="start", description="Start Frankenstein Bot"),
|
||||
BotCommand(command="help", description="Show help information"),
|
||||
BotCommand(command="about", description="About the bot"),
|
||||
BotCommand(command="admin", description="Admin panel placeholder"),
|
||||
BotCommand(command="start", description="Start the bot"),
|
||||
]
|
||||
await bot.set_my_commands(commands, scope=BotCommandScopeDefault())
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import random
|
||||
import string
|
||||
from io import BytesIO
|
||||
from captcha.image import ImageCaptcha
|
||||
|
||||
def generate_captcha():
|
||||
"""Generates a random captcha and returns (text, image_bytes)"""
|
||||
captcha_text = ''.join(random.choices(string.ascii_uppercase + string.digits, k=5))
|
||||
image = ImageCaptcha(width=280, height=90)
|
||||
data = image.generate(captcha_text)
|
||||
return captcha_text, data.getvalue()
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
class Database:
|
||||
def __init__(self, db_path="bot.db"):
|
||||
self.db_path = db_path
|
||||
self.init_db()
|
||||
|
||||
def get_connection(self):
|
||||
return sqlite3.connect(self.db_path)
|
||||
|
||||
def init_db(self):
|
||||
with self.get_connection() as conn:
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
username TEXT,
|
||||
full_name TEXT,
|
||||
language_code TEXT,
|
||||
first_seen TIMESTAMP,
|
||||
is_banned INTEGER DEFAULT 0
|
||||
)
|
||||
""")
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
content_type TEXT,
|
||||
text TEXT,
|
||||
json_data TEXT,
|
||||
timestamp TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id)
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
def log_user(self, user):
|
||||
with self.get_connection() as conn:
|
||||
# Check if user already exists to preserve is_banned status if we were using INSERT OR REPLACE
|
||||
# But INSERT OR REPLACE would overwrite is_banned if we don't include it.
|
||||
# Better to use INSERT OR IGNORE and then UPDATE or a more complex query.
|
||||
conn.execute("""
|
||||
INSERT INTO users (user_id, username, full_name, language_code, first_seen)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(user_id) DO UPDATE SET
|
||||
username=excluded.username,
|
||||
full_name=excluded.full_name,
|
||||
language_code=excluded.language_code
|
||||
""", (user.id, user.username, user.full_name, user.language_code, datetime.now()))
|
||||
conn.commit()
|
||||
|
||||
def ban_user(self, user_id):
|
||||
with self.get_connection() as conn:
|
||||
conn.execute("UPDATE users SET is_banned = 1 WHERE user_id = ?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
def unban_user(self, user_id):
|
||||
with self.get_connection() as conn:
|
||||
conn.execute("UPDATE users SET is_banned = 0 WHERE user_id = ?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
def is_user_banned(self, user_id):
|
||||
with self.get_connection() as conn:
|
||||
cursor = conn.execute("SELECT is_banned FROM users WHERE user_id = ?", (user_id,))
|
||||
row = cursor.fetchone()
|
||||
return bool(row[0]) if row else False
|
||||
|
||||
def log_message(self, message):
|
||||
self.log_user(message.from_user)
|
||||
with self.get_connection() as conn:
|
||||
content_type = message.content_type
|
||||
text = message.text or message.caption or ""
|
||||
try:
|
||||
json_data = message.model_dump_json()
|
||||
except Exception:
|
||||
# Fallback if model_dump_json fails due to non-serializable types
|
||||
try:
|
||||
import json
|
||||
# Use model_dump to get a dict, then serialize manually
|
||||
# exclude_none=True can help, and we can handle unknown types
|
||||
data_dict = message.model_dump(exclude_none=True)
|
||||
def custom_serializer(obj):
|
||||
return str(obj)
|
||||
json_data = json.dumps(data_dict, default=custom_serializer)
|
||||
except Exception as e:
|
||||
json_data = json.dumps({"error": f"Failed to serialize message: {str(e)}"})
|
||||
|
||||
cursor = conn.execute("""
|
||||
INSERT INTO messages (user_id, content_type, text, json_data, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", (message.from_user.id, content_type, text, json_data, datetime.now()))
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
|
||||
def get_stats(self):
|
||||
with self.get_connection() as conn:
|
||||
cursor = conn.execute("SELECT COUNT(*) FROM users")
|
||||
user_count = cursor.fetchone()[0]
|
||||
return {"user_count": user_count}
|
||||
|
||||
def get_recent_messages(self, limit=10):
|
||||
with self.get_connection() as conn:
|
||||
cursor = conn.execute("""
|
||||
SELECT m.id, u.full_name, m.text, m.timestamp
|
||||
FROM messages m
|
||||
JOIN users u ON m.user_id = u.user_id
|
||||
ORDER BY m.timestamp DESC
|
||||
LIMIT ?
|
||||
""", (limit,))
|
||||
return cursor.fetchall()
|
||||
|
||||
def get_message_details(self, msg_id):
|
||||
with self.get_connection() as conn:
|
||||
cursor = conn.execute("""
|
||||
SELECT m.id, u.full_name, u.username, u.user_id, m.content_type, m.timestamp, m.text
|
||||
FROM messages m
|
||||
JOIN users u ON m.user_id = u.user_id
|
||||
WHERE m.id = ?
|
||||
""", (msg_id,))
|
||||
return cursor.fetchone()
|
||||
|
||||
db = Database()
|
||||
@@ -0,0 +1,31 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
class I18n:
|
||||
def __init__(self, default_locale="en"):
|
||||
self.default_locale = default_locale
|
||||
self.translations = {}
|
||||
self.load_translations()
|
||||
|
||||
def load_translations(self):
|
||||
locales_dir = "locales"
|
||||
if not os.path.exists(locales_dir):
|
||||
return
|
||||
|
||||
for filename in os.listdir(locales_dir):
|
||||
if filename.endswith(".json"):
|
||||
locale = filename[:-5]
|
||||
with open(os.path.join(locales_dir, filename), "r", encoding="utf-8") as f:
|
||||
self.translations[locale] = json.load(f)
|
||||
|
||||
def get(self, key, locale=None, **kwargs):
|
||||
if locale is None:
|
||||
locale = self.default_locale
|
||||
text = self.translations.get(locale, {}).get(key, self.translations.get(self.default_locale, {}).get(key, key))
|
||||
try:
|
||||
return text.format(**kwargs)
|
||||
except (KeyError, IndexError):
|
||||
return text
|
||||
|
||||
i18n = I18n()
|
||||
_ = i18n.get
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"welcome": "Welcome! Send me any message (text, image, video, etc.) and I will redirect it to the admins after you solve a simple captcha.",
|
||||
"welcome_no_captcha": "Welcome! Send me any message (text, image, video, etc.) and I will redirect it to the admins",
|
||||
"solve_captcha": "Please type the letters/numbers you see on the image to verify you're human.",
|
||||
"redirecting": "Redirecting your message...",
|
||||
"captcha_solved": "Captcha solved! Your message has been redirected to the admins.",
|
||||
"captcha_error": "Captcha solved, but there was an error redirecting your message.",
|
||||
"incorrect_captcha": "Incorrect captcha. Please try again with this new image:",
|
||||
"stats_title": "Bot Stats:",
|
||||
"total_users": "Total Users: {count}",
|
||||
"no_logs": "No logs found.",
|
||||
"recent_messages": "Recent messages:",
|
||||
"msg_not_found": "Message with ID {id} not found.",
|
||||
"msg_details": "Message ID: {id}\nUser: {full_name} (@{username}) [ID: {user_id}]\nType: {content_type}\nTimestamp: {timestamp}\n\nContent:\n{text}",
|
||||
"id_command": "Current Chat ID: <code>{id}</code>",
|
||||
"processing_message": "Forwarding your message...",
|
||||
"message_processed": "Forwarded!",
|
||||
"forwarding_prefix": "Forwarding, ID [{id}]:",
|
||||
"user_banned": "User {user_id} has been banned.",
|
||||
"user_unbanned": "User {user_id} has been unbanned."
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"welcome": "Вітаю! Надішліть мені будь-яке повідомлення (текст, зображення, відео тощо), і я перешлю його до адмінів після того, як ви пройдете просту капчу.",
|
||||
"welcome_no_captcha": "Вітаю! Надішліть мені будь-яке повідомлення (текст, зображення, відео тощо), і я перешлю його до адмінів.",
|
||||
"solve_captcha": "Будь ласка, введіть літери/цифри, які ви бачите на зображенні, щоб підтвердити, що ви людина.",
|
||||
"redirecting": "Перенаправляю ваше повідомлення...",
|
||||
"captcha_solved": "Капчу пройдено! Ваше повідомлення було переслано до адмінів.",
|
||||
"captcha_error": "Капчу пройдено, але сталася помилка під час перенаправлення вашого повідомлення.",
|
||||
"incorrect_captcha": "Неправильна капча. Будь ласка, спробуйте ще раз із цим новим зображенням:",
|
||||
"stats_title": "Статистика бота:",
|
||||
"total_users": "Усього користувачів: {count}",
|
||||
"no_logs": "Логів не знайдено.",
|
||||
"recent_messages": "Останні повідомлення:",
|
||||
"msg_not_found": "Повідомлення з ID {id} не знайдено.",
|
||||
"msg_details": "ID повідомлення: {id}\nКористувач: {full_name} (@{username}) [ID: {user_id}]\nТип: {content_type}\nЧас: {timestamp}\n\nВміст:\n{text}",
|
||||
"id_command": "Поточний ID чату: <code>{id}</code>",
|
||||
"processing_message": "Відправляємо ваше повідомлення...",
|
||||
"message_processed": "Відправлено!",
|
||||
"forwarding_prefix": "Пересилання, ID [{id}]:",
|
||||
"user_banned": "Користувач {user_id} заблокований.",
|
||||
"user_unbanned": "Користувач {user_id} розблокований."
|
||||
}
|
||||
@@ -8,7 +8,8 @@ from aiogram.enums import ParseMode
|
||||
|
||||
from bot.config import settings
|
||||
from bot.handlers import get_handlers_router
|
||||
from bot.middlewares import LoggingMiddleware
|
||||
from bot.middlewares import LoggingMiddleware, I18nMiddleware, BanMiddleware
|
||||
from bot.utils.db import db
|
||||
from bot.ui_commands import set_bot_commands
|
||||
|
||||
# Setup logging
|
||||
@@ -23,7 +24,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def main():
|
||||
logger.info("Initializing Frankenstein Bot...")
|
||||
logger.info("Initializing Bot...")
|
||||
|
||||
# Initialize Bot instance with default HTML parsing mode
|
||||
bot = Bot(
|
||||
@@ -36,6 +37,8 @@ async def main():
|
||||
|
||||
# Register middlewares (e.g. LoggingMiddleware)
|
||||
dp.update.outer_middleware(LoggingMiddleware())
|
||||
dp.update.outer_middleware(BanMiddleware())
|
||||
dp.update.outer_middleware(I18nMiddleware())
|
||||
|
||||
# Register routers (handlers)
|
||||
dp.include_router(get_handlers_router())
|
||||
@@ -73,4 +76,4 @@ if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
logger.info("Frankenstein Bot execution stopped.")
|
||||
logger.info("Bot execution stopped.")
|
||||
|
||||
@@ -6,4 +6,5 @@ dependencies = [
|
||||
"aiogram>=3.29.1",
|
||||
"pydantic>=2.13.4",
|
||||
"pydantic-settings>=2.14.2",
|
||||
"captcha",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export -o requirements.txt
|
||||
aiofiles==25.1.0 \
|
||||
--hash=sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2 \
|
||||
--hash=sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695
|
||||
# via aiogram
|
||||
aiogram==3.29.1 \
|
||||
--hash=sha256:63cd0123e8ab065f78974ad58ff57c1a5c8c35aac1055761859c4a5ad27eaf04 \
|
||||
--hash=sha256:7ac92c64b71d33eb8e3656de4517a1533a88b61728e5dd00b1e11393b669b521
|
||||
# via frankenstein-bot
|
||||
aiohappyeyeballs==2.7.1 \
|
||||
--hash=sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d \
|
||||
--hash=sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472
|
||||
# via aiohttp
|
||||
aiohttp==3.14.1 \
|
||||
--hash=sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983 \
|
||||
--hash=sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340 \
|
||||
--hash=sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d \
|
||||
--hash=sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8 \
|
||||
--hash=sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397 \
|
||||
--hash=sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba \
|
||||
--hash=sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5 \
|
||||
--hash=sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035 \
|
||||
--hash=sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35 \
|
||||
--hash=sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333 \
|
||||
--hash=sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203 \
|
||||
--hash=sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1 \
|
||||
--hash=sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251 \
|
||||
--hash=sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365 \
|
||||
--hash=sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94 \
|
||||
--hash=sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe \
|
||||
--hash=sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9 \
|
||||
--hash=sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3 \
|
||||
--hash=sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602 \
|
||||
--hash=sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444 \
|
||||
--hash=sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15 \
|
||||
--hash=sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2 \
|
||||
--hash=sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496 \
|
||||
--hash=sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448 \
|
||||
--hash=sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6 \
|
||||
--hash=sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c \
|
||||
--hash=sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c \
|
||||
--hash=sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc \
|
||||
--hash=sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0 \
|
||||
--hash=sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0 \
|
||||
--hash=sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844 \
|
||||
--hash=sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719 \
|
||||
--hash=sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1 \
|
||||
--hash=sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95 \
|
||||
--hash=sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1 \
|
||||
--hash=sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a \
|
||||
--hash=sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec \
|
||||
--hash=sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004 \
|
||||
--hash=sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d \
|
||||
--hash=sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca \
|
||||
--hash=sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665 \
|
||||
--hash=sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3
|
||||
# via aiogram
|
||||
aiosignal==1.4.0 \
|
||||
--hash=sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e \
|
||||
--hash=sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7
|
||||
# via aiohttp
|
||||
annotated-types==0.7.0 \
|
||||
--hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \
|
||||
--hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89
|
||||
# via pydantic
|
||||
attrs==26.1.0 \
|
||||
--hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 \
|
||||
--hash=sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32
|
||||
# via aiohttp
|
||||
certifi==2026.6.17 \
|
||||
--hash=sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432 \
|
||||
--hash=sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db
|
||||
# via aiogram
|
||||
frozenlist==1.8.0 \
|
||||
--hash=sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686 \
|
||||
--hash=sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd \
|
||||
--hash=sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7 \
|
||||
--hash=sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d \
|
||||
--hash=sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79 \
|
||||
--hash=sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f \
|
||||
--hash=sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7 \
|
||||
--hash=sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef \
|
||||
--hash=sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe \
|
||||
--hash=sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930 \
|
||||
--hash=sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37 \
|
||||
--hash=sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128 \
|
||||
--hash=sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2 \
|
||||
--hash=sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f \
|
||||
--hash=sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df \
|
||||
--hash=sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c \
|
||||
--hash=sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0 \
|
||||
--hash=sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad \
|
||||
--hash=sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30 \
|
||||
--hash=sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c \
|
||||
--hash=sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a \
|
||||
--hash=sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8 \
|
||||
--hash=sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed \
|
||||
--hash=sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24 \
|
||||
--hash=sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e \
|
||||
--hash=sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e \
|
||||
--hash=sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8 \
|
||||
--hash=sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806 \
|
||||
--hash=sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a \
|
||||
--hash=sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2 \
|
||||
--hash=sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0 \
|
||||
--hash=sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b \
|
||||
--hash=sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d \
|
||||
--hash=sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a
|
||||
# via
|
||||
# aiohttp
|
||||
# aiosignal
|
||||
idna==3.18 \
|
||||
--hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \
|
||||
--hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848
|
||||
# via yarl
|
||||
magic-filter==1.0.12 \
|
||||
--hash=sha256:4751d0b579a5045d1dc250625c4c508c18c3def5ea6afaf3957cb4530d03f7f9 \
|
||||
--hash=sha256:e5929e544f310c2b1f154318db8c5cdf544dd658efa998172acd2e4ba0f6c6a6
|
||||
# via aiogram
|
||||
multidict==6.7.1 \
|
||||
--hash=sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581 \
|
||||
--hash=sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1 \
|
||||
--hash=sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c \
|
||||
--hash=sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262 \
|
||||
--hash=sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d \
|
||||
--hash=sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601 \
|
||||
--hash=sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362 \
|
||||
--hash=sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2 \
|
||||
--hash=sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb \
|
||||
--hash=sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d \
|
||||
--hash=sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5 \
|
||||
--hash=sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd \
|
||||
--hash=sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5 \
|
||||
--hash=sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37 \
|
||||
--hash=sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56 \
|
||||
--hash=sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f \
|
||||
--hash=sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889 \
|
||||
--hash=sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048 \
|
||||
--hash=sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59 \
|
||||
--hash=sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709 \
|
||||
--hash=sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c \
|
||||
--hash=sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee \
|
||||
--hash=sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609 \
|
||||
--hash=sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1 \
|
||||
--hash=sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31 \
|
||||
--hash=sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7 \
|
||||
--hash=sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9 \
|
||||
--hash=sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4 \
|
||||
--hash=sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2 \
|
||||
--hash=sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c \
|
||||
--hash=sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2 \
|
||||
--hash=sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a \
|
||||
--hash=sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e \
|
||||
--hash=sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489 \
|
||||
--hash=sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d \
|
||||
--hash=sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b \
|
||||
--hash=sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d \
|
||||
--hash=sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
propcache==0.5.2 \
|
||||
--hash=sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427 \
|
||||
--hash=sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0 \
|
||||
--hash=sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917 \
|
||||
--hash=sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3 \
|
||||
--hash=sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa \
|
||||
--hash=sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821 \
|
||||
--hash=sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56 \
|
||||
--hash=sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999 \
|
||||
--hash=sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb \
|
||||
--hash=sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4 \
|
||||
--hash=sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03 \
|
||||
--hash=sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5 \
|
||||
--hash=sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979 \
|
||||
--hash=sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5 \
|
||||
--hash=sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078 \
|
||||
--hash=sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837 \
|
||||
--hash=sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c \
|
||||
--hash=sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8 \
|
||||
--hash=sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe \
|
||||
--hash=sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d \
|
||||
--hash=sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539 \
|
||||
--hash=sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285 \
|
||||
--hash=sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e \
|
||||
--hash=sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe \
|
||||
--hash=sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39 \
|
||||
--hash=sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2 \
|
||||
--hash=sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0 \
|
||||
--hash=sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335 \
|
||||
--hash=sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80 \
|
||||
--hash=sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370 \
|
||||
--hash=sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4 \
|
||||
--hash=sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825 \
|
||||
--hash=sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366 \
|
||||
--hash=sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702 \
|
||||
--hash=sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e \
|
||||
--hash=sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
pydantic==2.13.4 \
|
||||
--hash=sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba \
|
||||
--hash=sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6
|
||||
# via
|
||||
# aiogram
|
||||
# frankenstein-bot
|
||||
# pydantic-settings
|
||||
pydantic-core==2.46.4 \
|
||||
--hash=sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e \
|
||||
--hash=sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb \
|
||||
--hash=sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5 \
|
||||
--hash=sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462 \
|
||||
--hash=sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b \
|
||||
--hash=sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519 \
|
||||
--hash=sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6 \
|
||||
--hash=sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565 \
|
||||
--hash=sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898 \
|
||||
--hash=sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb \
|
||||
--hash=sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596 \
|
||||
--hash=sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d \
|
||||
--hash=sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd \
|
||||
--hash=sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1 \
|
||||
--hash=sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3 \
|
||||
--hash=sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4 \
|
||||
--hash=sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b \
|
||||
--hash=sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac \
|
||||
--hash=sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4 \
|
||||
--hash=sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914 \
|
||||
--hash=sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424 \
|
||||
--hash=sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0 \
|
||||
--hash=sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e \
|
||||
--hash=sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a \
|
||||
--hash=sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c \
|
||||
--hash=sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000 \
|
||||
--hash=sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28 \
|
||||
--hash=sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9 \
|
||||
--hash=sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02 \
|
||||
--hash=sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7 \
|
||||
--hash=sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff
|
||||
# via pydantic
|
||||
pydantic-settings==2.14.2 \
|
||||
--hash=sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440 \
|
||||
--hash=sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f
|
||||
# via frankenstein-bot
|
||||
python-dotenv==1.2.2 \
|
||||
--hash=sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a \
|
||||
--hash=sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3
|
||||
# via pydantic-settings
|
||||
typing-extensions==4.16.0 \
|
||||
--hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \
|
||||
--hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5
|
||||
# via
|
||||
# aiogram
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.2 \
|
||||
--hash=sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 \
|
||||
--hash=sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464
|
||||
# via
|
||||
# pydantic
|
||||
# pydantic-settings
|
||||
yarl==1.24.2 \
|
||||
--hash=sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b \
|
||||
--hash=sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc \
|
||||
--hash=sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8 \
|
||||
--hash=sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b \
|
||||
--hash=sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9 \
|
||||
--hash=sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd \
|
||||
--hash=sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67 \
|
||||
--hash=sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420 \
|
||||
--hash=sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50 \
|
||||
--hash=sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa \
|
||||
--hash=sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f \
|
||||
--hash=sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe \
|
||||
--hash=sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761 \
|
||||
--hash=sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57 \
|
||||
--hash=sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14 \
|
||||
--hash=sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd \
|
||||
--hash=sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1 \
|
||||
--hash=sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024 \
|
||||
--hash=sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb \
|
||||
--hash=sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543 \
|
||||
--hash=sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed \
|
||||
--hash=sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8 \
|
||||
--hash=sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3 \
|
||||
--hash=sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535 \
|
||||
--hash=sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630 \
|
||||
--hash=sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215 \
|
||||
--hash=sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf \
|
||||
--hash=sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0 \
|
||||
--hash=sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1 \
|
||||
--hash=sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8 \
|
||||
--hash=sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d \
|
||||
--hash=sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208 \
|
||||
--hash=sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0 \
|
||||
--hash=sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8 \
|
||||
--hash=sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2 \
|
||||
--hash=sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14
|
||||
# via aiohttp
|
||||
@@ -125,6 +125,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "captcha"
|
||||
version = "0.7.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pillow" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b4/65/8e186bb798f33ba390eab897c995b0fcee92bc030e0f40cb8ea01f34dd07/captcha-0.7.1.tar.gz", hash = "sha256:a1b462bcc633a64d8db5efa7754548a877c698d98f87716c620a707364cabd6b", size = 226561, upload-time = "2025-03-01T05:00:13.395Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/da/ff/3f0982ecd37c2d6a7266c22e7ea2e47d0773fe449984184c5316459d2776/captcha-0.7.1-py3-none-any.whl", hash = "sha256:8b73b5aba841ad1e5bdb856205bf5f09560b728ee890eb9dae42901219c8c599", size = 147606, upload-time = "2025-03-01T05:00:10.433Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.6.17"
|
||||
@@ -140,6 +152,7 @@ version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "aiogram" },
|
||||
{ name = "captcha" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
]
|
||||
@@ -147,6 +160,7 @@ dependencies = [
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "aiogram", specifier = ">=3.29.1" },
|
||||
{ name = "captcha" },
|
||||
{ name = "pydantic", specifier = ">=2.13.4" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.14.2" },
|
||||
]
|
||||
@@ -255,6 +269,56 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "propcache"
|
||||
version = "0.5.2"
|
||||
|
||||
Reference in New Issue
Block a user