feat(captcha): add validation and thread editing
This commit is contained in:
@@ -131,6 +131,10 @@ class Config:
|
||||
).lower() == "true"
|
||||
captcha_answer = os.getenv(key="CAPTCHA_ANSWER", default=None)
|
||||
|
||||
# Validate captcha configuration
|
||||
if enable_captcha and not captcha_answer:
|
||||
raise ConfigError("ENABLE_CAPTCHA установлен, но CAPTCHA_ANSWER не задан")
|
||||
|
||||
return {
|
||||
# Pyrogram
|
||||
"api_id": api_id,
|
||||
|
||||
+103
-18
@@ -9,16 +9,19 @@ 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
|
||||
|
||||
|
||||
@@ -45,17 +48,68 @@ class Lolz:
|
||||
thread = (response.json()).get("thread", {})
|
||||
|
||||
if not thread:
|
||||
raise LolzResponseError(f"Тема {thread_id} не найдена или вернула пустой ответ")
|
||||
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
|
||||
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.client.threads.get(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:
|
||||
@@ -79,12 +133,16 @@ class Lolz:
|
||||
logger.info(f"Тема {thread_id} успешно поднята.")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Не удалось поднять тему {thread_id}. Статус: {response.status_code}")
|
||||
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
|
||||
raise LolzNetworkError(
|
||||
f"Превышено время ожидания при поднятии темы {thread_id}"
|
||||
) from e
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при поднятии темы {thread_id}: {e}")
|
||||
raise LolzAPIError(f"Не удалось поднять тему {thread_id}") from e
|
||||
@@ -96,13 +154,17 @@ class Lolz:
|
||||
post = (response.json()).get("post", {})
|
||||
|
||||
if not post:
|
||||
raise LolzResponseError(f"Пост {post_id} не найден или вернул пустой ответ")
|
||||
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
|
||||
raise LolzNetworkError(
|
||||
f"Превышено время ожидания при получении поста {post_id}"
|
||||
) from e
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении поста {post_id}: {e}")
|
||||
raise LolzAPIError(f"Не удалось получить пост {post_id}") from e
|
||||
@@ -137,11 +199,19 @@ class Lolz:
|
||||
await asyncio.sleep(1)
|
||||
|
||||
except asyncio.TimeoutError as e:
|
||||
logger.error(f"Превышено время ожидания при получении постов из темы {thread_id}, страница {page}: {e}")
|
||||
raise LolzNetworkError(f"Превышено время ожидания при получении постов из темы {thread_id}") from 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
|
||||
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}.")
|
||||
@@ -165,11 +235,17 @@ class Lolz:
|
||||
return comments
|
||||
|
||||
except asyncio.TimeoutError as e:
|
||||
logger.error(f"Превышено время ожидания при получении комментариев к посту {post_id}: {e}")
|
||||
raise LolzNetworkError(f"Превышено время ожидания при получении комментариев к посту {post_id}") from 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
|
||||
raise LolzAPIError(
|
||||
f"Не удалось получить комментарии к посту {post_id}"
|
||||
) from e
|
||||
|
||||
async def has_comments(
|
||||
self, post_id: Optional[int] = None, post: Dict[str, Any] = None
|
||||
@@ -192,12 +268,21 @@ class Lolz:
|
||||
logger.info(f"Комментарий к посту {post_id} успешно опубликован.")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Не удалось опубликовать комментарий к посту {post_id}. Статус: {response.status_code}")
|
||||
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
|
||||
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
|
||||
raise LolzAPIError(
|
||||
f"Не удалось создать комментарий к посту {post_id}"
|
||||
) from e
|
||||
|
||||
@@ -141,6 +141,51 @@ class TelegramStarsBot:
|
||||
logger.error(f"Критическая ошибка при отправке звезд: {e}")
|
||||
return False
|
||||
|
||||
async def add_captcha(self):
|
||||
captcha_text = self.config._load_captcha()
|
||||
if (
|
||||
not captcha_text
|
||||
or not self.config.enable_captcha
|
||||
or not self.config.captcha_answer
|
||||
):
|
||||
if not self.config.enable_captcha:
|
||||
logger.error("Капча выключена. Редактирование темы невозможно.")
|
||||
elif not self.config.captcha_answer:
|
||||
logger.error(
|
||||
"Ответ для капчи не установлен. Редактирование темы невозможно."
|
||||
)
|
||||
elif not captcha_text:
|
||||
logger.error(
|
||||
"Вопрос (текст) капчи не установлен. Редактирование темы невозможно."
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
try:
|
||||
thread = await self.lolz_api.get_thread(thread_id=self.config.thread_id)
|
||||
thread_text = thread.get("post_body", "")
|
||||
|
||||
# Check if captcha already exists in thread
|
||||
if "[spoiler=Капча" in thread_text:
|
||||
logger.info(f"Капча уже существует в теме {self.config.thread_id}, пропускаю добавление.")
|
||||
return True
|
||||
|
||||
captcha_wrapper = f"\n\n[spoiler=Капча (обязательно);align=center][CENTER]{captcha_text}[/CENTER][/spoiler]"
|
||||
thread_text += captcha_wrapper
|
||||
|
||||
await self.lolz_api.edit_thread(
|
||||
thread_id=self.config.thread_id, post_body=thread_text
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Тема {self.config.thread_id} отредактирована. Текст с капчей добавлен"
|
||||
)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Критическая ошибка при редактировании темы: {e}")
|
||||
return False
|
||||
|
||||
async def _process_single_post(self, post: Dict[str, Any]):
|
||||
post_message_id = None
|
||||
|
||||
@@ -178,6 +223,12 @@ class TelegramStarsBot:
|
||||
self.processed_manager.mark_processed(post_id)
|
||||
return
|
||||
|
||||
if self.config.enable_captcha and self.config.captcha_answer:
|
||||
if not str(self.config.captcha_answer) in post_content:
|
||||
logger.info(f"Пост {post_id} не содержит капчу. Пропускаю обработку.")
|
||||
self.processed_manager.mark_processed(post_id)
|
||||
return
|
||||
|
||||
links = TelegramLinkExtractor.extract(post_content)
|
||||
if not links:
|
||||
logger.info(f"В посте {post_id} не найдено ссылок Telegram.")
|
||||
@@ -380,6 +431,9 @@ class TelegramStarsBot:
|
||||
# _main_loop - Основной цикл для остлеживания темы
|
||||
# _bump_loop - Цикл для авто-поднятия темы
|
||||
|
||||
if self.config.enable_captcha:
|
||||
await self.add_captcha()
|
||||
|
||||
if self.config.enable_auto_bump:
|
||||
await asyncio.gather(self._main_loop(), self._bump_loop())
|
||||
else:
|
||||
@@ -388,6 +442,7 @@ class TelegramStarsBot:
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка в главном процессе бота: {e}")
|
||||
raise
|
||||
|
||||
finally:
|
||||
if self.client and self.client.is_connected:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user