текущая версия (рабочая)
This commit is contained in:
@@ -0,0 +1,408 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from tortoise.exceptions import DoesNotExist
|
||||
|
||||
from app import config
|
||||
from app.models import Review, Settings
|
||||
from app.services import GPTClient, OzonClient, WildberriesClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
def _settings_keyboard(settings: Settings) -> types.InlineKeyboardMarkup:
|
||||
"""Клавиатура настроек: автоответ с текстом, автоответ без текста, анализ, модель."""
|
||||
return types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=f"Авто-ответ (с текстом): {'✅' if settings.auto_response_enabled else '❌'}",
|
||||
callback_data="toggle_auto_response",
|
||||
)
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=f"Авто-ответ (без текста): {'✅' if settings.auto_response_empty_enabled else '❌'}",
|
||||
callback_data="toggle_auto_response_empty",
|
||||
)
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=f"Анализ отзывов: {'✅' if settings.analysis_enabled else '❌'}",
|
||||
callback_data="toggle_analyze",
|
||||
)
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=f"GPT модель: {settings.gpt_model}",
|
||||
callback_data="change_model",
|
||||
)
|
||||
],
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class CustomReplyStates(StatesGroup):
|
||||
waiting_for_text = State()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("auto:"))
|
||||
async def callback_auto(call: CallbackQuery) -> None:
|
||||
action = call.data.split(":", 1)[1]
|
||||
|
||||
settings = await Settings.get_from_context(
|
||||
message_thread_id=call.message.message_thread_id
|
||||
)
|
||||
|
||||
if action == "enable":
|
||||
settings.auto_response_enabled = True
|
||||
await settings.save()
|
||||
await call.answer("Автоответы включены.")
|
||||
elif action == "disable":
|
||||
settings.auto_response_enabled = False
|
||||
await settings.save()
|
||||
await call.answer("Автоответы выключены.")
|
||||
|
||||
status_text = "Включены" if settings.auto_response_enabled else "Выключены"
|
||||
await call.message.edit_text(
|
||||
f"Статус автоответов: <b>{status_text}</b>",
|
||||
parse_mode="HTML",
|
||||
reply_markup=call.message.reply_markup,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("model:"))
|
||||
async def callback_model(call: CallbackQuery) -> None:
|
||||
model = call.data.split(":", 1)[1]
|
||||
|
||||
settings = await Settings.get_from_context(
|
||||
message_thread_id=call.message.message_thread_id
|
||||
)
|
||||
settings.gpt_model = model
|
||||
await settings.save()
|
||||
|
||||
await call.answer(f"Модель изменена на {model}")
|
||||
await call.message.edit_text(
|
||||
f"Модель изменена на <b>{model}</b>", parse_mode="HTML"
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("ozon_delete:"))
|
||||
async def callback_ozon_delete(call: CallbackQuery) -> None:
|
||||
comment_id = call.data.split(":", 1)[1]
|
||||
|
||||
ozon_client = OzonClient()
|
||||
result = await ozon_client.delete_comment(comment_id)
|
||||
|
||||
if "error" not in result:
|
||||
try:
|
||||
review = await Review.filter(response_id=comment_id).first()
|
||||
if review:
|
||||
review.response_text = None
|
||||
review.response_id = None
|
||||
review.responded_at = None
|
||||
await review.save()
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating review after comment deletion: {e}")
|
||||
|
||||
await call.answer("Комментарий успешно удалён на Ozon.")
|
||||
await call.message.reply("🗑 Ответ успешно удален с Ozon")
|
||||
else:
|
||||
await call.answer("Не удалось удалить комментарий.")
|
||||
await call.message.reply(
|
||||
f"❌ Ошибка удаления комментария: {result.get('error')}"
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("wb_delete:"))
|
||||
async def callback_wb_delete(call: CallbackQuery) -> None:
|
||||
reply_id = call.data.split(":", 1)[1]
|
||||
|
||||
wb_client = WildberriesClient()
|
||||
result = await wb_client.delete_comment(reply_id)
|
||||
|
||||
if result.get("success"):
|
||||
try:
|
||||
review = await Review.filter(response_id=reply_id).first()
|
||||
if review:
|
||||
review.response_text = None
|
||||
review.response_id = None
|
||||
review.responded_at = None
|
||||
await review.save()
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating review after comment deletion: {e}")
|
||||
|
||||
await call.answer("Комментарий успешно удалён на Wildberries.")
|
||||
await call.message.reply("🗑 Ответ успешно удален с Wildberries")
|
||||
else:
|
||||
await call.answer("Не удалось удалить комментарий.")
|
||||
await call.message.reply(
|
||||
f"❌ Ошибка удаления комментария: {result.get('error')}"
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("generate_answer:"))
|
||||
async def callback_generate_answer(call: CallbackQuery) -> None:
|
||||
review_id = call.data.split(":", 1)[1]
|
||||
|
||||
try:
|
||||
review = await Review.get(id=review_id)
|
||||
settings = await Settings.get_from_context(
|
||||
message_thread_id=call.message.message_thread_id
|
||||
)
|
||||
|
||||
gpt_client = GPTClient()
|
||||
answer = await gpt_client.generate_answer(settings, review)
|
||||
|
||||
markup = InlineKeyboardBuilder()
|
||||
markup.row(
|
||||
InlineKeyboardButton(
|
||||
text="Редактировать",
|
||||
callback_data=f"custom_reply:{review_id}",
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="Отправить",
|
||||
callback_data=f"send_answer:{review_id}",
|
||||
),
|
||||
)
|
||||
|
||||
await call.message.reply(
|
||||
f"#review{review_id}\n\n<blockquote>{answer}</blockquote>",
|
||||
parse_mode="HTML",
|
||||
reply_markup=markup.as_markup(),
|
||||
)
|
||||
|
||||
# Store the generated answer temporarily
|
||||
review.response_text = answer
|
||||
await review.save()
|
||||
|
||||
await call.answer("Ответ сгенерирован")
|
||||
except DoesNotExist:
|
||||
await call.answer("Отзыв не найден.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating answer: {e}")
|
||||
await call.answer("Произошла ошибка при генерации ответа.")
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("quick_reply:"))
|
||||
async def callback_quick_reply(call: CallbackQuery) -> None:
|
||||
"""Подставить шаблонный ответ для пустого отзыва и показать кнопку «Отправить ответ»."""
|
||||
parts = call.data.split(":", 2)
|
||||
if len(parts) != 3:
|
||||
await call.answer("Неверные данные.")
|
||||
return
|
||||
_, review_id, template = parts
|
||||
try:
|
||||
review = await Review.get(id=review_id)
|
||||
settings = await Settings.get_from_context(review=review)
|
||||
text = (
|
||||
(settings.template_empty_high_rating or config.TEMPLATE_EMPTY_HIGH_RATING)
|
||||
if template == "high"
|
||||
else (settings.template_empty_low_rating or config.TEMPLATE_EMPTY_LOW_RATING)
|
||||
)
|
||||
review.response_text = text
|
||||
await review.save()
|
||||
|
||||
markup = InlineKeyboardBuilder()
|
||||
markup.row(
|
||||
InlineKeyboardButton(
|
||||
text="Отправить ответ",
|
||||
callback_data=f"send_answer:{review_id}",
|
||||
),
|
||||
)
|
||||
await call.message.reply(
|
||||
f"#review{review_id}\n\nБудет отправлено:\n<blockquote>{text}</blockquote>\n\nНажмите «Отправить ответ».",
|
||||
parse_mode="HTML",
|
||||
reply_markup=markup.as_markup(),
|
||||
)
|
||||
await call.answer("Текст подставлен. Нажмите «Отправить ответ».")
|
||||
except DoesNotExist:
|
||||
await call.answer("Отзыв не найден.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error in quick_reply: {e}")
|
||||
await call.answer("Ошибка при подстановке ответа.")
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("custom_reply:"))
|
||||
async def callback_custom_reply(call: CallbackQuery, state: FSMContext) -> None:
|
||||
"""Initiate custom reply for a review."""
|
||||
review_id = call.data.split(":", 1)[1]
|
||||
await state.update_data(review_id=review_id)
|
||||
await state.set_state(CustomReplyStates.waiting_for_text)
|
||||
|
||||
await call.message.reply(
|
||||
f"✏️ Введите ваш ответ на отзыв #review{review_id}:", parse_mode="HTML"
|
||||
)
|
||||
|
||||
await call.answer("Введите ваш ответ")
|
||||
|
||||
|
||||
@router.message(CustomReplyStates.waiting_for_text)
|
||||
async def process_custom_reply(message: Message, state: FSMContext) -> None:
|
||||
custom_text = message.text
|
||||
|
||||
state_data = await state.get_data()
|
||||
review_id = state_data.get("review_id")
|
||||
|
||||
try:
|
||||
review = await Review.get(id=review_id)
|
||||
|
||||
review.response_text = custom_text
|
||||
await review.save()
|
||||
|
||||
await state.clear()
|
||||
|
||||
markup = InlineKeyboardBuilder()
|
||||
markup.row(
|
||||
InlineKeyboardButton(
|
||||
text="Редактировать",
|
||||
callback_data=f"custom_reply:{review_id}",
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="Отправить",
|
||||
callback_data=f"send_answer:{review_id}",
|
||||
),
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
f"#review{review_id}\n\n<blockquote>{custom_text}</blockquote>\n\nВыберите действие:",
|
||||
parse_mode="HTML",
|
||||
reply_markup=markup.as_markup(),
|
||||
reply_to_message_id=review.message_id,
|
||||
)
|
||||
|
||||
except DoesNotExist:
|
||||
await message.reply("Отзыв не найден.")
|
||||
await state.clear()
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing custom reply: {e}")
|
||||
await message.reply("Произошла ошибка при обработке ответа.")
|
||||
await state.clear()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("send_answer:"))
|
||||
async def callback_send_answer(call: CallbackQuery) -> None:
|
||||
review_id = call.data.split(":")[1]
|
||||
|
||||
try:
|
||||
review = await Review.get(id=review_id)
|
||||
if not review.response_text:
|
||||
await call.answer("Сначала выберите или введите ответ.", show_alert=True)
|
||||
return
|
||||
if review.responded_at:
|
||||
await call.message.edit_text("Ответ на отзыв уже отправлен.")
|
||||
await call.answer("Ответ уже отправлен.", show_alert=True)
|
||||
return
|
||||
|
||||
if review.platform == "ozon":
|
||||
ozon_client = OzonClient()
|
||||
result = await ozon_client.post_comment(
|
||||
review.external_id, review.response_text
|
||||
)
|
||||
|
||||
if "comment_id" in result:
|
||||
review.response_id = result["comment_id"]
|
||||
await review.save()
|
||||
|
||||
markup = InlineKeyboardBuilder()
|
||||
markup.add(
|
||||
InlineKeyboardButton(
|
||||
text="Удалить",
|
||||
callback_data=f"ozon_delete:{result['comment_id']}",
|
||||
)
|
||||
)
|
||||
|
||||
await call.message.answer(
|
||||
f"✅ Ответ на #review{review_id} успешно отправлен на Ozon",
|
||||
reply_to_message_id=review.message_id,
|
||||
reply_markup=markup.as_markup(),
|
||||
)
|
||||
else:
|
||||
await call.message.reply(
|
||||
f"❌ Ошибка отправки ответа на Ozon: {result.get('error')}"
|
||||
)
|
||||
|
||||
elif review.platform == "wb":
|
||||
wb_client = WildberriesClient()
|
||||
result = await wb_client.post_comment(
|
||||
review.external_id, review.response_text
|
||||
)
|
||||
|
||||
if result.get("success"):
|
||||
await call.message.answer(
|
||||
f"✅ Ответ на #review{review_id} успешно отправлен на Wildberries!",
|
||||
reply_to_message_id=review.message_id,
|
||||
)
|
||||
else:
|
||||
await call.message.reply(
|
||||
f"❌ Ошибка отправки ответа на Wildberries: {result.get('error')}"
|
||||
)
|
||||
|
||||
await call.answer("Ответ отправлен")
|
||||
review.responded_at = datetime.now()
|
||||
await review.save()
|
||||
except DoesNotExist:
|
||||
await call.answer("Отзыв не найден.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending answer: {e}")
|
||||
await call.answer("Произошла ошибка при отправке ответа.")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "toggle_auto_response")
|
||||
async def toggle_auto_response(callback: types.CallbackQuery) -> None:
|
||||
settings = await Settings.get_from_context(
|
||||
message_thread_id=callback.message.message_thread_id
|
||||
)
|
||||
settings.auto_response_enabled = not settings.auto_response_enabled
|
||||
await settings.save()
|
||||
|
||||
status_text = "Включен" if settings.auto_response_enabled else "Выключен"
|
||||
await callback.answer(f"Авто-ответ (с текстом): {status_text}")
|
||||
await callback.message.edit_reply_markup(reply_markup=_settings_keyboard(settings))
|
||||
|
||||
|
||||
@router.callback_query(F.data == "toggle_auto_response_empty")
|
||||
async def toggle_auto_response_empty(callback: types.CallbackQuery) -> None:
|
||||
settings = await Settings.get_from_context(
|
||||
message_thread_id=callback.message.message_thread_id
|
||||
)
|
||||
settings.auto_response_empty_enabled = not settings.auto_response_empty_enabled
|
||||
await settings.save()
|
||||
|
||||
status_text = "Включен" if settings.auto_response_empty_enabled else "Выключен"
|
||||
await callback.answer(f"Авто-ответ (без текста): {status_text}")
|
||||
await callback.message.edit_reply_markup(reply_markup=_settings_keyboard(settings))
|
||||
|
||||
|
||||
@router.callback_query(F.data == "change_model")
|
||||
async def change_model(callback: types.CallbackQuery) -> None:
|
||||
settings = await Settings.get_from_context(
|
||||
message_thread_id=callback.message.message_thread_id
|
||||
)
|
||||
if settings.gpt_model == "gpt-4o":
|
||||
settings.gpt_model = "gpt-4o-mini"
|
||||
else:
|
||||
settings.gpt_model = "gpt-4o"
|
||||
await settings.save()
|
||||
|
||||
await callback.answer(f"Модель изменена на {settings.gpt_model}")
|
||||
await callback.message.edit_reply_markup(reply_markup=_settings_keyboard(settings))
|
||||
|
||||
|
||||
@router.callback_query(F.data == "toggle_analyze")
|
||||
async def toggle_analyze(callback: types.CallbackQuery) -> None:
|
||||
settings = await Settings.get_from_context(
|
||||
message_thread_id=callback.message.message_thread_id
|
||||
)
|
||||
settings.analysis_enabled = not settings.analysis_enabled
|
||||
await settings.save()
|
||||
|
||||
status_text = "Включен" if settings.analysis_enabled else "Выключен"
|
||||
await callback.answer(f"Анализ отзывов: {status_text}")
|
||||
await callback.message.edit_reply_markup(reply_markup=_settings_keyboard(settings))
|
||||
Reference in New Issue
Block a user