├── poll-bot.log ├── Procfile ├── env.example ├── res ├── price.jpg └── timetable.jpg ├── screens ├── бот1.PNG ├── бот2.PNG ├── бот3.PNG └── table.PNG ├── requirements.txt ├── utils.py ├── config.py ├── creds.example.json ├── LICENSE ├── README.md ├── google_table.py └── bot.py /poll-bot.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python bot.py -------------------------------------------------------------------------------- /env.example: -------------------------------------------------------------------------------- 1 | TELEGRAM_TOKEN="" 2 | LOG_FILE_PATH="" 3 | DOC_URL="" -------------------------------------------------------------------------------- /res/price.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pylounge/fdcbaza-telegram-bot/HEAD/res/price.jpg -------------------------------------------------------------------------------- /screens/бот1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pylounge/fdcbaza-telegram-bot/HEAD/screens/бот1.PNG -------------------------------------------------------------------------------- /screens/бот2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pylounge/fdcbaza-telegram-bot/HEAD/screens/бот2.PNG -------------------------------------------------------------------------------- /screens/бот3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pylounge/fdcbaza-telegram-bot/HEAD/screens/бот3.PNG -------------------------------------------------------------------------------- /res/timetable.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pylounge/fdcbaza-telegram-bot/HEAD/res/timetable.jpg -------------------------------------------------------------------------------- /screens/table.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pylounge/fdcbaza-telegram-bot/HEAD/screens/table.PNG -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiogram 2 | httplib2 3 | apiclient 4 | oauth2client 5 | pygsheets 6 | loguru 7 | python-dotenv==1.0.0 8 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | def get_command_number(message) -> tuple: 2 | """Очищает и парсит сообщение.""" 3 | text_msg: str = message.strip(" @#") 4 | command, number = text_msg.lower().split(' ') 5 | return (command, number) 6 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | 4 | load_dotenv() 5 | 6 | settings = { 7 | 'TOKEN': os.getenv('TELEGRAM_TOKEN'), 8 | 'LOG_FILE': os.getenv('LOG_FILE_PATH'), 9 | 'DOC_URL': os.getenv('DOC_URL') 10 | } 11 | -------------------------------------------------------------------------------- /creds.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "service_account", 3 | "project_id": "", 4 | "private_key_id": "", 5 | "private_key": "-----BEGIN PRIVATE KEY-----\n=\n-----END PRIVATE KEY-----\n", 6 | "client_email": "", 7 | "client_id": "", 8 | "auth_uri": "https://accounts.", 9 | "token_uri": "https://oauth2.", 10 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2", 11 | "client_x509_cert_url": "https://www.googleapis.com/robot/v1" 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 pylounge 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 | # 💃 Бот для студии танцев [Freaky Dance Crew](https://vk.com/freaky_dance_crew) 2 | 3 | Telegram бота для студии студия уличных танцев на Python в связке с библиотеками Aiogram и Pygsheets. 4 | Бот будет выдаёт справочную информацию и взаимодействует с Google Sheets - программой для работы с электронными таблицами, входящая в состав бесплатного набора веб-редакторов Google Docs Editors от Google. 5 | 6 | ### 👨‍💻 Технологии 7 | :heavy_check_mark: Python3 :heavy_check_mark: Aiogram :heavy_check_mark: Google Sheet API 8 | 9 | ### 👾Запуск: 10 | ``` 11 | python -m venv env 12 | ./env/Scripts/activate 13 | python -m pip install -r requirements.txt 14 | python bot.py 15 | ``` 16 | Для начала работы необходимо отправить команду ```/start```. Список доступных команд - ```!бот```. 17 | 18 | ### 🖌️ Пример работы 19 | ![alt text](https://github.com/pylounge/fdcbaza-telegram-bot/blob/main/screens/бот1.PNG) 20 | 21 | 22 | ![alt text](https://github.com/pylounge/fdcbaza-telegram-bot/blob/main/screens/бот2.PNG) 23 | 24 | 25 | ![alt text](https://github.com/pylounge/fdcbaza-telegram-bot/blob/main/screens/бот3.PNG) 26 | 27 | ### Таблица 28 | ![alt text](https://github.com/pylounge/fdcbaza-telegram-bot/blob/main/screens/table.PNG) 29 | -------------------------------------------------------------------------------- /google_table.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Union 2 | import pygsheets 3 | 4 | class GoogleTable: 5 | """Класс для работы с Google Sheet.""" 6 | def __init__( 7 | self, credence_service_file:str = "", googlesheet_file_url:str = "" 8 | ) -> None: 9 | """Инициализирует класс. 10 | Args: 11 | credence_service_file (str): Путь до сервисного файла credence.json (Google Sheet API). 12 | googlesheet_file_url (str): Ссылка на Google Sheet. 13 | Returns: 14 | """ 15 | self.credence_service_file = credence_service_file 16 | self.googlesheet_file_url = googlesheet_file_url 17 | 18 | def _get_googlesheet_by_url( 19 | self, googlesheet_client: pygsheets.client.Client 20 | ) -> pygsheets.Spreadsheet: 21 | """Получает Google.Docs таблицу по ссылке на документ.""" 22 | sheets: pygsheets.Spreadsheet = googlesheet_client.open_by_url( 23 | self.googlesheet_file_url 24 | ) 25 | return sheets.sheet1 26 | 27 | def _get_googlesheet_client(self): 28 | """Авторизуется с помощью сервисного ключа и 29 | возвращает клиентский объект Google Docs. 30 | """ 31 | return pygsheets.authorize( 32 | service_file=self.credence_service_file 33 | ) 34 | 35 | def search_abonement( 36 | self, 37 | data: List[List[Union[str, bool]]], 38 | search_col: int = 1, 39 | balance_col: int = 4, 40 | end_date_col: int = 5 41 | ) -> Union[List[str],int]: 42 | """Возвращает информацию из определнных столбоцв таблицы (куда записаны абонементы). 43 | Args: 44 | data (List[List[Union[str, bool]]]): Данные для поиска в таблице. 45 | search_col (int): Диапазон поиска по столбцам. 46 | balance_col (int): Нормер столбца таблицы с Балансом 47 | end_date_col (int): Нормер столбца таблицы с Датой окончания действия абонимента 48 | Returns (List[str]|int): Возвращает список с Датой окончания действия абонимента и Балансом 49 | или -1 если абонемент не найден. 50 | """ 51 | googlesheet_client: pygsheets.client.Client = self._get_googlesheet_client() 52 | wks: pygsheets.Spreadsheet = self._get_googlesheet_by_url(googlesheet_client) 53 | try: 54 | find_cell = wks.find(data, matchEntireCell=True, cols=(search_col, search_col))[0] 55 | except: 56 | return -1 57 | find_cell_row = find_cell.row 58 | end_date = wks.get_value((find_cell_row, end_date_col)) 59 | balance = wks.get_value((find_cell_row, balance_col)) 60 | return [end_date, balance] 61 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | from aiogram import Bot, types 2 | from aiogram.dispatcher import Dispatcher, filters 3 | from aiogram.utils import executor 4 | from loguru import logger 5 | import config 6 | from google_table import GoogleTable 7 | from utils import get_command_number 8 | 9 | logger.add( 10 | config.settings["LOG_FILE"], 11 | format="{time} {level} {message}", 12 | level="DEBUG", 13 | rotation="1 week", 14 | compression="zip", 15 | ) 16 | 17 | 18 | class FreakTelegramBot(Bot): 19 | """Класс информационного бота для студии танцев.""" 20 | def __init__( 21 | self, 22 | token:str, 23 | parse_mode:'aiogram.enums.ParseMode', 24 | google_table:GoogleTable=None, 25 | ) -> None: 26 | """Инициализирует класс. 27 | Args: 28 | tokens (str): Токен бота для доступа к Telegram API. 29 | parse_mode (ParseMode): id публичной страницы ВКонтакте от имени которой опрос. 30 | google_table (GoogleTable): Агрегат для работы с Google Sheet. 31 | Returns:- 32 | """ 33 | super().__init__(token, parse_mode=parse_mode) 34 | self._google_table: GoogleTable = google_table 35 | 36 | bot: FreakTelegramBot = FreakTelegramBot( 37 | token=config.settings["TOKEN"], 38 | parse_mode=types.ParseMode.HTML, 39 | google_table=GoogleTable("creds.json", config.settings['DOC_URL']) 40 | ) 41 | dp = Dispatcher(bot) 42 | 43 | 44 | @dp.message_handler(filters.Regexp(regexp=r"(((А|а)бонемент)(\s)(\d+))")) 45 | async def abonement_handler(message_from: types.Message) -> None: 46 | """Обработчик команды Абонемент.""" 47 | user_id: str = str(message_from.from_id) 48 | command, number = get_command_number(message_from.md_text) 49 | 50 | values = bot._google_table.search_abonement(number) 51 | if values == -1: 52 | message = 'Такого абонемента не существует, либо его срок действия закончился 😰' 53 | else: 54 | end_date_value = values[0] 55 | balance_value = int(values[1]) 56 | last_digit = balance_value % 10 57 | 58 | if last_digit == 1 and balance_value != 11: 59 | balance_value = f'{balance_value} занятие' 60 | elif last_digit in (2, 3, 4) and balance_value not in (12, 13, 14): 61 | balance_value = f'{balance_value} занятия' 62 | else: 63 | balance_value = f'{balance_value} занятий' 64 | message = f'🗓 Ваш абонемент заканчивается {end_date_value}\n💃 У Вас осталось {balance_value}' 65 | try: 66 | await message_from.reply(message) 67 | except Exception as send_error: 68 | logger.debug(f"{send_error.message}: Trouble id: {user_id}") 69 | return 70 | 71 | @dp.message_handler(filters.Regexp(regexp=r"(((Б|б)от))")) 72 | async def bot_commands_handler(message_from: types.Message) -> None: 73 | """Обработчик команды Бот.""" 74 | user_id: str = str(message_from.from_id) 75 | 76 | message = ( 77 | f"🤖 КОМАНДЫ ДЛЯ ЧАТ-БОТА: 🤖\n\n" 78 | f"❗ Бот ❗\n" 79 | f"-- все доступные команды чат-бота 📣\n\n" 80 | f"❗ Абонемент *** ❗\n" 81 | f"-- (*** - № абонемента) информация о Вашем абонементе (дата окончания и количество оставшихся занятий) 🔖\n\n" 82 | f"❗ Как добраться ❗\n" 83 | f"-- наш адрес, карта и инструкция, как нас найти 🗺\n\n" 84 | f"❗ Цены ❗\n" 85 | f"-- цены на занятия и программа лояльности 💰\n\n" 86 | f"❗ Расписание ❗\n" 87 | f"-- расписание занятий 📆\n\n" 88 | f"Если у Вас иной вопрос, то напишите и вам ответит администратор 👤 @melnikovvv" 89 | ) 90 | try: 91 | await message_from.reply(message) 92 | except Exception as send_error: 93 | logger.debug(f"{send_error.message}: Trouble id: {user_id}") 94 | return 95 | 96 | @dp.message_handler(filters.Regexp(regexp=r"(((Ц|ц)ены))")) 97 | async def prices_handler(message_from: types.Message) -> None: 98 | """Обработчик команды Цены.""" 99 | user_id: str = str(message_from.from_id) 100 | 101 | message = 'https://vk.com/wall-208780733_645' 102 | try: 103 | await message_from.reply(message) 104 | except Exception as send_error: 105 | logger.debug(f"{send_error.message}: Trouble id: {user_id}") 106 | return 107 | 108 | @dp.message_handler(filters.Regexp(regexp=r"(((Р|р)асписание))")) 109 | async def schedule_kids_handler(message_from: types.Message) -> None: 110 | """Обработчик команды Расписание.""" 111 | user_id: str = str(message_from.from_id) 112 | 113 | message = "https://vk.com/wall-208780733_643" 114 | try: 115 | await message_from.reply(message) 116 | except Exception as send_error: 117 | logger.debug(f"{send_error.message}: Trouble id: {user_id}") 118 | return 119 | 120 | @dp.message_handler(filters.Regexp(regexp=r"(((К|к)ак)(\s)(добраться))")) 121 | async def how_to_get_handler(message_from: types.Message) -> None: 122 | """Обработчик команды Как добраться.""" 123 | user_id: str = str(message_from.from_id) 124 | 125 | message = "https://vk.com/wall-208780733_279" 126 | try: 127 | await message_from.reply(message) 128 | except Exception as send_error: 129 | logger.debug(f"{send_error.message}: Trouble id: {user_id}") 130 | return 131 | 132 | 133 | if __name__ == '__main__': 134 | executor.start_polling(dp, skip_updates=True) 135 | --------------------------------------------------------------------------------