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 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()

View file

@ -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])

View file

@ -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()