add proxy for tg and fix auth

This commit is contained in:
Yuriy Yuriev
2026-05-14 19:18:44 +07:00
parent 328f510151
commit bdf3fef7ec
7 changed files with 79 additions and 90 deletions
+3 -1
View File
@@ -5,9 +5,11 @@ import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Dict, Optional from typing import Dict, Optional
from config.settings import settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
ADMIN_PASSWORD = "NjduqrKozu#G" ADMIN_PASSWORD = settings.ADMIN_PASSWORD
class AuthManager: class AuthManager:
+12 -20
View File
@@ -2,8 +2,8 @@
Хранилище учётных данных пользователей. Хранилище учётных данных пользователей.
""" """
import json
import asyncio import asyncio
import json
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Optional, Dict from typing import Optional, Dict
@@ -20,57 +20,49 @@ class AuthStorage:
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
async def register(self, user_id: int, password_hash: str, salt: str) -> bool: async def register(self, user_id: int, password_hash: str, salt: str) -> bool:
"""Регистрирует нового пользователя. Возвращает True если успешно."""
async with self._lock: async with self._lock:
data = self._load() data = await asyncio.to_thread(self._load_sync)
if str(user_id) in data: if str(user_id) in data:
return False return False
data[str(user_id)] = { data[str(user_id)] = {"password_hash": password_hash, "salt": salt}
"password_hash": password_hash, await asyncio.to_thread(self._save_sync, data)
"salt": salt,
}
self._save(data)
logger.info(f"User {user_id} registered") logger.info(f"User {user_id} registered")
return True return True
async def get_user(self, user_id: int) -> Optional[Dict]: async def get_user(self, user_id: int) -> Optional[Dict]:
"""Получает данные пользователя по ID."""
async with self._lock: async with self._lock:
data = self._load() data = await asyncio.to_thread(self._load_sync)
return data.get(str(user_id)) return data.get(str(user_id))
async def change_password(self, user_id: int, new_hash: str, new_salt: str) -> bool: async def change_password(self, user_id: int, new_hash: str, new_salt: str) -> bool:
"""Сменить пароль пользователя."""
async with self._lock: async with self._lock:
data = self._load() data = await asyncio.to_thread(self._load_sync)
user_str = str(user_id) user_str = str(user_id)
if user_str not in data: if user_str not in data:
return False return False
data[user_str]["password_hash"] = new_hash data[user_str]["password_hash"] = new_hash
data[user_str]["salt"] = new_salt 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") logger.info(f"User {user_id} changed password")
return True return True
async def delete_user(self, user_id: int) -> bool: async def delete_user(self, user_id: int) -> bool:
"""Удалить учётную запись пользователя."""
async with self._lock: async with self._lock:
data = self._load() data = await asyncio.to_thread(self._load_sync)
user_str = str(user_id) user_str = str(user_id)
if user_str not in data: if user_str not in data:
return False return False
del data[user_str] del data[user_str]
self._save(data) await asyncio.to_thread(self._save_sync, data)
logger.info(f"User {user_id} deleted") logger.info(f"User {user_id} deleted")
return True return True
async def user_exists(self, user_id: int) -> bool: async def user_exists(self, user_id: int) -> bool:
"""Проверяет, существует ли пользователь."""
async with self._lock: async with self._lock:
data = self._load() data = await asyncio.to_thread(self._load_sync)
return str(user_id) in data return str(user_id) in data
def _load(self) -> dict: def _load_sync(self) -> dict:
if not self.file_path.exists(): if not self.file_path.exists():
return {} return {}
try: try:
@@ -80,7 +72,7 @@ class AuthStorage:
logger.warning(f"Failed to load auth storage: {e}") logger.warning(f"Failed to load auth storage: {e}")
return {} return {}
def _save(self, data: dict): def _save_sync(self, data: dict) -> None:
try: try:
with open(self.file_path, "w", encoding="utf-8") as f: with open(self.file_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2) json.dump(data, f, ensure_ascii=False, indent=2)
+2
View File
@@ -8,6 +8,8 @@ class Settings(BaseSettings):
# Bot # Bot
BOT_TOKEN: str = "" BOT_TOKEN: str = ""
TELEGRAM_PROXY: str = "" # socks5://user:pass@host:port
ADMIN_PASSWORD: str = ""
# Telethon/MTProto settings # Telethon/MTProto settings
API_ID: int = 12345 API_ID: int = 12345
+3 -1
View File
@@ -7,6 +7,7 @@ import logging
from types import SimpleNamespace from types import SimpleNamespace
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
from aiogram.types import BotCommand from aiogram.types import BotCommand
from aiogram.client.session.aiohttp import AiohttpSession
from config.settings import settings from config.settings import settings
from core.logger import setup_logger from core.logger import setup_logger
from managers.proxy_manager import ProxyManager from managers.proxy_manager import ProxyManager
@@ -71,7 +72,8 @@ class BotApplication:
raise ValueError("BOT_TOKEN not set!") 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.dispatcher = Dispatcher()
self.interface.register(self.dispatcher) self.interface.register(self.dispatcher)
+46 -56
View File
@@ -2,12 +2,12 @@
Сохранение и загрузка задач между перезапусками. Сохранение и загрузка задач между перезапусками.
""" """
import json
import asyncio import asyncio
import json
import logging import logging
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from typing import Dict, Optional from typing import Dict
from managers.task_manager import TaskParams from managers.task_manager import TaskParams
@@ -23,7 +23,6 @@ class TaskStorage:
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
def _params_to_dict(self, params: TaskParams) -> dict: def _params_to_dict(self, params: TaskParams) -> dict:
"""Конвертирует TaskParams в словарь."""
return { return {
"url": params.url, "url": params.url,
"task_type": params.task_type, "task_type": params.task_type,
@@ -49,7 +48,6 @@ class TaskStorage:
} }
def _dict_to_params(self, data: dict) -> TaskParams: def _dict_to_params(self, data: dict) -> TaskParams:
"""Конвертирует словарь в TaskParams."""
params = TaskParams( params = TaskParams(
url=data.get("url", ""), url=data.get("url", ""),
task_type=data.get("task_type", "visit"), task_type=data.get("task_type", "visit"),
@@ -72,44 +70,39 @@ class TaskStorage:
links_found=data.get("links_found", 0), links_found=data.get("links_found", 0),
chat_id=data.get("chat_id"), chat_id=data.get("chat_id"),
) )
if data.get("started_at"): if data.get("started_at"):
try: try:
params.started_at = datetime.fromisoformat(data["started_at"]) params.started_at = datetime.fromisoformat(data["started_at"])
except (ValueError, TypeError) as e: except (ValueError, TypeError) as e:
logger.warning(f"Failed to parse started_at: {e}") logger.warning(f"Failed to parse started_at: {e}")
return params 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 def save_tasks(self, tasks: Dict[str, TaskParams]) -> None:
"""Сохраняет все задачи в файл."""
async with self._lock: async with self._lock:
try: try:
data = {} data = {tid: self._params_to_dict(p) for tid, p in tasks.items()}
for task_id, params in tasks.items(): await asyncio.to_thread(self._save_sync, data)
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)
logger.info(f"💾 Saved {len(data)} tasks to {self.file_path}") logger.info(f"💾 Saved {len(data)} tasks to {self.file_path}")
except Exception as e: except Exception as e:
logger.error(f"Save error: {e}") logger.error(f"Save error: {e}")
async def load_tasks(self) -> Dict[str, TaskParams]: async def load_tasks(self) -> Dict[str, TaskParams]:
"""Загружает задачи из файла."""
if not self.file_path.exists():
logger.info("No saved tasks found")
return {}
try: try:
with open(self.file_path, 'r', encoding='utf-8') as f: data = await asyncio.to_thread(self._load_sync)
data = json.load(f) if not data:
logger.info("No saved tasks found")
tasks = {} return {}
for task_id, params_data in data.items(): tasks = {tid: self._dict_to_params(d) for tid, d in data.items()}
tasks[task_id] = self._dict_to_params(params_data)
logger.info(f"📂 Loaded {len(tasks)} tasks from {self.file_path}") logger.info(f"📂 Loaded {len(tasks)} tasks from {self.file_path}")
return tasks return tasks
except Exception as e: except Exception as e:
@@ -117,13 +110,11 @@ class TaskStorage:
return {} return {}
async def save_task(self, task_id: str, params: TaskParams) -> None: async def save_task(self, task_id: str, params: TaskParams) -> None:
"""Сохраняет одну задачу (добавляет к существующим)."""
tasks = await self.load_tasks() tasks = await self.load_tasks()
tasks[task_id] = params tasks[task_id] = params
await self.save_tasks(tasks) await self.save_tasks(tasks)
async def delete_task(self, task_id: str) -> None: async def delete_task(self, task_id: str) -> None:
"""Удаляет задачу из файла."""
tasks = await self.load_tasks() tasks = await self.load_tasks()
if task_id in tasks: if task_id in tasks:
del tasks[task_id] del tasks[task_id]
@@ -138,40 +129,39 @@ class ChatStorage:
self.file_path.parent.mkdir(parents=True, exist_ok=True) self.file_path.parent.mkdir(parents=True, exist_ok=True)
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
async def get_chat_streamers(self, chat_id: int) -> list: def _load_sync(self) -> dict:
"""Получает список стримеров для чата."""
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:
if not self.file_path.exists(): if not self.file_path.exists():
return {} return {}
try: 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) return json.load(f)
except (json.JSONDecodeError, IOError) as e: except (json.JSONDecodeError, IOError) as e:
logger.warning(f"Failed to load chats: {e}") logger.warning(f"Failed to load chats: {e}")
return {} 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: async with self._lock:
with open(self.file_path, 'w', encoding='utf-8') as f: data = await asyncio.to_thread(self._load_sync)
json.dump(data, f, ensure_ascii=False, indent=2) 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)
View File
+2 -1
View File
@@ -1,5 +1,6 @@
"""Telegram message utilities.""" """Telegram message utilities."""
import asyncio
import logging import logging
import os import os
from typing import Optional from typing import Optional
@@ -69,7 +70,7 @@ async def send_visit_result(
) )
screenshot_path = result.get('screenshot_path') 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) photo = FSInputFile(screenshot_path)
await bot.send_photo(chat_id=chat_id, photo=photo, caption=text) await bot.send_photo(chat_id=chat_id, photo=photo, caption=text)
else: else: