Правки админки: инлайн-кнопки, отмена рассылки, FAQ в меню, вакансии в БД
- Отмена в «Заявке»/«Карьере» возвращает меню с кнопкой «Админка» для админов. - Рассылка: инлайн-кнопки «Отправить»/«Отмена» в предпросмотре. - Управление админами — инлайн-кнопки «Добавить»/«Удалить» (без текстовых команд), выбор удаляемого админа кнопкой. - Быстрые ответы из админки показываются в «Частые вопросы» инлайн-кнопками. - Вакансии «Карьеры» перенесены в БД (vacancy_options): направления игроков и должности сотрудников редактируются из админки (раздел «Вакансии»), а в опросе показываются инлайн-кнопками из БД. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,7 @@ def get_admin_keyboard(is_super: bool = False) -> str:
|
||||
keyboard.add_button("Кандидаты 👥", color=VkKeyboardColor.SECONDARY)
|
||||
keyboard.add_line()
|
||||
keyboard.add_button("Управление услугами ⚙️", color=VkKeyboardColor.SECONDARY)
|
||||
keyboard.add_button("Вакансии 💼", color=VkKeyboardColor.SECONDARY)
|
||||
keyboard.add_line()
|
||||
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 += "\n➕ Добавленные из бота:\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(
|
||||
user_id,
|
||||
"👑 Управление администраторами\n\n"
|
||||
f"{lines}\n"
|
||||
"Команды:\n"
|
||||
"• Добавить админа: админ доб [VK_ID]\n"
|
||||
"• Удалить админа: админ удал [VK_ID]\n"
|
||||
" (снять можно только добавленных из бота)\n\n"
|
||||
"Управляйте кнопками ниже 👇\n\n"
|
||||
"ℹ️ Сменить/добавить СУПЕР-АДМИНА можно только в коде: откройте config.py, "
|
||||
"впишите 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):
|
||||
@@ -634,6 +636,10 @@ class VKBotApplication:
|
||||
if state.startswith("ADMIN_"):
|
||||
self._handle_admin_wizard(user_id, text_clean, text_lower, state, attachments)
|
||||
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"]:
|
||||
self._send_admin_panel(user_id)
|
||||
return
|
||||
@@ -692,6 +698,29 @@ class VKBotApplication:
|
||||
elif text_lower.startswith("рассылка"):
|
||||
self._handle_admin_broadcast(user_id, text_clean)
|
||||
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 ["быстрые ответы 💬", "быстрые ответы", "фак"]:
|
||||
self._send_faq_admin(user_id)
|
||||
return
|
||||
@@ -710,6 +739,28 @@ class VKBotApplication:
|
||||
elif text_lower in ["администраторы 👑", "администраторы", "админы"]:
|
||||
self._send_admins_panel(user_id)
|
||||
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("админ доб"):
|
||||
self._handle_admin_add_admin(user_id, text_clean)
|
||||
return
|
||||
@@ -750,7 +801,7 @@ class VKBotApplication:
|
||||
self.send_message(
|
||||
user_id,
|
||||
"Заполнение заявки отменено. Мы вернулись в главное меню. Чем я могу помочь?",
|
||||
keyboard=get_main_keyboard()
|
||||
keyboard=get_main_keyboard(is_admin=self._is_admin(user_id))
|
||||
)
|
||||
return
|
||||
|
||||
@@ -761,7 +812,7 @@ class VKBotApplication:
|
||||
self.send_message(
|
||||
user_id,
|
||||
"Отклик отменён. Вы вернулись в главное меню.",
|
||||
keyboard=get_main_keyboard()
|
||||
keyboard=get_main_keyboard(is_admin=self._is_admin(user_id))
|
||||
)
|
||||
return
|
||||
|
||||
@@ -830,7 +881,7 @@ class VKBotApplication:
|
||||
self.send_message(
|
||||
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()
|
||||
)
|
||||
# Дополнительно — быстрые ответы из админки инлайн-кнопками
|
||||
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
|
||||
|
||||
# 3. Возврат в главное меню
|
||||
@@ -1707,7 +1771,7 @@ class VKBotApplication:
|
||||
)
|
||||
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:
|
||||
@@ -1757,26 +1821,37 @@ class VKBotApplication:
|
||||
"""Базовая проверка свободного ответа в отклике (не пусто, разумной длины)."""
|
||||
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):
|
||||
"""Выбор ветки: игрок в состав или сотрудник."""
|
||||
if text_lower in ["игроком в состав 🎮", "игроком", "игрок", "в состав", "состав"]:
|
||||
db.update_user_state(user_id, "VAC_P_DISCIPLINE")
|
||||
self.send_message(
|
||||
user_id,
|
||||
"🎮 Набор в состав\n\nШаг 1 из 5: по какой дисциплине вы играете?",
|
||||
keyboard=get_vacancy_discipline_keyboard()
|
||||
)
|
||||
self._send_vac_options(user_id, "player",
|
||||
"🎮 Набор в состав\n\nШаг 1 из 5: по какой дисциплине вы играете?")
|
||||
elif text_lower in ["сотрудником 💼", "сотрудником", "сотрудник", "работником", "в команду"]:
|
||||
db.update_user_state(user_id, "VAC_W_POSITION")
|
||||
self.send_message(
|
||||
user_id,
|
||||
"💼 Набор в команду\n\nШаг 1 из 3: на какую должность вы претендуете?",
|
||||
keyboard=get_vacancy_position_keyboard()
|
||||
)
|
||||
self._send_vac_options(user_id, "worker",
|
||||
"💼 Набор в команду\n\nШаг 1 из 3: на какую должность вы претендуете?",
|
||||
include_other=True)
|
||||
elif text_lower in ["назад в меню ↩️", "назад", "меню", "в меню"]:
|
||||
db.clear_vac_data(user_id)
|
||||
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:
|
||||
self.send_message(
|
||||
user_id,
|
||||
@@ -1788,21 +1863,25 @@ class VKBotApplication:
|
||||
|
||||
def _handle_vac_p_discipline(self, user_id: int, text: str, text_lower: str):
|
||||
discipline = None
|
||||
syn = {
|
||||
"Counter-Strike 2": ["кс", "cs", "cs2", "csgo", "ксго", "контра", "counter"],
|
||||
"Dota 2": ["дота", "dota", "дотка", "доту"],
|
||||
"World of Tanks": ["танк", "wot", "tanks", "ворлд оф танкс", "вот"],
|
||||
}
|
||||
for name, words in syn.items():
|
||||
if any(w in text_lower for w in words):
|
||||
discipline = name
|
||||
# 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:
|
||||
self.send_message(
|
||||
user_id,
|
||||
"Пожалуйста, выберите дисциплину на клавиатуре ниже.",
|
||||
keyboard=get_vacancy_discipline_keyboard()
|
||||
)
|
||||
syn = {
|
||||
"Counter-Strike 2": ["кс", "cs", "cs2", "csgo", "ксго", "контра", "counter"],
|
||||
"Dota 2": ["дота", "dota", "дотка", "доту"],
|
||||
"World of Tanks": ["танк", "wot", "tanks", "ворлд оф танкс", "вот"],
|
||||
}
|
||||
for name, words in syn.items():
|
||||
if any(w in text_lower for w in words):
|
||||
discipline = name
|
||||
break
|
||||
if not discipline:
|
||||
self._send_vac_options(user_id, "player", "Пожалуйста, выберите дисциплину на кнопках ниже:")
|
||||
return
|
||||
db.set_vac_field(user_id, "discipline", discipline)
|
||||
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):
|
||||
known = {
|
||||
"smm / контент": "SMM / контент",
|
||||
"монтажёр / дизайнер": "Монтажёр / дизайнер",
|
||||
"менеджер": "Менеджер",
|
||||
"разработчик": "Разработчик",
|
||||
}
|
||||
if text_lower in ["другое ✍️", "другое", "иное"]:
|
||||
db.update_user_state(user_id, "VAC_W_POSITION_OTHER")
|
||||
self.send_message(
|
||||
@@ -1874,15 +1947,17 @@ class VKBotApplication:
|
||||
keyboard=get_vacancy_cancel_keyboard()
|
||||
)
|
||||
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 self._valid_free_text(text):
|
||||
self.send_message(
|
||||
user_id,
|
||||
"Выберите должность на клавиатуре или нажмите «Другое ✍️».",
|
||||
keyboard=get_vacancy_position_keyboard()
|
||||
)
|
||||
self._send_vac_options(user_id, "worker",
|
||||
"Выберите должность на кнопках ниже или нажмите «Другое ✍️»:",
|
||||
include_other=True)
|
||||
return
|
||||
position = text.strip()
|
||||
db.set_vac_field(user_id, "position", position)
|
||||
@@ -1940,7 +2015,7 @@ class VKBotApplication:
|
||||
"✅ Спасибо за отклик!\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):
|
||||
@@ -2070,6 +2145,25 @@ class VKBotApplication:
|
||||
# ЗАПУСК И ОБРАБОТКА 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):
|
||||
"""Мастера админки: добавление/изменение/удаление услуг и быстрых ответов, смена картинок."""
|
||||
if text_lower in ["отмена ❌", "отмена", "в админку ↩️", "в админку", "назад"]:
|
||||
@@ -2223,6 +2317,57 @@ class VKBotApplication:
|
||||
back_to_faq(f"✅ Ответ №{fid} удалён.")
|
||||
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_-состояние — сброс
|
||||
self._admin_ctx.pop(user_id, None)
|
||||
db.update_user_state(user_id, "FREE_CHAT")
|
||||
@@ -2497,6 +2642,13 @@ class VKBotApplication:
|
||||
content = text[len("рассылка"):].strip()
|
||||
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: подтверждение и запуск
|
||||
if content_lower in ["подтвердить", "да", "отправить", "!"]:
|
||||
pending = self._pending_broadcasts.get(user_id)
|
||||
@@ -2544,10 +2696,12 @@ class VKBotApplication:
|
||||
"— — —\n"
|
||||
f"{content}\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):
|
||||
"""Фоновая отправка рассылки с троттлингом и подсказкой об отписке."""
|
||||
|
||||
+43
@@ -123,6 +123,24 @@ class DatabaseManager:
|
||||
except sqlite3.OperationalError:
|
||||
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 (добавляются супер-админом из бота)
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS admins (
|
||||
@@ -434,6 +452,31 @@ class DatabaseManager:
|
||||
)
|
||||
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:
|
||||
with self._get_connection() as conn:
|
||||
|
||||
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 |
Binary file not shown.
|
Before Width: | Height: | Size: 75 KiB |
Reference in New Issue
Block a user