Админка, защита и UX: быстрые ответы, статусы кандидатов, мульти-операторы, бан, прогресс-бар
Бот: - Прогресс-бар (▰▰▱ Шаг N из 3) в шагах заявки - Быстрые ответы по ключевым словам (таблица custom_faq) — отвечают до зова оператора; редактирование через админ-панель (раздел «Быстрые ответы»: добавить/список/удалить) - Отклики в статистике: счётчик игроков/сотрудников по дисциплинам и должностям - Статусы кандидатов (новый/на собесе/отказ/принят) — раздел «Кандидаты» в админке - Несколько операторов: уведомления уходят всем из OPERATOR_PEER_IDS - Часы работы (WORK_HOUR_START/END): вне часов хендофф сообщает «ответим утром» - Временный бан при повторном флуде (усиление анти-DDoS) - Старт-пинг администратору «бот запущен» (лёгкий healthcheck) Док и зависимости: - README.md (запуск, .env, автозапуск, админ-команды, структура) - requirements.txt Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+81
@@ -113,6 +113,22 @@ class DatabaseManager:
|
||||
role TEXT,
|
||||
experience TEXT,
|
||||
contact TEXT,
|
||||
status TEXT DEFAULT 'новый',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
# Миграция: статус для существующих таблиц applications
|
||||
try:
|
||||
cursor.execute("ALTER TABLE applications ADD COLUMN status TEXT DEFAULT 'новый'")
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
|
||||
# Table for editable quick FAQ answers (быстрые ответы по ключевым словам)
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS custom_faq (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
keyword TEXT NOT NULL,
|
||||
answer TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
@@ -410,6 +426,71 @@ class DatabaseManager:
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
# --- Быстрые ответы (custom FAQ) ---
|
||||
def add_custom_faq(self, keyword: str, answer: str) -> int:
|
||||
with self._get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("INSERT INTO custom_faq (keyword, answer) VALUES (?, ?)",
|
||||
(keyword.strip().lower(), answer.strip()))
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
|
||||
def get_custom_faq(self) -> list:
|
||||
"""Список (id, keyword, answer) всех быстрых ответов."""
|
||||
with self._get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT id, keyword, answer FROM custom_faq ORDER BY id")
|
||||
return cursor.fetchall()
|
||||
|
||||
def delete_custom_faq(self, faq_id: int) -> bool:
|
||||
with self._get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM custom_faq WHERE id = ?", (faq_id,))
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
def match_custom_faq(self, text_lower: str) -> Optional[str]:
|
||||
"""Возвращает ответ, если в тексте встречается одно из ключевых слов."""
|
||||
for _id, keyword, answer in self.get_custom_faq():
|
||||
if keyword and keyword in text_lower:
|
||||
return answer
|
||||
return None
|
||||
|
||||
# --- Отклики: статусы и статистика ---
|
||||
def set_application_status(self, app_id: int, status: str) -> bool:
|
||||
with self._get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("UPDATE applications SET status = ? WHERE id = ?", (status, app_id))
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
def get_recent_applications(self, limit: int = 10) -> list:
|
||||
with self._get_connection() as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"SELECT id, track, discipline, position, contact, status, created_at "
|
||||
"FROM applications ORDER BY id DESC LIMIT ?", (limit,)
|
||||
)
|
||||
return [dict(r) for r in cursor.fetchall()]
|
||||
|
||||
def get_application_stats(self) -> Dict[str, Any]:
|
||||
"""Статистика откликов: всего, игроки/сотрудники, по дисциплинам и должностям."""
|
||||
with self._get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
stats = {
|
||||
"total": cursor.execute("SELECT COUNT(*) FROM applications").fetchone()[0],
|
||||
"players": cursor.execute("SELECT COUNT(*) FROM applications WHERE track='player'").fetchone()[0],
|
||||
"workers": cursor.execute("SELECT COUNT(*) FROM applications WHERE track='worker'").fetchone()[0],
|
||||
}
|
||||
cursor.execute("SELECT discipline, COUNT(*) FROM applications WHERE track='player' "
|
||||
"AND discipline IS NOT NULL GROUP BY discipline ORDER BY COUNT(*) DESC")
|
||||
stats["by_discipline"] = cursor.fetchall()
|
||||
cursor.execute("SELECT position, COUNT(*) FROM applications WHERE track='worker' "
|
||||
"AND position IS NOT NULL GROUP BY position ORDER BY COUNT(*) DESC")
|
||||
stats["by_position"] = cursor.fetchall()
|
||||
return stats
|
||||
|
||||
def add_web_registration(self, reg_id: str, data: dict):
|
||||
"""Сохранить заявку с веб-формы в базу данных SQLite."""
|
||||
with self._get_connection() as conn:
|
||||
|
||||
Reference in New Issue
Block a user