diff --git a/bot.py b/bot.py index 2b1cce6..6f06a1b 100644 --- a/bot.py +++ b/bot.py @@ -3,15 +3,16 @@ import threading import time import schedule +from telegram import InlineKeyboardMarkup, ParseMode +from telegram.error import BadRequest from telegram.ext import Updater, CommandHandler, InlineQueryHandler, CallbackQueryHandler from exceptions import * +from push_information import __init__ as push_init from vvs import inline_station_search, handle_vvs, handle_multiple_stations_reply from weather_meteomedia import handle_meteomedia -from weather_openweathermap import handle_weather from weather_openweathermap import __init__ as openweathermap_init -from push_information import __init__ as push_init - +from weather_openweathermap import handle_weather token_file = open("token", "r") updater = Updater(token=token_file.read(), use_context=True) @@ -23,7 +24,21 @@ logging.basicConfig(format='%(acstime)s - %(name)s - %(levelname)s - %(message)s def send_message(chat_id: int, message: str): - dispatcher.bot.send_message(chat_id=chat_id, text=message, disable_notification=True, parse_mode="Markdown") + dispatcher.bot.send_message(chat_id=chat_id, text=message, disable_notification=True, parse_mode=ParseMode.MARKDOWN) + + +def edit_message_and_delete_answer(chat_id: int, message_id: int, message: str, + reply_markup: InlineKeyboardMarkup = None): + dispatcher.bot.delete_message(chat_id=chat_id, message_id=message_id) + try: + dispatcher.bot.edit_message_text(chat_id=chat_id, message_id=message_id - 1, text=message, + reply_markup=reply_markup) + except BadRequest: + try: + dispatcher.bot.edit_message_text(chat_id=chat_id, message_id=message_id - 2, text=message, + reply_markup=reply_markup) + except BadRequest: + pass cease_continuous_run = threading.Event() diff --git a/push_information.py b/push_information.py index cee8730..630c507 100644 --- a/push_information.py +++ b/push_information.py @@ -6,11 +6,12 @@ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, CallbackQueryHandler, CallbackContext from telegram.ext.filters import Filters -from bot import send_message +from bot import send_message, edit_message_and_delete_answer from weather_openweathermap import City, get_city_by_name users = {} -subs_in_edit = {} + +LIST, ADD_CITY, ADD_TIME, EDIT, EDIT_CITY, EDIT_TIME = range(6) class Subscription(object): @@ -107,8 +108,7 @@ def push_callback(update: Update, context: CallbackContext): else: user = User(update.effective_chat.id) users[update.effective_chat.id] = user - string = f"Hello, {update.effective_user.first_name}!\n" - string += f"You have {len(user.subs)} subscriptions:\n" + string = f"Hello, {update.effective_user.first_name}!\nYou have {len(user.subs)} subscriptions:\n" button_list = [] for i, subscription in enumerate(user.subs): @@ -116,116 +116,147 @@ def push_callback(update: Update, context: CallbackContext): InlineKeyboardButton(text=f"{subscription.city.name} at {time.strftime('%H:%M', subscription.time)}", callback_data=f"/push_edit {i}")) button_list.append(InlineKeyboardButton(text="Add", callback_data="/push_add")) + button_list.append(InlineKeyboardButton(text="End", callback_data="/cancel")) reply_markup = InlineKeyboardMarkup.from_column(button_list) update.message.reply_text(string, reply_markup=reply_markup) - return "list" + context.bot.delete_message(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id) + return LIST def edit_sub_callback(update: Update, context: CallbackContext): - global users, subs_in_edit + global users user = users[update.effective_chat.id] sub_idx = int(update.callback_query.data[11:]) sub = user.subs[sub_idx] - subs_in_edit[update.effective_chat.id] = sub + context.user_data['sub'] = sub button_list = [[InlineKeyboardButton(text="City", callback_data="/push_edit_city"), InlineKeyboardButton(text="Time", callback_data="/push_edit_time")], - [InlineKeyboardButton(text="Delete", callback_data=f"/push_edit_delete {sub_idx}")]] + [InlineKeyboardButton(text="Delete", callback_data=f"/push_edit_delete {sub_idx}"), + InlineKeyboardButton(text="Cancel", callback_data="/cancel")]] reply_markup = InlineKeyboardMarkup(button_list) - update.effective_chat.send_message("Change subscription.\nWhat do you want to change?", reply_markup=reply_markup) - return "edit" + update.callback_query.answer() + update.callback_query.edit_message_text(f"Change subscription {sub}.\nWhat do you want to change?", + reply_markup=reply_markup) + return EDIT def edit_city_callback(update: Update, context: CallbackContext): - update.effective_chat.send_message("Tell me the city you want information for:") - return "edit_city" + update.callback_query.answer() + update.callback_query.edit_message_text("Tell me the city you want information for:") + return EDIT_CITY def edited_city_callback(update: Update, context: CallbackContext): - global users, subs_in_edit + global users user = users[update.effective_chat.id] city_name = update.message.text city = get_city_by_name(city_name) if city.name == "none": - update.message.reply_text("This city was not found! Try again") + edit_message_and_delete_answer(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id, + message=f"The city {city_name} was not found! Try again") return None else: - subs_in_edit[user.chat_id].edit_city(city) - update.message.reply_text(f"City changed to {city.name}.") - del subs_in_edit[update.effective_chat.id] - update.message.reply_text(f"Subscription now is {subs_in_edit[user.chat_id]}") + context.user_data['sub'].edit_city(city) + edit_message_and_delete_answer(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id, + message=f"Subscription now is {context.user_data['sub']}") + + del context.user_data['sub'] return ConversationHandler.END def edit_time_callback(update: Update, context: CallbackContext): - update.effective_chat.send_message("When do you want to receive updates?") - return "edit_time" + update.callback_query.answer() + update.callback_query.edit_message_text("When do you want to receive updates?") + return EDIT_TIME def edited_time_callback(update: Update, context: CallbackContext): global users user = users[update.effective_chat.id] - sub_in_edit = subs_in_edit[user.chat_id] + sub_in_edit = context.user_data['sub'] time_string = update.message.text - if time_string.find(':') == -1: - sub_in_edit.edit_time(time.strptime(time_string, "%H")) - else: - sub_in_edit.edit_time(time.strptime(time_string, "%H:%M")) - del subs_in_edit[user.chat_id] - update.message.reply_text(f"Subscription now is {sub_in_edit}") + try: + if time_string.find(':') == -1: + sub_in_edit.edit_time(time.strptime(time_string, "%H")) + else: + sub_in_edit.edit_time(time.strptime(time_string, "%H:%M")) + except ValueError: + edit_message_and_delete_answer(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id, + message="This time was not recognized! Try again") + return None + del context.user_data['sub'] + edit_message_and_delete_answer(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id, + message=f"Subscription now is {sub_in_edit}") return ConversationHandler.END def delete_subscription_callback(update: Update, context: CallbackContext): global users users[update.effective_chat.id].del_subscription(int(update.callback_query.data[18:])) - update.effective_chat.send_message("Deleted") + update.callback_query.answer() + update.callback_query.edit_message_text("Deleted") schedule_subscriptions() return ConversationHandler.END def add_sub_callback(update: Update, context: CallbackContext): - update.effective_chat.send_message("Add a new subscription.\nTell me the city you want information for:") - return "add_city" + update.callback_query.answer() + update.callback_query.edit_message_text("Add a new subscription.\nTell me the city you want information for:") + return ADD_CITY def add_city_callback(update: Update, context: CallbackContext): - global users, subs_in_edit + global users user = users[update.effective_chat.id] city_name = update.message.text city = get_city_by_name(city_name) if city.name == "none": - update.message.reply_text("This city was not found! Try again") + edit_message_and_delete_answer(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id, + message="This city was not found! Try again") return None else: - update.message.reply_text(f"I found {city.name}. When do you want to receive updates?") - subs_in_edit[user.chat_id] = Subscription(user.chat_id, city) - return "time" + edit_message_and_delete_answer(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id, + message=f"I found {city.name}. When do you want to receive updates?") + context.user_data['sub'] = Subscription(user.chat_id, city) + return ADD_TIME -def time_callback(update: Update, context: CallbackContext): - global users, subs_in_edit +def add_time_callback(update: Update, context: CallbackContext): + global users user = users[update.effective_chat.id] - sub_in_edit = subs_in_edit[update.effective_chat.id] + sub_in_edit = context.user_data['sub'] time_string = update.message.text - if time_string.find(':') == -1: - sub_in_edit.time = time.strptime(time_string, "%H") - else: - sub_in_edit.time = time.strptime(time_string, "%H:%M") + try: + if time_string.find(':') == -1: + sub_in_edit.edit_time(time.strptime(time_string, "%H")) + else: + sub_in_edit.edit_time(time.strptime(time_string, "%H:%M")) + except ValueError: + edit_message_and_delete_answer(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id, + message="This time was not recognized! Try again") + return None user.add_subscription(sub_in_edit) - update.message.reply_text(f"Subscription now is: {str(sub_in_edit)}") - del subs_in_edit[update.effective_chat.id] + edit_message_and_delete_answer(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id, + message=f"Subscription now is: {str(sub_in_edit)}") + del context.user_data['sub'] return ConversationHandler.END def cancel_callback(update: Update, context: CallbackContext): - update.message.reply_text("Timeout") + context.bot.delete_message(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id) + return ConversationHandler.END + + +def cancel_button_callback(update: Update, context: CallbackContext): + update.callback_query.answer() + update.effective_message.delete() return ConversationHandler.END def __init__() -> ConversationHandler: load_users_from_file() - push_handler = CommandHandler(command="push", callback=push_callback) + push_handler = CommandHandler(command=["push", "subs"], callback=push_callback) edit_sub_handler = CallbackQueryHandler(callback=edit_sub_callback, pattern="^\/push_edit ") edit_city_handler = CallbackQueryHandler(callback=edit_city_callback, pattern="^\/push_edit_city") @@ -237,14 +268,14 @@ def __init__() -> ConversationHandler: add_sub_handler = CallbackQueryHandler(callback=add_sub_callback, pattern="^\/push_add") add_city_handler = MessageHandler(callback=add_city_callback, filters=Filters.text) - - time_handler = MessageHandler(callback=time_callback, filters=Filters.text) + add_time_handler = MessageHandler(callback=add_time_callback, filters=Filters.text) fallback_handler = CommandHandler(command="cancel", callback=cancel_callback) + fallback_callback_handler = CallbackQueryHandler(callback=cancel_button_callback, pattern="^\/cancel") return ConversationHandler(entry_points=[push_handler], - states={"list": [edit_sub_handler, add_sub_handler], "add_city": [add_city_handler], - "edit": [edit_city_handler, edit_time_handler, delete_subscription_handler], - "edit_city": [edited_city_handler], "edit_time": [edited_time_handler], - "time": [time_handler]}, - fallbacks=[fallback_handler], conversation_timeout=10) + states={LIST: [edit_sub_handler, add_sub_handler], ADD_CITY: [add_city_handler], + EDIT: [edit_city_handler, edit_time_handler, delete_subscription_handler], + EDIT_CITY: [edited_city_handler], EDIT_TIME: [edited_time_handler], + ADD_TIME: [add_time_handler]}, + fallbacks=[fallback_handler, fallback_callback_handler]) diff --git a/weather_openweathermap.py b/weather_openweathermap.py index 702281d..265d0a4 100644 --- a/weather_openweathermap.py +++ b/weather_openweathermap.py @@ -2,6 +2,7 @@ import json from datetime import datetime import requests +from telegram import ParseMode from exceptions import ServerCommunicationError, NoArgError, WeatherStationNotFoundError @@ -38,16 +39,11 @@ class HourlyWeather: self.description = json_data['weather'][0]['description'] def __str__(self): - string = "" - string += "*{time}*: {main} | {temperature:.0f}°C 🧑: {feels_like:.0f}°C" \ - .format(time=self.time.strftime("%Hh"), - main=self.main, - temperature=self.temperature, - feels_like=self.feels_like) + string = f"*{self.time.strftime('%Hh')}*: {self.main} | {self.temperature:.0f}°C 🧑: {self.feels_like:.0f}°C" if self.probability_of_precipitation != 0: - string += " ☔: {pop:.0f}% {rain}mm".format(pop=100 * self.probability_of_precipitation, rain=self.rain) + string += f" ☔: {100 * self.probability_of_precipitation:.0f}% {self.rain}mm" - string += " 💨{wind_speed:.1f}km/h".format(wind_speed=3.6 * self.wind_speed) + string += f" 💨{3.6 * self.wind_speed:.1f}km/h" return string @@ -75,17 +71,11 @@ class DailyWeather: self.description = json_data['weather'][0]['description'] def __str__(self): - string = "" - string += "*{time}*: {main} | {min}°C - {day}°C - {max}°C 🧑: {feels_like_day:.0f}°C" \ - .format(time=self.time.strftime("%d"), - main=self.main, - min=self.temp['min'], - day=self.temp['day'], - max=self.temp['max'], - feels_like_day=self.feels_like) + string = f"*{self.time.strftime('%d')}*: {self.main} | {self.temp['min']:.0f}°C - {self.temp['day']:.0f}°C" \ + f" - {self.temp['max']:.0f}°C 🧑: {self.feels_like:.0f}°C" if self.probability_of_precipitation != 0: - string += " ☔: {pop:.0f}% {rain}mm".format(pop=100 * self.probability_of_precipitation, rain=self.rain) - string += " 💨: {wind_speed:.1f}km/h".format(wind_speed=3.6 * self.wind_speed) + string += f" ☔: {100 * self.probability_of_precipitation:.0f}% {self.rain}mm" + string += f" 💨: {3.6 * self.wind_speed:.1f}km/h" return string @@ -194,6 +184,6 @@ def handle_weather(update, context): if requested_city.name == "none": raise WeatherStationNotFoundError requested_city.query_weather() - update.message.reply_text(str(requested_city.weather), disable_notification=True, parse_mode="Markdown") + update.message.reply_text(str(requested_city.weather), disable_notification=True, parse_mode=ParseMode.MARKDOWN) requested_city.weather.clear()