Tg бот для IT + автоматизация ИПРО-2 используя MTS Exolve. Упаковка в Docker и автоматизация сборки с GitHub Actions

от автора

Статья написана с целью передачи проекта всем кто ищет информацию по написанию простых ботов, началу работы с docker и github Actions.

1. Введение

У нас Tg бот написан был года 2,5 назад, на тот момент нужен был помощник для приема заявок от пользователей. Было очень не удобно принимать огромное количество звонков с разных объектов и порой терялись задачи в мессенджерах.
Не так давно, вечерком, начал перерабатывать функционал. Так была добавлена база для хранения заявок, админ панель, и автоматизирована постановка на охрану пары объектов. Обо всем по порядку…

Ссылка на GitHub https://github.com/LeoAlecksey/scripts/tree/main/tg_bot

  1. Небольшой обзор кода Tg-бота, написан на Python библиотека pyTelegramBotAPI, в нем:
  2. Используем Telebot-api, MTS Exsolve Api, Mysql (простейшие запросы).
  3. Напишем Dockerfile для упаковки проекта.
  4. Bonus — Используем GitHub Actions для автоматизации сборки

    Код рабочий, в данный момент развернут и не вызывает проблем при работе.

    Структура проекта

    main.py — код основной программы.
    button.py — описаны кнопки, которые будут видны в боте.
    .env — содержит переменные
    Dockerfile — описана сборка контейнера
    /.github/workflows/Prod_build_TG_bot.yml — задача по сборке контейнера в GitHub Actions при внесении изменений в main.
    requests.db — файл базы данных MySQL, используется для хранения обращений пользователей.
    start.sh — позволяет портировать переменные окружения из .env в контейнер и использовать при запуске.

    Описание

    Представленный бот предоставляет следующие функции:

    Тех. поддержка

    • Возможность отправки заявок в IT отдел, ограничение — подача заявки одним сообщением.
    • Отслеживание статуса обращения пользователем.
    • Изменение статуса обращения сотрудниками IT отдела, в т.ч. удаление обращений.
    • Админ панель для IT отдела доступна исключительно из группы IT_ADM

    Взаимодействие с сигнализацией ИПРО-2

    • Возможность производить постановку и снятие с охраны офисов. Доступно только из группы secure.
    • Данные о пользователе взаимодействовавшем с сигнализацией отправляются в группу администраторов офиса office

    Информация

    • Предоставление ссылок на информационные ресурсы компании.

2. Обзор кода main.py

2.1 Импортируем библиотеки и переменные окружения заданные в файле .env

import os import telebot from dotenv import load_dotenv from telebot import * import button import requests import sqlite3  load_dotenv() token = os.getenv('TOKEN') # Токен TG бота, выдает BotFather. bot = telebot.TeleBot(token) secure_chat_id = os.getenv('SECURE') # ID чата группы в телеге, из которого можно взаимодействовать с функционалом сигнализации. office = os.getenv('OFFICE') # ID чата группы в телеге, в который будут сыпаться оповещения о постановке и снятии охраны определенным пользователем. it_adm = os.getenv('IT_ADM') # ID чата группы в телеге, в которую будут падать заявки на проведения работ. api_mts_key = os.getenv('API_MTS_KEY') # Token Api МТС из личного кабинета Exolve. number_mts = os.getenv('NUMBER') # Номер приобретенный в ЛК МТС Exolve. number_1 = os.getenv('NUMBER_1') # Номер тел. Сим установленная на объекте 1. number_2 = os.getenv('NUMBER_2') # Номер тел. Сим установленная на объекте 2.

2.2 Добавляем обработчики:

menu = ['💻 Тех. поддержка',['admin'],'📚 Информация',['start'],'🤖','🆙 Офис 2 👑','🆙 Офис 1 ⚙️','🔻 Офис 2 👑','🔻 Офис 1 ⚙️','⚠️ Проверить статус заявки','❌CLOSE❌','🖥 Оставить заявку','👈 назад','Постановка на охрану 👮‍♀️','Запрос на снятие с охраны ⚡️']  @bot.message_handler(commands=['admin']) # admin панель (будет доступна только из группы администраторов IT_ADM) def admin(message):     if message.chat.title == 'Chat Name IT': # здесь Chat Name IT меняем на имя группы администраторов.         bot.send_message(chat_id=it_adm, text=f'Добрый день!\nВы в меню администратора.\nЧто Вам необходимо?\n', reply_markup=button.markup_admin())     else:         bot.send_message(message.chat.id,'Вы не являетесь администратором', reply_markup=button.markup_start())  @bot.message_handler(commands=['start']) # кнопка Start def start(message):     bot.send_message(message.chat.id, f"👋 Привет,{message.from_user.first_name}!", reply_markup=button.markup_start())     bot.send_message(message.chat.id, '❓ Выберете интересующий Вас раздел', reply_markup=button.markup_main())  @bot.message_handler(commands=['close']) # кнопка close def close(message):     bot.send_message(message.chat.id, 'Goodbye', reply_markup=None)

Функция def admin(message) выполняет проверку, если админ панель пытаются открыть не из группы администраторов, будет отправлено сообщение пользователю ‘Вы не являетесь администратором’.
Добавлены основные обработчики команд, ниже представлены команды которые можно использовать для взаимодействия с ботом:
/start — запускает бота;
/admin — переход в админ панель;
/close — выход из меню бота.

2.3 Основные меню и действия:

@bot.message_handler(func=lambda message: True, content_types=['audio', 'photo', 'voice', 'video', 'document', 'text', 'location', 'contact', 'sticker']) def get_text_messages(message):       ##########################     START      ##############################      if message.text == '👋':         bot.send_message(message.chat.id, ' Вы вернулись в главное меню.\n❓ Выберете интересующий Вас раздел', parse_mode='HTML', reply_markup=button.markup_main())      elif message.text == '👈 назад':             bot.send_message(message.chat.id, '👋', reply_markup=button.markup_main())         ##########################      MAIN      ##############################      elif message.text == '🤖':         if message.chat.title == 'Chat Name Secure': # здесь Chat Name Secure меняем на имя группы администраторов.             bot.send_message(chat_id=secure_chat_id, text=f'Добрый день!\nВы в меню охраны.\nЧто необходимо сделать?\n', reply_markup=button.markup_secure())         else:             bot.send_message(message.chat.id,'Вы не можете использовать данную фунуцию...\nОбратитесь к администратору.', reply_markup=button.markup_start())      elif message.text == '❌CLOSE❌':         bot.send_message(message.chat.id, 'Goodbye', reply_markup=button.markup_start())      elif message.text == '💻 Тех. поддержка':         bot.send_message(message.chat.id, 'Выберите подменю', reply_markup=button.markup_it())      elif message.text == '📚 Информация':         bot.send_message(message.chat.id, 'Подробно про компанию по ' + '[ссылке](https://digniori.ru/)', parse_mode='Markdown')         bot.send_message(message.chat.id, 'Облачное хранилище по ' + '[ссылке](https://cloud.digniori-arts.ru/)', parse_mode='Markdown')       ##########################      SECURE     ##############################     elif message.text == 'Постановка на охрану 👮‍♀️':         bot.send_message(chat_id=secure_chat_id, text=f'Какой объект, необходимо поставить на охрану???', reply_markup=button.markup_up())      elif message.text == 'Запрос на снятие с охраны ⚡️':         bot.send_message(chat_id=secure_chat_id, text=f'Какой объект, необходимо снять с охраны???', reply_markup=button.markup_down())

2.4 Действия при выборе постановки или снятия с охраны:

В данной части будем использовать api MTS Exolve c целью автоматизации постановки и снятия с охраны объектов в которых установлена охранная сигнализация ИПРО-2.
Данная задача появилась в связи с тем, что количество поддерживаемых пользователей приложением, для дистанционной постановки и снятия с охраны, ограниченно 5 номерами. В данном случае возможно добавить необходимых пользователей в группу Secure.

    elif message.text == '🆙 Офис 1 ⚙️':         try:             headers = {                 'Authorization': f'{api_mts_key}',                 'Content-Type': 'application/json',             }             data = {"number": f"{number_mts}", "destination": f"{number_ms}", "text" : "O1"}             requests.post('https://api.exolve.ru/messaging/v1/SendSMS', headers=headers, json=data)              bot.send_message(chat_id=secure_chat_id, text=f'Спасибо за информацию, охранная система включена.\nУ Вас есть 15 сек покинуть помещение.', reply_markup=button.markup_start())             bot.send_message(chat_id=secure_chat_id, text=f'👮‍♀️', reply_markup=button.markup_main())             bot.send_message(chat_id=office, text=f'Cотрудник {message.from_user.first_name}, ушел. \nВключена сигнализация на Малой Семеновской ⚙️.\nNikname:@{message.from_user.username}', parse_mode='Markdown')          except Exception as e:               bot.send_message(message.chat.id, 'Произошла ошибка... Попробуйте снова.', reply_markup=button.markup_start())      elif message.text == '🔻 Офис 1 ⚙️':         #try:         headers = {             'Authorization': f'{api_mts_key}',             'Content-Type': 'application/json',         }          data = {"number": f"{number_mts}", "destination": f"{number_ms}", "text" : "O0"}         requests.post('https://api.exolve.ru/messaging/v1/SendSMS', headers=headers, json=data)          bot.send_message(chat_id=secure_chat_id, text=f'Спасибо за информацию, охранная система будет выключена.\nВремя ожидания 20 сек.', reply_markup=button.markup_main())         bot.send_message(chat_id=secure_chat_id, text=f'👌 Сигнализация отключена', reply_markup=button.markup_main())         bot.send_message(chat_id=office, text=f'Cотрудник {message.from_user.first_name}, отключил сигнализацию на Малой Семеновской ⚙️.\nNikname:@{message.from_user.username}', parse_mode='Markdown')      elif message.text == '🆙 Офис 2 👑':         try:             headers = {                 'Authorization': f'{api_mts_key}',                 'Content-Type': 'application/json',             }             data = {"number": f"{number_mts}", "destination": f"{number_nvk}", "text" : "O1"}             requests.post('https://api.exolve.ru/messaging/v1/SendSMS', headers=headers, json=data)                        bot.send_message(chat_id=office, text=f'''Cотрудник {message.from_user.first_name}, ушел. \nНаправлен запрос на включение сигнализации на Новокузнецкой 👑.\nNikname:@{message.from_user.username}''', parse_mode='Markdown')             bot.send_message(chat_id=secure_chat_id, text=f'Спасибо за информацию, охранная система включена.\nУ Вас есть 15 сек покинуть помещение.')             bot.send_message(chat_id=secure_chat_id, text=f'👮‍♀️', reply_markup=button.markup_main())          except Exception as e:               bot.send_message(message.chat.id, 'Произошла ошибка... Попробуйте снова.', reply_markup=button.markup_start())      elif message.text == '🔻 Офис 2 👑':         try:                 headers = {                     'Authorization': f'{api_mts_key}',                     'Content-Type': 'application/json',                 }             data = {"number": f"{number_mts}", "destination": f"{number_nvk}", "text" : "O0"}             requests.post('https://api.exolve.ru/messaging/v1/SendSMS', headers=headers, json=data)              bot.send_message(chat_id=secure_chat_id, text=f'Спасибо за информацию, охранная система будет выключена.\nВремя ожидания 20 сек.')             bot.send_message(chat_id=secure_chat_id, text=f'👌 Сигнализация отключена', reply_markup=button.markup_main())             bot.send_message(chat_id=office, text=f'Cотрудник {message.from_user.first_name}, отключил сигнализацию на Новокузнецкой 👑.\nNikname:@{message.from_user.username}', parse_mode='Markdown')          except Exception as e:               bot.send_message(message.chat.id, 'Произошла ошибка... Попробуйте снова.', reply_markup=button.markup_start())

2.5 Обращения пользователей и админ меню:

Административное меню открывается только из группы IT_ADM по команде /admin.

    ##########################    USER_TASKS    ##############################      elif message.text == '⚠️ Проверить статус заявки':         bot.send_message(message.chat.id, 'Введите номер Вашего обращения:', reply_markup=button.markup_it())         bot.register_next_step_handler(message, stat_user_tasks)      elif message.text == '🖥 Оставить заявку':             bot.send_message(message.chat.id, f'{message.from_user.first_name}, Введите название объекта и опишите проблему:')         bot.register_next_step_handler(message, add_task)       ##########################    ADMIN_!!!    ##############################      elif message.text == 'Списки задач📄':         bot.send_message(chat_id=it_adm, text=f'Какой список Вас интересует?', reply_markup=button.markup_task_l())      elif message.text == '🖊 Изменить статус':         bot.send_message(chat_id=it_adm, text=f'Введите номер задачи для изменения статуса', reply_markup=button.markup_admin())         bot.register_next_step_handler(message, stat_task)      elif message.text == '⛔️Удалить задачу⛔️':         bot.send_message(chat_id=it_adm, text=f'ВНИМАНИЕ\nУдаленные из базы задачи не восстановить!\nЕсли вы уверены, введите номер задачи.', reply_markup=button.markup_admin())         bot.register_next_step_handler(message, del_task)       ##########################    ADMIN_TASKS    ##############################             elif message.text == 'Все задачи 📄':  # Просмотр всех задач статуса задачи          try:             connection = sqlite3.connect('./requests.db')             cursor = connection.cursor()             cursor.execute('SELECT * FROM request')             tasks = cursor.fetchall()             su = str('')             for task in tasks:                 s1 = f' \t\t Обращение № {task[0]} \nОт: @{task[1]}. \nпроблема:\n{task[2]}\nСтатус: {task[3]}\n\n'                 su = su + s1                 s1 = ''             connection.close()             bot.send_message(chat_id=it_adm, text=f'{su}', parse_mode='HTML')             bot.send_message(chat_id=it_adm, text=f'Готово!', reply_markup=button.markup_task_l())          except Exception as e:             bot.send_message(chat_id=it_adm, text=f'Список пуст.', reply_markup=button.markup_task_l())      elif message.text == '❗️ Активные':  # Просмотр активных задач статуса задачи          try:             connection = sqlite3.connect('./requests.db')             cursor = connection.cursor()             cursor.execute('SELECT * FROM request')             tasks = cursor.fetchall()             ac = str('')             for task in tasks:                 if task[3] == 'В работе':                     a1 = f' \t\t Обращение № {task[0]} \nОт: @{task[1]}. \nпроблема:\n{task[2]}\nСтатус: {task[3]}\n\n'                     ac = ac + a1                     a1 = ''                                 else:                     continue              connection.close()             bot.send_message(chat_id=it_adm, text=f'{ac}', parse_mode='HTML')             bot.send_message(chat_id=it_adm, text=f'Готово!', reply_markup=button.markup_task_l())         except Exception as e:               bot.send_message(chat_id=it_adm, text=f'Список пуст. Вы отлично поработали!', reply_markup=button.markup_task_l())      elif message.text == '✅ Выполненные':         connection = sqlite3.connect('./requests.db')         cursor = connection.cursor()         try:             connection = sqlite3.connect('./requests.db')             cursor = connection.cursor()             cursor.execute('SELECT * FROM request')             tasks = cursor.fetchall()             da = str('')             for task in tasks:                 if task[3] != 'В работе':                     d1 = f' \t\t Обращение № {task[0]} \nОт: @{task[1]}. \nпроблема:\n{task[2]}\nСтатус: {task[3]}\n\n'                     da = da + d1                     d1 = ''                          else:                     continue             connection.close()             bot.send_message(chat_id=it_adm, text=f'{da}', parse_mode='HTML')             bot.send_message(chat_id=it_adm, text=f'Готово!', reply_markup=button.markup_task_l())          except Exception as e:               bot.send_message(chat_id=it_adm, text=f'Список пуст.', reply_markup=button.markup_task_l())      elif message.text == '👈 admin menu':         bot.send_message(chat_id=it_adm, text='Вы вернулись в меню Администратора...', reply_markup=button.markup_admin())  # Функция удаления задачи  def del_task(message):      try:         if message.text in menu:              get_text_messages(message)               else:             connection = sqlite3.connect('./requests.db')             cursor = connection.cursor()              try:                 del_id = int(message.text)                 cursor.execute('SELECT * FROM request')                 tasks = cursor.fetchall()                 try:                     for task in tasks:                          if task[0] == del_id:                             cursor.execute('DELETE FROM request WHERE id = ?', (del_id,))                             connection.commit()                             bot.send_message(chat_id=it_adm, text=f' \t Обращение № {task[0]} \nОт: {task[1]}. \nпроблема:\n{task[2]}\n Статус: {task[3]}\nУДАЛЕНО!✔️\n', reply_markup=button.markup_admin())                             raise StopIteration                          else:                             continue                      else:                         bot.send_message(chat_id=it_adm, text='!Не правильно введен номер!\nНажмите "⛔️Удалить задачу⛔️" и введите номер.' , reply_markup=button.markup_admin())                    except StopIteration:                     pass                  except (SyntaxError, ValueError):                 bot.send_message(chat_id=it_adm, text='Вы не ввели номер...\nНажмите "⛔️Удалить задачу⛔️" и введите номер.', reply_markup=button.markup_admin())                connection.close()      except Exception as e:           bot.send_message(chat_id=it_adm, text='Произошла ошибка... Попробуйте снова.\nНажмите "⛔️Удалить задачу⛔️" и введите номер.', reply_markup=button.markup_admin())  # Функция изменения статуса задачи  def stat_task(message):     try:             if message.text in menu:                  get_text_messages(message)                   else:                 connection = sqlite3.connect('./requests.db')                 cursor = connection.cursor()                  try:                     stat_task_adm = int(message.text)                     cursor.execute('SELECT * FROM request')                     tasks = cursor.fetchall()                     try:                         for task in tasks:                              if task[0] == stat_task_adm:                                 cursor.execute('UPDATE request SET status = ? WHERE id = ?', ('Выполнено!', stat_task_adm))                                 connection.commit()                                 bot.send_message(chat_id=it_adm, text=f' \t Обращение № {task[0]} \nОт: {task[1]}. \nпроблема:\n{task[2]}\n Статус: {task[3]}\n\n 🔝Статус ОБНОВЛЕН!🔝', reply_markup=button.markup_admin())                                 raise StopIteration                              else:                                 continue                          else:                             bot.send_message(chat_id=it_adm, text=f'!Не правильно введен номер!\nНажмите "🖊 Изменить статус" и введите номер.' , reply_markup=button.markup_admin())                        except StopIteration:                         pass                      except (SyntaxError, ValueError):                     bot.send_message(chat_id=it_adm, text=f'Вы не ввели номер...\nНажмите "🖊 Изменить статус" и введите номер.', reply_markup=button.markup_admin())                    connection.close()      except Exception as e:           bot.send_message(chat_id=it_adm, text=f'Произошла ошибка... Попробуйте снова.\nНажмите "🖊 Изменить статус" и введите номер.', reply_markup=button.markup_admin())  # Функция вывода статуса задачи пользователю  def stat_user_tasks(message):      try:         if message.text in menu:              get_text_messages(message)               else:             connection = sqlite3.connect('./requests.db')             cursor = connection.cursor()              try:                 nomber = int(message.text)                 cursor.execute('SELECT * FROM request')                 tasks = cursor.fetchall()                 try:                     for task in tasks:                          if task[0] == nomber:                             bot.send_message(message.chat.id, f' \t Обращение № {task[0]} \nОт: {task[1]}. \nпроблема:\n{task[2]}\n Статус: {task[3]}\n\n', reply_markup=button.markup_it())                             raise StopIteration                          else:                             continue                      else:                         bot.send_message(message.chat.id,'!Не правильно введен номер!\nНажмите "⚠️ Проверить статус заявки" и введите номер.' , reply_markup=button.markup_it())                    except StopIteration:                     pass                  except (SyntaxError, ValueError):                 bot.send_message(message.chat.id,'Вы не ввели номер...\nНажмите "⚠️ Проверить статус заявки" и введите номер.', reply_markup=button.markup_it())                connection.close()      except Exception as e:           bot.send_message(message.chat.id, 'Произошла ошибка... Попробуйте снова.\nНажмите "⚠️ Проверить статус заявки" и введите номер.', reply_markup=button.markup_it())  #Функция внесения задачи пользователем  def add_task(message):       try:         if message.text in menu:              get_text_messages(message)               else:              connection = sqlite3.connect('./requests.db')             cursor = connection.cursor()                  # Создаем таблицу request             cursor.execute('''             CREATE TABLE IF NOT EXISTS request (             id INTEGER PRIMARY KEY,             username TEXT,             example TEXT,             status TEXT DEFAULT 'В работе'             )             ''')              cursor.execute('INSERT INTO request (username, example, status) VALUES (?, ?, ?)', (message.from_user.username, message.text, 'В работе'))             connection.commit()              last_id = cursor.lastrowid             connection.close()              bot.send_message(message.chat.id, f'Номер Вашего обращения: {last_id} \nСпасибо!\n\nПо номеру обращения Вы можете проверить статус.', reply_markup=button.markup_it())             bot.send_message(chat_id=it_adm, text=f'{message.from_user.first_name} оставил заявку: \n{message.text}\nNikname: @{message.from_user.username}', reply_markup=None)      except Exception as e:           bot.send_message(message.chat.id, 'Произошла ошибка... Попробуйте снова.\n\nНажмите кнопку "🖥 Оставить заявку" и введите сообщение.', reply_markup=button.markup_it())

2.6 Запуск программы:

if __name__ == "__main__":     # bot.polling(none_stop=True, interval=0) # ранее использовалось.     while True:         try:             bot.polling(none_stop=True)          except Exception as e:             logger.error(e)               time.sleep(15)

3. Обзор кода button.py

В целом здесь пояснять особо нечего… Задаем функции, внутри которых описываем клавиатуру и задаем положение кнопок при выводе.

 from telebot import types   def markup_start():      markup_start = types.ReplyKeyboardMarkup(resize_keyboard=True)      btn0 = types.KeyboardButton('👋')     markup_start.row(btn0)      return markup_start   def markup_main():      markup_main = types.ReplyKeyboardMarkup(resize_keyboard=True)      btn1 = types.KeyboardButton('🤖')     btn2 = types.KeyboardButton('💻 Тех. поддержка')     btn3 = types.KeyboardButton('📚 Информация')     btn4 = types.KeyboardButton('❌CLOSE❌')     markup_main.row(btn1, btn2)     markup_main.row(btn3, btn4)      return markup_main   def markup_it():      markup_it = types.ReplyKeyboardMarkup(resize_keyboard=True)      btn1_it = types.KeyboardButton('⚠️ Проверить статус заявки')     btn2_it = types.KeyboardButton('🖥 Оставить заявку')     btn3_it = types.KeyboardButton('👈 назад')     markup_it.row(btn1_it, btn2_it)     markup_it.row(btn3_it)      return markup_it   def markup_admin():      markup_adm = types.ReplyKeyboardMarkup(resize_keyboard=True)      btn1_adm= types.KeyboardButton('Списки задач📄')     btn2_adm= types.KeyboardButton('🖊 Изменить статус')     btn3_adm = types.KeyboardButton('⛔️Удалить задачу⛔️')     btn4_adm = types.KeyboardButton('👈 назад')     markup_adm.row(btn1_adm, btn2_adm)     markup_adm.row(btn3_adm, btn4_adm)      return markup_adm   def markup_task_l():      markup_adm = types.ReplyKeyboardMarkup(resize_keyboard=True)      btn1_adm= types.KeyboardButton('Все задачи 📄')     btn2_adm= types.KeyboardButton('❗️ Активные')     btn3_adm = types.KeyboardButton('✅ Выполненные')     btn4_adm = types.KeyboardButton('👈 admin menu')     markup_adm.row(btn1_adm, btn2_adm)     markup_adm.row(btn3_adm, btn4_adm)      return markup_adm   def markup_secure():      markup_sec = types.ReplyKeyboardMarkup(resize_keyboard=True)      btn1_sec= types.KeyboardButton('Постановка на охрану 👮‍♀️')     btn2_sec= types.KeyboardButton('Запрос на снятие с охраны ⚡️')     btn3_sec = types.KeyboardButton('👈 назад')     btn4_sec  = types.KeyboardButton('❌CLOSE❌')     markup_sec.row(btn1_sec, btn2_sec)     markup_sec.row(btn3_sec, btn4_sec)      return markup_sec   def markup_up():      markup_up = types.ReplyKeyboardMarkup(resize_keyboard=True)      btn1_up= types.KeyboardButton('🆙 Офис 1 ⚙️')     btn2_up= types.KeyboardButton('🆙 Офис 2 👑')     btn3_up = types.KeyboardButton('👈 назад')     btn4_up  = types.KeyboardButton('❌CLOSE❌')     markup_up.row(btn1_up, btn2_up)     markup_up.row(btn3_up, btn4_up)      return markup_up   def markup_down():      markup_down = types.ReplyKeyboardMarkup(resize_keyboard=True)      btn1_down= types.KeyboardButton('🔻 Офис 1 ⚙️')     btn2_down= types.KeyboardButton('🔻 Офис 2 👑')     btn3_down = types.KeyboardButton('👈 назад')     btn4_down  = types.KeyboardButton('❌CLOSE❌')     markup_down.row(btn1_down, btn2_down)     markup_down.row(btn3_down, btn4_down)      return markup_down

4. Содержание .env

TOKEN=’423….1:B……sd’ # Токен бота в Telegram
OFFICE=’-1000000000000′ # id группы администрации офиса
IT_ADM=’-1000000000001′ # id группы IT отдела
SECURE=’-1000000000002′ # id группы безопасности
API_MTS_KEY=’Bearer s…p’ # api токен для доступа к МТС Exolve
NUMBER=’79111111110′ # номер телефона добавленного в личный кабинет МТС Exolve
NUMBER_1=’79111111111′ # номер телефона сим в блоке управления сигнализацией офис 1
NUMBER_2=’79111111112′ # номер телефона сим в блоке управления сигнализацией офис 2

Из данного файла переменные окружения будут вытягиваться в тело основной программы.

5. Описание start.sh

Для удобства было принято решение создать скрипт для запуска контейнера.
Данный скрипт имеет следующее содержание:

 #!/usr/bin/bash     # или /usr/bin/env bash  source /usr/src/app/.env  # позволяет портировать переменные в текущую оболочку.  python /usr/src/app/main.py # запуск программы.

Стоит отметить, что не обладаю огромным опытом в использовании Docker, и этот способ который был придуман для переноса переменных окружения в контейнер. Есть иные описанные варианты в основном при использовании Docker Compose или явное определение переменных в Dockerfile.

6. Описание Dockerfile

Создадим файл Dockerfile. Со следующим содержанием:

 FROM python:3.11   # Базовый образ на основе которого будет создан контейнер. Бот был написан на Python 3.11 по этому и используется данный образ.  LABEL developed_by='@LeoAlecksey' # метаданные полезной нагрузки не несут.  WORKDIR /usr/src/app   # Выбор рабочей директории.  COPY ./requirements.txt .  # Копируем файл с перечнем использованных в проекте пакетов.  RUN python -m pip install --upgrade pip    # Обновляем пакетный менеджер pip.  RUN python -m pip install -r requirements.txt  # Устанавливаем пакеты для проекта в соответствии с перечнем.  COPY ./main.py .   # копируем файлы в проекта в контейнер.  COPY ./button.py .  COPY ./LICENCE.md .  COPY ./requests.db ./requests.db  COPY ./.env .  COPY ./.gitignore .  COPY ./start.sh .  CMD ["/bin/bash", "/usr/src/app/start.sh"]     # команда которая будет выполнена при запуске контейнера. 

7. Bonus

В момент добавления функционала по работе с сигнализацией, и тестирования контейнера приходилось пересобирать его не один раз. Решил попробовать автоматическую сборку, так как проект хранился в GitHub, один из вариантов GitHub Actions.

7.1 Создаем workflows

Создаем директорию .github/workflows и файл Prod_build_TG_bot.yml
Prod_build_TG_bot.yml — описывает рабочий процесс в GitHub Actions.

 name: build_prod_Tg_bot    # имя   on:    # настройки триггера, в данном случае при пуше в ветку main будет запускаться процесс.   push:     branches:      - main   jobs:   ## Сборка Docker-image с tg_bot Push on DockerHub ##  ####################################################    docker_image_build_and_push:     name: Publish TG_bot to Docker Hub     runs-on: ubuntu-latest      # базовый образ операционной системы в котором будут производиться действия по сборке.     steps:       - uses: actions/checkout@v4   # клонирует наш репозиторий в операционную систему.         with:           fetch-depth: 0        - name: Output Run Attempt    # берем номер запуска сборки для версионирования.         run: echo ${{ github.run_number }}        - name: Login to Docker Hub # Логинимся на hub.docker.com         run: docker login -u ${{ secrets.DOCKER_NAME }} -p ${{ secrets.DOCKER_PASS }}        - name: Build Container image # создаем контейнер с тегом TG-Bot-v run_attempt - количество попыток запуска run_number - номер запуска сборки.         run: docker build -t ${{ secrets.DOCKER_REPO }}:TG-Bot-v${{ github.run_attempt }}.${{ github.run_number }} .       - name: Publish Docker image # пушим контейнер в docker hub         run: docker push ${{ secrets.DOCKER_REPO }}:TG-Bot-v${{ github.run_attempt }}.${{ github.run_number }}


GitHub, номер запуска сборки.


HubDocker, версионирование по номеру запуска сборки.

7.2 Добавляем переменные

Переменные:
DOCKER_NAME — логин для Docker hub.
DOCKER_PASS — пароль для Docker hub.
DOCKER_REPO — репозиторий Docker hub в который будет добавлен новый контейнер. Прим: leoalecksey/scripts

Переменные определяем в репозитории проекта на GitHub Settings — Secrets and varriables — Actions, далее Secrets — New Repository secret.

Главное меню

Меню обращений

Admin группа и меню

Статус обращения

Попытка перехода в меню сигнализации от пользователя

Постановка на охрану через группу Secure

Оповещение в группу администраторов офиса

Заключение

Мой путь в изучение Docker начался именно с простейшей упаковки данной программы, так как запуск контейнера Hello world! не дает даже малейшего понимания о работе технологии. Данная статья поможет в понимании и начале использования такого средства автоматизации как GitHub Actions.
Хотелось бы отметить, что реализация довольно простая, постепенно добавлялся новый функционал, но для понимания хватит базовых знаний Python.

Отдельная благодарность ребятам из MTS Exolve, за быстрое взаимодействие и действительно удобный сервис с отличной документацией!

P.S. не являюсь разработчиком, заранее прошу не кидать тапками, а поделиться опытом если нашли места где можно оптимизировать логику или код.

Буду рад обратной связи!


ссылка на оригинал статьи https://habr.com/ru/articles/868678/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *