├── .github
└── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── SECURITY.md
├── app
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-311.pyc
│ ├── gpt.cpython-311.pyc
│ ├── map.cpython-311.pyc
│ ├── parser.cpython-311.pyc
│ └── sql.cpython-311.pyc
└── sql.py
├── config.py
├── main.py
└── requirements.txt
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | USDT: TWss5Hb3i92Wo8g2ELLW521AsnzJEx38NA
2 |
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.vscode
2 | /__pycache__
3 | app/database.db
4 | config.py
5 | app/__pycache__/sql.cpython-311.pyc
6 | app/__pycache__/sql.cpython-311.pyc
7 | config.py
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Sergey Milleu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HelpDesk Telegram Bot
2 |
3 |
4 | **Новые возможности: v0.4 beta**
5 |
6 | > - Отправка уведомлений о задачах как пользователям, так и администраторам.
7 | > - Добавлена кнопка выполнения задачи внутри уведомления.
8 | > - Список истории задач с пагинацией.
9 | > - По закрытию задачи теперь можно и нужно оставлять комментарий.
10 | > - Учитывается время исполнения задачи в часах
11 | > - Предусмотрены модераторы и главный админ(который получает все сообщения о поставке задач).
12 |
13 | 
14 |
15 |
16 | ## Описание
17 | Этот проект представляет собой Telegram бота, который предоставляет функционал HelpDesk системы. В текущей версии реализованы следующие функции:
18 |
19 | - **Анкетирование:** Пользователи могут заполнять анкету, предоставляя информацию о наименовании организации, адресе заявки и контактном номере телефона.
20 |
21 | - **Хранение заявок в базе данных:** Все созданные заявки сохраняются в базе данных. Информация о заявках доступна как администратору, так и пользователю в соответствующем разделе бота.
22 |
23 | - **Уведомления о новых заявках:** Администратор получает уведомление с подробностями о новой заявке, что помогает оперативно реагировать на запросы пользователей.
24 |
25 | - **База данных:** Проект использует базу данных, состоящую из таблицы заявок и таблицы пользователей для хранения информации.
26 |
27 | - **Административная панель:** Администратору доступен вывод всех заявок в очереди, а также изменение статуса с "В работе" на "Завершён".
28 |
29 |
30 | Необходимые пакеты:
31 | ``` pip install aiogram==2.25.2 ```
32 |
33 | Запуск:
34 | ``` python main.py ```
35 |
36 | ## Репозиторий
37 | Этот репозиторий является первым публичным для проекта. Здесь содержится исходный код бота, документация, и другие файлы, необходимые для развертывания и работы бота. Разработка ведется с акцентом на обеспечение удобства пользователей и оперативного реагирования на заявки.
38 |
39 | ## Примечание
40 | Этот README.md может быть дополнен дальнейшей информацией о конфигурации, установке, использовании и других функциях бота по мере развития проекта.
41 |
42 |
43 | ## Спонсорство
44 | Если вы считаете этот проект полезным, рассмотрите возможность поддержки его развития:
45 |
46 | - **SberBank:** [SberBank](https://messenger.online.sberbank.ru/sl/phavjfiRFywAoLOKf)
47 | - **USDT:** `TWss5Hb3i92Wo8g2ELLW521AsnzJEx38NA`
48 |
49 | Мы очень ценим вашу поддержку!
50 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Политика безопасности
2 |
3 | ## Обзор
4 |
5 | Целью этой политики безопасности является обеспечение защиты конфиденциальности данных и безопасности работы пользователей с HelpDesk Telegram Bot v0.4 beta, предоставляющим функционал HelpDesk системы.
6 |
7 | ## Область применения
8 |
9 | Эта политика применяется ко всем разработчикам, администраторам и пользователям, взаимодействующим с HelpDesk Telegram Bot версии v0.4 beta.
10 |
11 | ## Основные принципы
12 |
13 | - Безопасность информации - наш приоритет.
14 | - Соблюдение стандартов и законодательства по защите данных.
15 | - Постоянное обновление и аудит безопасности кода и инфраструктуры.
16 |
17 | ## Меры безопасности
18 |
19 | 1. **Аутентификация и авторизация:**
20 | - Реализация безопасной аутентификации пользователей в Telegram.
21 | - Ограничение доступа к административной панели только для уполномоченных администраторов.
22 |
23 | 2. **Хранение и обработка данных:**
24 | - Защита конфиденциальных данных пользователей (например, номеров телефонов и адресов) с помощью механизмов шифрования.
25 | - Соблюдение стандартов GDPR и других законов о защите данных при обработке информации пользователей.
26 |
27 | 3. **Тестирование безопасности:**
28 | - Проведение регулярных тестов на проникновение и аудит безопасности кода.
29 | - Использование инструментов статического анализа кода и сканеров уязвимостей для выявления потенциальных угроз.
30 |
31 | 4. **Обучение и осведомленность:**
32 | - Обучение сотрудников основам безопасности информации и процедурам защиты данных.
33 | - Проведение регулярных обзоров безопасности кода и обновление обучающих материалов.
34 |
35 | 5. **Реагирование на инциденты:**
36 | - Ведение журнала инцидентов безопасности и оперативное реагирование на них в соответствии с установленной процедурой.
37 | - Сообщение об обнаруженных уязвимостях в коде и инфраструктуре и оперативное их устранение.
38 |
39 | ## Обновления политики
40 |
41 | Эта политика безопасности будет пересматриваться и обновляться по мере необходимости, чтобы соответствовать изменениям в требованиях безопасности и стратегии проекта Telegram бота версии v0.4 beta.
42 |
43 | Последнее обновление: 02/05/2024
44 |
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prasx/HelpDesk/928425476499949adb91c7fd2de4259caa1c656c/app/__init__.py
--------------------------------------------------------------------------------
/app/__pycache__/__init__.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prasx/HelpDesk/928425476499949adb91c7fd2de4259caa1c656c/app/__pycache__/__init__.cpython-311.pyc
--------------------------------------------------------------------------------
/app/__pycache__/gpt.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prasx/HelpDesk/928425476499949adb91c7fd2de4259caa1c656c/app/__pycache__/gpt.cpython-311.pyc
--------------------------------------------------------------------------------
/app/__pycache__/map.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prasx/HelpDesk/928425476499949adb91c7fd2de4259caa1c656c/app/__pycache__/map.cpython-311.pyc
--------------------------------------------------------------------------------
/app/__pycache__/parser.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prasx/HelpDesk/928425476499949adb91c7fd2de4259caa1c656c/app/__pycache__/parser.cpython-311.pyc
--------------------------------------------------------------------------------
/app/__pycache__/sql.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prasx/HelpDesk/928425476499949adb91c7fd2de4259caa1c656c/app/__pycache__/sql.cpython-311.pyc
--------------------------------------------------------------------------------
/app/sql.py:
--------------------------------------------------------------------------------
1 | import sqlite3
2 | import json
3 |
4 | DB_PATH = 'app/database.db'
5 |
6 | def execute_query(query, params=None):
7 | """
8 | Выполняет SQL-запрос к базе данных.
9 |
10 | Parameters:
11 | query (str): SQL-запрос.
12 | params (tuple): Параметры для запроса (по умолчанию None).
13 |
14 | Returns:
15 | list: Результат выполнения запроса.
16 | """
17 | with sqlite3.connect(DB_PATH) as conn:
18 | cursor = conn.cursor()
19 | if params:
20 | cursor.execute(query, params)
21 | else:
22 | cursor.execute(query)
23 | conn.commit()
24 | return cursor.fetchall()
25 |
26 | def create_tables():
27 | """
28 | Создает таблицы в базе данных, если они не существуют.
29 | """
30 | users = '''
31 | CREATE TABLE IF NOT EXISTS users (
32 | tg_id INTEGER PRIMARY KEY,
33 | pos TEXT,
34 | data_reg TEXT,
35 | profile TEXT
36 | )
37 | '''
38 | ticket = '''
39 | CREATE TABLE IF NOT EXISTS ticket (
40 | number_ticket INTEGER PRIMARY KEY AUTOINCREMENT,
41 | tg_id_ticket INTEGER,
42 | organization TEXT,
43 | addres_ticket TEXT,
44 | message_ticket TEXT,
45 | time_ticket TEXT,
46 | state_ticket TEXT,
47 | ticket_comm TEXT
48 | )
49 | '''
50 | execute_query(users)
51 | execute_query(ticket)
52 |
53 |
54 | def add_user(tg_id, pos, data_reg, profile):
55 | """
56 | Добавляет нового пользователя в базу данных.
57 |
58 | Parameters:
59 | tg_id (int): Telegram ID пользователя.
60 | pos (str): Позиция пользователя.
61 | data_reg (str): Дата регистрации пользователя.
62 | profile (dict): Профиль пользователя в формате словаря.
63 | """
64 | query = '''INSERT INTO users (tg_id, pos, data_reg, profile) VALUES (?, ?, ?, ?)'''
65 | profile_json = json.dumps(profile, ensure_ascii=False)
66 | execute_query(query, (tg_id, pos, data_reg, profile_json))
67 |
68 |
69 | def get_user_by_id(tg_id):
70 | """
71 | Возвращает информацию о пользователе по его Telegram ID.
72 |
73 | Parameters:
74 | tg_id (int): Telegram ID пользователя.
75 |
76 | Returns:
77 | dict: Информация о пользователе.
78 | """
79 | query = 'SELECT * FROM users WHERE tg_id=?'
80 | result = execute_query(query, (tg_id,))
81 | if result:
82 | return {
83 | 'tg_id': result[0][0],
84 | 'pos': result[0][1],
85 | 'data_reg': result[0][2],
86 | 'profile': json.loads(result[0][3])
87 | }
88 | return None
89 |
90 |
91 | def add_ticket(tg_id_ticket, organization, addres_ticket, message_ticket, time_ticket, state_ticket, ticket_comm):
92 | """
93 | Добавляет новую задачу в базу данных.
94 |
95 | Parameters:
96 | tg_id_ticket (int): Telegram ID пользователя, создавшего задачу.
97 | organization (str): Название организации.
98 | addres_ticket (str): Адрес задачи.
99 | message_ticket (str): Сообщение задачи.
100 | time_ticket (str): Время создания задачи.
101 | state_ticket (str): Статус задачи.
102 | ticket_comm (str): Комментарий к задаче.
103 | """
104 | query = '''
105 | INSERT INTO ticket (tg_id_ticket, organization, addres_ticket, message_ticket, time_ticket, state_ticket, ticket_comm)
106 | VALUES (?, ?, ?, ?, ?, ?, ?)
107 | '''
108 | execute_query(query, (tg_id_ticket, organization, addres_ticket, message_ticket, time_ticket, state_ticket, ticket_comm))
109 |
110 |
111 | def get_last_ticket_number():
112 | """
113 | Возвращает номер последней задачи в таблице ticket.
114 |
115 | Returns:
116 | int: Номер последней задачи.
117 | """
118 | query = "SELECT number_ticket FROM ticket ORDER BY number_ticket DESC LIMIT 1"
119 | result = execute_query(query)
120 | return result[0][0] if result else 0
121 |
122 |
123 | def get_total_tickets_by_status(tg_id, status):
124 | """
125 | Возвращает общее количество задач с указанным статусом для заданного пользователя.
126 |
127 | Parameters:
128 | tg_id (int): Telegram ID пользователя.
129 | status (str): Статус задачи.
130 |
131 | Returns:
132 | int: Общее количество задач с указанным статусом.
133 | """
134 | query = 'SELECT COUNT(*) FROM ticket WHERE tg_id_ticket=? AND state_ticket=?'
135 | result = execute_query(query, (tg_id, status))
136 | return result[0][0] if result else 0
137 |
138 |
139 | def get_total_tickets_by_status_admin(status):
140 | """
141 | Возвращает общее количество задач с указанным статусом для администратора.
142 |
143 | Parameters:
144 | status (str): Статус задачи.
145 |
146 | Returns:
147 | int: Общее количество задач с указанным статусом.
148 | """
149 | query = 'SELECT COUNT(*) FROM ticket WHERE state_ticket=?'
150 | result = execute_query(query, (status,))
151 | return result[0][0] if result else 0
152 |
153 |
154 | def get_total_tickets_by_status_for_user(tg_id, status):
155 | """
156 | Возвращает общее количество задач с указанным статусом для заданного пользователя.
157 |
158 | Parameters:
159 | tg_id (int): Telegram ID пользователя.
160 | status (str): Статус задачи.
161 |
162 | Returns:
163 | str: Общее количество задач с указанным статусом (в виде строки).
164 | """
165 | total_tickets = get_total_tickets_by_status(tg_id, status)
166 | return str(total_tickets) if total_tickets else "0"
167 |
168 |
169 | def get_tickets_in_progress_by_user_id(tg_id):
170 | """
171 | Возвращает список задач в работе для указанного пользователя.
172 |
173 | Parameters:
174 | tg_id (int): Telegram ID пользователя.
175 |
176 | Returns:
177 | list: Список задач в работе.
178 | """
179 | query = 'SELECT * FROM ticket WHERE tg_id_ticket=? AND state_ticket=?'
180 | result = execute_query(query, (tg_id, "В работе"))
181 | return result
182 |
183 |
184 | def update_pos(pos_value, column, value):
185 | """
186 | Обновляет позицию пользователя в базе данных.
187 |
188 | Parameters:
189 | pos_value (str): Новое значение позиции.
190 | column (str): Название столбца, в котором нужно обновить значение.
191 | value (int): Значение, по которому происходит обновление.
192 |
193 | Returns:
194 | None
195 | """
196 | query = f"UPDATE users SET pos = ? WHERE {column} = ?"
197 | execute_query(query, (pos_value, value))
198 |
199 |
200 | def update_profile_data(tg_id, field_name, new_value):
201 | """
202 | Обновляет данные профиля пользователя внутри ячейки profile.
203 |
204 | Parameters:
205 | tg_id (int): Telegram ID пользователя.
206 | field_name (str): Название поля, которое нужно обновить.
207 | new_value: Новое значение для поля.
208 |
209 | Returns:
210 | None
211 | """
212 | query_select = 'SELECT profile FROM users WHERE tg_id=?'
213 | profile_json = execute_query(query_select, (tg_id,))[0][0]
214 | if profile_json:
215 | profile_dict = json.loads(profile_json)
216 | profile_dict[field_name] = new_value
217 | query_update = "UPDATE users SET profile = ? WHERE tg_id = ?"
218 | execute_query(query_update, (json.dumps(profile_dict, ensure_ascii=False), tg_id))
219 |
220 |
221 | def read_cell(column, condition_column, condition_value):
222 | """
223 | Читает данные из указанной ячейки в таблице пользователей.
224 |
225 | Parameters:
226 | column (str): Название столбца, из которого нужно прочитать данные.
227 | condition_column (str): Название столбца для условия выборки.
228 | condition_value: Значение, по которому происходит выборка.
229 |
230 | Returns:
231 | str: Значение ячейки (в виде строки).
232 | """
233 | query = f"SELECT {column} FROM users WHERE {condition_column} = ?"
234 | result = execute_query(query, (condition_value,))
235 | return str(result[0])[2:-3] if result else None
236 |
237 |
238 | def read_profile(tg_id):
239 | """
240 | Возвращает данные профиля пользователя из ячейки profile.
241 |
242 | Parameters:
243 | tg_id (int): Telegram ID пользователя.
244 |
245 | Returns:
246 | dict: Данные профиля пользователя.
247 | """
248 | query = "SELECT profile FROM users WHERE tg_id=?"
249 | result = execute_query(query, (tg_id,))
250 | if result:
251 | return json.loads(result[0][0])
252 | return None
253 |
254 |
255 | def get_all_tickets_in_progress():
256 | """
257 | Возвращает список всех тикетов, находящихся в процессе выполнения.
258 |
259 | Returns:
260 | list: Список кортежей с данными о тикетах.
261 | """
262 | conn = sqlite3.connect(DB_PATH)
263 | cursor = conn.cursor()
264 | cursor.execute('SELECT * FROM ticket WHERE state_ticket=?', ("В работе",))
265 | all_tickets_in_progress = cursor.fetchall()
266 | conn.close()
267 | return all_tickets_in_progress
268 |
269 |
270 | def get_ticket_info(ticket_id):
271 | """
272 | Возвращает информацию о заданном тикете.
273 |
274 | Parameters:
275 | ticket_id (int): Номер тикета.
276 |
277 | Returns:
278 | tuple: Кортеж с данными о тикете.
279 | """
280 | conn = sqlite3.connect(DB_PATH)
281 | cursor = conn.cursor()
282 | cursor.execute('SELECT * FROM ticket WHERE number_ticket=?', (ticket_id,))
283 | ticket_info = cursor.fetchone()
284 | conn.close()
285 | return ticket_info
286 |
287 |
288 | def update_ticket_status(ticket_id, new_status):
289 | """
290 | Обновляет статус заданного тикета.
291 |
292 | Parameters:
293 | ticket_id (int): Номер тикета.
294 | new_status (str): Новый статус.
295 |
296 | Returns:
297 | None
298 | """
299 | conn = sqlite3.connect(DB_PATH)
300 | cursor = conn.cursor()
301 | query = "UPDATE ticket SET state_ticket=? WHERE number_ticket=?"
302 | cursor.execute(query, (new_status, ticket_id))
303 | conn.commit()
304 | conn.close()
305 |
306 |
307 | def get_completed_tickets_by_user(tg_id):
308 | """
309 | Возвращает список завершенных тикетов для указанного пользователя.
310 |
311 | Parameters:
312 | tg_id (int): Telegram ID пользователя.
313 |
314 | Returns:
315 | list: Список кортежей с данными о завершенных тикетах.
316 | """
317 | conn = sqlite3.connect(DB_PATH)
318 | cursor = conn.cursor()
319 | cursor.execute('SELECT * FROM ticket WHERE tg_id_ticket=? AND state_ticket=?', (tg_id, "Завершена"))
320 | completed_tickets = cursor.fetchall()
321 | conn.close()
322 | return completed_tickets
323 |
324 |
325 | def update_ticket_comment(ticket_id, ticket_comm):
326 | """
327 | Обновляет комментарий в существующем тикете.
328 |
329 | Parameters:
330 | ticket_id (int): Номер тикета.
331 | ticket_comm (str): Новый комментарий.
332 |
333 | Returns:
334 | bool: Флаг успешного выполнения операции (True - успешно, False - ошибка).
335 | """
336 | query = 'UPDATE ticket SET ticket_comm = ? WHERE number_ticket = ?'
337 | execute_query(query, (ticket_comm, ticket_id))
338 | return True
339 |
340 |
341 | def read_ticket_comment(ticket_id):
342 | """
343 | Читает комментарий из существующего тикета.
344 |
345 | Parameters:
346 | ticket_id (int): Номер тикета.
347 |
348 | Returns:
349 | str: Комментарий к тикету.
350 | """
351 | query = 'SELECT ticket_comm FROM ticket WHERE number_ticket = ?'
352 | result = execute_query(query, (ticket_id,))
353 | if result:
354 | return result[0][0]
355 | else:
356 | return None
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | BOT_TOKEN = 'YOU_BOT_TOKEN' #test
2 |
3 |
4 | ADMIN_USERS = [ID MODER, ID MODER]
5 | ADMIN_MESSAGE = ID ADMIN
6 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | import aiogram
2 | from aiogram import Bot, Dispatcher, types
3 | from aiogram.contrib.middlewares.logging import LoggingMiddleware
4 | from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
5 | from aiogram import executor
6 | from app import sql
7 | import config
8 | from config import ADMIN_USERS, ADMIN_MESSAGE
9 | import datetime
10 |
11 | import asyncio
12 | loop = asyncio.get_event_loop()
13 | import logging
14 | logging.basicConfig(level=logging.INFO)
15 |
16 |
17 | bot = Bot(token=config.BOT_TOKEN)
18 | dp = Dispatcher(bot)
19 | dp.middleware.setup(LoggingMiddleware())
20 | # Создание таблиц в базе данных SQLite
21 | sql.create_tables()
22 |
23 |
24 | @dp.message_handler(commands=['start'])
25 | async def send_start(message: types.Message):
26 | user_id = message.from_user.id
27 | data_reg = message.date
28 | user = sql.get_user_by_id(user_id)
29 |
30 | if not user:
31 | # Если пользователь отсутствует, добавляем его
32 | user_info = {
33 | 'tg_id': user_id,
34 | 'pos': 'main_menu',
35 | 'data_reg': data_reg,
36 | 'profile': {"organization": "Нет данных", "organization_adress": "Нет данных", "organization_inn": "Нет данных", "organization_phone": "Нет данных", "history_ticket": "", "data_ticket": "", "user_name": ""}
37 | }
38 | sql.add_user(**user_info)
39 | text_no_user = f"Добро пожаловать в HelpDesk компании ЭниКей! Для работы в сервисе необходимо заполнить данные."
40 | keyboard = InlineKeyboardMarkup()
41 | keyboard.add(InlineKeyboardButton(text="🏢 Моя компания", callback_data="my_company"))
42 | await message.answer(text_no_user, reply_markup=keyboard, parse_mode="HTML")
43 |
44 | else:
45 | # Проверка открытых\закрытых тикетов
46 | open_ticket = sql.get_total_tickets_by_status_for_user(user_id, "В работе")
47 | closed_ticket = sql.get_total_tickets_by_status_for_user(user_id, "Завершена")
48 | # Чтение профиля
49 | profile = sql.read_profile(user_id)
50 | sql.update_pos('main_menu', 'tg_id', user_id)
51 | organization = profile.get("organization", "Нет данных")
52 | organization_phone = profile.get("organization_phone", "Нет данных")
53 |
54 | text_user = (f"🧑💻 Главное меню \n\n"
55 | f"📋 Компания: {organization}\n"
56 | f"☎️ Контактный номер: {organization_phone}\n\n"
57 |
58 | f"📬Открытых заявок: {open_ticket}\n"
59 | f"📭Закрытых заявок: {closed_ticket}\n"
60 | f"\nВыберите интересующее действие ⬇️"
61 | )
62 | keyboard = InlineKeyboardMarkup()
63 | keyboard.add(InlineKeyboardButton(text="🏢 Моя компания", callback_data="my_company"),
64 | InlineKeyboardButton(text="📥 Мои заявки", callback_data="my_ticket"))
65 | keyboard.add(InlineKeyboardButton(text="📤 Новая заявка", callback_data="new_ticket"))
66 |
67 | # Проверяем, является ли пользователь администратором
68 | if user_id in ADMIN_USERS:
69 | ticket_menu_admin = types.InlineKeyboardButton("🤘 Тикет меню", callback_data="admin_panel")
70 | keyboard.add(ticket_menu_admin)
71 | await message.answer(text_user, reply_markup=keyboard, parse_mode="HTML")
72 |
73 |
74 | # Главное меню пользователя мимикрия под /start
75 | def main_menu(tg_id):
76 | sql.update_pos('main_menu', 'tg_id', tg_id)
77 | user_id = tg_id
78 | open_ticket = sql.get_total_tickets_by_status_for_user(tg_id, "В работе")
79 | closed_ticket = sql.get_total_tickets_by_status_for_user(tg_id, "Завершена")
80 | profile = sql.read_profile(tg_id)
81 | organization = profile.get("organization", "Нет данных")
82 | organization_phone = profile.get("organization_phone", "Нет данных")
83 |
84 | text = (f"🧑💻 Главное меню \n\n"
85 | f"📋 Компания: {organization}\n"
86 | f"☎️ Контактный номер: {organization_phone}\n\n"
87 |
88 | f"📬Открытых заявок: {open_ticket}\n"
89 | f"📭Закрытых заявок: {closed_ticket}\n"
90 | f"\nВыберите интересующее действие ⬇️"
91 | )
92 | keyboard = InlineKeyboardMarkup()
93 | keyboard.add(InlineKeyboardButton(text="🏢 Моя компания", callback_data="my_company"),
94 | InlineKeyboardButton(text="📥 Мои заявки", callback_data="my_ticket"))
95 | keyboard.add(InlineKeyboardButton(text="📤 Новая заявка", callback_data="new_ticket"))
96 |
97 | # Проверяем, является ли пользователь администратором
98 | if user_id in ADMIN_USERS:
99 | ticket_menu_admin = types.InlineKeyboardButton("🤘Тикет меню", callback_data="admin_panel")
100 | keyboard.add(ticket_menu_admin)
101 | return text, keyboard
102 |
103 |
104 | def new_ticket(tg_id):
105 | text = (f"📤 Создание новой заявки\n\n"
106 | # f" - 📝 Опишите вашу проблему.\n"
107 | f" - 🧩 Пожалуйста, опишите вашу проблему и укажите как можно подробнее.\n\n"
108 | f"Пример оформления заявки: \nНе работает принтер на 4 ПК, необходимо проверить подключение.")
109 | keyboard = InlineKeyboardMarkup()
110 | keyboard.add(InlineKeyboardButton(text="⬅️ Назад", callback_data="main_menu"))
111 | return text, keyboard
112 |
113 |
114 | def my_ticket(tg_id):
115 | profile = sql.read_profile(tg_id)
116 | user_tickets_in_progress = sql.get_tickets_in_progress_by_user_id(tg_id)
117 | total_user_tickets_in_progress = len(user_tickets_in_progress)
118 | open_ticket = str(total_user_tickets_in_progress) if total_user_tickets_in_progress else "0"
119 | organization = profile.get("organization")
120 | organization_address = profile.get("organization_adress")
121 |
122 | if user_tickets_in_progress:
123 | text = (f"📥 Мои заявки в работе\n\n"
124 | f"Компания: {organization}\n"
125 | f"Адрес заявки: {organization_address}\n"
126 | f"Заявок в работе: {open_ticket}\n\n"
127 | )
128 | for ticket in user_tickets_in_progress:
129 | # Использование индексов для доступа к данным кортежа
130 | text += (f"Номер заявки: #{ticket[0]}
\n"
131 | f"Описание: {ticket[4]}\n"
132 | f"Дата: {ticket[5]}\n"
133 | f"Статус: {ticket[6]}\n"
134 | )
135 | else:
136 | text = '📥 Мои заявки \n\nУ вас пока нет заявок в работе.. 🤷♂️ \n- Что бы оставить заявку воспользуйтесь меню "📤 Новая заявка"'
137 |
138 | keyboard = InlineKeyboardMarkup()
139 | keyboard.add(InlineKeyboardButton(text="☑️ История заявок", callback_data="my_ticket_history"))
140 | keyboard.add(InlineKeyboardButton(text="⬅️ Назад", callback_data="main_menu"))
141 | return text, keyboard
142 |
143 |
144 | def my_ticket_history(tg_id, page=1, page_size=4):
145 | completed_tickets = sql.get_completed_tickets_by_user(tg_id)
146 | # Проверяем, есть ли завершенные заявки
147 | if completed_tickets:
148 | # Проверяем, нужна ли пагинация
149 | if len(completed_tickets) > page_size:
150 | start_index = (page - 1) * page_size
151 | end_index = start_index + page_size
152 | current_page_tickets = completed_tickets[start_index:end_index]
153 | text = f"📨 История ваших завершенных заявок (страница {page}):\n\n"
154 | else:
155 | current_page_tickets = completed_tickets
156 | text = "📨 История ваших завершенных заявок:\n\n"
157 |
158 | for ticket in current_page_tickets:
159 | text += f"✅\n" \
160 | f"├ Номер заявки: #{ticket[0]}
\n" \
161 | f"├ Время создания: {ticket[5]}\n" \
162 | f"├ Сообщение: - {ticket[4]}\n" \
163 | f"└ Комментарий исполнителя: - {ticket[7]}\n\n"
164 |
165 | else:
166 | text = "🤷♂️ Упс.. У вас нет истории заявок."
167 |
168 | keyboard = InlineKeyboardMarkup()
169 | # Создаем кнопки для навигации по страницам, если нужно
170 | if len(completed_tickets) > page_size:
171 | if page > 1:
172 | keyboard.row(InlineKeyboardButton(text="🔙 Предыдущая", callback_data=f"my_ticket_page_{page - 1}"))
173 | if end_index < len(completed_tickets):
174 | keyboard.insert(InlineKeyboardButton(text="🔜 Следующая", callback_data=f"my_ticket_page_{page + 1}"))
175 | keyboard.add(InlineKeyboardButton(text="⬅️ Назад", callback_data="my_ticket"))
176 | return text, keyboard
177 |
178 |
179 | def my_company(tg_id):
180 | profile = sql.read_profile(tg_id)
181 | organization = profile.get("organization", "Нет данных")
182 | organization_address = profile.get("organization_adress", "Нет данных")
183 | organization_inn = profile.get("organization_inn", "Нет данных")
184 | organization_phone = profile.get("organization_phone", "Нет данных")
185 |
186 | # Формирование текста для отображения данных о компании
187 | text = (f"🏢 Информация о компании\n\n"
188 | f"📋 Компания: {organization}\n"
189 | f"📍 Адрес: {organization_address}\n"
190 | f"📑 ИНН: {organization_inn}\n"
191 | f"☎️ Контактный номер: {organization_phone}\n\n"
192 | f"ЗАПОЛНИТЬ ДАННЫЕ О КОМПАНИИ ⬇️ " )
193 |
194 | keyboard = InlineKeyboardMarkup()
195 | keyboard.add(InlineKeyboardButton(text=f"{'✅' if organization != 'Нет данных' else '❌'} Наименование компании", callback_data="edit_company_name"))
196 | keyboard.add(InlineKeyboardButton(text=f"{'✅' if organization_address != 'Нет данных' else '❌'} Фактический адрес", callback_data="edit_company_adress"))
197 | keyboard.add(InlineKeyboardButton(text=f"{'✅' if organization_inn != 'Нет данных' else '❌'} ИНН", callback_data="edit_company_inn"))
198 | keyboard.add(InlineKeyboardButton(text=f"{'✅' if organization_phone != 'Нет данных' else '❌'} Контактный номер", callback_data="edit_company_phone"))
199 | keyboard.add(InlineKeyboardButton(text="⬅️ В меню", callback_data="main_menu"))
200 | return text, keyboard
201 |
202 |
203 | def edit_company_name(tg_id):
204 | text = f"📋 Введите наименование организации. \nПример: ООО РОГА И КОПЫТА
"
205 | keyboard = InlineKeyboardMarkup()
206 | keyboard.add(InlineKeyboardButton(text="⬅️ Назад", callback_data="my_company"))
207 | return text, keyboard
208 |
209 | def edit_company_adress(tg_id):
210 | text = f"📍Введите фактический адрес организации. \nПример: г. Иваново, ул. Пушкина, д. 3 оф. 1
"
211 | keyboard = InlineKeyboardMarkup()
212 | keyboard.add(InlineKeyboardButton(text="⬅️ Назад", callback_data="my_company"))
213 | return text, keyboard
214 |
215 | def edit_company_inn(tg_id):
216 | text = f"📑 Введите ИНН организации. \nПример: 3700010101
"
217 | keyboard = InlineKeyboardMarkup()
218 | keyboard.add(InlineKeyboardButton(text="⬅️ Назад", callback_data="my_company"))
219 | return text, keyboard
220 |
221 | def edit_company_phone(tg_id):
222 | text = f"☎️ Введите контактный номер телефона. \nПример: +79100009999
"
223 | keyboard = InlineKeyboardMarkup()
224 | keyboard.add(InlineKeyboardButton(text="⬅️ Назад", callback_data="my_company"))
225 | return text, keyboard
226 |
227 | def done_ticket(tg_id):
228 | last_ticket_number = sql.get_last_ticket_number()
229 | text = f'🎉🥳 Успех, ваша заявка зарегистрирована! \n\nНомер заявки: #{last_ticket_number}
. \n\nPS: Отслеживайте статус поставленных задач в разделе "📥 Мои заявки"'
230 | keyboard = InlineKeyboardMarkup()
231 | keyboard.add(InlineKeyboardButton(text="🧑💻 Главное меню", parse_mode="HTML", callback_data="main_menu"))
232 | return text, keyboard
233 |
234 |
235 | # Административный раздел
236 | def admin_panel():
237 | total_open_tickets = sql.get_total_tickets_by_status_admin("В работе") # Получаем общее количество заявок "В работе"
238 | total_closed_tickets = sql.get_total_tickets_by_status_admin("Завершена") # Получаем общее количество завершенных заявок
239 | all_tickets_in_progress = sql.get_all_tickets_in_progress()
240 |
241 | text = f"🤘 Тикет меню 💲\n\n"
242 | text += f"🔥Заявок в работе: {total_open_tickets}\n"
243 | text += f"👍Завершенных заявок: {total_closed_tickets}\n\n"
244 | text += f"⚠️ Внимание! Закрытые задачи не могут быть возвращены в работу. Пожалуйста, будьте внимательны при их закрытии!"
245 |
246 | keyboard = InlineKeyboardMarkup()
247 | for ticket in all_tickets_in_progress:
248 | ticket_info = f"Заявка #{ticket[0]} - {ticket[5]}" # Номер и описание заявки
249 | keyboard.add(InlineKeyboardButton(text=ticket_info, callback_data=f"ticket_{ticket[0]}"))
250 | keyboard.add(InlineKeyboardButton(text="⬅️ Назад", callback_data="main_menu"))
251 | return text, keyboard
252 |
253 |
254 |
255 | @dp.callback_query_handler(lambda query: query.data.startswith(('ticket_', 'my_ticket_page_')))
256 | async def handle_ticket_callback(query: types.CallbackQuery):
257 | user_id = query.from_user.id
258 | tg_id = user_id
259 |
260 |
261 | if query.data.startswith('ticket_'):
262 | ticket_id = query.data.split('_')[1]
263 | ticket_info = sql.get_ticket_info(ticket_id)
264 | sql.update_pos(f'ticket_details_{ticket_info[0]}', 'tg_id', user_id)
265 | await query.answer()
266 | text = f"Детали заявки: #{ticket_info[0]}\n\n
" \
267 | f"Пользователь ID: {ticket_info[1]}\n" \
268 | f"Организация: {ticket_info[2]}\n" \
269 | f"Адрес: {ticket_info[3]}\n\n" \
270 | f"Сообщение от пользователя: - {ticket_info[4]}\n\n" \
271 | f"Время создания: {ticket_info[5]}\n" \
272 | f"Статус: {ticket_info[6]}\n\n" \
273 | f"⚠️ Для завершения задачи введите комментарий. В ответ вам придет сообщение с подтвержением!"
274 |
275 | keyboard = types.InlineKeyboardMarkup()
276 | back_button = types.InlineKeyboardButton("⬅️ Назад", callback_data="admin_panel")
277 |
278 | keyboard.add(back_button)
279 | await query.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
280 |
281 |
282 | if query.data.startswith('my_ticket_page_'):
283 | page = int(query.data.split('_')[3]) # Получаем номер страницы из колбека
284 | await query.answer() # Ответим на колбек, чтобы убрать "крутилку"
285 | tg_id = query.from_user.id
286 | # Получаем текст сообщения и клавиатуру с учетом текущей страницы
287 | text, keyboard = my_ticket_history(tg_id, page)
288 | # Редактируем сообщение с новым текстом и клавиатурой
289 | await query.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
290 |
291 |
292 |
293 | # Группа колбеков на батоны
294 | @dp.callback_query_handler()
295 | async def inline_kb_answer_callback_handler(query: types.CallbackQuery):
296 | user_id = query.from_user.id
297 | tg_id = user_id
298 |
299 | if query.data == 'admin_panel':
300 | # Обновление ячейки 'pos' в базе данных
301 | sql.update_pos('admin_panel', 'tg_id', user_id)
302 | await query.answer()
303 | text, keyboard = admin_panel()
304 | await query.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
305 |
306 | if query.data == 'main_menu':
307 | # Обновление ячейки 'pos' в базе данных
308 | sql.update_pos('main_menu', 'tg_id', user_id)
309 | await query.answer()
310 | text, keyboard = main_menu(tg_id)
311 | await query.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
312 |
313 | if query.data.startswith('complete_'):
314 | ticket_id = query.data.split('_')[1]
315 | # Обновление ячейки 'pos' в базе данных
316 | sql.update_pos('complete_', 'tg_id', user_id)
317 | await query.answer()
318 | sql.update_ticket_status(ticket_id, "Завершена")
319 | ticket_comm_done = sql.read_ticket_comment(ticket_id)
320 | ticket_info = sql.get_ticket_info(ticket_id)
321 |
322 | current_time = datetime.datetime.now()
323 | time_ticket = datetime.datetime.strptime(ticket_info[5], "%Y-%m-%d %H:%M:%S")
324 | time_difference = current_time - time_ticket
325 |
326 | # Преобразуем общее количество секунд в объект timedelta
327 | total_seconds = time_difference.total_seconds()
328 | hours = int(total_seconds // 3600)
329 |
330 | # Отправка сообщения пользователю о завершении задачи
331 | user_id = ticket_info[1] # ID пользователя, поставившего задачу
332 | completion_message = f"🎉 Задача #{ticket_id}
выполнена!\nВремя выполнения: {hours} часа(ов).\n\nОтвет исполнителя: - {ticket_comm_done}\n\n⚠️ Пожалуйста, проверьте корректность исполнения задачи."
333 |
334 | back_button_user = types.InlineKeyboardButton("🧑💻 Главное меню", callback_data="main_menu")
335 | history_ticket = InlineKeyboardButton(text="☑️ История заявок", callback_data="my_ticket_history")
336 | keyboard_markup_user = types.InlineKeyboardMarkup().add(history_ticket, back_button_user)
337 |
338 | back_button_admin = types.InlineKeyboardButton("🤘Тикет меню", callback_data="admin_panel")
339 | keyboard_markup_admin = types.InlineKeyboardMarkup().add(back_button_admin)
340 |
341 | await bot.send_message(user_id, completion_message, reply_markup=keyboard_markup_user, parse_mode="HTML")
342 | await bot.send_message(query.from_user.id, completion_message, reply_markup=keyboard_markup_admin, parse_mode="HTML")
343 |
344 |
345 | if query.data == 'my_company':
346 | # Обновление ячейки 'pos' в базе данных
347 | sql.update_pos('my_company', 'tg_id', user_id)
348 | await query.answer()
349 | text, keyboard = my_company(tg_id)
350 | await query.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
351 |
352 | if query.data == 'edit_company_name':
353 | # Обновление ячейки 'pos' в базе данных
354 | sql.update_pos('edit_company_name', 'tg_id', user_id)
355 | await query.answer()
356 | text, keyboard = edit_company_name(tg_id)
357 | await query.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
358 |
359 | if query.data == 'edit_company_adress':
360 | # Обновление ячейки 'pos' в базе данных
361 | sql.update_pos('edit_company_adress', 'tg_id', user_id)
362 | await query.answer()
363 | text, keyboard = edit_company_adress(tg_id)
364 | await query.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
365 |
366 | if query.data == 'edit_company_inn':
367 | # Обновление ячейки 'pos' в базе данных
368 | sql.update_pos('edit_company_inn', 'tg_id', user_id)
369 | await query.answer()
370 | text, keyboard = edit_company_inn(tg_id)
371 | await query.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
372 |
373 | if query.data == 'edit_company_phone':
374 | # Обновление ячейки 'pos' в базе данных
375 | sql.update_pos('edit_company_phone', 'tg_id', user_id)
376 | await query.answer()
377 | text, keyboard = edit_company_phone(tg_id)
378 | await query.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
379 |
380 | if query.data == 'new_ticket':
381 | # Обновление ячейки 'pos' в базе данных
382 | sql.update_pos('new_ticket', 'tg_id', user_id)
383 | await query.answer()
384 | text, keyboard = new_ticket(tg_id)
385 | await query.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
386 |
387 | if query.data == 'my_ticket':
388 | # Обновление ячейки 'pos' в базе данных
389 | sql.update_pos('my_ticket', 'tg_id', user_id)
390 | await query.answer()
391 | text, keyboard = my_ticket(tg_id)
392 | await query.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
393 |
394 | if query.data == 'my_ticket_history':
395 | # Обновление ячейки 'pos' в базе данных
396 | sql.update_pos('my_ticket_history', 'tg_id', user_id)
397 | await query.answer()
398 | text, keyboard = my_ticket_history(tg_id)
399 | await query.message.edit_text(text, reply_markup=keyboard, parse_mode="HTML")
400 |
401 |
402 |
403 |
404 | # Обратотка текстовых сообщений
405 | @dp.message_handler()
406 | async def handle_text_input(message: types.Message):
407 |
408 | user_id = message.from_user.id
409 | username = message.from_user.username
410 | profile = sql.read_profile(user_id)
411 | organization_name = profile.get("organization", "")
412 | organization_address = profile.get("organization_adress", "")
413 | organization_phone = profile.get("organization_phone", "Нет данных")
414 | user_position = sql.read_cell('pos', 'tg_id', user_id)
415 |
416 | if user_position.startswith('ticket_details_'):
417 | parts = user_position.split('_')
418 | if len(parts) == 3 and parts[2].isdigit():
419 | ticket_id = int(parts[2])
420 | # Обновление комментария в базе данных
421 | comment_text = message.text
422 | sql.update_ticket_comment(ticket_id, comment_text)
423 |
424 | # Создаем кнопку "✅ Выполнить"
425 | complete_button = types.InlineKeyboardButton("✅ Завершить задачу", callback_data=f"complete_{ticket_id}")
426 | keyboard = types.InlineKeyboardMarkup()
427 | keyboard.add(complete_button)
428 |
429 | # Вставляем переменные в текст сообщения
430 | success_message = f"Комментарий к тикету #{ticket_id}
успешно записан!\n\nОтвет исполнителя: - {comment_text}\n\n⚠️ Если вы допустили ошибку, просто отправьте исправленное сообщение еще раз."
431 | await message.reply(success_message, reply_markup=keyboard, parse_mode="HTML")
432 | else:
433 | await message.reply("Ошибка формата номера тикета", parse_mode="HTML")
434 |
435 | if user_position == 'edit_company_name':
436 | sql.update_profile_data(user_id, 'organization', message.text)
437 | text, keyboard = my_company(user_id)
438 | await message.reply(text, reply_markup=keyboard, parse_mode="HTML")
439 |
440 |
441 | if user_position == 'edit_company_adress':
442 | sql.update_profile_data(user_id, 'organization_adress', message.text)
443 | text, keyboard = my_company(user_id)
444 | await message.reply(text, reply_markup=keyboard, parse_mode="HTML")
445 |
446 | if user_position == 'edit_company_inn':
447 | sql.update_profile_data(user_id, 'organization_inn', message.text)
448 | text, keyboard = my_company(user_id)
449 | await message.reply(text, reply_markup=keyboard, parse_mode="HTML")
450 |
451 | if user_position == 'edit_company_phone':
452 | sql.update_profile_data(user_id, 'organization_phone', message.text)
453 | text, keyboard = my_company(user_id)
454 | await message.reply(text, reply_markup=keyboard, parse_mode="HTML")
455 |
456 | if user_position == 'new_ticket':
457 | user_ticket = user_id
458 | organization = organization_name
459 | addres_ticket = organization_address
460 | message_ticket = message.text
461 | time_ticket = message.date
462 | state_ticket = "В работе"
463 | ticket_comm = ""
464 |
465 | # Добавляем новую заявку в базу данных
466 | sql.add_ticket(user_ticket, organization, addres_ticket, message_ticket, time_ticket, state_ticket, ticket_comm)
467 | # Получаем номер последней добавленной заявки
468 | last_ticket_number = sql.get_last_ticket_number()
469 |
470 | if last_ticket_number:
471 | # Обновляем профиль пользователя с номером последней добавленной заявки
472 | sql.update_profile_data(user_id, 'history_ticket', str(last_ticket_number))
473 | sql.update_profile_data(user_id, 'data_ticket', str(time_ticket))
474 | sql.update_profile_data(user_id, 'user_name', str(username))
475 |
476 |
477 | # Меню благодарочки
478 | text, keyboard = done_ticket(user_id)
479 | await message.reply(text, reply_markup=keyboard, parse_mode="HTML")
480 |
481 | admin_panel = types.InlineKeyboardButton("🤘Тикет меню🫰", callback_data="admin_panel")
482 | keyboard_markup = types.InlineKeyboardMarkup().add(admin_panel)
483 |
484 | # Отправка сообщения администратору
485 | admin_text = (f"📬❗️\nПользователь @{username} создал новую заявку с номером #{last_ticket_number}
."
486 | f"\n\nСообщение от пользователя:\n - {message_ticket}"
487 | f"\n\nТелефон: {organization_phone}\n"
488 | f"Компания: {organization}\n"
489 | f"Адрес: {addres_ticket}\n"
490 | )
491 |
492 | # Добавляем клавиатуру к уведомлению
493 | await bot.send_message(ADMIN_MESSAGE, admin_text, parse_mode="HTML", reply_markup=keyboard_markup)
494 | else:
495 | await message.reply("Ошибка при получении заявки.")
496 |
497 |
498 | if __name__ == '__main__':
499 | executor = aiogram.executor.Executor(dp, loop=loop, skip_updates=True)
500 | executor.start_polling()
501 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | aiogram==2.25.2
2 |
--------------------------------------------------------------------------------