Files
Auto-Stars-Improved/modules/lolz.py
T
root 5c325bd8b9
Build and Push (dev) / build (push) Successful in 3m31s
feat(config): add proxy support and API timeout
2026-08-16 05:15:24 +05:00

314 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import logging
import asyncio
from LOLZTEAM.Client import Forum
from typing import Dict, Any, Optional, Union, List
from config import Config
logger = logging.getLogger(__name__)
class LolzAPIError(Exception):
"""Base exception for Lolz API errors"""
pass
class LolzNetworkError(LolzAPIError):
"""Network-related errors"""
pass
class LolzResponseError(LolzAPIError):
"""API returned invalid or empty response"""
pass
class Lolz:
def __init__(self, config: Config):
if not config.lolz_token:
raise ValueError("LOLZ_TOKEN обязателен")
try:
if config.proxy_lolz:
if config.proxy_username and config.proxy_password:
proxy = f"{config.proxy_protocol}://{config.proxy_username}:{config.proxy_password}@{config.proxy_host}:{config.proxy_port}"
logger.info(
"Инициализация Lolz клиента с прокси: %s://%s:%s (с авторизацией)",
config.proxy_protocol,
config.proxy_host,
config.proxy_port
)
else:
proxy = f"{config.proxy_protocol}://{config.proxy_host}:{config.proxy_port}"
logger.info(
"Инициализация Lolz клиента с прокси: %s://%s:%s (без авторизации)",
config.proxy_protocol,
config.proxy_host,
config.proxy_port
)
self.client = Forum(
token=config.lolz_token, proxy=proxy, timeout=config.api_timeout
)
else:
logger.info("Инициализация Lolz клиента без прокси")
self.client = Forum(token=config.lolz_token, timeout=config.api_timeout)
# ! Не рекомендуется включать этот логгер,
# ! ибо логи занимают очень много места в экране с этим логгером
# self.client.settings.logger.enable()
except Exception as e:
logger.error(f"Не удалось инициализировать Lolz клиент: {e}")
raise LolzAPIError(f"Не удалось инициализировать Lolz клиент: {e}") from e
async def get_thread(self, thread_id: Union[str, int]) -> Dict[str, Any]:
logger.debug(f"Запрашиваю данные темы {thread_id}...")
try:
response = await self.client.threads.get(thread_id=thread_id)
thread = (response.json()).get("thread", {})
if not thread:
raise LolzResponseError(
f"Тема {thread_id} не найдена или вернула пустой ответ"
)
return thread
except asyncio.TimeoutError as e:
logger.error(
f"Превышено время ожидания при получении темы {thread_id}: {e}"
)
raise LolzNetworkError(
f"Превышено время ожидания при получении темы {thread_id}"
) from e
except Exception as e:
logger.error(f"Ошибка при получении темы {thread_id}: {e}")
raise LolzAPIError(f"Не удалось получить тему {thread_id}") from e
async def edit_thread(
self, thread_id: Union[str, int], post_body: str = None, **kwargs
) -> bool:
logger.debug(f"Редактирую данные темы {thread_id}...")
try:
if post_body:
thread = await self.get_thread(thread_id=thread_id)
# Чтобы отредактировать соддержимое темы - надо отредактировать первый пост
post = thread.get("first_post", {})
post_id = post.get("post_id")
response = await self.client.posts.edit(
post_id=post_id, post_body=post_body
)
if response.status_code != 200:
logger.error(
f"Не удалось отредактировать пост {post_id}. Статус: {response.status_code}"
)
return False
response = await self.client.threads.edit(thread_id=thread_id, **kwargs)
if response.status_code == 200:
logger.info(f"Тема {thread_id} успешно отредактирована.")
return True
else:
logger.error(
f"Не удалось отредактировать тему {thread_id}. Статус: {response.status_code}"
)
return False
except asyncio.TimeoutError as e:
logger.error(
f"Превышено время ожидания при редактировании темы {thread_id}: {e}"
)
raise LolzNetworkError(
f"Превышено время ожидания при редактировании темы {thread_id}"
) from e
except Exception as e:
logger.error(f"Ошибка при редактировании темы {thread_id}: {e}")
raise LolzAPIError(f"Не удалось отредактировать тему {thread_id}") from e
async def can_bump(
self, thread_id: Optional[int] = None, thread: Dict[str, Any] = None
) -> bool:
if not thread:
if not thread_id:
raise ValueError("Необходимо указать thread_id или thread")
thread = await self.get_thread(thread_id=thread_id)
try:
return thread.get("permissions", {}).get("bump", {}).get("can", False)
except (KeyError, AttributeError, TypeError) as e:
logger.error(f"Неверная структура темы для проверки поднятия: {e}")
return False
async def bump_thread(self, thread_id: Union[str, int]) -> bool:
logger.debug(f"Поднимаю тему {thread_id}...")
try:
response = await self.client.threads.bump(thread_id=thread_id)
if response.status_code == 200:
logger.info(f"Тема {thread_id} успешно поднята.")
return True
else:
logger.error(
f"Не удалось поднять тему {thread_id}. Статус: {response.status_code}"
)
return False
except asyncio.TimeoutError as e:
logger.error(f"Превышено время ожидания при поднятии темы {thread_id}: {e}")
raise LolzNetworkError(
f"Превышено время ожидания при поднятии темы {thread_id}"
) from e
except Exception as e:
logger.error(f"Ошибка при поднятии темы {thread_id}: {e}")
raise LolzAPIError(f"Не удалось поднять тему {thread_id}") from e
async def get_post(self, post_id: Union[str, int]) -> Dict[str, Any]:
logger.debug(f"Запрашиваю данные поста {post_id}...")
try:
response = await self.client.posts.get(post_id=post_id)
post = (response.json()).get("post", {})
if not post:
raise LolzResponseError(
f"Пост {post_id} не найден или вернул пустой ответ"
)
return post
except asyncio.TimeoutError as e:
logger.error(f"Превышено время ожидания при получении поста {post_id}: {e}")
raise LolzNetworkError(
f"Превышено время ожидания при получении поста {post_id}"
) from e
except Exception as e:
logger.error(f"Ошибка при получении поста {post_id}: {e}")
raise LolzAPIError(f"Не удалось получить пост {post_id}") from e
async def get_thread_posts(
self, thread_id: Union[str, int], start_page: int = 1
) -> tuple[List[Dict[str, Any]], int]:
"""Fetch all pages starting from start_page.
Returns (posts, last_page) so the caller can resume from last_page
on the next cycle instead of re-fetching from page 1 every time.
"""
all_posts = []
page = start_page
last_page = start_page
while True:
try:
response = await self.client.posts.list(thread_id=thread_id, page=page)
posts = (response.json()).get("posts", [])
if len(posts) == 0:
break
all_posts.extend(posts)
last_page = page
logger.debug(
f"Получено {len(posts)} постов из темы {thread_id} на странице {page}."
)
page += 1
await asyncio.sleep(1)
except asyncio.TimeoutError as e:
logger.error(
f"Превышено время ожидания при получении постов из темы {thread_id}, страница {page}: {e}"
)
raise LolzNetworkError(
f"Превышено время ожидания при получении постов из темы {thread_id}"
) from e
except Exception as e:
logger.error(
f"Ошибка при получении постов из темы {thread_id}, страница {page}: {e}"
)
raise LolzAPIError(
f"Не удалось получить посты из темы {thread_id}"
) from e
if all_posts:
logger.info(f"Всего получено {len(all_posts)} постов из темы {thread_id}.")
else:
logger.info(f"Постов в теме {thread_id} не найдено.")
return all_posts, last_page
async def get_all_thread_posts(
self, thread_id: Union[str, int]
) -> List[Dict[str, Any]]:
posts, _ = await self.get_thread_posts(thread_id=thread_id, start_page=1)
return posts
async def get_post_comments(self, post_id: int) -> List[Dict[str, Any]]:
logger.debug(f"Запрашиваю комментарии к посту {post_id}...")
try:
response = await self.client.posts.comments.list(post_id=post_id)
comments = (response.json()).get("comments", [])
logger.debug(f"Найдено {len(comments)} комментариев к посту {post_id}.")
return comments
except asyncio.TimeoutError as e:
logger.error(
f"Превышено время ожидания при получении комментариев к посту {post_id}: {e}"
)
raise LolzNetworkError(
f"Превышено время ожидания при получении комментариев к посту {post_id}"
) from e
except Exception as e:
logger.error(f"Ошибка при получении комментариев к посту {post_id}: {e}")
raise LolzAPIError(
f"Не удалось получить комментарии к посту {post_id}"
) from e
async def has_comments(
self, post_id: Optional[int] = None, post: Dict[str, Any] = None
) -> bool:
if not post:
if not post_id:
raise ValueError("Необходимо указать post_id или post")
post = await self.get_post(post_id=post_id)
return post.get("post_comment_count", 0) > 0
async def create_comment(self, post_id: int, comment_body: str) -> bool:
logger.info(f"Публикую комментарий к посту {post_id}...")
try:
response = await self.client.posts.comments.create(
post_id=post_id, comment_body=comment_body
)
if response.status_code == 200:
logger.info(f"Комментарий к посту {post_id} успешно опубликован.")
return True
else:
logger.error(
f"Не удалось опубликовать комментарий к посту {post_id}. Статус: {response.status_code}"
)
return False
except asyncio.TimeoutError as e:
logger.error(
f"Превышено время ожидания при создании комментария к посту {post_id}: {e}"
)
raise LolzNetworkError(
f"Превышено время ожидания при создании комментария к посту {post_id}"
) from e
except Exception as e:
logger.error(f"Ошибка при создании комментария к посту {post_id}: {e}")
raise LolzAPIError(
f"Не удалось создать комментарий к посту {post_id}"
) from e