Приветствую в своём блоге, сегодня я расскажу вам о своём небольшом проекте. Недавно я уже писал, про этого бота, это был первый проект такого плана. До этого я никогда раньше не писал ботов, поэтому этот проект, так называемая, "проба пера") В этом обновлении я добавил базу данных к боту, вместо файла json. Сейчас мы попробуем разобрать весь код подробнее.
Структура проекта:
-data/
-db.db
-.env
-bot.py
-btns.py
-db_tool.py
-parse_func.py
-requirements.txt
Немного расскажу о структуре проекта, имеем папку data/ в ней лежит файл базы данных db.db, БД использовал SQLite, она отлично подошла для такого небольшого проекта.
Файл .env содержит переменные окружения, там у нас записан токен бота, токен Open Weather Map и директория, из которой подтягивается БД.
Файл bot.py содержит основной код программы, btns.py - кнопки для бота решил поместить в отдельный файл, чтобы разгрузить основной.
Файл db_tool.py содержит функции для работы с базой данных, parse_func.py - функции парсинга данных.
Файл requirements.txt - зависимости.
Код
Теперь покажу немного кода. Весь код будет доступен по ссылке, здесь покажу только основные моменты.
bot.py
# Создаем бота
bot = telebot.TeleBot(config('API_TOKEN'))Функция для проверки событий в другом потоке:
def schedule_checker():
while True:
try:
schedule.run_pending()
sleep(2)
except Exception as err:
print(f'{datetime.now().strftime("%d/%m/%Y %H:%M")} - {err}')В этой функции мы получаем всех пользователей из БД, а затем получаем значение, настроенного времени с текущим, если совпадает отправляем ежедневную сводку. В коде есть замечание, я получаю текущее время и прибавляю к нему timedelta, это из-за часовых поясов, у меня на сервере -3 от Москвы.
def check_time():
try:
for user in get_users():
try:
if get_values(user)['time'] == (datetime.now() + timedelta(hours=3)).strftime('%H:%M'):
daily_notif(user)
except telebot.apihelper.ApiTelegramException as err:
if 'bot was blocked by the user' in err.description:
delete(user)
except Exception as err:
print(f'{datetime.now().strftime("%d/%m/%Y %H:%M")} - {err}')Функция для получения значений, записанных в БД и отправка пользователю:
def bot_info(message):
markup = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
markup.add(buttons['settings_btn'])
markup.add(buttons['info_btn'])
values = get_values(message.chat.id)
bot.send_message(chat_id=message.chat.id,
text=f'Настройки бота:\n\n'
f'⏰ Время: {values["time"]}\n'
f'🏰 Город: {values["city"]}\n'
f'🌤 Сводка о погоде: {values["weather"]}\n'
f'💰 Курсы валют: {values["currency"]}\n'
f'📖 Цитата: {values["quote"]}',
reply_markup=markup)Запускаем бота и поток с проверкой времени:
if __name__ == '__main__':
print('Запуск бота..')
schedule.every().minute.do(check_time)
Thread(target=schedule_checker, daemon=True).start()Обрабатываем команду /start:
@bot.message_handler(commands=['start'])
def send_welcome(message):
if not str(message.chat.id) in get_users():
add(message.chat.id, '07:00', 'Москва', 'вкл', 'вкл', 'вкл')
markup = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
markup.add(buttons['settings_btn'])
markup.add(buttons['info_btn'])
bot.send_message(message.chat.id, 'Привет! Это бот-информатор.\n'
'Он присылает сообщение каждый день в одно и тоже время.\nСодержание сообщения и время определяет пользователь.',
reply_markup=markup)Дальше идёт обработка текстовых сообщений пользователя, а именно какие кнопки были нажаты, для примера кнопка настроек:
match message.text:
case 'ℹ️Инфоℹ️':
bot_info(message)
case '🛠Настройки🛠':
markup = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
markup.add(buttons['city_btn'], buttons_list[2], buttons_list[1], buttons_list[0],
buttons['time_btn'],
buttons['main_menu_btn'])
bot.send_message(message.chat.id, text='Для настройки параметра, кликни на кнопку.', reply_markup=markup)Запускаем бесконечную прослушку бота:
bot.infinity_polling()С основными моментами в файле bot.py, вроде всё.
btns.py
from telebot.types import KeyboardButton
from db_tool import get_values
# Кнопки
buttons = {
'settings_btn': KeyboardButton('🛠Настройки🛠'),
'city_btn': KeyboardButton('🏰Город🏰'),
'weath_btn_on': KeyboardButton('🌤Погода✅'),
'weath_btn_off': KeyboardButton('🌤Погода❎'),
'curr_btn_on': KeyboardButton('💰Курс✅'),
'curr_btn_off': KeyboardButton('💰Курс❎'),
'quote_btn_on': KeyboardButton('📖Цитата✅'),
'quote_btn_off': KeyboardButton('📖Цитата❎'),
'time_btn': KeyboardButton('⏰Время⏰'),
'main_menu_btn': KeyboardButton('🔝Главная🔝'),
'info_btn': KeyboardButton('ℹ️Инфоℹ️'),
}
# Получаем состояние кнопок
def get_buttons(chat_id):
buttons_list = []
values = get_values(chat_id)
match values['weather']:
case 'вкл':
weather_btn = buttons['weath_btn_on']
buttons_list.append(weather_btn)
case 'выкл':
weather_btn = buttons['weath_btn_off']
buttons_list.append(weather_btn)
match values['quote']:
case 'вкл':
quote_btn = buttons['quote_btn_on']
buttons_list.append(quote_btn)
case 'выкл':
quote_btn = buttons['quote_btn_off']
buttons_list.append(quote_btn)
match values['currency']:
case 'вкл':
cur_btn = buttons['curr_btn_on']
buttons_list.append(cur_btn)
case 'выкл':
cur_btn = buttons['curr_btn_off']
buttons_list.append(cur_btn)
# Возвращаем список состояний кнопок
return buttons_listБольше всего было интересно работать с БД, написал функции для работы с ней. Вот пример функции для получения значений по chat_id пользователя:
db_tools.py
def get_values(chat_id):
with sqlite3.connect(f'{config("DIRECTORY")}/db.db') as conn:
cur = conn.cursor()
try:
with conn:
cur.execute('SELECT * FROM Users WHERE chat_id = ?', (f'{chat_id}',))
user = cur.fetchone()
return {
'chat_id': user[1],
'time': user[2],
'city': user[3],
'weather': user[4],
'quote': user[5],
'currency': user[6],
}
except Exception as err:
print(f'{datetime.now().strftime("%d/%m/%Y %H:%M")} - {err}')В результате получаем словарь со всеми нужными значениями.
Про парсинг писать особо нечего, ибо там все просто, кому интересно, можете глянуть код на GitFlic.
P.S. Вы знали, что у нас есть российский аналог GitHub? Я не знал, а теперь знаю, так что проекты буду там свои выкладывать отныне. Поддержим российского производителя))