Правки админки: инлайн-кнопки, отмена рассылки, FAQ в меню, вакансии в БД

- Отмена в «Заявке»/«Карьере» возвращает меню с кнопкой «Админка» для админов.
- Рассылка: инлайн-кнопки «Отправить»/«Отмена» в предпросмотре.
- Управление админами — инлайн-кнопки «Добавить»/«Удалить» (без текстовых команд),
  выбор удаляемого админа кнопкой.
- Быстрые ответы из админки показываются в «Частые вопросы» инлайн-кнопками.
- Вакансии «Карьеры» перенесены в БД (vacancy_options): направления игроков и
  должности сотрудников редактируются из админки (раздел «Вакансии»), а в опросе
  показываются инлайн-кнопками из БД.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kenik
2026-06-24 19:36:53 +03:00
parent 755e364d42
commit 564581decf
8 changed files with 247 additions and 50 deletions
+195 -41
View File
@@ -112,6 +112,7 @@ def get_admin_keyboard(is_super: bool = False) -> str:
keyboard.add_button("Кандидаты 👥", color=VkKeyboardColor.SECONDARY) keyboard.add_button("Кандидаты 👥", color=VkKeyboardColor.SECONDARY)
keyboard.add_line() keyboard.add_line()
keyboard.add_button("Управление услугами ⚙️", color=VkKeyboardColor.SECONDARY) keyboard.add_button("Управление услугами ⚙️", color=VkKeyboardColor.SECONDARY)
keyboard.add_button("Вакансии 💼", color=VkKeyboardColor.SECONDARY)
keyboard.add_line() keyboard.add_line()
keyboard.add_button("Быстрые ответы 💬", color=VkKeyboardColor.SECONDARY) keyboard.add_button("Быстрые ответы 💬", color=VkKeyboardColor.SECONDARY)
keyboard.add_button("Рассылка 📣", color=VkKeyboardColor.SECONDARY) keyboard.add_button("Рассылка 📣", color=VkKeyboardColor.SECONDARY)
@@ -528,17 +529,18 @@ class VKBotApplication:
lines += ("".join(f" • vk.com/id{a}\n" for a in cfg) or "\n") lines += ("".join(f" • vk.com/id{a}\n" for a in cfg) or "\n")
lines += "\n➕ Добавленные из бота:\n" lines += "\n➕ Добавленные из бота:\n"
lines += ("".join(f" • vk.com/id{a}\n" for a in runtime) or "\n") lines += ("".join(f" • vk.com/id{a}\n" for a in runtime) or "\n")
kb = VkKeyboard(inline=True)
kb.add_button(" Добавить админа", color=VkKeyboardColor.POSITIVE)
kb.add_line()
kb.add_button("🗑 Удалить админа", color=VkKeyboardColor.NEGATIVE)
self.send_message( self.send_message(
user_id, user_id,
"👑 Управление администраторами\n\n" "👑 Управление администраторами\n\n"
f"{lines}\n" f"{lines}\n"
"Команды:\n" "Управляйте кнопками ниже 👇\n\n"
"• Добавить админа: админ доб [VK_ID]\n"
"• Удалить админа: админ удал [VK_ID]\n"
" (снять можно только добавленных из бота)\n\n"
"ℹ️ Сменить/добавить СУПЕР-АДМИНА можно только в коде: откройте config.py, " "ℹ️ Сменить/добавить СУПЕР-АДМИНА можно только в коде: откройте config.py, "
"впишите VK ID в список SUPER_ADMIN_IDS и перезапустите бота.", "впишите VK ID в список SUPER_ADMIN_IDS и перезапустите бота.",
keyboard=get_admin_keyboard(is_super=True) keyboard=kb.get_keyboard()
) )
def _handle_admin_add_admin(self, user_id: int, text: str): def _handle_admin_add_admin(self, user_id: int, text: str):
@@ -634,6 +636,10 @@ class VKBotApplication:
if state.startswith("ADMIN_"): if state.startswith("ADMIN_"):
self._handle_admin_wizard(user_id, text_clean, text_lower, state, attachments) self._handle_admin_wizard(user_id, text_clean, text_lower, state, attachments)
return return
# Инлайн-кнопки предпросмотра рассылки
if user_id in self._pending_broadcasts and text_lower in ["отправить ✅", "отправить", "отмена ❌", "отмена"]:
self._handle_admin_broadcast(user_id, "рассылка отмена" if "отмен" in text_lower else "рассылка подтвердить")
return
if text_lower in ["админка", "админка ⚙️", "/admin"]: if text_lower in ["админка", "админка ⚙️", "/admin"]:
self._send_admin_panel(user_id) self._send_admin_panel(user_id)
return return
@@ -692,6 +698,29 @@ class VKBotApplication:
elif text_lower.startswith("рассылка"): elif text_lower.startswith("рассылка"):
self._handle_admin_broadcast(user_id, text_clean) self._handle_admin_broadcast(user_id, text_clean)
return return
elif text_lower in ["вакансии 💼", "вакансии"]:
self._send_vacancy_admin(user_id)
return
elif text_lower in [" вакансия игрок", "вакансия игрок"]:
db.update_user_state(user_id, "ADMIN_VAC_ADD_PLAYER")
self.send_message(user_id, "➕ Название нового направления для ИГРОКОВ (например, Valorant 🎯):",
keyboard=get_cancel_admin_keyboard())
return
elif text_lower in ["➕ вакансия сотрудник", "вакансия сотрудник"]:
db.update_user_state(user_id, "ADMIN_VAC_ADD_WORKER")
self.send_message(user_id, "➕ Название новой должности для СОТРУДНИКОВ (например, Аналитик):",
keyboard=get_cancel_admin_keyboard())
return
elif text_lower in ["🗑 удалить вакансию", "удалить вакансию"]:
items = [(i, f"🎮 {l}") for i, l in db.get_vacancy_options("player")]
items += [(i, f"💼 {l}") for i, l in db.get_vacancy_options("worker")]
if not items:
self.send_message(user_id, "Список вакансий пуст.")
self._send_vacancy_admin(user_id)
return
db.update_user_state(user_id, "ADMIN_VAC_DEL_PICK")
self.send_message(user_id, "🗑 Какую вакансию удалить? Выберите:", keyboard=get_pick_keyboard(items))
return
elif text_lower in ["быстрые ответы 💬", "быстрые ответы", "фак"]: elif text_lower in ["быстрые ответы 💬", "быстрые ответы", "фак"]:
self._send_faq_admin(user_id) self._send_faq_admin(user_id)
return return
@@ -710,6 +739,28 @@ class VKBotApplication:
elif text_lower in ["администраторы 👑", "администраторы", "админы"]: elif text_lower in ["администраторы 👑", "администраторы", "админы"]:
self._send_admins_panel(user_id) self._send_admins_panel(user_id)
return return
elif text_lower in [" добавить админа", "добавить админа"]:
if not self._is_super_admin(user_id):
self.send_message(user_id, "⛔ Только супер-админ.")
return
db.update_user_state(user_id, "ADMIN_ADMIN_ADD")
self.send_message(user_id, "➕ Пришлите VK ID нового администратора (числом).\n"
"ID можно узнать на vk.com/<id> или через сервисы определения id.",
keyboard=get_cancel_admin_keyboard())
return
elif text_lower in ["🗑 удалить админа", "удалить админа"]:
if not self._is_super_admin(user_id):
self.send_message(user_id, "⛔ Только супер-админ.")
return
runtime = db.get_admins()
if not runtime:
self.send_message(user_id, "Список добавленных из бота админов пуст.")
self._send_admins_panel(user_id)
return
db.update_user_state(user_id, "ADMIN_ADMIN_DEL_PICK")
self.send_message(user_id, "🗑 Кого удалить из админов? Выберите:",
keyboard=get_pick_keyboard([(a, f"vk.com/id{a}") for a in runtime]))
return
elif text_lower.startswith("админ доб"): elif text_lower.startswith("админ доб"):
self._handle_admin_add_admin(user_id, text_clean) self._handle_admin_add_admin(user_id, text_clean)
return return
@@ -750,7 +801,7 @@ class VKBotApplication:
self.send_message( self.send_message(
user_id, user_id,
"Заполнение заявки отменено. Мы вернулись в главное меню. Чем я могу помочь?", "Заполнение заявки отменено. Мы вернулись в главное меню. Чем я могу помочь?",
keyboard=get_main_keyboard() keyboard=get_main_keyboard(is_admin=self._is_admin(user_id))
) )
return return
@@ -761,7 +812,7 @@ class VKBotApplication:
self.send_message( self.send_message(
user_id, user_id,
"Отклик отменён. Вы вернулись в главное меню.", "Отклик отменён. Вы вернулись в главное меню.",
keyboard=get_main_keyboard() keyboard=get_main_keyboard(is_admin=self._is_admin(user_id))
) )
return return
@@ -830,7 +881,7 @@ class VKBotApplication:
self.send_message( self.send_message(
user_id, user_id,
"Произошла непредвиденная ошибка в системе. Возвращаю вас в главное меню.", "Произошла непредвиденная ошибка в системе. Возвращаю вас в главное меню.",
keyboard=get_main_keyboard() keyboard=get_main_keyboard(is_admin=self._is_admin(user_id))
) )
# ------------------------------------------ # ------------------------------------------
@@ -873,6 +924,19 @@ class VKBotApplication:
"❓ Выберите тему, которая вас интересует, на клавиатуре ниже:", "❓ Выберите тему, которая вас интересует, на клавиатуре ниже:",
keyboard=get_faq_keyboard() keyboard=get_faq_keyboard()
) )
# Дополнительно — быстрые ответы из админки инлайн-кнопками
custom = db.get_custom_faq()
if custom:
kb = VkKeyboard(inline=True)
for n, (_id, kw, _ans) in enumerate(custom[:6]):
if n and n % 2 == 0:
kb.add_line()
kb.add_button(kw[:40], color=VkKeyboardColor.SECONDARY)
self.send_message(
user_id,
"💬 Популярные вопросы — нажмите, чтобы получить ответ:",
keyboard=kb.get_keyboard()
)
return return
# 3. Возврат в главное меню # 3. Возврат в главное меню
@@ -1707,7 +1771,7 @@ class VKBotApplication:
) )
confirm_msg += "Спасибо за обращение!\n\nВы вернулись в главное меню бота." confirm_msg += "Спасибо за обращение!\n\nВы вернулись в главное меню бота."
self.send_message(user_id, confirm_msg, keyboard=get_main_keyboard()) self.send_message(user_id, confirm_msg, keyboard=get_main_keyboard(is_admin=self._is_admin(user_id)))
# Журналируем заявку для статистики и истории # Журналируем заявку для статистики и истории
try: try:
@@ -1757,26 +1821,37 @@ class VKBotApplication:
"""Базовая проверка свободного ответа в отклике (не пусто, разумной длины).""" """Базовая проверка свободного ответа в отклике (не пусто, разумной длины)."""
return 1 <= len(text.strip()) <= 300 return 1 <= len(text.strip()) <= 300
def _send_vac_options(self, user_id: int, track: str, question: str, include_other: bool = False):
"""Шлёт вопрос (с кнопкой отмены) и инлайн-кнопки доступных опций вакансии из БД."""
self.send_message(user_id, question, keyboard=get_vacancy_cancel_keyboard())
kb = VkKeyboard(inline=True)
opts = db.get_vacancy_options(track)
n = 0
for _id, label in opts[:5]:
if n and n % 2 == 0:
kb.add_line()
kb.add_button(label[:40], color=VkKeyboardColor.SECONDARY)
n += 1
if include_other:
kb.add_line()
kb.add_button("Другое ✍️", color=VkKeyboardColor.SECONDARY)
self.send_message(user_id, "Выберите вариант 👇", keyboard=kb.get_keyboard())
def _handle_vac_choose(self, user_id: int, text: str, text_lower: str): def _handle_vac_choose(self, user_id: int, text: str, text_lower: str):
"""Выбор ветки: игрок в состав или сотрудник.""" """Выбор ветки: игрок в состав или сотрудник."""
if text_lower in ["игроком в состав 🎮", "игроком", "игрок", "в состав", "состав"]: if text_lower in ["игроком в состав 🎮", "игроком", "игрок", "в состав", "состав"]:
db.update_user_state(user_id, "VAC_P_DISCIPLINE") db.update_user_state(user_id, "VAC_P_DISCIPLINE")
self.send_message( self._send_vac_options(user_id, "player",
user_id, "🎮 Набор в состав\n\nШаг 1 из 5: по какой дисциплине вы играете?")
"🎮 Набор в состав\n\nШаг 1 из 5: по какой дисциплине вы играете?",
keyboard=get_vacancy_discipline_keyboard()
)
elif text_lower in ["сотрудником 💼", "сотрудником", "сотрудник", "работником", "в команду"]: elif text_lower in ["сотрудником 💼", "сотрудником", "сотрудник", "работником", "в команду"]:
db.update_user_state(user_id, "VAC_W_POSITION") db.update_user_state(user_id, "VAC_W_POSITION")
self.send_message( self._send_vac_options(user_id, "worker",
user_id,
"💼 Набор в команду\n\nШаг 1 из 3: на какую должность вы претендуете?", "💼 Набор в команду\n\nШаг 1 из 3: на какую должность вы претендуете?",
keyboard=get_vacancy_position_keyboard() include_other=True)
)
elif text_lower in ["назад в меню ↩️", "назад", "меню", "в меню"]: elif text_lower in ["назад в меню ↩️", "назад", "меню", "в меню"]:
db.clear_vac_data(user_id) db.clear_vac_data(user_id)
db.update_user_state(user_id, "FREE_CHAT") db.update_user_state(user_id, "FREE_CHAT")
self.send_message(user_id, "🏠 Вы вернулись в главное меню.", keyboard=get_main_keyboard()) self.send_message(user_id, "🏠 Вы вернулись в главное меню.", keyboard=get_main_keyboard(is_admin=self._is_admin(user_id)))
else: else:
self.send_message( self.send_message(
user_id, user_id,
@@ -1788,6 +1863,14 @@ class VKBotApplication:
def _handle_vac_p_discipline(self, user_id: int, text: str, text_lower: str): def _handle_vac_p_discipline(self, user_id: int, text: str, text_lower: str):
discipline = None discipline = None
# 1) Сопоставление с опциями из БД (нажата инлайн-кнопка или введено название)
for _id, label in db.get_vacancy_options("player"):
ll = label.lower()
if text.strip() == label or text_lower == ll or ll in text_lower or text_lower in ll:
discipline = label
break
# 2) Фолбэк-синонимы для свободного ввода
if not discipline:
syn = { syn = {
"Counter-Strike 2": ["кс", "cs", "cs2", "csgo", "ксго", "контра", "counter"], "Counter-Strike 2": ["кс", "cs", "cs2", "csgo", "ксго", "контра", "counter"],
"Dota 2": ["дота", "dota", "дотка", "доту"], "Dota 2": ["дота", "dota", "дотка", "доту"],
@@ -1798,11 +1881,7 @@ class VKBotApplication:
discipline = name discipline = name
break break
if not discipline: if not discipline:
self.send_message( self._send_vac_options(user_id, "player", "Пожалуйста, выберите дисциплину на кнопках ниже:")
user_id,
"Пожалуйста, выберите дисциплину на клавиатуре ниже.",
keyboard=get_vacancy_discipline_keyboard()
)
return return
db.set_vac_field(user_id, "discipline", discipline) db.set_vac_field(user_id, "discipline", discipline)
db.update_user_state(user_id, "VAC_P_RANK") db.update_user_state(user_id, "VAC_P_RANK")
@@ -1860,12 +1939,6 @@ class VKBotApplication:
# ----- Ветка сотрудника ----- # ----- Ветка сотрудника -----
def _handle_vac_w_position(self, user_id: int, text: str, text_lower: str): def _handle_vac_w_position(self, user_id: int, text: str, text_lower: str):
known = {
"smm / контент": "SMM / контент",
"монтажёр / дизайнер": "Монтажёр / дизайнер",
"менеджер": "Менеджер",
"разработчик": "Разработчик",
}
if text_lower in ["другое ✍️", "другое", "иное"]: if text_lower in ["другое ✍️", "другое", "иное"]:
db.update_user_state(user_id, "VAC_W_POSITION_OTHER") db.update_user_state(user_id, "VAC_W_POSITION_OTHER")
self.send_message( self.send_message(
@@ -1874,15 +1947,17 @@ class VKBotApplication:
keyboard=get_vacancy_cancel_keyboard() keyboard=get_vacancy_cancel_keyboard()
) )
return return
# Известная кнопка или произвольный непустой текст принимаем как должность # Сопоставление с должностями из БД (инлайн-кнопка), иначе — произвольный текст
position = known.get(text_lower) position = None
for _id, label in db.get_vacancy_options("worker"):
if text.strip() == label or text_lower == label.lower():
position = label
break
if not position: if not position:
if not self._valid_free_text(text): if not self._valid_free_text(text):
self.send_message( self._send_vac_options(user_id, "worker",
user_id, "Выберите должность на кнопках ниже или нажмите «Другое ✍️»:",
"Выберите должность на клавиатуре или нажмите «Другое ✍️».", include_other=True)
keyboard=get_vacancy_position_keyboard()
)
return return
position = text.strip() position = text.strip()
db.set_vac_field(user_id, "position", position) db.set_vac_field(user_id, "position", position)
@@ -1940,7 +2015,7 @@ class VKBotApplication:
"✅ Спасибо за отклик!\n\n" "✅ Спасибо за отклик!\n\n"
"Мы изучим вашу анкету и свяжемся, если кандидатура подойдёт. Удачи! 🎮\n\n" "Мы изучим вашу анкету и свяжемся, если кандидатура подойдёт. Удачи! 🎮\n\n"
"Вы вернулись в главное меню.", "Вы вернулись в главное меню.",
keyboard=get_main_keyboard() keyboard=get_main_keyboard(is_admin=self._is_admin(user_id))
) )
# Антиспам: ограничиваем частоту уведомлений об откликах от одного пользователя # Антиспам: ограничиваем частоту уведомлений об откликах от одного пользователя
if self._notify_allowed(user_id): if self._notify_allowed(user_id):
@@ -2070,6 +2145,25 @@ class VKBotApplication:
# ЗАПУСК И ОБРАБОТКА LONGPOLL # ЗАПУСК И ОБРАБОТКА LONGPOLL
# ------------------------------------------ # ------------------------------------------
def _send_vacancy_admin(self, user_id: int):
"""Раздел управления вакансиями (направления игроков / должности сотрудников)."""
players = db.get_vacancy_options("player")
workers = db.get_vacancy_options("worker")
lines = "🎮 Направления (игроки):\n"
lines += ("".join(f" • [{i}] {l}\n" for i, l in players) or "\n")
lines += "\n💼 Должности (сотрудники):\n"
lines += ("".join(f" • [{i}] {l}\n" for i, l in workers) or "\n")
kb = VkKeyboard(inline=True)
kb.add_button(" Вакансия игрок", color=VkKeyboardColor.POSITIVE)
kb.add_button("➕ Вакансия сотрудник", color=VkKeyboardColor.POSITIVE)
kb.add_line()
kb.add_button("🗑 Удалить вакансию", color=VkKeyboardColor.NEGATIVE)
self.send_message(
user_id,
"💼 Управление вакансиями\n\n" + lines + "\nУправляйте кнопками ниже 👇",
keyboard=kb.get_keyboard()
)
def _handle_admin_wizard(self, user_id: int, text: str, text_lower: str, state: str, attachments: list): def _handle_admin_wizard(self, user_id: int, text: str, text_lower: str, state: str, attachments: list):
"""Мастера админки: добавление/изменение/удаление услуг и быстрых ответов, смена картинок.""" """Мастера админки: добавление/изменение/удаление услуг и быстрых ответов, смена картинок."""
if text_lower in ["отмена ❌", "отмена", "в админку ↩️", "в админку", "назад"]: if text_lower in ["отмена ❌", "отмена", "в админку ↩️", "в админку", "назад"]:
@@ -2223,6 +2317,57 @@ class VKBotApplication:
back_to_faq(f"✅ Ответ №{fid} удалён.") back_to_faq(f"✅ Ответ №{fid} удалён.")
return return
# ---------- Вакансии ----------
def back_to_vacancies(msg=None):
self._admin_ctx.pop(user_id, None)
db.update_user_state(user_id, "FREE_CHAT")
if msg:
self.send_message(user_id, msg)
self._send_vacancy_admin(user_id)
if state == "ADMIN_VAC_ADD_PLAYER":
if not self._valid_free_text(text):
self.send_message(user_id, "⚠️ Введите название направления.", keyboard=get_cancel_admin_keyboard())
return
db.add_vacancy_option("player", text.strip())
back_to_vacancies("✅ Направление для игроков добавлено.")
return
if state == "ADMIN_VAC_ADD_WORKER":
if not self._valid_free_text(text):
self.send_message(user_id, "⚠️ Введите название должности.", keyboard=get_cancel_admin_keyboard())
return
db.add_vacancy_option("worker", text.strip())
back_to_vacancies("✅ Должность для сотрудников добавлена.")
return
if state == "ADMIN_VAC_DEL_PICK":
vid = pick_id(text)
if not vid.isdigit() or not db.delete_vacancy_option(int(vid)):
self.send_message(user_id, "⚠️ Выберите вакансию кнопкой ниже.")
return
back_to_vacancies(f"✅ Вакансия №{vid} удалена.")
return
# ---------- Администраторы (супер-админ) ----------
if state == "ADMIN_ADMIN_ADD":
if not self._is_super_admin(user_id):
db.update_user_state(user_id, "FREE_CHAT")
self._send_admin_panel(user_id)
return
self._handle_admin_add_admin(user_id, "админ доб " + pick_id(text))
db.update_user_state(user_id, "FREE_CHAT")
return
if state == "ADMIN_ADMIN_DEL_PICK":
if not self._is_super_admin(user_id):
db.update_user_state(user_id, "FREE_CHAT")
self._send_admin_panel(user_id)
return
self._handle_admin_remove_admin(user_id, "админ удал " + pick_id(text))
db.update_user_state(user_id, "FREE_CHAT")
return
# Неизвестное ADMIN_-состояние — сброс # Неизвестное ADMIN_-состояние — сброс
self._admin_ctx.pop(user_id, None) self._admin_ctx.pop(user_id, None)
db.update_user_state(user_id, "FREE_CHAT") db.update_user_state(user_id, "FREE_CHAT")
@@ -2497,6 +2642,13 @@ class VKBotApplication:
content = text[len("рассылка"):].strip() content = text[len("рассылка"):].strip()
content_lower = content.lower() content_lower = content.lower()
# Отмена рассылки
if content_lower in ["отмена", "отменить", "отмена ❌"]:
self._pending_broadcasts.pop(user_id, None)
self.send_message(user_id, "❌ Рассылка отменена.")
self._send_admin_panel(user_id)
return
# Шаг 2: подтверждение и запуск # Шаг 2: подтверждение и запуск
if content_lower in ["подтвердить", "да", "отправить", "!"]: if content_lower in ["подтвердить", "да", "отправить", "!"]:
pending = self._pending_broadcasts.get(user_id) pending = self._pending_broadcasts.get(user_id)
@@ -2544,10 +2696,12 @@ class VKBotApplication:
"— — —\n" "— — —\n"
f"{content}\n" f"{content}\n"
"— — —\n\n" "— — —\n\n"
"Чтобы отправить — напишите: рассылка подтвердить\n" "Отправить или отменить?"
"Чтобы отменить — просто проигнорируйте."
) )
self.send_message(user_id, preview) kb = VkKeyboard(inline=True)
kb.add_button("Отправить ✅", color=VkKeyboardColor.POSITIVE, payload={"cmd": "bc_send"})
kb.add_button("Отмена ❌", color=VkKeyboardColor.NEGATIVE, payload={"cmd": "bc_cancel"})
self.send_message(user_id, preview, keyboard=kb.get_keyboard())
def _run_broadcast(self, admin_id: int, text: str, recipients: list): def _run_broadcast(self, admin_id: int, text: str, recipients: list):
"""Фоновая отправка рассылки с троттлингом и подсказкой об отписке.""" """Фоновая отправка рассылки с троттлингом и подсказкой об отписке."""
+43
View File
@@ -123,6 +123,24 @@ class DatabaseManager:
except sqlite3.OperationalError: except sqlite3.OperationalError:
pass pass
# Table for vacancy options (направления игроков и должности сотрудников)
cursor.execute("""
CREATE TABLE IF NOT EXISTS vacancy_options (
id INTEGER PRIMARY KEY AUTOINCREMENT,
track TEXT NOT NULL, -- 'player' | 'worker'
label TEXT NOT NULL,
is_active INTEGER DEFAULT 1
)
""")
cursor.execute("SELECT COUNT(*) FROM vacancy_options")
if cursor.fetchone()[0] == 0:
seed = [
("player", "Counter-Strike 2 🔫"), ("player", "Dota 2 🎮"), ("player", "World of Tanks 🛡️"),
("worker", "SMM / контент"), ("worker", "Монтажёр / дизайнер"),
("worker", "Менеджер"), ("worker", "Разработчик"),
]
cursor.executemany("INSERT INTO vacancy_options (track, label) VALUES (?, ?)", seed)
# Table for runtime-added admins (добавляются супер-админом из бота) # Table for runtime-added admins (добавляются супер-админом из бота)
cursor.execute(""" cursor.execute("""
CREATE TABLE IF NOT EXISTS admins ( CREATE TABLE IF NOT EXISTS admins (
@@ -434,6 +452,31 @@ class DatabaseManager:
) )
conn.commit() conn.commit()
# --- Вакансии (направления игроков / должности сотрудников) ---
def get_vacancy_options(self, track: str) -> list:
"""Активные опции для трека ('player'|'worker') как список (id, label)."""
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT id, label FROM vacancy_options WHERE track = ? AND is_active = 1 ORDER BY id",
(track,)
)
return cursor.fetchall()
def add_vacancy_option(self, track: str, label: str) -> int:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("INSERT INTO vacancy_options (track, label) VALUES (?, ?)", (track, label.strip()))
conn.commit()
return cursor.lastrowid
def delete_vacancy_option(self, option_id: int) -> bool:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("UPDATE vacancy_options SET is_active = 0 WHERE id = ?", (option_id,))
conn.commit()
return cursor.rowcount > 0
# --- Администраторы (добавленные в рантайме супер-админом) --- # --- Администраторы (добавленные в рантайме супер-админом) ---
def add_admin(self, user_id: int) -> bool: def add_admin(self, user_id: int) -> bool:
with self._get_connection() as conn: with self._get_connection() as conn:
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB