mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-27 14:51:19 +00:00
- Add pyproject.toml with uv and ruff configuration - Pin Python version to 3.13 via .python-version - Add Makefile commands: lint, format, fix - Apply ruff formatting to entire codebase - Remove unused imports (base64 in yookassa/simple_subscription) - Update .gitignore for new config files
268 lines
6.9 KiB
Python
268 lines
6.9 KiB
Python
import logging
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.config import settings
|
|
from app.database.crud.faq import (
|
|
bulk_update_order,
|
|
create_faq_page,
|
|
delete_faq_page,
|
|
get_faq_page_by_id,
|
|
get_faq_pages,
|
|
get_faq_setting,
|
|
set_faq_enabled,
|
|
update_faq_page,
|
|
)
|
|
from app.database.models import FaqPage, FaqSetting
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class FaqService:
|
|
MAX_PAGE_LENGTH = 3500
|
|
|
|
@staticmethod
|
|
def _normalize_language(language: str) -> str:
|
|
base_language = language or settings.DEFAULT_LANGUAGE or 'ru'
|
|
return base_language.split('-')[0].lower()
|
|
|
|
@staticmethod
|
|
def normalize_language(language: str) -> str:
|
|
return FaqService._normalize_language(language)
|
|
|
|
@classmethod
|
|
async def get_setting(
|
|
cls,
|
|
db: AsyncSession,
|
|
language: str,
|
|
*,
|
|
fallback: bool = True,
|
|
) -> FaqSetting | None:
|
|
lang = cls._normalize_language(language)
|
|
setting = await get_faq_setting(db, lang)
|
|
|
|
if setting or not fallback:
|
|
return setting
|
|
|
|
default_lang = cls._normalize_language(settings.DEFAULT_LANGUAGE)
|
|
if lang != default_lang:
|
|
return await get_faq_setting(db, default_lang)
|
|
|
|
return setting
|
|
|
|
@classmethod
|
|
async def is_enabled(cls, db: AsyncSession, language: str) -> bool:
|
|
pages = await cls.get_pages(db, language)
|
|
return bool(pages)
|
|
|
|
@classmethod
|
|
async def set_enabled(
|
|
cls,
|
|
db: AsyncSession,
|
|
language: str,
|
|
enabled: bool,
|
|
) -> FaqSetting:
|
|
lang = cls._normalize_language(language)
|
|
return await set_faq_enabled(db, lang, enabled)
|
|
|
|
@classmethod
|
|
async def toggle_enabled(
|
|
cls,
|
|
db: AsyncSession,
|
|
language: str,
|
|
) -> FaqSetting:
|
|
lang = cls._normalize_language(language)
|
|
setting = await get_faq_setting(db, lang)
|
|
new_status = True
|
|
if setting:
|
|
new_status = not setting.is_enabled
|
|
return await set_faq_enabled(db, lang, new_status)
|
|
|
|
@classmethod
|
|
async def get_pages(
|
|
cls,
|
|
db: AsyncSession,
|
|
language: str,
|
|
*,
|
|
include_inactive: bool = False,
|
|
fallback: bool = True,
|
|
) -> list[FaqPage]:
|
|
lang = cls._normalize_language(language)
|
|
pages = await get_faq_pages(db, lang, include_inactive=include_inactive)
|
|
|
|
if pages:
|
|
setting = await get_faq_setting(db, lang)
|
|
if setting and not setting.is_enabled and not include_inactive:
|
|
return []
|
|
return pages
|
|
|
|
if not fallback:
|
|
return []
|
|
|
|
default_lang = cls._normalize_language(settings.DEFAULT_LANGUAGE)
|
|
if lang == default_lang:
|
|
return []
|
|
|
|
fallback_pages = await get_faq_pages(
|
|
db,
|
|
default_lang,
|
|
include_inactive=include_inactive,
|
|
)
|
|
|
|
if not fallback_pages:
|
|
return []
|
|
|
|
setting = await get_faq_setting(db, default_lang)
|
|
if setting and not setting.is_enabled and not include_inactive:
|
|
return []
|
|
|
|
return fallback_pages
|
|
|
|
@classmethod
|
|
async def get_page(
|
|
cls,
|
|
db: AsyncSession,
|
|
page_id: int,
|
|
language: str,
|
|
*,
|
|
fallback: bool = True,
|
|
include_inactive: bool = False,
|
|
) -> FaqPage | None:
|
|
page = await get_faq_page_by_id(db, page_id)
|
|
if not page:
|
|
return None
|
|
|
|
lang = cls._normalize_language(language)
|
|
default_lang = cls._normalize_language(settings.DEFAULT_LANGUAGE)
|
|
|
|
if not include_inactive and not page.is_active:
|
|
return None
|
|
|
|
if page.language == lang:
|
|
return page
|
|
|
|
if fallback and page.language == default_lang:
|
|
return page
|
|
|
|
return None
|
|
|
|
@classmethod
|
|
async def create_page(
|
|
cls,
|
|
db: AsyncSession,
|
|
*,
|
|
language: str,
|
|
title: str,
|
|
content: str,
|
|
display_order: int | None = None,
|
|
is_active: bool | None = None,
|
|
) -> FaqPage:
|
|
lang = cls._normalize_language(language)
|
|
is_active_value = True if is_active is None else bool(is_active)
|
|
page = await create_faq_page(
|
|
db,
|
|
language=lang,
|
|
title=title,
|
|
content=content,
|
|
display_order=display_order,
|
|
is_active=is_active_value,
|
|
)
|
|
|
|
setting = await get_faq_setting(db, lang)
|
|
if not setting:
|
|
await set_faq_enabled(db, lang, True)
|
|
|
|
return page
|
|
|
|
@classmethod
|
|
async def update_page(
|
|
cls,
|
|
db: AsyncSession,
|
|
page: FaqPage,
|
|
*,
|
|
title: str | None = None,
|
|
content: str | None = None,
|
|
display_order: int | None = None,
|
|
is_active: bool | None = None,
|
|
) -> FaqPage:
|
|
return await update_faq_page(
|
|
db,
|
|
page,
|
|
title=title,
|
|
content=content,
|
|
display_order=display_order,
|
|
is_active=is_active,
|
|
)
|
|
|
|
@classmethod
|
|
async def delete_page(cls, db: AsyncSession, page_id: int) -> None:
|
|
await delete_faq_page(db, page_id)
|
|
|
|
@classmethod
|
|
async def reorder_pages(
|
|
cls,
|
|
db: AsyncSession,
|
|
language: str,
|
|
pages: list[FaqPage],
|
|
) -> None:
|
|
lang = cls._normalize_language(language)
|
|
ordered = [page for page in pages if page.language == lang]
|
|
payload = [(page.id, index + 1) for index, page in enumerate(ordered)]
|
|
await bulk_update_order(db, payload)
|
|
|
|
@staticmethod
|
|
def split_content_into_pages(
|
|
content: str,
|
|
*,
|
|
max_length: int | None = None,
|
|
) -> list[str]:
|
|
if not content:
|
|
return []
|
|
|
|
normalized = content.replace('\r\n', '\n').strip()
|
|
if not normalized:
|
|
return []
|
|
|
|
limit = max_length or FaqService.MAX_PAGE_LENGTH
|
|
if len(normalized) <= limit:
|
|
return [normalized]
|
|
|
|
paragraphs = [paragraph.strip() for paragraph in normalized.split('\n\n') if paragraph.strip()]
|
|
|
|
pages: list[str] = []
|
|
current = ''
|
|
|
|
def flush_current() -> None:
|
|
nonlocal current
|
|
if current:
|
|
pages.append(current.strip())
|
|
current = ''
|
|
|
|
for paragraph in paragraphs:
|
|
candidate = f'{current}\n\n{paragraph}'.strip() if current else paragraph
|
|
if len(candidate) <= limit:
|
|
current = candidate
|
|
continue
|
|
|
|
flush_current()
|
|
|
|
if len(paragraph) <= limit:
|
|
current = paragraph
|
|
continue
|
|
|
|
start = 0
|
|
while start < len(paragraph):
|
|
chunk = paragraph[start : start + limit]
|
|
pages.append(chunk.strip())
|
|
start += limit
|
|
|
|
current = ''
|
|
|
|
flush_current()
|
|
|
|
if not pages:
|
|
return [normalized[:limit]]
|
|
|
|
return pages
|