Напоминалки: выбор часового пояса кнопками городов + ручной ввод

При гейте часового пояса теперь показывается клавиатура с частыми
российскими городами-поясами (Калининград..Камчатка). Клик по кнопке
шлёт 'Город, UTC+N' — _parse_tz_offset распознаёт и это, и обычный
ручной ввод вида '+3'/'-5', если своего города в списке нет.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kenik
2026-09-05 15:18:29 +03:00
parent 0085195941
commit e41db35f13
3 changed files with 49 additions and 9 deletions
+15 -9
View File
@@ -488,12 +488,16 @@ class VKBotApplication:
return f"{name} (vk.com/id{uid})" if name else f"vk.com/id{uid}" return f"{name} (vk.com/id{uid})" if name else f"vk.com/id{uid}"
def _parse_tz_offset(self, raw: str) -> Optional[int]: def _parse_tz_offset(self, raw: str) -> Optional[int]:
"""Парсит смещение от UTC вида '+3', '-5', '0'. None, если не число """Парсит смещение от UTC: либо чистое число '+3'/'-5'/'0' (ручной ввод),
или вне диапазона реальных часовых поясов (-12..+14).""" либо 'Город, UTC+N' (клик по кнопке get_timezone_keyboard). None, если
не удалось распознать или число вне диапазона реальных поясов (-12..+14)."""
raw = raw.strip() raw = raw.strip()
if not re.match(r"^[+-]?\d{1,2}$", raw): m = re.search(r"UTC\s*([+-]?\d{1,2})\s*$", raw, re.I)
if not m:
m = re.match(r"^([+-]?\d{1,2})$", raw)
if not m:
return None return None
val = int(raw) val = int(m.group(1))
return val if -12 <= val <= 14 else None return val if -12 <= val <= 14 else None
def _require_reminder_tz(self, user_id: int) -> bool: def _require_reminder_tz(self, user_id: int) -> bool:
@@ -505,9 +509,10 @@ class VKBotApplication:
db.update_user_state(user_id, "ADMIN_REMINDER_TZ_SETUP") db.update_user_state(user_id, "ADMIN_REMINDER_TZ_SETUP")
self.send_message( self.send_message(
user_id, user_id,
"🌐 Прежде чем пользоваться напоминалками, укажите свой часовой пояс " "🌐 Прежде чем пользоваться напоминалками, укажите свой часовой пояс.\n"
"относительно UTC.\nНапример: +3 (Москва), +5 (Екатеринбург), 0 (UTC).", "Выберите город кнопкой ниже, либо введите смещение от UTC вручную "
keyboard=get_cancel_admin_keyboard() "(например, +3), если своего города в списке нет.",
keyboard=get_timezone_keyboard()
) )
return False return False
@@ -1784,8 +1789,9 @@ class VKBotApplication:
if state == "ADMIN_REMINDER_TZ_SETUP": if state == "ADMIN_REMINDER_TZ_SETUP":
offset = self._parse_tz_offset(text) offset = self._parse_tz_offset(text)
if offset is None: if offset is None:
self.send_message(user_id, "⚠️ Формат: число от -12 до +14, например +3 или -5.", self.send_message(user_id, "⚠️ Выберите город кнопкой ниже, либо введите число "
keyboard=get_cancel_admin_keyboard()) "от -12 до +14 вручную, например +3 или -5.",
keyboard=get_timezone_keyboard())
return return
db.set_admin_tz_offset(user_id, offset) db.set_admin_tz_offset(user_id, offset)
db.update_user_state(user_id, "FREE_CHAT") db.update_user_state(user_id, "FREE_CHAT")
+21
View File
@@ -213,6 +213,27 @@ def get_reminders_admin_keyboard() -> str:
return keyboard.get_keyboard() return keyboard.get_keyboard()
def get_timezone_keyboard() -> str:
"""Клавиатура выбора часового пояса: частые российские города-пояса
кнопками (кнопка шлёт 'Город, UTC±N' _parse_tz_offset вытаскивает
смещение из этого текста), плюс можно просто ввести своё смещение
вручную, если города в списке нет."""
cities = [
("Калининград", 2), ("Москва", 3), ("Самара", 4),
("Екатеринбург", 5), ("Омск", 6), ("Красноярск", 7),
("Иркутск", 8), ("Якутск", 9), ("Владивосток", 10),
("Магадан", 11), ("Камчатка", 12),
]
keyboard = VkKeyboard(one_time=False)
for i, (city, offset) in enumerate(cities):
if i and i % 2 == 0:
keyboard.add_line()
keyboard.add_button(f"{city}, UTC+{offset}", color=VkKeyboardColor.SECONDARY)
keyboard.add_line()
keyboard.add_button("Отмена ❌", color=VkKeyboardColor.NEGATIVE)
return keyboard.get_keyboard()
def get_pick_keyboard(items: list) -> str: def get_pick_keyboard(items: list) -> str:
"""Клавиатура выбора из списка пар (id, label). Кнопка отправляет '<id>. <label>'.""" """Клавиатура выбора из списка пар (id, label). Кнопка отправляет '<id>. <label>'."""
keyboard = VkKeyboard(one_time=False) keyboard = VkKeyboard(one_time=False)
+13
View File
@@ -459,6 +459,19 @@ def test_reminder_tz_gate_before_list(app, monkeypatch):
assert _state(admin) == "FREE_CHAT" assert _state(admin) == "FREE_CHAT"
def test_reminder_tz_setup_via_city_button(app, monkeypatch):
"""Часовой пояс можно выбрать кнопкой города, а не только вводом числа."""
admin = 80010
monkeypatch.setattr(bot.config, "ADMIN_IDS", [admin])
app.handle_message(admin, "⏰ Напоминалки")
assert _state(admin) == "ADMIN_REMINDER_TZ_SETUP"
app.handle_message(admin, "Екатеринбург, UTC+5") # клик по кнопке клавиатуры
assert _state(admin) == "FREE_CHAT"
assert bot.db.get_admin_tz_offset(admin) == 5
def test_reminder_creation_converts_author_local_time_to_server(app, monkeypatch): def test_reminder_creation_converts_author_local_time_to_server(app, monkeypatch):
admin = 80007 admin = 80007
monkeypatch.setattr(bot.config, "ADMIN_IDS", [admin]) monkeypatch.setattr(bot.config, "ADMIN_IDS", [admin])