Telegram-bot: моя історія. Частина друга


Доброго часу доби, Хабрахабр! Даний матеріал є продовженням першої частини, в якій висвітлено інструменти і можливості розробки продукту на хмарній платформі. Прикладом є актуальне мобільний розширення доступу до розкладу пар в університеті — Telegram-bot.

Приділивши час першої публікації, ви можете ознайомитися не тільки з початковим розгортанням і необхідними ресурсами, але і з оглядом додатки від автора. Інформація нижче сконцентрована більшою мірою на описі технічної сторони і меншою на висновках за підсумками на поточний момент.

Технічний огляд проекту
Паралельно з поясненнями ви можете розраховувати на скріншоти з місць подій» для більш глибокого розуміння процесу написання, також рекомендується звертати увагу на повний вихідний кодgithub.com) і при достатньому інтересі — вникати в надані посилання по причині відсутності опису тих чи інших, на розсуд автора, пояснень використовуються і наданих компонентів. Якщо частина опису не зрозуміло, питання можна задати автору в коментарях або діалогах, або приділити дві хвилини на взаємодію з предметною областю напряму, що дозволить краще орієнтуватися в статті і наведених прикладах.

Розклад пар університету
На момент проектування архітектури не було інших варіантів отримання розкладу лекцій, крім прямого збору інформації з офіційного сайту (парсинг). API для платформи, на якій розташовується розклад, в наявності немає, а можливість отримати інформацію безпосередньо з бази даних відсутня.

Необхідна технічна операція (github.com) тісно пов'язана з корисними запланованими роботами на сервері, відомості про яких розташовані в кінці розділу.

Використовуються модулями є:
  • Beautiful Soup — робота зі структурою даних розмітки HTML;
  • Requests — відправка HTTP-запитів.
Файл dataLib.py містить масив параметрів-значень для здійснення запиту на кожну академічну групу і функцію для внесення тимчасового діапазону dateToDateForm(), щоб визначати рамки дат, для яких необхідно розклад.

groupExample = {'TimeTableForm[date1]': firstDay, 'TimeTableForm[date2]': lastDay,
'TimeTableForm[faculty]':5, 'TimeTableForm[course]':3, 'TimeTableForm[group]':613}

Далі слід відправка post-запиту для отримання об'єкта сторінки і подальша передача в об'єкт soup, має ряд методів для зручної роботи.

for formData in dataLib.dateToDateForm(firstDay, lastDay):
r = requests.post(config.url, data = formData)
soup = BeautifulSoup(r.text, 'html.parser')
dataProcessing.dataProcessing(soup)

Модуль dataProcessing має однойменну функцію, в якій обробляються необхідні дані. Наприклад, змінна mainHtmlTable — це базова таблиця з розкладом, має свій унікальний ідентифікатор.

mainHtmlTable = soup.find("table", {"id": "timeTableGroup"})


(частину таблиці для наочного прикладу, повну версію можна «помацати»за адресою, попередньо вибравши факультет, курс та групу)

Ще один приклад — дати, заснований на отриманні всіх елементів блочного типу, що мають усередині себе текст довжиною рівною десяти («12.12.2016» — десять символів).

divDate = [i.text[:-5] for i in mainHtmlTable.findAll('div') if len(i.get_text()) == 10][:-2]

Слідом йде виклик ще ряду взаємопов'язаних методів і функцій, що надають підсумкові значення, для подальшої зручної роботи.
Я користуюся трьома масивами з назвою факультетів, курсів і груп, безпосередньо самим розкладом і датами, які дозволяють зручно обробляти дані і робити подальші операції.

Загальна концепція взаємодії з користувачем
Одними з найважливіших якостей розробки є бачення кінцевого результату і грамотне побудова архітектури. На будь-якому етапі професійного розвитку необхідно прикладати великі зусилля для модульності і компактності написаного коду, взаємозв'язку компонентів системи і простоти відбувається.

Виконана робота не стала винятком, незважаючи на протилежне у вихідній точці продукту, і за підсумками сформувався певний концептуальний шаблон проектування для більшості операцій, що включає в себе обробник запитів від користувача (Handler), виконання ряду наступних операцій (Query) і генерацію вихідних даних (Reply) у відповідь.


В даному шаблоні немає нічого незвичайного, причому, якщо мати можливість визначити таку модель поведінки додатка заздалегідь, можна запобігти витрату великої кількості часу і зусиль даремно.

Приклад обробки запитів
В подальшому ділянці основного файлу-обробника (github.com) всіх запитів можна спостерігати post-запит від користувача, який здійснив вибір одного з пунктів меню з назвою «Оновлення» (дане текстове значення доступно як атрибут об'єкта повідомлень «message» внаслідок дублювання вибору меню в чат), внесення даних про користувача (ім'я, прізвище, ідентифікатор чату, текстове значення «news» як вибір пункту меню і нікнейм) з допомогою методу otherFeature() класу middlewareUserData (github.com). Ця функція записує дії користувачів для статистики використання функцій програми. Після попередніх обробок надсилається відповідь за прив'язаного ідентифікатором чату текстове повідомлення з інформацією про місце розташування новин проекту.

@bot.message_handler(func=lambda mess: "Оновлення" == mess.text, content_types=['text'])
def handle_text(message):

middlewareUserData.otherFeature(message.chat.first_name, message.chat.last_name, message.chat.id 'news', message.chat.username)
bot.send_message(message.chat.id 'Оновлення та додаткову інформацію можна подивитися тут https://telegram.me/dutbotupdates')

Ілюстрація процесу наведена нижче. Картинки, пов'язані з таблицями баз даних, клікабельні (можна розглянути краще), тому що представлені в поганій якості, але саме так, щоб не втрачати цілісність картини розробки.



Концепція отримання розкладу
Розклад на сьогоднішній або завтрашній день доступно двома способами. Розглянемо перший варіант — через поступове введення даних: необхідно вибрати факультет, курс та групу. Нагадаю, що атрибут «text» об'єкта «message» — це текстове значення вибору пункту меню.

1. Кнопка «Отримати розклад» — користувач надсилає базовий набір даних:

middlewareUserData.getUser(message.chat.first_name, message.chat.last_name, message.chat.id message.chat.username)

Отже, дія відображається в таблиці бази даних у такому форматі (наступні приклади роботи бази даних в кінці розділу):


2. Вибір факультету:

middlewareUserData.updateFaculty(message.text, str(message.chat.id))

3. Вибір курсу і групи, скрипт якого дещо відрізняється від попередніх, тому що відображення списку група на екрані відбувається після запиту до таблиці з повним розкладом, де вилучається весь список груп на заданий користувачем факультет і курс:

middlewareUserData.updateCourse(message.text[:1], str(message.chat.id))

fucAndCourse = middlewareUserData.getFacultyAndGroup(str(message.chat.id))

groupList = selectData.selectGroup(fucAndCourse[0], fucAndCourse[1])

basicMarkupRows.markRowGroupList(groupList, message)

4. Кнопка «На сьогодні» або «завтра», код якої містить актуальний набір дат (сьогодні, завтра, індекс сьогоднішнього дня), перевірку введення від користувача (база даних одна для всіх, необхідно взяти результат певного користувача) і саме відображення розкладу (github.com):

day = selectData.selectDates()

lastGroup = middlewareUserData.summaryVerification(str(message.chat.id))

bot.send_message(message.chat.id "Розклад на сьогодні ({0}):".format(day[1]))
message.text = printController.show(lastGroup, day[1])
bot.send_message(message.chat.id message.text)

Ілюстрація почергового заповнення таблиці до дій користувача:



Повернутися назад в попереднє меню
Якщо ви уважно читали попередній розділ, то неодмінно звернули б увагу на найважливіший елемент таблиці — «empty». Функція повернення назад відштовхується безпосередньо від кількості таких записів:

«Отримати розклад» — формується першочерговий об'єкт разом з чотирма порожніми значеннями (текстове значення «empty»), тому що не обраний факультет, курс, група та пункт фінального меню (наприклад, розклад на визначений день або підписка на групу);
Вибір факультету — вже на один «empty» менше, вибір курсу — вже на два.

Скрипт (github.com) визначає кількість таких полів у рядку таблиці для користувача, знаючи заздалегідь, до якого кількістю який метод з необхідних викликати. Наприклад, ми зупинилися на виборі курсу, значить, ми зробили вибір факультету і у нас залишилося три «порожніх» значення:

if emptyCount == 3:
backButton.cancelOperation(str(message.chat.id), 3)
basicMarkupRows.markRowGetFacultyList(message)

Метод cancelOperation (github.com) визначає функцію, яка займається очищенням поля, яке ми хочемо скасувати:
if self.emptyCount == 3:
self.cancelFaculty(self.chatid)

Якщо ви помилилися з факультетом, його потрібно змінити. Під капотом ситуація наступна — ідентифікатора користувача міняємо значення факультету (наприклад, «Інформаційні технології») на порожнє значення («порожній») через простий SQL-запит «Update»:

def cancelFaculty(self, chatid):
self.chatid = chatid
self.cursor.execute("UPDATE statistic SET faculty = (%s) WHERE id IN (SELECT max(id) FROM statistic WHERE chatid = (%s))", ('empty', self.chatid))
self.connection.commit()

Досить змінити дані в таблиці статистики, взяти полі «факультет», де ідентифікатор поля таблиці є максимальним для певного ідентифікатора користувача (тобто вибір самого останнього рядка таблиці для потрібного користувача — це поточний стан меню і даних).

Підписка на розклад групи

Функція оформлення доступу до розкладу групи на два дні з першого меню заснована на одному внесення своєї групи в таблицю, тобто необхідно вибрати бажану групу і підтвердити підписку, яка доступна в самому останньому меню (github.com).

if message.text == "Підписатися на цю групу":
middlewareUserData.subscribe(message.chat.first_name, message.chat.last_name, message.chat.id lastGroup, message.chat.username)

message.text = 'Ви підписались на групу {0}.'.format(lastGroup)
bot.send_message(message.chat.id message.text)

Функція subscribe() (github.com) вставляє в окрему, не пов'язану з іншими таблицю зберігання підписок знайомі нам дані, але найголовнішими є ідентифікатор і сама група.

def subscribe(self, firstname, lastname, chatid, group, username):
* селф-змінні*
self.cursor.execute("INSERT INTO subscribers 
(firstname, lastname, chatid, groupa, username) VALUES (%s, %s, %s, %s, %s)", (self.firstname, self.lastname, self.chatid, self.group, self.username))
self.connection.commit()

Заплановані роботи на сервері
Проста у використанні бібліотека APScheduler, прекрасно сумісна з платформою, на якій розташоване додаток, дозволяє планувати різноманітні завдання на виконання у визначений час.

Мій бот потребує щоденного оновлення розкладу пар і сьогоднішніх і завтрашніх дат, які успішно доступні через функцію у коді нижче. Так як сервер розташований в іншому часовому поясі, мені необхідно відняти від потрібного часу дві години (оновлення у мене відбуваються у 00:01). З понеділка по п'ятницю вилучається інформація на поточний тиждень, у неділю (індекс дня дорівнює п'яти) — на наступну.

@sched.scheduled_job('крок', day_of_week='mon-sun', hour=22, minute=1)
@sched.scheduled_job('крок', day_of_week='mon-fri', hour=22, minute=1)
@sched.scheduled_job('крок', day_of_week=5, hour=22, minute=1)

Вихідний кодgithub.com) не є реально діючим в бойових умовах і поки доопрацьовується, але концепція визначена вірно.

Робота з базами даних
Платформа Heroku дозволяє безпосередньо працювати з базами даних з рідної консолі (Linux Ubuntu 16.04) або ж отримувати доступ через SQL Shell (Windows 7), попередньо вводячи дані по типу паролів і логінів.

Серверна частина бота, пов'язана з базами даних, обмежена простими SQL-запитами (наприклад, select, insert, update), а необхідність створення і видалення таблиць відповідними командами create і drop.

Висновки

Набираючи програмний код на коліні, я навіть не припускав, що мої очікування щодо підсумкового ефекту не виправдаються, до щастя — в кращу сторону. Але ніхто з тією чи іншою мірою причетних до додатка не думає, що поточне положення справ не заслужено. Займаючись модернізацією архітектури, ні разу не планувалося використовувати бази даних або вводити додаткові функції. Але в підсумку все це присутнє в плані розробки і вдалося не тільки довести проект до серйозного відрізка, але й отримати необхідний якісний результат як в загальному, так і в особистому плані. На якомусь етапі довелося «відмовитися» від звичної моделі поведінки, так як необхідно було виконувати різні ролі в процесі життєвого циклу розробки програмного забезпечення. У процесі роботи навіть вдалося зайнятися соціальним маркетингом, поширюючи інформацію про розроблений продукт серед студентів, «висмоктуючи», як тоді здавалося, що хоч якийсь онлайн.

Навіть такий незначний результат — більше 100 унікальних користувачів щодня — не тільки позитивно позначається на ставленні до автора ресурсу, кожен день вирішального проблеми оточуючих, але і є безперечним мотиватором продовжувати займатися поліпшенням процесів у всіх сферах, що стосуються предметної області.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.