Dockerized and fixed errors

This commit is contained in:
Remy Moll
2022-01-15 22:14:12 +01:00
parent 3bbe3e6cc6
commit 54b52f78bf
48 changed files with 35 additions and 11 deletions

View File

@@ -0,0 +1,2 @@
# Placeholder
from . import clock, help, weather, status, zvv, lists, alias, plaintext, reddit, search

64
app/bot/commands/alias.py Normal file
View File

@@ -0,0 +1,64 @@
from .template import *
FIRST = range(1)
class Alias(BotFunc):
"""create a new command for command-paths you often use"""
def __init__(self, dispatcher, db):
super().__init__(db)
self.dispatcher = dispatcher
# do not interact with him yet!
def create_handler(self):
conv_handler = ConversationHandler(
entry_points=[CommandHandler('alias', self.entry_point)],
states={
FIRST: [
CallbackQueryHandler(self.print_all, pattern="^all$"),
CallbackQueryHandler(self.create_alias, pattern="^new$"),
CallbackQueryHandler(self.delete_alias, pattern='^delete$'),
]
},
fallbacks=[CommandHandler('alias', self.entry_point)],
)
return conv_handler
def entry_point(self, update: Update, context: CallbackContext) -> None:
test = self.dispatcher
keyboard = [
[InlineKeyboardButton("All aliases", callback_data="all")],
[InlineKeyboardButton("Create new alias", callback_data="new")],
[InlineKeyboardButton("Delete alias", callback_data="delete")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
super().log_activity(receive=True, execute=False, send=True)
update.message.reply_text("What exactly do you want?", reply_markup=reply_markup)
return FIRST
def print_all(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
all_alias = ""
for k in self.persistence["bot"]["aliases"]:
all_alias += k + " - " + self.persistence["bot"]["aliases"] +"\n"
query.edit_message_text(text="List of all commands:\n" + all_alias)
return ConversationHandler.END
def create_alias(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
all_alias = ""
for k in self.persistence["bot"]["aliases"]:
all_alias += k + " - " + self.persistence["bot"]["aliases"] +"\n"
query.edit_message_text(text="List of all commands:\n" + all_alias)
return ConversationHandler.END
def delete_alias(self, update: Update, context: CallbackContext) -> None:
return ConversationHandler.END

206
app/bot/commands/clock.py Normal file
View File

@@ -0,0 +1,206 @@
from .template import *
import time
import numpy
from PIL import Image
import io
CHOOSE, ADDARG = range(2)
MESSAGE, WAKE, ALARM, IMAGE, ART = range(3,8)
class Clock(BotFunc):
"""pass on commands to clock-module"""
def __init__(self, db_utils, clock_module, art_api):
super().__init__(db_utils)
self.clock = clock_module
self.art_api = art_api
def create_handler(self):
handler = ConversationHandler(
entry_points=[CommandHandler("clock", self.entry_point)],
states={
CHOOSE : [
CallbackQueryHandler(self.wake_light, pattern="^wake$"),
CallbackQueryHandler(self.alarm_blink, pattern="^alarm$"),
CallbackQueryHandler(self.show_message, pattern="^message$"),
CallbackQueryHandler(self.show_image, pattern="^image$"),
CallbackQueryHandler(self.art_gallery, pattern="^gallery$"),
],
ADDARG : [MessageHandler(Filters.text, callback=self.get_arg1)],
MESSAGE: [MessageHandler(Filters.text, callback=self.exec_show_message)],
WAKE : [MessageHandler(Filters.text, callback=self.exec_wake_light)],
ALARM : [MessageHandler(Filters.text, callback=self.exec_alarm_blink)],
IMAGE : [MessageHandler(Filters.photo, callback=self.exec_show_image)],
ART : [MessageHandler(Filters.text, callback=self.exec_art_gallery)],
},
fallbacks=[CommandHandler('clock', self.entry_point)],
)
return handler
def entry_point(self, update: Update, context: CallbackContext) -> None:
keyboard = [
[InlineKeyboardButton("Make a wake-light", callback_data="wake")],
[InlineKeyboardButton("Blink as alarm", callback_data="alarm")],
[InlineKeyboardButton("Show a message", callback_data="message")],
[InlineKeyboardButton("Show an image", callback_data="image")],
[InlineKeyboardButton("Art gallery!", callback_data="gallery")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text("What exactly do you want?", reply_markup=reply_markup)
return CHOOSE
def wake_light(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
query.edit_message_text("Ok. How long should the color cycle last? (In seconds)")
return WAKE
def alarm_blink(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
query.edit_message_text("Ok. How long should it blink? (In seconds)")
self.next_state = {ALARM : "What frequency (Hertz)"}
return ADDARG
def show_message(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
query.edit_message_text("Ok. What message will I show?")
return MESSAGE
def show_image(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
query.edit_message_text("How long (in minutes) should the image be displayed?")
self.next_state = {IMAGE : "Please send me the photo to display."}
return ADDARG
def art_gallery(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
query.edit_message_text("Ok. How long should we display art? (in hours")
self.next_state = {ART : "And how many artworks would you like to see during that time?"}
return ADDARG
def get_arg1(self, update: Update, context: CallbackContext) -> None:
a = update.message.text
self.additional_argument = a
update.message.reply_text("Furthermore: "+ list(self.next_state.values())[0])
return list(self.next_state.keys())[0]
###### actually running clock actions
def exec_wake_light(self, update: Update, context: CallbackContext) -> None:
duration = update.message.text
matrices = []
start_color = numpy.array([153, 0, 51])
end_color = numpy.array([255, 255, 0])
col_show = numpy.zeros((*self.clock.MOP.shape, 3))
col_show[:,:,...] = start_color
gradient = end_color - start_color
# steps are shown at a frequency of ?? frames / second =>
for i in range(duration * 2): # / 0.5
ct = i/20 * gradient
col_show[:,:,...] = [int(x) for x in ct+start_color]
matrices.append(col_show)
self.clock.out.queue.append({"matrices" : matrices})
return ConversationHandler.END
def exec_alarm_blink(self, update: Update, context: CallbackContext) -> None:
duration = self.additional_argument
matrices = []
duration = int(duration * 2)
empty = numpy.zeros((*self.clock.MOP.shape,3))
red = numpy.ones_like(empty) * 255
for _ in range(int(duration / 2)):
matrices.append(red)
matrices.append(empty)
self.clock.out.queue.append({"matrices": matrices})
return ConversationHandler.END
def exec_show_image(self, update: Update, context: CallbackContext) -> None:
duration = self.additional_argument
i = update.message.photo
img = update.message.photo[0]
bot = img.bot
id = img.file_id
file = bot.getFile(id).download_as_bytearray()
width = self.clock.shape[1]
height = self.clock.shape[0]
img = Image.open(io.BytesIO(file))
im_height = img.height
im_width = img.width
scalex = im_width // width
scaley = im_height // height
scale = min(scalex, scaley)
t = img.resize((width, height),box=(0,0,width*scale,height*scale))
a = numpy.asarray(t)
matrices = [a for _ in range(2*60*duration)]
self.clock.out.queue.append({"matrices": matrices})
return ConversationHandler.END
def exec_show_message(self, update: Update, context: CallbackContext) -> None:
message_str = update.message.text
update.message.reply_text("Now showing: " + message_str)
self.clock.run(self.clock.text_scroll,(message_str,))
return ConversationHandler.END
def exec_art_gallery(self, update: Update, context: CallbackContext) -> None:
duration = float(self.additional_argument)
number = int(update.message.text)
def output(number, duration):
for i in range(number):
img = self.art_api.get_random_art() # returns an PIL.Image object
im_height = img.height
im_width = img.width
width = self.clock.shape[1]
height = self.clock.shape[0]
scalex = im_width // width
scaley = im_height // height
scale = min(scalex, scaley)
t = img.resize((width, height),box=(0,0,width*scale,height*scale))
a = numpy.asarray(t)
self.clock.IO.put(a)
time.sleep(duration*3600 / number)
update.message.reply_text("Ok. Showing art for the next "+ str(duration) + " hours.")
self.clock.run(output,(number, duration))
return ConversationHandler.END
# TODO FIx this to work with the new backend

128
app/bot/commands/help.py Normal file
View File

@@ -0,0 +1,128 @@
from .template import *
FIRST, EXECUTE = range(2)
class Help(BotFunc):
"""Shows the functions and their usage"""
def __init__(self, db):
super().__init__(db)
self.available_commands = {}
def create_handler(self):
conv_handler = ConversationHandler(
entry_points=[CommandHandler('help', self.entry_point)],
states={
FIRST: [
CallbackQueryHandler(self.print_all, pattern="^all$"),
CallbackQueryHandler(self.choose_specific, pattern="^specific$"),
CallbackQueryHandler(self.print_one, pattern='func-'),
],
EXECUTE :[CallbackQueryHandler(self.execute_now)],
# ConversationHandler.TIMEOUT : [
# CallbackQueryHandler(self.timeout)
# ]
},
fallbacks=[CommandHandler('help', self.entry_point)],
conversation_timeout=15,
)
return conv_handler
def add_commands(self, commands):
# commands is a dict {"name": class}
for k in commands:
if k != "plaintext":
self.available_commands[k] = commands[k].__doc__
def entry_point(self, update: Update, context: CallbackContext) -> None:
super().entry_point(update, context)
keyboard = [
[
InlineKeyboardButton("All commands", callback_data="all"),
InlineKeyboardButton("Just one", callback_data="specific"),
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
super().log_activity(read=True, execute=True, send=True) # at this point every step has been fulfilled
if update.message:
update.message.reply_text("What exactly do you want?", reply_markup=reply_markup)
else:
update._effective_chat.send_message("What exactly do you want?", reply_markup=reply_markup)
return FIRST
def print_all(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
all_cmd = ""
for h in self.available_commands:
all_cmd += "{} - `{}`\n".format(h, self.available_commands[h])
query.edit_message_text(text="List of all commands:\n" + all_cmd, parse_mode = ParseMode.MARKDOWN)
return ConversationHandler.END
def choose_specific(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
keyboard = [[InlineKeyboardButton(k, callback_data="func-" + k)] for k in self.available_commands]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text(
text="What command should be printed?", reply_markup=reply_markup
)
return FIRST
def print_one(self, update: Update, context: CallbackContext) -> None:
"""Show new choice of buttons"""
query = update.callback_query
name = query.data.replace("func-", "")
query.answer()
message = name + ": `" + self.available_commands[name] + "`"
keyboard = [[InlineKeyboardButton("Call " + name + " now", callback_data=name),]]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text(
text= message,
reply_markup = reply_markup,
parse_mode = ParseMode.MARKDOWN_V2
)
return EXECUTE
def execute_now(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
name = query.data
query.answer()
funcs = context.dispatcher.handlers[0]
for func in funcs:
if name == func.entry_points[0].command[0]:
break
callback = func.entry_points[0].handle_update
callback(update, context.dispatcher, check_result=True, context=context)
return ConversationHandler.END
def timeout(self, update: Update, context: CallbackContext) -> None:
"""For dying conversation. Currently unused."""
query = update.callback_query
name = query.data.replace("func-", "")
query.answer()
message = name + ": `" + self.available_commands[name] + "`"
query.edit_message_text(
text= "Timed out...",
parse_mode = ParseMode.MARKDOWN_V2
)
return ConversationHandler.END

184
app/bot/commands/lists.py Normal file
View File

@@ -0,0 +1,184 @@
from .template import *
NAME, NEW, ACTION, ITEMADD, ITEMREMOVE = range(5)
class Lists(BotFunc):
"""Create and edit lists"""
def __init__(self, db_utils):
super().__init__(db_utils)
self.current_name = ""
# self.db_utils set through super()
def create_handler(self):
conv_handler = ConversationHandler(
entry_points=[CommandHandler('list', self.entry_point)],
states={
NAME: [
CallbackQueryHandler(self.choose_list, pattern="^list-"),
CallbackQueryHandler(self.new_list, pattern="^new$"),
],
NEW : [MessageHandler(Filters.text, callback=self.new_listname)],
ACTION: [
CallbackQueryHandler(self.list_add, pattern="^add$"),
CallbackQueryHandler(self.list_remove, pattern="^remove$"),
CallbackQueryHandler(self.list_clear, pattern="^clear$"),
CallbackQueryHandler(self.list_delete, pattern="^delete$"),
CallbackQueryHandler(self.list_print, pattern="^print$"),
CallbackQueryHandler(self.list_menu, pattern="^overview$"),
],
ITEMADD : [MessageHandler(Filters.text, callback=self.list_add_item)],
ITEMREMOVE : [CallbackQueryHandler(self.list_remove_index)]
},
fallbacks=[CommandHandler('list', self.entry_point)],
)
return conv_handler
def entry_point(self, update: Update, context: CallbackContext) -> None:
super().entry_point(update, context)
lists = self.db_utils.list_get()
keyboard = [[InlineKeyboardButton(k, callback_data="list-"+k)] for k in lists] + [[InlineKeyboardButton("New list", callback_data="new")]]
reply_markup = InlineKeyboardMarkup(keyboard)
super().log_activity(read=True, execute=False, send=True)
update.message.reply_text(text="Here are the existing lists. You can also create a new one:", reply_markup=reply_markup)
return NAME
def choose_list(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
data = query.data
name = data.replace("list-","")
query.answer()
self.current_name = name
keyboard = [
[InlineKeyboardButton("Add item", callback_data="add")],
[InlineKeyboardButton("Remove item", callback_data="remove")],
[InlineKeyboardButton("Clear list", callback_data="clear")],
[InlineKeyboardButton("Print list", callback_data="print")],
[InlineKeyboardButton("Delete list", callback_data="delete")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text("Very well. For " + name + " the following actions are available:", reply_markup=reply_markup)
return ACTION
def list_menu(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
keyboard = [
[InlineKeyboardButton("Add item", callback_data="add")],
[InlineKeyboardButton("Remove item", callback_data="remove")],
[InlineKeyboardButton("Clear list", callback_data="clear")],
[InlineKeyboardButton("Print list", callback_data="print")],
[InlineKeyboardButton("Delete list", callback_data="delete")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text("Very well. For " + self.current_name + " the following actions are available:", reply_markup=reply_markup)
return ACTION
def new_list(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
query.edit_message_text("What's the name of the new list?")
return NEW
def new_listname(self, update: Update, context: CallbackContext) -> None:
name = update.message.text
try:
self.db_utils.list_create(name)
keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("To the menu!", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard)
self.current_name = name
update.message.reply_text("Thanks. List " + name + " was successfully created.", reply_markup=reply_markup)
super().log_activity(read=False, execute=True, send=True)
return ACTION
except Exception as e:
update.message.reply_text("Oh no! Encountered exception: {}".format(e))
return ConversationHandler.END
def list_add(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
query.edit_message_text("What would you like to add?")
return ITEMADD
def list_remove(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
sl = self.db_utils.list_get(self.current_name)
keyboard = [[InlineKeyboardButton(k, callback_data=i)] for i,k in enumerate(sl)]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text("Which item would you like to remove?", reply_markup = reply_markup)
return ITEMREMOVE
def list_clear(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
self.db_utils.list_update(self.current_name, replace=[])
keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text("List " + self.current_name + " cleared", reply_markup=reply_markup)
return ACTION
def list_delete(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
self.db_utils.list_delete(self.current_name)
query.edit_message_text("List " + self.current_name + " deleted")
return ConversationHandler.END
def list_print(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
it = self.db_utils.list_get(self.current_name)
if it:
content = "\n".join(it)
else:
content = "List empty"
keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text("Content of " + self.current_name + ":\n" + content, reply_markup=reply_markup)
return ACTION
def list_add_item(self, update: Update, context: CallbackContext) -> None:
item = update.message.text
self.db_utils.list_update(self.current_name, append=item)
keyboard = [[InlineKeyboardButton("Add some more", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text("Added " + item, reply_markup=reply_markup)
return ACTION
def list_remove_index(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
ind = int(query.data)
query.answer()
old = self.db_utils.list_get(self.current_name)
name = old.pop(ind)
self.db_utils.list_update(self.current_name, replace=old)
keyboard = [[InlineKeyboardButton("Remove another", callback_data="remove"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text("Removed " + name, reply_markup=reply_markup)
return ACTION

View File

@@ -0,0 +1,19 @@
from .template import *
class Plain(BotFunc):
"""Not a command: just keeps logs and usage_data"""
def __init__(self, db_utils):
super().__init__(db_utils)
def create_handler(self):
h = MessageHandler(Filters.text, callback=self.add_to_log)
return h
def add_to_log(self, update: Update, context: CallbackContext) -> None:
super().entry_point(update, context)
super().log_activity(
read = True,
send = False,
execute = False
)

179
app/bot/commands/reddit.py Normal file
View File

@@ -0,0 +1,179 @@
from re import U
from .template import *
CHOOSE_NUM = 1
class Joke(BotFunc):
"""Tells a joke from reddit."""
def __init__(self, api, db):
super().__init__(db)
self.available_commands = {}
self.api = api
def create_handler(self):
conv_handler = ConversationHandler(
entry_points=[CommandHandler('joke', self.entry_point)],
states={
CHOOSE_NUM: [CallbackQueryHandler(self.get_jokes),],
},
fallbacks=[CommandHandler('joke', self.entry_point)],
# conversation_timeout=5,
)
return conv_handler
def entry_point(self, update: Update, context: CallbackContext) -> None:
super().entry_point(update, context)
keyboard = [[InlineKeyboardButton(str(i), callback_data=str(i)) for i in range(1,11)]]
reply_markup = InlineKeyboardMarkup(keyboard)
super().log_activity(read=True, execute=True, send=True) # at this point every step has been fulfilled
update.message.reply_text("How many jokes?", reply_markup=reply_markup)
return CHOOSE_NUM
def get_jokes(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
number = int(query.data)
query.answer()
jokes = self.api.get_random_rising("jokes", number, "text")
# formating
message = ""
for j in jokes:
message += "<b>" + j["title"] + "</b> \n" + j["content"] + "\n\n"
if message == "":
message += "Could not fetch jokes."
query.edit_message_text(text = message, parse_mode = ParseMode.HTML)
return ConversationHandler.END
CHOOSE_TOPIC = 0
class Meme(BotFunc):
"""Gets the latest memes from reddit"""
def __init__(self, api, db):
super().__init__(db)
self.available_commands = {}
self.api = api
def create_handler(self):
conv_handler = ConversationHandler(
entry_points=[CommandHandler('meme', self.entry_point)],
states={
CHOOSE_TOPIC: [CallbackQueryHandler(self.choose_topic)],
CHOOSE_NUM :[CallbackQueryHandler(self.get_memes)],
},
fallbacks=[CommandHandler('meme', self.entry_point)],
)
return conv_handler
def entry_point(self, update: Update, context: CallbackContext) -> None:
keyboard = [
[InlineKeyboardButton("General", callback_data="memes"),],
[InlineKeyboardButton("Dank memes", callback_data="dankmemes"),],
[InlineKeyboardButton("Maths", callback_data="mathmemes"),],
[InlineKeyboardButton("Physics", callback_data="physicsmemes"),],
[InlineKeyboardButton("Biology", callback_data="biologymemes"),],
]
reply_markup = InlineKeyboardMarkup(keyboard)
super().log_activity(read=True, execute=True, send=True) # at this point every step has been fulfilled
update.message.reply_text("What kind of memes?", reply_markup=reply_markup)
return CHOOSE_TOPIC
def choose_topic(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
d = query.data
query.answer()
keyboard = [[InlineKeyboardButton(str(i), callback_data=d + "-" + str(i)) for i in range(1,11)]]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text("How many memes?", reply_markup=reply_markup)
return CHOOSE_NUM
def get_memes(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
data = query.data.split("-")
query.answer()
memes = self.api.get_random_rising(data[0], int(data[1]), "photo")
if len(memes) != 0:
for m in memes:
super().log_activity(read=False, execute=False, send=True) # we just sent an additional message
update.effective_chat.send_photo(photo = m["image"],caption = m["caption"])
else:
update.effective_chat.send_message("Sorry, the meme won't yeet.")
return ConversationHandler.END
# class News(BotFunc):
# """Gets the latest news from reddit"""
# def __init__(self, api, prst):
# super().__init__(prst)
# self.available_commands = {}
# self.api = api
# def create_handler(self):
# conv_handler = ConversationHandler(
# entry_points=[CommandHandler('news', self.entry_point)],
# states={
# CHOOSE_TOPIC: [CallbackQueryHandler(self.choose_topic)],
# CHOOSE_NUM :[CallbackQueryHandler(self.get_news)],
# },
# fallbacks=[CommandHandler('news', self.entry_point)],
# )
# return conv_handler
# def entry_point(self, update: Update, context: CallbackContext) -> None:
# super().entry_point()
# keyboard = [
# [InlineKeyboardButton("World", callback_data="worldnews"),],
# [InlineKeyboardButton("Germany", callback_data="germannews"),],
# [InlineKeyboardButton("France", callback_data="francenews"),],
# [InlineKeyboardButton("Europe", callback_data="eunews"),],
# [InlineKeyboardButton("USA", callback_data="usanews"),],
# ]
# reply_markup = InlineKeyboardMarkup(keyboard)
# update.message.reply_text("What kind of news?", reply_markup=reply_markup)
# return CHOOSE_TOPIC
# def choose_topic(self, update: Update, context: CallbackContext) -> None:
# super().entry_point()
# query = update.callback_query
# d = query.data
# query.answer()
# keyboard = [[InlineKeyboardButton(str(i), callback_data=d + "-" + str(i)) for i in range(1,11)]]
# reply_markup = InlineKeyboardMarkup(keyboard)
# query.edit_message_text("How many entries?", reply_markup=reply_markup)
# return CHOOSE_NUM
# def get_news(self, update: Update, context: CallbackContext) -> None:
# query = update.callback_query
# data = query.data.split("-")
# query.answer()
# #try:
# news = self.api.get_top(data[0], data[1], "text")
# # formating
# message = ""
# for j in news:
# message += "<b>" + j["title"] + "</b> \n" + j["content"] + "\n\n"
# if message == "":
# message += "Could not fetch news."
# query.edit_message_text(news, paresemode=ParseMode.HTML)
# return ConversationHandler.END

View File

@@ -0,0 +1,61 @@
from .template import *
SEARCH, MORE = range(2)
class Search(BotFunc):
"""Browse the web for a topic."""
def __init__(self, api, db):
super().__init__(db)
self.available_commands = {}
self.api = api
def create_handler(self):
conv_handler = ConversationHandler(
entry_points=[CommandHandler('search', self.entry_point)],
states={
SEARCH: [MessageHandler(Filters.text, self.get_results),],
MORE: [CallbackQueryHandler(self.show_more, pattern="^more$"),],
},
fallbacks=[CommandHandler('search', self.entry_point)],
conversation_timeout=20,
)
return conv_handler
def entry_point(self, update: Update, context: CallbackContext) -> None:
super().entry_point(update, context)
update.message.reply_text("What are we searching?")
return SEARCH
def get_results(self, update: Update, context: CallbackContext) -> None:
search = update.message.text
results = self.api.get_result(search)
keyboard = [[InlineKeyboardButton("More!", callback_data="more")]]
reply_markup = InlineKeyboardMarkup(keyboard)
# formating
self.results = results
if results:
first = results[0]
message = first["text"] + "\n(" + first["url"] + ")\n\n"
else:
message = "No results for search query."
update.message.reply_text(text = message, reply_markup=reply_markup)
super().log_activity(read = True, execute = True, send = True)
return MORE
def show_more(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
message = ""
for r in self.results:
message += r["text"] + "\n(" + r["url"] + ")\n\n"
query.edit_message_text(message)
super().log_activity(read = False, execute = False, send = True)
return ConversationHandler.END

103
app/bot/commands/status.py Normal file
View File

@@ -0,0 +1,103 @@
from .template import *
import datetime
import requests
import socket
import json
FIRST = 1
class Status(BotFunc):
"""Shows a short status of the program."""
def __init__(self, name, version, db_utils):
super().__init__(db_utils)
self.start_time = datetime.datetime.now()
self.name = name
self.version = version
def create_handler(self):
conv_handler = ConversationHandler(
entry_points=[CommandHandler('status', self.entry_point)],
states={
FIRST: [
CallbackQueryHandler(self.send_log, pattern="^full$"),
]
},
fallbacks=[CommandHandler('status', self.entry_point)],
)
return conv_handler
def entry_point(self, update: Update, context: CallbackContext) -> None:
super().entry_point(update, context)
keyboard = [
[
InlineKeyboardButton("And the log?", callback_data="full"),
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
delta = str(datetime.datetime.now() - self.start_time)
message = "BeebBop, this is " + self.name + " (V." + self.version + ")\n"
try:
ip = requests.get('https://api.ipify.org').text
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.connect(('8.8.8.8', 80))
(addr, port) = s.getsockname()
local_ips = addr
except:
ip = "not fetchable"
local_ips = "not fetchable"
message += "Status: Running 🟢\n"
message += "Uptime: `" + delta[:delta.rfind(".")] + "`\n"
# message += "Reboots: `" + str(self.persistence["global"]["reboots"]) + "`\n"
message += "IP (public): `" + ip + "`\n"
message += "IP (private): `" + str(local_ips) + "`\n"
u = str(self.get_ngrok_url())
message += "URL: [" + u + "](" + u + ")\n"
# TODO new DB
tot_r = self.db_utils.chat_count("read")
message += "Total messages read: `{}`\n".format(tot_r)
tot_s = self.db_utils.chat_count("send")
message += "Total messages sent: `{}`\n".format(tot_s)
tot_e = self.db_utils.chat_count("execute")
message += "Total commands executed: `{}`\n".format(tot_e)
if update.message:
update.message.reply_text(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN)
else:
update._effective_chat.send_message(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN)
super().log_activity(read = True, execute = True, send = True)
return FIRST
def send_log(self, update: Update, context: CallbackContext) -> None:
query = update.callback_query
wanted = query.data.replace("status-","")
query.answer()
with open("persistence/server.log") as l:
query.message.reply_document(l)
super().log_activity(read = False, execute = False, send = True)
return ConversationHandler.END
def get_ngrok_url(self):
try:
url = "http://localhost:4040/api/tunnels/"
res = requests.get(url)
res_unicode = res.content.decode("utf-8")
res_json = json.loads(res_unicode)
for i in res_json["tunnels"]:
if i['name'] == 'command_line':
return i['public_url']
except:
return "Not available"

View File

@@ -0,0 +1,31 @@
import logging
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, ParseMode
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext, MessageHandler, Filters
from telegram.ext import (
Updater,
CommandHandler,
CallbackQueryHandler,
ConversationHandler,
CallbackContext,
)
import datetime
class BotFunc():
"""Base class for a specific bot-functionality"""
def __init__(self, db_utils):
self.logger = logging.getLogger(__name__)
self.db_utils = db_utils
def log_activity(self, **kwargs):
# mark that a new command has been executed
self.db_utils.sensor_log(**kwargs)
def entry_point(self, update: Update, context: CallbackContext) -> None:
if update.message.text:
self.logger.info("Chat said: {}".format(update.message.text))
else:
self.logger.info("Chat said: {}".format(update.message))

117
app/bot/commands/weather.py Normal file
View File

@@ -0,0 +1,117 @@
from .template import *
import datetime
FIRST = 1
class Weather(BotFunc):
"""Shows a weatherforecast for a given location"""
def __init__(self, api, db):
"""initialize api and persistence"""
super().__init__(db)
self.api = api
self.city = ""
def create_handler(self):
"""returns the handlers with button-logic"""
conv_handler = ConversationHandler(
entry_points=[CommandHandler('weather', self.entry_point)],
states={
FIRST: [
CallbackQueryHandler(self.choose_city, pattern="^city-"),
CallbackQueryHandler(self.choose_time, pattern="^time-"),
]
},
fallbacks=[CommandHandler('weather', self.entry_point)],
)
return conv_handler
def entry_point(self, update: Update, context: CallbackContext) -> None:
super().entry_point(update, context)
"""Reacts the call of the command. Prints the first buttons"""
keyboard = [
[
InlineKeyboardButton("Zürich", callback_data="city-zurich"),
InlineKeyboardButton("Freiburg", callback_data="city-freiburg"),
InlineKeyboardButton("Mulhouse", callback_data="city-mulhouse"),
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
if update.message:
update.message.reply_text("Which city?", reply_markup=reply_markup)
else:
update.callback_query.edit_message_text("Which city", reply_markup=reply_markup)
return FIRST
def choose_city(self, update: Update, context: CallbackContext) -> None:
"""Prompt same text & keyboard as `start` does but not as new message"""
# Get CallbackQuery from Update
query = update.callback_query
data = query.data
self.city = data.replace("city-","")
query.answer()
keyboard = [
[
InlineKeyboardButton("Now", callback_data="time-now"),
InlineKeyboardButton("Tomorrow", callback_data="time-tomorrow"),
InlineKeyboardButton("7 days", callback_data="time-7"),
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text(
text = "Which time?", reply_markup=reply_markup
)
return FIRST
def choose_time(self, update: Update, context: CallbackContext) -> None:
"""Show new choice of buttons"""
query = update.callback_query
query.answer()
forecast_time = query.data.replace("time-","")
weather = self.get_weather(self.city, forecast_time)
query.edit_message_text(
text = "Broadcast for {}: \n\n{}".format(self.city, weather),
parse_mode = ParseMode.HTML
)
super().log_activity(read = True, execute = True, send = True)
return ConversationHandler.END
def get_weather(self, city, forecast_time) -> None:
"""get the weather that matches the given params"""
locations = {"freiburg": [47.9990, 7.8421], "zurich": [47.3769, 8.5417], "mulhouse": [47.7508, 7.3359]}
city = locations[city]
categories = {"Clouds": "", "Rain": "🌧", "Thunderstorm": "🌩", "Drizzle": ":droplet:", "Snow": "", "Clear": "", "Mist": "🌫", "Smoke": "Smoke", "Haze": "Haze", "Dust": "Dust", "Fog": "Fog", "Sand": "Sand", "Dust": "Dust", "Ash": "Ash", "Squall": "Squall", "Tornado": "Tornado",}
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
today = datetime.datetime.today().weekday()
weather = self.api.show_weather(city)
message = ""
if forecast_time == "now" or forecast_time == "7":
now = weather.pop(0)
message += "<b>Now:</b> " + categories[now["short"]] + "\n"
message += "🌡" + str(now["temps"][0]) + "°\n\n"
tod = weather.pop(0)
message += "<b>" + "Today" + ":</b> " + categories[tod["short"]] + "\n"
message += "🌡 ❄ " + str(tod["temps"][0]) + "° , 🌡 🔥 " + str(tod["temps"][1]) + "°\n\n"
if forecast_time == "tomorrow" or forecast_time == "7":
if forecast_time == "tomorrow": # previous statement was not executed: tomorrow is at weather[2]
tom = weather.pop(2)
else:
tom = weather.pop(0)
message += "<b>" + "Tomorrow" + ":</b> " + categories[tom["short"]] + "\n"
message += "🌡 ❄ " + str(tom["temps"][0]) + "° , 🌡 🔥 " + str(tom["temps"][1]) + "°\n\n"
if forecast_time == "7":
for i, day in enumerate(weather):
message += "<b>" + days[(today + i + 2) % 7] + ":</b> " + categories[day["short"]] + "\n"
message += "🌡 ❄ " + str(day["temps"][0]) + "° , 🌡 🔥 " + str(day["temps"][1]) + "°\n\n"
return message

87
app/bot/commands/zvv.py Normal file
View File

@@ -0,0 +1,87 @@
from .template import *
import datetime
import requests
START, DEST = range(2)
class Zvv(BotFunc):
"""Connects to the swiss travel-api to get public transport routes"""
def __init__(self, db):
super().__init__(db)
self.start = ""
self.dest = ""
pass
def create_handler(self):
conv_handler = ConversationHandler(
entry_points=[CommandHandler('zvv', self.entry_point)],
states={
START: [MessageHandler(Filters.text, callback=self.get_start)],
DEST: [MessageHandler(Filters.text, callback=self.get_dest)]
},
fallbacks=[CommandHandler('zvv', self.entry_point)],
)
return conv_handler
def entry_point(self, update: Update, context: CallbackContext) -> None:
super().entry_point(update, context)
update.message.reply_text("What is the start point?")
return START
def get_start(self, update: Update, context: CallbackContext) -> None:
loc = update.message.text
self.start = loc
update.message.reply_text("Ok. Going from " + loc + ", what is the destination?")
return DEST
def get_dest(self, update: Update, context: CallbackContext) -> None:
loc = update.message.text
self.dest = loc
route = self.get_result()
update.message.reply_text("Here are the routes I've got:\n" + route)
super().log_activity(read=True, execute=True, send=True)
return ConversationHandler.END
def get_result(self):
url = "http://transport.opendata.ch/v1/connections"
start = self.start
dest = self.dest
data = {"from" : start, "to" : dest, "limit" : 2}
try:
routes = requests.get(url, params=data).json()
result = routes["connections"]
text = result[0]["from"]["station"]["name"] + "" + result[0]["to"]["station"]["name"] + "\n\n"
for con in result:
text += "Start: " + datetime.datetime.fromtimestamp(int(con["from"]["departureTimestamp"])).strftime("%d/%m - %H:%M") + "\n"
text += "🏁 " + datetime.datetime.fromtimestamp(int(con["to"]["arrivalTimestamp"])).strftime("%d/%m - %H:%M") + "\n"
text += "" + con["duration"] + "\n"
text += "🗺️ Route:\n"
for step in con["sections"]:
if step["journey"] != None:
text += step["journey"]["passList"][0]["station"]["name"] + " (" + datetime.datetime.fromtimestamp(int(step["journey"]["passList"][0]["departureTimestamp"])).strftime("%H:%M") + ")\n"
text += "➡️ Linie " + self.number_to_emoji(step["journey"]["number"]) + "\n"
text += step["journey"]["passList"][-1]["station"]["name"] + " (" + datetime.datetime.fromtimestamp(int(step["journey"]["passList"][-1]["arrivalTimestamp"])).strftime("%H:%M") +")\n"
else:
text += "Walk."
text += "\n"
return text
except:
return "Invalid api call."
def number_to_emoji(self, number):
out = ""
numbers = ["0","1","2","3","4","5","6","7","8","9"]
for i in str(number):
out += numbers[int(i)]
return str(out)