Made conversation nicer

Messages now get deleted and deleted for a smoother experience.
Other small changes:
 - States as constants
 - String formatting with f-string-literals
This commit is contained in:
JuliusFreudenberger 2020-09-24 19:34:29 +02:00
parent d6debf6a46
commit 4f403561ba
3 changed files with 112 additions and 76 deletions

23
bot.py
View file

@ -3,15 +3,16 @@ import threading
import time import time
import schedule import schedule
from telegram import InlineKeyboardMarkup, ParseMode
from telegram.error import BadRequest
from telegram.ext import Updater, CommandHandler, InlineQueryHandler, CallbackQueryHandler from telegram.ext import Updater, CommandHandler, InlineQueryHandler, CallbackQueryHandler
from exceptions import * from exceptions import *
from push_information import __init__ as push_init
from vvs import inline_station_search, handle_vvs, handle_multiple_stations_reply from vvs import inline_station_search, handle_vvs, handle_multiple_stations_reply
from weather_meteomedia import handle_meteomedia from weather_meteomedia import handle_meteomedia
from weather_openweathermap import handle_weather
from weather_openweathermap import __init__ as openweathermap_init 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") token_file = open("token", "r")
updater = Updater(token=token_file.read(), use_context=True) 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): 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() cease_continuous_run = threading.Event()

View file

@ -6,11 +6,12 @@ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, CallbackQueryHandler, CallbackContext from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, CallbackQueryHandler, CallbackContext
from telegram.ext.filters import Filters 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 from weather_openweathermap import City, get_city_by_name
users = {} users = {}
subs_in_edit = {}
LIST, ADD_CITY, ADD_TIME, EDIT, EDIT_CITY, EDIT_TIME = range(6)
class Subscription(object): class Subscription(object):
@ -107,8 +108,7 @@ def push_callback(update: Update, context: CallbackContext):
else: else:
user = User(update.effective_chat.id) user = User(update.effective_chat.id)
users[update.effective_chat.id] = user users[update.effective_chat.id] = user
string = f"Hello, {update.effective_user.first_name}!\n" string = f"Hello, {update.effective_user.first_name}!\nYou have {len(user.subs)} subscriptions:\n"
string += f"You have {len(user.subs)} subscriptions:\n"
button_list = [] button_list = []
for i, subscription in enumerate(user.subs): 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)}", InlineKeyboardButton(text=f"{subscription.city.name} at {time.strftime('%H:%M', subscription.time)}",
callback_data=f"/push_edit {i}")) callback_data=f"/push_edit {i}"))
button_list.append(InlineKeyboardButton(text="Add", callback_data="/push_add")) 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) reply_markup = InlineKeyboardMarkup.from_column(button_list)
update.message.reply_text(string, reply_markup=reply_markup) 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): def edit_sub_callback(update: Update, context: CallbackContext):
global users, subs_in_edit global users
user = users[update.effective_chat.id] user = users[update.effective_chat.id]
sub_idx = int(update.callback_query.data[11:]) sub_idx = int(update.callback_query.data[11:])
sub = user.subs[sub_idx] 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"), button_list = [[InlineKeyboardButton(text="City", callback_data="/push_edit_city"),
InlineKeyboardButton(text="Time", callback_data="/push_edit_time")], 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) reply_markup = InlineKeyboardMarkup(button_list)
update.effective_chat.send_message("Change subscription.\nWhat do you want to change?", reply_markup=reply_markup) update.callback_query.answer()
return "edit" 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): def edit_city_callback(update: Update, context: CallbackContext):
update.effective_chat.send_message("Tell me the city you want information for:") update.callback_query.answer()
return "edit_city" 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): def edited_city_callback(update: Update, context: CallbackContext):
global users, subs_in_edit global users
user = users[update.effective_chat.id] user = users[update.effective_chat.id]
city_name = update.message.text city_name = update.message.text
city = get_city_by_name(city_name) city = get_city_by_name(city_name)
if city.name == "none": 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 return None
else: else:
subs_in_edit[user.chat_id].edit_city(city) context.user_data['sub'].edit_city(city)
update.message.reply_text(f"City changed to {city.name}.") edit_message_and_delete_answer(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id,
del subs_in_edit[update.effective_chat.id] message=f"Subscription now is {context.user_data['sub']}")
update.message.reply_text(f"Subscription now is {subs_in_edit[user.chat_id]}")
del context.user_data['sub']
return ConversationHandler.END return ConversationHandler.END
def edit_time_callback(update: Update, context: CallbackContext): def edit_time_callback(update: Update, context: CallbackContext):
update.effective_chat.send_message("When do you want to receive updates?") update.callback_query.answer()
return "edit_time" update.callback_query.edit_message_text("When do you want to receive updates?")
return EDIT_TIME
def edited_time_callback(update: Update, context: CallbackContext): def edited_time_callback(update: Update, context: CallbackContext):
global users global users
user = users[update.effective_chat.id] 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 time_string = update.message.text
try:
if time_string.find(':') == -1: if time_string.find(':') == -1:
sub_in_edit.edit_time(time.strptime(time_string, "%H")) sub_in_edit.edit_time(time.strptime(time_string, "%H"))
else: else:
sub_in_edit.edit_time(time.strptime(time_string, "%H:%M")) sub_in_edit.edit_time(time.strptime(time_string, "%H:%M"))
del subs_in_edit[user.chat_id] except ValueError:
update.message.reply_text(f"Subscription now is {sub_in_edit}") 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 return ConversationHandler.END
def delete_subscription_callback(update: Update, context: CallbackContext): def delete_subscription_callback(update: Update, context: CallbackContext):
global users global users
users[update.effective_chat.id].del_subscription(int(update.callback_query.data[18:])) 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() schedule_subscriptions()
return ConversationHandler.END return ConversationHandler.END
def add_sub_callback(update: Update, context: CallbackContext): 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:") update.callback_query.answer()
return "add_city" 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): def add_city_callback(update: Update, context: CallbackContext):
global users, subs_in_edit global users
user = users[update.effective_chat.id] user = users[update.effective_chat.id]
city_name = update.message.text city_name = update.message.text
city = get_city_by_name(city_name) city = get_city_by_name(city_name)
if city.name == "none": 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 return None
else: else:
update.message.reply_text(f"I found {city.name}. When do you want to receive updates?") edit_message_and_delete_answer(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id,
subs_in_edit[user.chat_id] = Subscription(user.chat_id, city) message=f"I found {city.name}. When do you want to receive updates?")
return "time" context.user_data['sub'] = Subscription(user.chat_id, city)
return ADD_TIME
def time_callback(update: Update, context: CallbackContext): def add_time_callback(update: Update, context: CallbackContext):
global users, subs_in_edit global users
user = users[update.effective_chat.id] 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 time_string = update.message.text
try:
if time_string.find(':') == -1: if time_string.find(':') == -1:
sub_in_edit.time = time.strptime(time_string, "%H") sub_in_edit.edit_time(time.strptime(time_string, "%H"))
else: else:
sub_in_edit.time = time.strptime(time_string, "%H:%M") 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) user.add_subscription(sub_in_edit)
update.message.reply_text(f"Subscription now is: {str(sub_in_edit)}") edit_message_and_delete_answer(chat_id=update.effective_chat.id, message_id=update.effective_message.message_id,
del subs_in_edit[update.effective_chat.id] message=f"Subscription now is: {str(sub_in_edit)}")
del context.user_data['sub']
return ConversationHandler.END return ConversationHandler.END
def cancel_callback(update: Update, context: CallbackContext): 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 return ConversationHandler.END
def __init__() -> ConversationHandler: def __init__() -> ConversationHandler:
load_users_from_file() 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_sub_handler = CallbackQueryHandler(callback=edit_sub_callback, pattern="^\/push_edit ")
edit_city_handler = CallbackQueryHandler(callback=edit_city_callback, pattern="^\/push_edit_city") 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_sub_handler = CallbackQueryHandler(callback=add_sub_callback, pattern="^\/push_add")
add_city_handler = MessageHandler(callback=add_city_callback, filters=Filters.text) add_city_handler = MessageHandler(callback=add_city_callback, filters=Filters.text)
add_time_handler = MessageHandler(callback=add_time_callback, filters=Filters.text)
time_handler = MessageHandler(callback=time_callback, filters=Filters.text)
fallback_handler = CommandHandler(command="cancel", callback=cancel_callback) fallback_handler = CommandHandler(command="cancel", callback=cancel_callback)
fallback_callback_handler = CallbackQueryHandler(callback=cancel_button_callback, pattern="^\/cancel")
return ConversationHandler(entry_points=[push_handler], return ConversationHandler(entry_points=[push_handler],
states={"list": [edit_sub_handler, add_sub_handler], "add_city": [add_city_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: [edit_city_handler, edit_time_handler, delete_subscription_handler],
"edit_city": [edited_city_handler], "edit_time": [edited_time_handler], EDIT_CITY: [edited_city_handler], EDIT_TIME: [edited_time_handler],
"time": [time_handler]}, ADD_TIME: [add_time_handler]},
fallbacks=[fallback_handler], conversation_timeout=10) fallbacks=[fallback_handler, fallback_callback_handler])

View file

@ -2,6 +2,7 @@ import json
from datetime import datetime from datetime import datetime
import requests import requests
from telegram import ParseMode
from exceptions import ServerCommunicationError, NoArgError, WeatherStationNotFoundError from exceptions import ServerCommunicationError, NoArgError, WeatherStationNotFoundError
@ -38,16 +39,11 @@ class HourlyWeather:
self.description = json_data['weather'][0]['description'] self.description = json_data['weather'][0]['description']
def __str__(self): def __str__(self):
string = "" string = f"*{self.time.strftime('%Hh')}*: {self.main} | {self.temperature:.0f}°C 🧑: {self.feels_like:.0f}°C"
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)
if self.probability_of_precipitation != 0: 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 return string
@ -75,17 +71,11 @@ class DailyWeather:
self.description = json_data['weather'][0]['description'] self.description = json_data['weather'][0]['description']
def __str__(self): def __str__(self):
string = "" string = f"*{self.time.strftime('%d')}*: {self.main} | {self.temp['min']:.0f}°C - {self.temp['day']:.0f}°C" \
string += "*{time}*: {main} | {min}°C - {day}°C - {max}°C 🧑: {feels_like_day:.0f}°C" \ f" - {self.temp['max']:.0f}°C 🧑: {self.feels_like:.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)
if self.probability_of_precipitation != 0: 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 return string
@ -194,6 +184,6 @@ def handle_weather(update, context):
if requested_city.name == "none": if requested_city.name == "none":
raise WeatherStationNotFoundError raise WeatherStationNotFoundError
requested_city.query_weather() 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() requested_city.weather.clear()