From bdf3fef7ec9a10ed490b1f9619586d20d5fdb039 Mon Sep 17 00:00:00 2001 From: Yuriy Yuriev Date: Thu, 14 May 2026 19:18:44 +0700 Subject: [PATCH] add proxy for tg and fix auth --- auth/manager.py | 4 +- auth/storage.py | 34 +++++------- config/settings.py | 2 + main.py | 4 +- managers/storage.py | 122 ++++++++++++++++++++------------------------ test_proxy.py | 0 utils/telegram.py | 3 +- 7 files changed, 79 insertions(+), 90 deletions(-) create mode 100644 test_proxy.py diff --git a/auth/manager.py b/auth/manager.py index 9da44a0..47cc771 100644 --- a/auth/manager.py +++ b/auth/manager.py @@ -5,9 +5,11 @@ import logging from datetime import datetime, timedelta from typing import Dict, Optional +from config.settings import settings + logger = logging.getLogger(__name__) -ADMIN_PASSWORD = "NjduqrKozu#G" +ADMIN_PASSWORD = settings.ADMIN_PASSWORD class AuthManager: diff --git a/auth/storage.py b/auth/storage.py index 6e360a4..1852c0a 100644 --- a/auth/storage.py +++ b/auth/storage.py @@ -2,8 +2,8 @@ Хранилище учётных данных пользователей. """ -import json import asyncio +import json import logging from pathlib import Path from typing import Optional, Dict @@ -20,57 +20,49 @@ class AuthStorage: self._lock = asyncio.Lock() async def register(self, user_id: int, password_hash: str, salt: str) -> bool: - """Регистрирует нового пользователя. Возвращает True если успешно.""" async with self._lock: - data = self._load() + data = await asyncio.to_thread(self._load_sync) if str(user_id) in data: return False - data[str(user_id)] = { - "password_hash": password_hash, - "salt": salt, - } - self._save(data) + data[str(user_id)] = {"password_hash": password_hash, "salt": salt} + await asyncio.to_thread(self._save_sync, data) logger.info(f"User {user_id} registered") return True async def get_user(self, user_id: int) -> Optional[Dict]: - """Получает данные пользователя по ID.""" async with self._lock: - data = self._load() + data = await asyncio.to_thread(self._load_sync) return data.get(str(user_id)) async def change_password(self, user_id: int, new_hash: str, new_salt: str) -> bool: - """Сменить пароль пользователя.""" async with self._lock: - data = self._load() + data = await asyncio.to_thread(self._load_sync) user_str = str(user_id) if user_str not in data: return False data[user_str]["password_hash"] = new_hash data[user_str]["salt"] = new_salt - self._save(data) + await asyncio.to_thread(self._save_sync, data) logger.info(f"User {user_id} changed password") return True async def delete_user(self, user_id: int) -> bool: - """Удалить учётную запись пользователя.""" async with self._lock: - data = self._load() + data = await asyncio.to_thread(self._load_sync) user_str = str(user_id) if user_str not in data: return False del data[user_str] - self._save(data) + await asyncio.to_thread(self._save_sync, data) logger.info(f"User {user_id} deleted") return True async def user_exists(self, user_id: int) -> bool: - """Проверяет, существует ли пользователь.""" async with self._lock: - data = self._load() + data = await asyncio.to_thread(self._load_sync) return str(user_id) in data - def _load(self) -> dict: + def _load_sync(self) -> dict: if not self.file_path.exists(): return {} try: @@ -80,9 +72,9 @@ class AuthStorage: logger.warning(f"Failed to load auth storage: {e}") return {} - def _save(self, data: dict): + def _save_sync(self, data: dict) -> None: try: with open(self.file_path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) except IOError as e: - logger.error(f"Failed to save auth storage: {e}") \ No newline at end of file + logger.error(f"Failed to save auth storage: {e}") diff --git a/config/settings.py b/config/settings.py index 09c73fb..2827c06 100644 --- a/config/settings.py +++ b/config/settings.py @@ -8,6 +8,8 @@ class Settings(BaseSettings): # Bot BOT_TOKEN: str = "" + TELEGRAM_PROXY: str = "" # socks5://user:pass@host:port + ADMIN_PASSWORD: str = "" # Telethon/MTProto settings API_ID: int = 12345 diff --git a/main.py b/main.py index 9c159fe..ce67652 100644 --- a/main.py +++ b/main.py @@ -7,6 +7,7 @@ import logging from types import SimpleNamespace from aiogram import Bot, Dispatcher from aiogram.types import BotCommand +from aiogram.client.session.aiohttp import AiohttpSession from config.settings import settings from core.logger import setup_logger from managers.proxy_manager import ProxyManager @@ -71,7 +72,8 @@ class BotApplication: raise ValueError("BOT_TOKEN not set!") - self.bot = Bot(token=settings.BOT_TOKEN) + session = AiohttpSession(proxy=settings.TELEGRAM_PROXY) if settings.TELEGRAM_PROXY else AiohttpSession() + self.bot = Bot(token=settings.BOT_TOKEN, session=session) self.dispatcher = Dispatcher() self.interface.register(self.dispatcher) diff --git a/managers/storage.py b/managers/storage.py index 3e0d4c0..35d5c85 100644 --- a/managers/storage.py +++ b/managers/storage.py @@ -2,12 +2,12 @@ Сохранение и загрузка задач между перезапусками. """ -import json import asyncio +import json import logging from pathlib import Path from datetime import datetime -from typing import Dict, Optional +from typing import Dict from managers.task_manager import TaskParams @@ -16,14 +16,13 @@ logger = logging.getLogger(__name__) class TaskStorage: """Хранилище задач в JSON файле.""" - + def __init__(self, file_path: str = "data/tasks.json"): self.file_path = Path(file_path) self.file_path.parent.mkdir(parents=True, exist_ok=True) self._lock = asyncio.Lock() - + def _params_to_dict(self, params: TaskParams) -> dict: - """Конвертирует TaskParams в словарь.""" return { "url": params.url, "task_type": params.task_type, @@ -47,9 +46,8 @@ class TaskStorage: "chat_id": params.chat_id, "started_at": params.started_at.isoformat() if params.started_at else None, } - + def _dict_to_params(self, data: dict) -> TaskParams: - """Конвертирует словарь в TaskParams.""" params = TaskParams( url=data.get("url", ""), task_type=data.get("task_type", "visit"), @@ -72,58 +70,51 @@ class TaskStorage: links_found=data.get("links_found", 0), chat_id=data.get("chat_id"), ) - if data.get("started_at"): try: params.started_at = datetime.fromisoformat(data["started_at"]) except (ValueError, TypeError) as e: logger.warning(f"Failed to parse started_at: {e}") - return params - + + def _save_sync(self, data: dict) -> None: + with open(self.file_path, "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + def _load_sync(self) -> dict: + if not self.file_path.exists(): + return {} + with open(self.file_path, "r", encoding="utf-8") as f: + return json.load(f) + async def save_tasks(self, tasks: Dict[str, TaskParams]) -> None: - """Сохраняет все задачи в файл.""" async with self._lock: try: - data = {} - for task_id, params in tasks.items(): - data[task_id] = self._params_to_dict(params) - - with open(self.file_path, 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False, indent=2) - + data = {tid: self._params_to_dict(p) for tid, p in tasks.items()} + await asyncio.to_thread(self._save_sync, data) logger.info(f"💾 Saved {len(data)} tasks to {self.file_path}") except Exception as e: logger.error(f"Save error: {e}") - + async def load_tasks(self) -> Dict[str, TaskParams]: - """Загружает задачи из файла.""" - if not self.file_path.exists(): - logger.info("No saved tasks found") - return {} - try: - with open(self.file_path, 'r', encoding='utf-8') as f: - data = json.load(f) - - tasks = {} - for task_id, params_data in data.items(): - tasks[task_id] = self._dict_to_params(params_data) - + data = await asyncio.to_thread(self._load_sync) + if not data: + logger.info("No saved tasks found") + return {} + tasks = {tid: self._dict_to_params(d) for tid, d in data.items()} logger.info(f"📂 Loaded {len(tasks)} tasks from {self.file_path}") return tasks except Exception as e: logger.error(f"Load error: {e}") return {} - + async def save_task(self, task_id: str, params: TaskParams) -> None: - """Сохраняет одну задачу (добавляет к существующим).""" tasks = await self.load_tasks() tasks[task_id] = params await self.save_tasks(tasks) - + async def delete_task(self, task_id: str) -> None: - """Удаляет задачу из файла.""" tasks = await self.load_tasks() if task_id in tasks: del tasks[task_id] @@ -132,46 +123,45 @@ class TaskStorage: class ChatStorage: """Хранилище привязки стримеров к чатам.""" - + def __init__(self, file_path: str = "data/chats.json"): self.file_path = Path(file_path) self.file_path.parent.mkdir(parents=True, exist_ok=True) self._lock = asyncio.Lock() - - async def get_chat_streamers(self, chat_id: int) -> list: - """Получает список стримеров для чата.""" - data = await self._load() - return data.get(str(chat_id), []) - - async def add_streamer(self, chat_id: int, channel: str) -> None: - """Добавляет стримера в чат.""" - data = await self._load() - key = str(chat_id) - if key not in data: - data[key] = [] - if channel not in data[key]: - data[key].append(channel) - await self._save(data) - - async def remove_streamer(self, chat_id: int, channel: str) -> None: - """Удаляет стримера из чата.""" - data = await self._load() - key = str(chat_id) - if key in data and channel in data[key]: - data[key].remove(channel) - await self._save(data) - - async def _load(self) -> dict: + + def _load_sync(self) -> dict: if not self.file_path.exists(): return {} try: - with open(self.file_path, 'r', encoding='utf-8') as f: + with open(self.file_path, "r", encoding="utf-8") as f: return json.load(f) except (json.JSONDecodeError, IOError) as e: logger.warning(f"Failed to load chats: {e}") return {} - - async def _save(self, data: dict) -> None: + + def _save_sync(self, data: dict) -> None: + with open(self.file_path, "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + async def get_chat_streamers(self, chat_id: int) -> list: async with self._lock: - with open(self.file_path, 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False, indent=2) \ No newline at end of file + data = await asyncio.to_thread(self._load_sync) + return data.get(str(chat_id), []) + + async def add_streamer(self, chat_id: int, channel: str) -> None: + async with self._lock: + data = await asyncio.to_thread(self._load_sync) + key = str(chat_id) + if key not in data: + data[key] = [] + if channel not in data[key]: + data[key].append(channel) + await asyncio.to_thread(self._save_sync, data) + + async def remove_streamer(self, chat_id: int, channel: str) -> None: + async with self._lock: + data = await asyncio.to_thread(self._load_sync) + key = str(chat_id) + if key in data and channel in data[key]: + data[key].remove(channel) + await asyncio.to_thread(self._save_sync, data) diff --git a/test_proxy.py b/test_proxy.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/telegram.py b/utils/telegram.py index 885188b..3e319c1 100644 --- a/utils/telegram.py +++ b/utils/telegram.py @@ -1,5 +1,6 @@ """Telegram message utilities.""" +import asyncio import logging import os from typing import Optional @@ -69,7 +70,7 @@ async def send_visit_result( ) screenshot_path = result.get('screenshot_path') - if screenshot_path and os.path.exists(screenshot_path): + if screenshot_path and await asyncio.to_thread(os.path.exists, screenshot_path): photo = FSInputFile(screenshot_path) await bot.send_photo(chat_id=chat_id, photo=photo, caption=text) else: