2 次代碼提交 e634700d7a ... aab2673480

作者 SHA1 備註 提交日期
  Milinuri Nirvalen aab2673480 Vk: Port multi-page tutorial from Telegram bot 1 年之前
  Milinuri Nirvalen a8625fcdae Vk: Port Telegram Bot messages 1 年之前
共有 3 個文件被更改,包括 291 次插入118 次删除
  1. 40 32
      sp_vk/bot.py
  2. 40 4
      sp_vk/keyboards.py
  3. 211 82
      sp_vk/messages.py

+ 40 - 32
sp_vk/bot.py

@@ -2,7 +2,7 @@
 Вк бот для доступа к SPMessages.
 
 Author: Milinuri Nirvalen
-Ver: 1.2 +4 (17, sp v5.7)
+Ver: 1.2 +5 (20, sp v5.7)
 """
 
 from sp.intents import Intent
@@ -71,7 +71,7 @@ def process_request(sp: SPMessages, request_text: str) -> str:
 async def home_handler(message: Message, sp: SPMessages):
     """Справка и главная клавиатура."""
     if sp.user["set_class"]:
-        await message.answer(messages.send_home_message(sp),
+        await message.answer(messages.HOME,
             keyboard=keyboards.get_home_keyboard(sp)
         )
     else:
@@ -83,11 +83,11 @@ async def home_handler(message: Message, sp: SPMessages):
 # Текстовая информация
 # ====================
 
-@bot.on.message(command="restrictions")
-@bot.on.message(payload={"cmd": "restrictions"})
-async def restrictions_handler(message: Message):
+@bot.on.message(command="cl_features")
+@bot.on.message(payload={"cmd": "cl_features"})
+async def cl_features_handler(message: Message):
     """Список огранчиений при отвязанном классе."""
-    await message.answer(messages.RESTRICTIONS)
+    await message.answer(messages.CL_FEATURES)
 
 @bot.on.message(command="info")
 @bot.on.message(payload={"cmd": "info"})
@@ -106,7 +106,7 @@ async def set_class_hadler(message: Message, sp: SPMessages, args: tuple[str]):
     res = sp.set_class(None if cl in ("-", "pass") else cl)
 
     if res is True:
-        text = messages.send_home_message(sp)
+        text = messages.HOME
     else:
         text = "👀 Такого класса не существует."
         text += f"\n💡 Доступныe классы: {', '.join(sp.sc.lessons)}"
@@ -122,7 +122,7 @@ async def reset_user_hadler(message: Message, sp: SPMessages, cl: Optional[str]=
         res = sp.set_class(cl)
 
         if res:
-            text = messages.send_home_message(sp)
+            text = messages.HOME
             kb = keyboards.get_home_keyboard(sp)
         else:
             text = "👀 Такого класса не существует."
@@ -202,7 +202,7 @@ async def week_sc_handler(message: Message, sp: SPMessages):
 async def notify_info_handler(message: Message, sp: SPMessages):
     """Отправдяет информацию об уведомлениях."""
     if sp.user["class_let"]:
-        text = messages.send_notifications_info(sp)
+        text = messages.send_notify_info(sp.user["enable"], sp.user["hours"])
         kb = keyboards.get_notify_keyboad(sp)
     else:
         text = "⚠️ Для работы системы уведомлений вам нужно указать класс."
@@ -219,7 +219,9 @@ async def switch_notify_handler(message: Message, sp: SPMessages):
         sp.user["notifications"] = True
 
     sp.save_user()
-    await message.answer(messages.send_notifications_info(sp),
+    await message.answer(messages.send_notify_info(
+            sp.user["enabled"], sp.user["hours"]
+        ),
         keyboard=keyboards.get_notify_keyboad(sp)
     )
 
@@ -285,23 +287,17 @@ async def counter_handler(message: Message, sp: SPMessages):
 @bot.on.message(payload={"group":"updates", "action": "last"})
 async def updates_command(message: Message, sp: SPMessages):
     """Оправляет список изменений в расписании."""
-    text = "🔔 Изменения в расписании:\n"
-
     updates = sp.sc.updates
-    if len(updates):
-        text = send_update(updates[-1])
-    else:
-        text = "Нет новых обновлений."
-
-    kb = keyboards.get_updates_keyboard(
-        max(len(updates)-1, 0), len(updates)
+    update = updates[-1] if len(updates) else None
+    await message.answer(messages.send_updates(update),
+        keyboard=keyboards.get_updates_keyboard(
+           max(len(updates)-1, 0), len(updates)
+        )
     )
-    await message.answer(text, keyboard=kb)
 
 @bot.on.message(payload_contains={"group":"updates"})
 async def updates_handler(message: Message, sp: SPMessages):
     """Обработчик клавиатуры списка обновлений."""
-    text = "🔔 Изменения "
     payload = message.get_payload_json()
 
     # Смена класса, если требутеся
@@ -310,32 +306,45 @@ async def updates_handler(message: Message, sp: SPMessages):
     else:
         cl = payload["cl"]
 
-    # Доплняем шапку сообщения
     if cl is not None and sp.user["set_class"]:
-        text += f"для {cl}:\n"
         intent = Intent.construct(sp.sc, cl=[cl])
     else:
-        text += "в расписании:\n"
         intent = Intent()
 
     updates = sp.sc.get_updates(intent)
 
     if len(updates):
         if payload["action"] == "switch":
-            text += send_update(updates[-1])
+            update = updates[-1]
             i = max(len(updates)-1, 0)
         elif payload["action"] == "next":
             i = (max(min(payload["i"], len(updates)-1), 0) + 1) % len(updates)
-            text += send_update(updates[i])
+            update = updates[i]
         elif payload["action"] == "back":
             i = (max(min(payload["i"], len(updates)-1), 0) - 1) % len(updates)
-            text += send_update(updates[i])
+            update = updates[i]
     else:
-        text += "✨ Нет новых обновлений."
+        update = None
         i = 0
 
-    kb = keyboards.get_updates_keyboard(i, len(updates), cl)
-    await message.answer(text, keyboard=kb)
+    await message.answer(
+        messages.send_updates(update, cl),
+        keyboard=keyboards.get_updates_keyboard(i, len(updates), cl)
+    )
+
+
+# Многостраничная справка
+# =======================
+
+@bot.on.message(command="tutorial")
+@bot.on.message(payload_contains={"group":"tutorial"})
+async def tutorial_handler(message: Message):
+    """Обработчик многостраничной справки."""
+    payload = message.get_payload_json()
+    page = 0 if payload is None else int(payload.get("page", 0))
+    await message.answer(messages.TUTORIAL_MESSAGES[page],
+        keyboard=keyboards.get_tutorial_keyboard(page)
+    )
 
 
 # Обработка текстовых запросов
@@ -352,11 +361,10 @@ async def message_handler(message: Message, sp: SPMessages):
 
     elif text in sp.sc.lessons:
         sp.set_class(text)
-        await message.answer(messages.send_home_message(sp),
+        await message.answer(messages.HOME,
             keyboard=keyboards.get_home_keyboard(sp)
         )
     else:
         text = "👀 Такого класса не существует."
         text += f"\n💡 Доступныe классы: {', '.join(sp.sc.lessons)}"
         await message.answer(text)
-

+ 40 - 4
sp_vk/keyboards.py

@@ -5,6 +5,7 @@ Author: Milinuri Nirvalen
 """
 
 from sp.messages import SPMessages
+from sp_vk.messages import TUTORIAL_MESSAGES
 
 from typing import Optional
 
@@ -21,7 +22,7 @@ TO_HOME = (
 # Для меню выбора кдасса
 SET_CLASS = (
     Keyboard()
-    .add(Text("Ограничения", payload={"cmd": "restrictions"}))
+    .add(Text("Ограничения", payload={"cmd": "cl_features"}))
     .add(Text("Пропустить",
         payload={"cmd": "pass"}), color=KeyboardButtonColor.NEGATIVE
     )
@@ -41,7 +42,7 @@ def get_home_keyboard(sp: SPMessages) -> dict:
     """
     cl = sp.user["class_let"]
     kb = Keyboard()
-    kb.add(Text("🏠 Справка", payload={"cmd": "home"}))
+    kb.add(Text("🌟 Обучения", payload={"group": "tutorial"}))
 
     if cl is not None:
         kb.add(Text("📚 на неделю", payload={"cmd": "week"}))
@@ -129,6 +130,7 @@ _COUNTERS = {
 }
 
 _TARGETS = {
+    "none": "Ничего",
     "cl": "Классы",
     "days": "Дни",
     "lessons": "Уроки",
@@ -189,8 +191,7 @@ def get_counter_keyboard(sp: SPMessages, counter: str, target: str) -> dict:
     return kb.get_json()
 
 
-def get_updates_keyboard(
-    page: int, total: int, cl: Optional[str]=None) -> dict:
+def get_updates_keyboard(page: int, total: int, cl: Optional[str]=None) -> dict:
     """Собирает клввиатуру для просмотра списка изменений расписания.
 
     Args:
@@ -214,3 +215,38 @@ def get_updates_keyboard(
         .add(Text("🏠 Домой", payload={"cmd": "home"}))
         .get_json()
     )
+
+def get_tutorial_keyboard(page: int) -> dict:
+    """Клавиатура многостраничного обучения.
+
+    Испльзуется для перемещения между страницами обучения.
+
+    Args:
+        page (int): Текущая страница справки.
+
+    Returns:
+        dict: Клавиатура для перемещения по справке.
+    """
+    kb = Keyboard()
+    # Если это первая страница -> без кнопки назад
+    if page == 0:
+        kb.add(Text("🚀 Начать", payload={"group":"tutorial", "page":1}))
+
+    # Кнопкеи для управления просмотром
+    elif page != len(TUTORIAL_MESSAGES)-1:
+        kb.add(Text("◁", payload={"group":"tutorial", "page":page-1}))
+        kb.add(Text("🌟 Дальше", payload={"group":"tutorial", "page":page+1}))
+
+        for i, x in enumerate(TUTORIAL_MESSAGES[1:-1]):
+            kb.row()
+            kb.add(Text(x.splitlines()[0],
+                payload={"group":"tutorial", "page":i}
+            ))
+        kb.row()
+        kb.add(Text("🏠 Домой", payload={"cmd": "home"}))
+
+    # Завершение обучения
+    else:
+        kb.add(Text("🎉 Завершить", payload={"cmd": "home"}))
+
+    return kb.get_json()

+ 211 - 82
sp_vk/messages.py

@@ -4,6 +4,9 @@ Cообщения, используемые в боте.
 Author: Milinuri Nirvalen
 """
 
+from typing import Optional
+
+from sp.messages import send_update
 from sp.messages import send_counter
 from sp.messages import SPMessages
 from sp.parser import Schedule
@@ -16,135 +19,205 @@ from sp.counters import group_counter_res
 from sp.counters import index_counter
 
 
-HOME = """💡 Некоторые примеры запросов:
-
-🏫 В запросах вы можете использовать:
-Урок/Кабинет: Получить все его упоминания.
-
-Классы: для которых нужно расписание.
-
-Дни недели:
-
-🌟 Порядок и форма аргументов не важны, балуйтесь!"""
-
-NO_CLASS_HOME = """💡 Некоторые примеры запросов:
-
-🏫 В запросах вы можете использовать:
-Урок/Кабинет: Получить все его упоминания.
-Классы: для которых нужно расписание.
-
-Дни недели:
-
-🌟 Порядок и форма аргументов не важны, балуйтесь!"""
-
-
-INFO = """
-🌲 Тестер @errorgirl2007
-🌲 Версия бота: 1.2 (17)"""
-
-
-SET_CLASS = """
-Для полноценной работы желательно указать ваш класс.
-Для быстрого просмотра расписания и списка изменений.
-
-🌟 Вы можете пропустить выбор класса кнопкой "пропустить" (команда /pass).
-Но это накладывает некоторые ограничения.
-Прочитать об ограничениях можно по кнопек ограничения (команда /restrictions).
-
-Способы указать класс:
-
-💡 Вы можете сменить класс в дальнейшем:
-
-
-RESTRICTIONS = """Всё перечисленное будет недоступно:
-
-
-🌟 На этом все отличия заканчиваются."""
-
-
-def send_home_message(sp: SPMessages) -> str:
-    """Отпавляет сообщение со справкой об использовании бота.
-
-    Args:
-        sp (SPMessages): Генератор сообщений
-
-    Returns:
-        str: Готовое сообщение
-    """
-    if sp.user["class_let"]:
-        return HOME
-    else:
-        return NO_CLASS_HOME
-
-def send_notifications_info(sp: SPMessages) -> str:
-    """Отправляет сообщение с информацией об уведомлениях.
+# Главнео сообщение справки бота
+HOME = ("💡 Некоторые примеры запросов:"
+    "\n-- 7в 6а на завтра"
+    "\n-- уроки 6а на вторник ср"
+    "\n-- 312 на вторник пятницу"
+    "\n-- химия 228 6а вторник"
+    "\n\n🏫 В запросах вы можете использовать:"
+    "\n* Урок/Кабинет: Получить все его упоминания."
+    "\n* Классы: для которого нужно расписание."
+    "\n* Дни недели:"
+    "\n-- Если день не указан - на сегодня/завтра."
+    "\n-- Понедельник-суббота (пн-сб)."
+    "\n-- Сегодня, завтра, неделя."
+    # "\n\n🌟 Как писать запросы? /tutorial"
+)
+
+
+INFO = (
+    "🌲 Тестер @errorgirl2007"
+    "\n🌲 Версия бота: 1.2 (20)"
+)
+
+# Сообщение при смене класса
+SET_CLASS = ("Для полноценной работы желательно указать ваш класс."
+    "\nВы сможете быстро просматривать расписание и получать уведомления."
+    "\nПочитать о всех преимуществах - /cl_features"
+    "\n\n🌟 Просто укажите класс следующим сообщеним (\"8в\")"
+    "\nИли после команды /set_class (прим. /set_class 7в)"
+    "\n\nВы можете пропустить выбор класса нажав кнопку (/pass)."
+    "\n\n💡 Вы можете сменить класс позже:"
+    "\n-- через команду /set_class [класс]."
+    "\n-- Кпонку Cменить класс."
+)
+
+# Какие преимущества получает указавгих класс пользователь
+CL_FEATURES = ("🌟 Если вы укажете класс, то сможете:"
+    "\n\n-- Быстро получать расписание для класса, кнопкой в главном меню."
+    "\n-- Не укзаывать ваш класс в текстовых запросах (прим. \"пн\")."
+    "\n-- Получать уведомления и рассылку расписание для класса."
+    "\n-- Просматривать список изменений для вашего класса."
+    "\n-- Использовать счётчик cl/lessons."
+    "\n\n💎 Список возможностей может пополняться."
+)
+
+TUTORIAL_MESSAGES = [
+    ("💡 Как писать запросы?"
+        "\nНа самом деле всё намно-о-ого легче."
+        "\nПройдите это простое обучение и убедитесь сами."
+        "\n\nВы можете пройти обучение с самого начала."
+        "\nИли выбрать нужную страницу, если уже проходили его."
+    ),
+
+    ("1. Будьте проще"
+        "\n\nСовсем не обязательно указывать в запросах \"посторонние\" слова."
+        "\nТакие как \"уроки на\", \"расписание\", \"для\" и подобные."
+        "\nОни никак не влияют на сам запрос."
+        "\n\n❌ уроки на завтра"
+        "\n✅ Завтра"
+        "\n\n❌ Расписание для 9в на вторник"
+        "\n✅ 9в вторник"
+        "\n\nПорядок ключевых слов не имеет значение."
+        "\n-- матем 8в = 8в матем"
+    ),
+
+    ("2. Классы"
+        "\n\nЧтобы получить расписание, достаточно указать нужный класс."
+        "\nЕсли день не указан, будет отправлено расписание на сегодня/завтра."
+        "\n🔸 На сегодня - если уроки ещё идут."
+        "\n🔸 На завтра - если уроки на сегодня уже кончились."
+        "\n\n-- 7а ➜ Расписание для 7а на сегодня/завтра."
+        "\n-- 7г 6а ➜ Сразу для нескольких классов."
+        "\n\n🔎 Также классы используются в поиске:"
+        "\n\n- химия 8б ➜ Все уроки химии для 9б."
+        "\n- 9в 312 ➜ Все уроки в 312 кабинете для 9в"
+        "\n\n💡 Посмотреть список всех классов можно в статусе:"
+        "\n-- По кнопке \"ещё\" в главном меню."
+        "\n-- По команде /info"
+    ),
+
+    ("3. Дни недели"
+        "\n\nВы можете более явно указать дни недели в запросах и поиске."
+        "\nЕсли указаать только день, то получите расписание для вашего класса."
+        "\n\n✏️ Доступные значения:"
+        "\n-- Понедельник - суббота."
+        "\n-- пн - сб."
+        "\n-- Сегодня, завтра, неделя."
+        "\n\n-- вт ➜ Расписание для вашего класса по умолчанию на вторник."
+        "\n\nНапомним про \"быть проще\":"
+        "\n❌ Уроки для 5г на среду"
+        "\n✅ 5г среда"
+        "\n\n🔎 Если день не указан, результат выводится на неделю."
+        "\n-- матем вт ➜ Все уроки математики на вторник"
+        "\n-- пт 312 ➜ Все уроки в 312 кабинете на пятницу"
+    ),
+
+    ("4. Поиск по урокам"
+        "\n\n🔎 Укажите точное название урока для его поиска в расписании."
+        "\nЕсли не указаны прочие параметры, расписание для всех на неделю."
+        "\n\n✏️ Вы можете указать класс, день, кабинет в параметрах."
+        "\n\n-- матем ➜ Вся математика за неделю для всех классов."
+        "\n-- химия вторник 10а ➜ Более точный поиск."
+        "\n\n⚠️ Если ввести несколько уроков, будет взят только первый."
+        "\nЧтобы результат поиска не было слишком длинным."
+        "\n\n💡 Посмотреть все классы можно в счётчиках:"
+        "\n-- По кнопке \"Ещё\" ➜ \"Счётчики\""
+        "\n-- По команде /counter"
+    ),
+
+    ("5. Поиск по кабинетам"
+        "\n🔎 Укажите кабинет, чтобы взглянуть на расписание от его лица."
+        "\nЕсли прочие параметры не указаны, расписание для всех на неделю."
+        "\n\n✏️ Вы можете указать класс, день, урок в параметрах."
+        "\n\n-- 328 ➜ Всё что проходит в 328 кабинете за неделю."
+        "\n-- 312 литер вторник 7а ➜ Более точный поиск."
+        "\n\n⚠️ Если указать несколько кабинетов, будет взят только первый."
+        "\nЧтобы результат поиска не был слишком длинным."
+        "\nОднако можно указать несколько предметов в поиске по кабинету."
+        "\n\n💡 Посмотреть все кабинеты можно в счётчиках:"
+        "\n-- По кнопке \"Ещё\" ➜ \"Счётчики\" ➜ \"По урокам\""
+        "\n-- По команде /counter ➜ \"По урокам\""
+    ),
+
+    ("6. Групповые чаты"
+        "\n\n🌟 Вы можете добавить бота в чатик."
+        "\nЭто позаолит использовать одного бота нескольким пользователям."
+        "\nКласс уставливается один на весь чат."
+        "\n\n/set_class [класс] - чтобы установить класс в чате."
+        "\nИли ответьте классом на сообщение бота (9в)."
+        "\n\n✏️ Чтобы писать запросы в чате, используйте команду /sc [запрос]"
+        "\nИли ответьте запросом на сообщение бота."
+    ),
+
+    ("🎉 Поздравляем с прохождением обучения!"
+        "\nТеперь вы знаете всё о составлении запросов к расписанию."
+        "\nПриятного вам использования бота."
+        "\nВы умничка. ❤️"
+    )
+]
+
+
+def send_notify_info(enabled: bool, hours: list[int]) -> str:
+    """Отправляет сообщение с информацией о статусе уведомлений.
+
+    Сообщение о статусе уведомлений содержит в себе:
+    Включены ли сейчас уведомления.
+    Краткая инфомрация об уведомленях.
+    В какие часы рассылается расписание уроков.
 
     Args:
-        sp (SPMessages): Генератор сообщений
+        enabled (bool): Включены ли уведомления пользователя.
+        hours (list[int]): В какие часы отправлять уведомления.
 
     Returns:
         str: Сообщение с информацией об уведомлениях.
     """
-    message = "Вы получите уведомление, если расписание изменится.\n"
-
-    if sp.user["notifications"]:
-        message += "\n🔔 уведомления включены."
-        message += "\n\nТакже вы можете настроить отправку расписания."
-        message += "\nВ указанное время бот отправит расписание вашего класса."
-        hours = sp.user["hours"]
-
-        if hours:
-            message += "\n\nРасписани будет отправлено в: "
+    if enabled:
+        message = ("🔔 уведомления включены."
+            "\nВы получите уведомление, если расписание изменится."
+            "\n\nТакже вы можете настроить отправку расписания."
+            "\nВ указанное время бот отправит расписание вашего класса."
+        )
+        if len(hours) > 0:
+            message += "\n\nРасписание будет отправлено в: "
             message += ", ".join(map(str, set(hours)))
     else:
-        message += "\n🔕 уведомления отключены."
+        message = "🔕 уведомления отключены.\nНикаких лишних сообщений."
 
     return message
 
+
 def send_counter_message(sc: Schedule, counter: str, target: str) -> str:
     """Собирает сообщение с результатами работы счётчиков.
 
+    В зависимости от выбранного счётчика использует соответствующую
+    функцию счётчика.
+
+    +----------+-------------------------+
+    | counter  | targets                 |
+    +----------+-------------------------+
+    | cl       | days, lessons. cabinets |
+    | days     | cl, lessons. cabinets   |
+    | lessons  | cl, days. main          |
+    | cabinets | cl, days. main          |
+    +----------+-------------------------+
+
     Args:
-        sc (Schedule): Расписание уроков
-        counter (str): Тип счётчика
-        target (str): Режим просмотра счётчика
+        sc (Schedule): Экземпляр расписания уроков.
+        counter (str): Тип счётчика.
+        target (str): Группа просмтора счётчика.
 
     Returns:
-        str: Готовое сообщение
+        str: Сообщение с результаатми счётчика.
     """
+    message = f"✨ Счётчик {counter}/{target}:"
     intent = Intent()
 
     if counter == "cl":
         if target == "lessons":
-            intent = Intent.construct(sc, cl=[sc.cl])
+            intent = intent.construct(sc, cl=sc.cl)
         res = cl_counter(sc, intent)
     elif counter == "days":
         res = days_counter(sc, intent)
@@ -153,7 +226,35 @@ def send_counter_message(sc: Schedule, counter: str, target: str) -> str:
     else:
         res = index_counter(sc, intent, cabinets_mode=True)
 
-    groups = group_counter_res(res)
-    message = f"✨ Счётчик {counter}/{target}:"
-    message += send_counter(groups, target=target)
+    if target == "none":
+        target = None
+
+    message += send_counter(group_counter_res(res), target=target)
+    return message
+
+def send_updates(
+    update: Optional[list]=None, cl: Optional[str]=None,
+) -> str:
+    """Собирает сообщение со страницей списка изменений расписания.
+
+    Args:
+        update (list, Optional): Странциа списка изменений расписания.
+        cl (str, Optional): Для какого класса представлены изменения.
+
+    Returns:
+        str: Сообщение со страницей списка изменений.
+    """
+    message = "🔔 Изменения "
+    message += " в расписании:\n" if cl is None else f" для {cl}:\n"
+
+    if update is not None:
+        update_text = send_update(update, cl=cl)
+
+        if len(update_text) > 4000:
+            message += "\n📚 Слишком много изменений."
+        else:
+            message += update_text
+    else:
+        message += "✨ Нет новых обновлений."
+
     return message