add proxy for tg and fix auth
This commit is contained in:
+3
-1
@@ -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:
|
||||
|
||||
+13
-21
@@ -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}")
|
||||
logger.error(f"Failed to save auth storage: {e}")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+56
-66
@@ -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)
|
||||
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)
|
||||
|
||||
+2
-1
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user