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 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:
|
||||||
|
|||||||
+13
-21
@@ -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,9 +72,9 @@ 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)
|
||||||
except IOError as e:
|
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
|
||||||
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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+56
-66
@@ -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
|
||||||
|
|
||||||
@@ -16,14 +16,13 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class TaskStorage:
|
class TaskStorage:
|
||||||
"""Хранилище задач в JSON файле."""
|
"""Хранилище задач в JSON файле."""
|
||||||
|
|
||||||
def __init__(self, file_path: str = "data/tasks.json"):
|
def __init__(self, file_path: str = "data/tasks.json"):
|
||||||
self.file_path = Path(file_path)
|
self.file_path = Path(file_path)
|
||||||
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()
|
||||||
|
|
||||||
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,
|
||||||
@@ -47,9 +46,8 @@ class TaskStorage:
|
|||||||
"chat_id": params.chat_id,
|
"chat_id": params.chat_id,
|
||||||
"started_at": params.started_at.isoformat() if params.started_at else None,
|
"started_at": params.started_at.isoformat() if params.started_at else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
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,58 +70,51 @@ 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:
|
||||||
logger.error(f"Load error: {e}")
|
logger.error(f"Load error: {e}")
|
||||||
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]
|
||||||
@@ -132,46 +123,45 @@ class TaskStorage:
|
|||||||
|
|
||||||
class ChatStorage:
|
class ChatStorage:
|
||||||
"""Хранилище привязки стримеров к чатам."""
|
"""Хранилище привязки стримеров к чатам."""
|
||||||
|
|
||||||
def __init__(self, file_path: str = "data/chats.json"):
|
def __init__(self, file_path: str = "data/chats.json"):
|
||||||
self.file_path = Path(file_path)
|
self.file_path = Path(file_path)
|
||||||
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)
|
||||||
|
|||||||
+2
-1
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user