Fixed persistence, better bot commands.
Better bot structure
This commit is contained in:
parent
6f80f26de1
commit
36ce0ee634
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,6 +1,6 @@
|
|||||||
# Persistence
|
# Persistence
|
||||||
prst.db.*
|
prst.*
|
||||||
|
log.txt
|
||||||
# API-Key (keep secret at all times)
|
# API-Key (keep secret at all times)
|
||||||
keys.py
|
keys.py
|
||||||
|
|
||||||
|
@ -1,23 +1,21 @@
|
|||||||
import emoji
|
import emoji
|
||||||
import requests
|
import requests
|
||||||
import Levenshtein as lev
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import bot.api.keys
|
import bot.api.keys
|
||||||
|
|
||||||
|
|
||||||
class TelegramIO():
|
class TelegramIO():
|
||||||
def __init__(self, persistence, commands):
|
def __init__(self, persistence):
|
||||||
"""Inits the Telegram-Interface
|
"""Inits the Telegram-Interface
|
||||||
"""
|
"""
|
||||||
self.base_url = "https://api.telegram.org/bot" + bot.api.keys.telegram_api + "/"
|
self.base_url = "https://api.telegram.org/bot" + bot.api.keys.telegram_api + "/"
|
||||||
self.persistence = persistence
|
self.persistence = persistence
|
||||||
self.commands = commands
|
|
||||||
# Dynamic variables for answering
|
# Dynamic variables for answering
|
||||||
self.chat_id = ""
|
self.chat_id = ""
|
||||||
self.offset = 0
|
self.offset = 0
|
||||||
self.message_id = ""
|
self.message_id = ""
|
||||||
|
self.message_queue = []
|
||||||
|
|
||||||
|
|
||||||
def update_commands(self,commands):
|
def update_commands(self,commands):
|
||||||
@ -35,38 +33,37 @@ class TelegramIO():
|
|||||||
try:
|
try:
|
||||||
result = requests.post(update_url,data=data)
|
result = requests.post(update_url,data=data)
|
||||||
result = result.json()["result"]
|
result = result.json()["result"]
|
||||||
|
self.message_queue = result
|
||||||
except:
|
except:
|
||||||
result = ""
|
result = []
|
||||||
|
|
||||||
return result
|
return len(result)
|
||||||
|
|
||||||
|
|
||||||
def handle_result(self, result):
|
def process_message(self):
|
||||||
"""Inspects the message and reacts accordingly. Can easily be extended"""
|
"""Inspects the first message from self.message_queue and reacts accordingly."""
|
||||||
message_data = result[0]
|
message_data = self.message_queue.pop(0)
|
||||||
|
|
||||||
self.persistence["bot"]["messages_read"] += 1
|
self.persistence["bot"]["messages_read"] += 1
|
||||||
self.offset = message_data["update_id"] + 1
|
self.offset = message_data["update_id"] + 1
|
||||||
|
|
||||||
if "edited_message" in message_data:
|
if "edited_message" in message_data:
|
||||||
return "nothing", "happened"
|
return
|
||||||
|
|
||||||
message = message_data["message"]
|
message = message_data["message"]
|
||||||
self.message_id = message["message_id"]
|
self.message_id = message["message_id"]
|
||||||
self.chat_id = message["chat"]["id"]
|
self.chat_id = message["chat"]["id"]
|
||||||
author = message["from"]
|
author = message["from"]
|
||||||
|
|
||||||
chat_members = self.persistence["bot"]["chat_members"]
|
if author["id"] not in self.persistence["bot"]["chat_members"]:
|
||||||
if str(author["id"]) not in chat_members:
|
|
||||||
name = ""
|
name = ""
|
||||||
if "first_name" in author:
|
if "first_name" in author:
|
||||||
name += author["first_name"] + " "
|
name += author["first_name"] + " "
|
||||||
if "last_name" in author:
|
if "last_name" in author:
|
||||||
name += author["last_name"]
|
name += author["last_name"]
|
||||||
if len(name) == 0:
|
if len(name) == 0:
|
||||||
name += "anonymous"
|
name = "anonymous"
|
||||||
chat_members[author["id"]] = name
|
self.persistence["bot"]["chat_members"][author["id"]] = name
|
||||||
self.persistence["bot"]["chat_members"] = chat_members
|
|
||||||
self.send_message("Welcome to this chat " + name + "!")
|
self.send_message("Welcome to this chat " + name + "!")
|
||||||
|
|
||||||
if "text" in message:
|
if "text" in message:
|
||||||
@ -75,45 +72,12 @@ class TelegramIO():
|
|||||||
if "entities" in message:
|
if "entities" in message:
|
||||||
for entry in message["entities"]:
|
for entry in message["entities"]:
|
||||||
if entry["type"] == "bot_command":
|
if entry["type"] == "bot_command":
|
||||||
return self.handle_command(message["text"][1:])
|
return message["text"] #self.handle_command(message["text"][1:])
|
||||||
|
|
||||||
elif "photo" in message:
|
elif "photo" in message:
|
||||||
print("Photo received, what do I do?")
|
print("Photo received, what do I do?")
|
||||||
|
|
||||||
return "nothing", "happened"
|
return
|
||||||
|
|
||||||
|
|
||||||
def handle_command(self, command):
|
|
||||||
"""Handles commands and stuff, using a bash-like syntax:
|
|
||||||
/[command] [argument 1] [argument 2] ...
|
|
||||||
"""
|
|
||||||
full = command.split(" ")
|
|
||||||
command = self.fuzzy_match_command(full[0])
|
|
||||||
if len(command) != 1:
|
|
||||||
if command[0] == "EXACT":
|
|
||||||
self.persistence["bot"]["commands_executed"] += 1
|
|
||||||
return command[1], full[1:]
|
|
||||||
else:
|
|
||||||
send = "Did you mean <code>" + command[1] + "</code>"
|
|
||||||
for i in range(2,len(command)):
|
|
||||||
send += " or <code>" + command[1] + "</code>"
|
|
||||||
send += "?"
|
|
||||||
self.send_message(send)
|
|
||||||
else:
|
|
||||||
self.send_message("Command <code>" + full[0] + "</code> not found. Please try again.")
|
|
||||||
|
|
||||||
return "nothing", ["happened"]
|
|
||||||
|
|
||||||
|
|
||||||
def fuzzy_match_command(self, input):
|
|
||||||
matches = ["not exact"]
|
|
||||||
for command in self.commands.keys():
|
|
||||||
if lev.ratio(input.lower(),command) > 0.8:
|
|
||||||
matches.append(command)
|
|
||||||
if lev.ratio(input.lower(),command) == 1:
|
|
||||||
return ["EXACT", command]
|
|
||||||
|
|
||||||
return matches
|
|
||||||
|
|
||||||
|
|
||||||
def send_thinking_note(self):
|
def send_thinking_note(self):
|
||||||
|
138
bot/framework.py
Normal file
138
bot/framework.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import datetime
|
||||||
|
from bot.api import telegram, google, weather, reddit
|
||||||
|
import Levenshtein as lev
|
||||||
|
|
||||||
|
class BotFramework():
|
||||||
|
"""Main functionality for a bot """
|
||||||
|
|
||||||
|
def __init__(self, name, version, prst):
|
||||||
|
"""Inits the Bot with a few conf. vars
|
||||||
|
Args: -> name:str - Name of the bot
|
||||||
|
-> version:str - Version number
|
||||||
|
-> prst:shelveObj - persistence
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.version = version
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
# Persistent variable
|
||||||
|
self.persistence = prst
|
||||||
|
# Uptime counter
|
||||||
|
self.start_time = datetime.datetime.now()
|
||||||
|
|
||||||
|
self.emoji_dict = {
|
||||||
|
"a" : ":regional_indicator_symbol_letter_a:",
|
||||||
|
"b" : ":regional_indicator_symbol_letter_b:",
|
||||||
|
"c" : ":regional_indicator_symbol_letter_c:",
|
||||||
|
"d" : ":regional_indicator_symbol_letter_d:",
|
||||||
|
"e" : ":regional_indicator_symbol_letter_e:",
|
||||||
|
"f" : ":regional_indicator_symbol_letter_f:",
|
||||||
|
"g" : ":regional_indicator_symbol_letter_g:",
|
||||||
|
"h" : ":regional_indicator_symbol_letter_h:",
|
||||||
|
"i" : ":regional_indicator_symbol_letter_i:",
|
||||||
|
"j" : ":regional_indicator_symbol_letter_j:",
|
||||||
|
"k" : ":regional_indicator_symbol_letter_k:",
|
||||||
|
"l" : ":regional_indicator_symbol_letter_l:",
|
||||||
|
"m" : ":regional_indicator_symbol_letter_m:",
|
||||||
|
"n" : ":regional_indicator_symbol_letter_n:",
|
||||||
|
"o" : ":regional_indicator_symbol_letter_o:",
|
||||||
|
"p" : ":regional_indicator_symbol_letter_p:",
|
||||||
|
"q" : ":regional_indicator_symbol_letter_q:",
|
||||||
|
"r" : ":regional_indicator_symbol_letter_r:",
|
||||||
|
"s" : ":regional_indicator_symbol_letter_s:",
|
||||||
|
"t" : ":regional_indicator_symbol_letter_t:",
|
||||||
|
"u" : ":regional_indicator_symbol_letter_u:",
|
||||||
|
"v" : ":regional_indicator_symbol_letter_v:",
|
||||||
|
"w" : ":regional_indicator_symbol_letter_w:",
|
||||||
|
"x" : ":regional_indicator_symbol_letter_x:",
|
||||||
|
"y" : ":regional_indicator_symbol_letter_y:",
|
||||||
|
"z" : ":regional_indicator_symbol_letter_z:",
|
||||||
|
"0" : ":keycap_digit_zero:",
|
||||||
|
"1" : ":keycap_digit_one:",
|
||||||
|
"2" : ":keycap_digit_two:",
|
||||||
|
"3" : ":keycap_digit_three:",
|
||||||
|
"4" : ":keycap_digit_four:",
|
||||||
|
"5" : ":keycap_digit_five:",
|
||||||
|
"6" : ":keycap_digit_six:",
|
||||||
|
"7" : ":keycap_digit_seven:",
|
||||||
|
"8" : ":keycap_digit_eight:",
|
||||||
|
"9" : ":keycap_digit_nine:",
|
||||||
|
}
|
||||||
|
|
||||||
|
self.telegram = telegram.TelegramIO(self.persistence)
|
||||||
|
|
||||||
|
def react_chats(self):
|
||||||
|
"""Checks unanswered messages and answers them"""
|
||||||
|
num = self.telegram.fetch_updates()
|
||||||
|
for i in range(num):
|
||||||
|
self.react_command()
|
||||||
|
|
||||||
|
|
||||||
|
def react_command(self):
|
||||||
|
"""Reacts if a new command is present
|
||||||
|
|
||||||
|
Returns command, params iff the command is a hardware-one (for the clock), else None"""
|
||||||
|
message = self.telegram.process_message()
|
||||||
|
if message == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
message = message[1:] #remove first "/"
|
||||||
|
tmp = message.split(" ")
|
||||||
|
cmd = tmp[0]
|
||||||
|
params = tmp[1:]
|
||||||
|
|
||||||
|
def call_command(cmd, par):
|
||||||
|
result = self.commands[cmd](*par)
|
||||||
|
# *params means the list is unpacked and handed over as separate arguments.
|
||||||
|
self.telegram.send_message(result)
|
||||||
|
|
||||||
|
if self.is_command(cmd): # first word
|
||||||
|
call_command(cmd, params)
|
||||||
|
elif cmd in self.persistence["bot"]["aliases"]:
|
||||||
|
dealias = self.persistence["bot"]["aliases"][cmd].split(" ") # as a list
|
||||||
|
new_cmd = dealias[0]
|
||||||
|
params = dealias[1:] + params
|
||||||
|
self.telegram.send_message("Substituted <code>" + cmd + "</code> to <code>" + self.persistence["bot"]["aliases"][cmd] + "</code> and got:")
|
||||||
|
call_command(new_cmd, params)
|
||||||
|
else:
|
||||||
|
self.telegram.send_message("Command <code>" + tmp[0] + "</code> not found.")
|
||||||
|
|
||||||
|
|
||||||
|
def is_command(self, input):
|
||||||
|
"""checks if we have a command. Returns true if yes and False if not
|
||||||
|
|
||||||
|
Also sends a mesage if close to an existing command
|
||||||
|
"""
|
||||||
|
max_match = 0
|
||||||
|
command_candidate = ""
|
||||||
|
for command in self.commands.keys():
|
||||||
|
match = lev.ratio(input.lower(),command)
|
||||||
|
if match > 0.7 and match > max_match:
|
||||||
|
max_match = match
|
||||||
|
command_candidate = command
|
||||||
|
if max_match == 1:
|
||||||
|
return True
|
||||||
|
if max_match != 0:
|
||||||
|
self.telegram.send_message("Did you mean <code>" + command_candidate + "</code>")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def emojify_word(self,word):
|
||||||
|
""""""
|
||||||
|
string_emoji = ""
|
||||||
|
for letter in word:
|
||||||
|
if letter in self.emoji_dict:
|
||||||
|
string_emoji += self.emoji_dict[letter.lower()]
|
||||||
|
else:
|
||||||
|
string_emoji += letter
|
||||||
|
return string_emoji
|
||||||
|
|
||||||
|
|
||||||
|
def write_bot_log(self, function_name, error_message):
|
||||||
|
""""""
|
||||||
|
out = datetime.datetime.now().strftime("%d.%m.%y - %H:%M")
|
||||||
|
out += " @ " + function_name
|
||||||
|
out += " --> " + error_message
|
||||||
|
self.persistence["bot"]["log"] += [out]
|
||||||
|
|
||||||
|
|
231
bot/main.py
231
bot/main.py
@ -1,33 +1,27 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from bot.api import telegram, google, weather, reddit
|
from bot.api import telegram, google, weather, reddit
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import datetime
|
import datetime
|
||||||
import emoji
|
import emoji
|
||||||
|
|
||||||
class ChatBot():
|
import bot.framework as FW
|
||||||
|
|
||||||
|
class ChatBot(FW.BotFramework):
|
||||||
""""""
|
""""""
|
||||||
def __init__(self, name, version, prst):
|
def __init__(self, name, version, prst, hw_commands):
|
||||||
"""Inits the Bot with a few conf. vars
|
"""Inits the Bot with a few conf. vars
|
||||||
Args: -> name:str - Name of the bot
|
Args: -> name:str - Name of the bot
|
||||||
-> version:str - Version number
|
-> version:str - Version number
|
||||||
-> prst:shelveObj - persistence
|
-> prst:shelveObj - persistence
|
||||||
"""
|
"""
|
||||||
|
super().__init__(name, version, prst)
|
||||||
self.version = version
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
# Persistent variable
|
|
||||||
self.persistence = prst
|
|
||||||
# Uptime counter
|
|
||||||
self.start_time = datetime.datetime.now()
|
|
||||||
self.persistence["bot"]["reboots"] += 1
|
|
||||||
|
|
||||||
# Available commands. Must be manually updated!
|
# Available commands. Must be manually updated!
|
||||||
self.commands = {
|
self.commands = dict({
|
||||||
"help" : self.bot_show_help,
|
"help" : self.bot_show_help,
|
||||||
"status" : self.bot_print_status,
|
"status" : self.bot_print_status,
|
||||||
"log" : self.bot_print_log,
|
"log" : self.bot_print_log,
|
||||||
@ -43,81 +37,10 @@ class ChatBot():
|
|||||||
"meme" : self.bot_send_meme,
|
"meme" : self.bot_send_meme,
|
||||||
"news" : self.bot_send_news,
|
"news" : self.bot_send_news,
|
||||||
"list" : self.bot_list,
|
"list" : self.bot_list,
|
||||||
|
"alias" : self.bot_save_alias,
|
||||||
|
}, **hw_commands)
|
||||||
|
# concat bot_commands + hw-commands
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
self.emoji_dict = {
|
|
||||||
"a" : ":regional_indicator_symbol_letter_a:",
|
|
||||||
"b" : ":regional_indicator_symbol_letter_b:",
|
|
||||||
"c" : ":regional_indicator_symbol_letter_c:",
|
|
||||||
"d" : ":regional_indicator_symbol_letter_d:",
|
|
||||||
"e" : ":regional_indicator_symbol_letter_e:",
|
|
||||||
"f" : ":regional_indicator_symbol_letter_f:",
|
|
||||||
"g" : ":regional_indicator_symbol_letter_g:",
|
|
||||||
"h" : ":regional_indicator_symbol_letter_h:",
|
|
||||||
"i" : ":regional_indicator_symbol_letter_i:",
|
|
||||||
"j" : ":regional_indicator_symbol_letter_j:",
|
|
||||||
"k" : ":regional_indicator_symbol_letter_k:",
|
|
||||||
"l" : ":regional_indicator_symbol_letter_l:",
|
|
||||||
"m" : ":regional_indicator_symbol_letter_m:",
|
|
||||||
"n" : ":regional_indicator_symbol_letter_n:",
|
|
||||||
"o" : ":regional_indicator_symbol_letter_o:",
|
|
||||||
"p" : ":regional_indicator_symbol_letter_p:",
|
|
||||||
"q" : ":regional_indicator_symbol_letter_q:",
|
|
||||||
"r" : ":regional_indicator_symbol_letter_r:",
|
|
||||||
"s" : ":regional_indicator_symbol_letter_s:",
|
|
||||||
"t" : ":regional_indicator_symbol_letter_t:",
|
|
||||||
"u" : ":regional_indicator_symbol_letter_u:",
|
|
||||||
"v" : ":regional_indicator_symbol_letter_v:",
|
|
||||||
"w" : ":regional_indicator_symbol_letter_w:",
|
|
||||||
"x" : ":regional_indicator_symbol_letter_x:",
|
|
||||||
"y" : ":regional_indicator_symbol_letter_y:",
|
|
||||||
"z" : ":regional_indicator_symbol_letter_z:",
|
|
||||||
"0" : ":keycap_digit_zero:",
|
|
||||||
"1" : ":keycap_digit_one:",
|
|
||||||
"2" : ":keycap_digit_two:",
|
|
||||||
"3" : ":keycap_digit_three:",
|
|
||||||
"4" : ":keycap_digit_four:",
|
|
||||||
"5" : ":keycap_digit_five:",
|
|
||||||
"6" : ":keycap_digit_six:",
|
|
||||||
"7" : ":keycap_digit_seven:",
|
|
||||||
"8" : ":keycap_digit_eight:",
|
|
||||||
"9" : ":keycap_digit_nine:",
|
|
||||||
}
|
|
||||||
|
|
||||||
self.telegram = telegram.TelegramIO(self.persistence, self.commands)
|
|
||||||
|
|
||||||
def add_commands(self, commands):
|
|
||||||
"""adds new commands to an existing list"""
|
|
||||||
self.commands = {**self.commands, **commands}
|
|
||||||
self.telegram.update_commands(self.commands)
|
|
||||||
|
|
||||||
|
|
||||||
def react_command(self, command, params):
|
|
||||||
""""""
|
|
||||||
result = self.commands[command](*params)
|
|
||||||
#*params means the list is unpacked and handed over as separate arguments.
|
|
||||||
self.telegram.send_message(result)
|
|
||||||
|
|
||||||
|
|
||||||
def emojify_word(self,word):
|
|
||||||
""""""
|
|
||||||
string_emoji = ""
|
|
||||||
for letter in word:
|
|
||||||
if letter in self.emoji_dict:
|
|
||||||
string_emoji += self.emoji_dict[letter.lower()]
|
|
||||||
else:
|
|
||||||
string_emoji += letter
|
|
||||||
return string_emoji
|
|
||||||
|
|
||||||
|
|
||||||
def write_bot_log(self, function_name, error_message):
|
|
||||||
""""""
|
|
||||||
out = datetime.datetime.now().strftime("%d.%m.%y - %H:%M")
|
|
||||||
out += " @ " + function_name
|
|
||||||
out += " --> " + error_message
|
|
||||||
self.persistence["bot"]["log"] += [out]
|
|
||||||
|
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
@ -137,13 +60,14 @@ class ChatBot():
|
|||||||
def bot_print_status(self, *args):
|
def bot_print_status(self, *args):
|
||||||
"""Prints the bots current status and relevant information"""
|
"""Prints the bots current status and relevant information"""
|
||||||
delta = str(datetime.datetime.now() - self.start_time)
|
delta = str(datetime.datetime.now() - self.start_time)
|
||||||
|
message = "BeebBop, this is " + self.name + " (V." + self.version + ")\n"
|
||||||
try:
|
try:
|
||||||
ip = requests.get('https://api.ipify.org').text
|
ip = requests.get('https://api.ipify.org').text
|
||||||
except:
|
except:
|
||||||
ip = "not fetchable"
|
ip = "not fetchable"
|
||||||
message = "<pre>Status: Running :green_circle:\n"
|
message += "<pre>Status: Running :green_circle:\n"
|
||||||
message += "Uptime: " + delta[:delta.rfind(".")] + "\n"
|
message += "Uptime: " + delta[:delta.rfind(".")] + "\n"
|
||||||
message += "Reboots: " + str(self.persistence["bot"]["reboots"]) + "\n"
|
message += "Reboots: " + str(self.persistence["global"]["reboots"]) + "\n"
|
||||||
message += "IP-Adress: " + ip + "\n"
|
message += "IP-Adress: " + ip + "\n"
|
||||||
message += "Messages read: " + str(self.persistence["bot"]["messages_read"]) + "\n"
|
message += "Messages read: " + str(self.persistence["bot"]["messages_read"]) + "\n"
|
||||||
message += "Messages sent: " + str(self.persistence["bot"]["messages_sent"]) + "\n"
|
message += "Messages sent: " + str(self.persistence["bot"]["messages_sent"]) + "\n"
|
||||||
@ -217,10 +141,22 @@ class ChatBot():
|
|||||||
|
|
||||||
|
|
||||||
def bot_show_help(self, *args):
|
def bot_show_help(self, *args):
|
||||||
"""Shows a list of all commands and their description"""
|
"""Show a help message.
|
||||||
|
|
||||||
|
Usage: help {keyword}
|
||||||
|
Keywords:
|
||||||
|
* no kw - list of all commands
|
||||||
|
* full - all commands and their docstring
|
||||||
|
* command-name - specific command and its docstring
|
||||||
|
"""
|
||||||
description = False
|
description = False
|
||||||
if "full" in args:
|
if len(args) > 0:
|
||||||
description = True
|
if args[0] == "full":
|
||||||
|
description = True
|
||||||
|
elif args[0] in self.commands:
|
||||||
|
send_text = "<b>" + args[0] + "</b>\n"
|
||||||
|
send_text += "<code>" + self.commands[args[0]].__doc__ + "</code>"
|
||||||
|
return send_text
|
||||||
|
|
||||||
send_text = "BeebBop, this is " + self.name + " (V." + self.version + ")\n"
|
send_text = "BeebBop, this is " + self.name + " (V." + self.version + ")\n"
|
||||||
send_text += "Here is what I can do up to now: \n"
|
send_text += "Here is what I can do up to now: \n"
|
||||||
@ -236,12 +172,16 @@ class ChatBot():
|
|||||||
|
|
||||||
|
|
||||||
def bot_print_log(self, *args):
|
def bot_print_log(self, *args):
|
||||||
"""Shows an error-log, mostly of bad api-requests. Usage
|
"""Show an error-log, mostly of bad api-requests.
|
||||||
log clear - clears log
|
|
||||||
log system - shows python output"""
|
Usage: log {keyword}
|
||||||
|
Keywords:
|
||||||
|
* clear - clears log
|
||||||
|
* system - shows python output
|
||||||
|
"""
|
||||||
|
|
||||||
if "clear" in args:
|
if "clear" in args:
|
||||||
self.persistence.write("log",[])
|
self.persistence["bot"]["log"] = []
|
||||||
return "Log cleared"
|
return "Log cleared"
|
||||||
elif "system" in args:
|
elif "system" in args:
|
||||||
path="persistence/log.txt"
|
path="persistence/log.txt"
|
||||||
@ -254,7 +194,7 @@ class ChatBot():
|
|||||||
return "could not read File"
|
return "could not read File"
|
||||||
|
|
||||||
send_text = ""
|
send_text = ""
|
||||||
for event in self.persistence.read("log"):
|
for event in self.persistence["bot"]["log"]:
|
||||||
send_text += event + "\n"
|
send_text += event + "\n"
|
||||||
if send_text == "":
|
if send_text == "":
|
||||||
send_text += "No errors up to now"
|
send_text += "No errors up to now"
|
||||||
@ -262,7 +202,13 @@ class ChatBot():
|
|||||||
|
|
||||||
|
|
||||||
def bot_show_wikipedia(self, *args):
|
def bot_show_wikipedia(self, *args):
|
||||||
"""Shows the wikipedia entry for a given term"""
|
"""Shows the wikipedia entry for a given term
|
||||||
|
|
||||||
|
Usage: wikipedia <language> <term>
|
||||||
|
Keywords:
|
||||||
|
* language - de, fr, en ...
|
||||||
|
* term - search term, can consist of multiple words
|
||||||
|
"""
|
||||||
if len(args) == 0:
|
if len(args) == 0:
|
||||||
return "Please provide the first argument for language (de or fr or en or ...) and then your query"
|
return "Please provide the first argument for language (de or fr or en or ...) and then your query"
|
||||||
args = list(args)
|
args = list(args)
|
||||||
@ -283,7 +229,12 @@ class ChatBot():
|
|||||||
|
|
||||||
def bot_zvv(self, *args):
|
def bot_zvv(self, *args):
|
||||||
"""Uses the swiss travel api to return the best route between a start- and endpoint.
|
"""Uses the swiss travel api to return the best route between a start- and endpoint.
|
||||||
usage: 'start' to 'finish'"""
|
|
||||||
|
Usage: zvv <start> 'to' <finish>
|
||||||
|
Keywords:
|
||||||
|
* start - start point (can be more than 1 word9
|
||||||
|
* end - end point
|
||||||
|
"""
|
||||||
if len(args) < 3:
|
if len(args) < 3:
|
||||||
return "Please specify a start- and endpoint as well as a separator (the 'to')"
|
return "Please specify a start- and endpoint as well as a separator (the 'to')"
|
||||||
|
|
||||||
@ -360,7 +311,12 @@ class ChatBot():
|
|||||||
|
|
||||||
|
|
||||||
def bot_tell_joke(self, *args):
|
def bot_tell_joke(self, *args):
|
||||||
"""Tells you the top joke on r/jokes"""
|
"""Tells you the top joke on r/jokes
|
||||||
|
|
||||||
|
Usage: joke {number}
|
||||||
|
Keywords:
|
||||||
|
* number - number of jokes
|
||||||
|
"""
|
||||||
|
|
||||||
params_sorted = self.match_reddit_params(*args)
|
params_sorted = self.match_reddit_params(*args)
|
||||||
|
|
||||||
@ -427,20 +383,33 @@ class ChatBot():
|
|||||||
|
|
||||||
|
|
||||||
def bot_list(self, *args):
|
def bot_list(self, *args):
|
||||||
"""Shows and interacts with a list. Usage
|
"""Interacts with a list (like a shopping list eg.)
|
||||||
list <name> <action> {object}
|
|
||||||
actions are: create, delete, print, clear, add, remove
|
Usage list <name> <action> {object}
|
||||||
example:
|
Keyword:
|
||||||
list new shopping : creates list name shopping
|
* name - name of list
|
||||||
|
* action - create, delete, all, print, clear, add, remove
|
||||||
|
* object - might not be needed: index to delete, or item to add
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
list create shopping : creates list name shopping
|
||||||
list shopping add bread : adds bread to the list
|
list shopping add bread : adds bread to the list
|
||||||
list shopping print
|
list shopping print
|
||||||
list shopping clear
|
list shopping clear
|
||||||
|
list all
|
||||||
"""
|
"""
|
||||||
output = ""
|
output = ""
|
||||||
# args = list(args)
|
# args = list(args)
|
||||||
if len(args) < 2:
|
if len(args) == 0:
|
||||||
return "Missing parameters"
|
return "Missing parameters"
|
||||||
try:
|
try:
|
||||||
|
if args[0] == "all":
|
||||||
|
try:
|
||||||
|
return "Existing lists are: " + list(self.persistence["global"]["lists"].keys()).join(" ")
|
||||||
|
except:
|
||||||
|
return "No lists created."
|
||||||
|
if len(args) < 2:
|
||||||
|
return "Missing parameters"
|
||||||
if args[0] == "create":
|
if args[0] == "create":
|
||||||
lname = " ".join(args[1:])
|
lname = " ".join(args[1:])
|
||||||
self.persistence["global"]["lists"][lname] = []
|
self.persistence["global"]["lists"][lname] = []
|
||||||
@ -454,6 +423,7 @@ class ChatBot():
|
|||||||
act = args[1]
|
act = args[1]
|
||||||
if act == "print":
|
if act == "print":
|
||||||
sl = self.persistence["global"]["lists"][lname]
|
sl = self.persistence["global"]["lists"][lname]
|
||||||
|
output += "Content of " + lname + ":\n"
|
||||||
for ind,thing in enumerate(sl):
|
for ind,thing in enumerate(sl):
|
||||||
output += str(ind+1) + ". " + thing + "\n"
|
output += str(ind+1) + ". " + thing + "\n"
|
||||||
elif act == "clear":
|
elif act == "clear":
|
||||||
@ -474,7 +444,48 @@ class ChatBot():
|
|||||||
|
|
||||||
def bot_save_alias(self, *args):
|
def bot_save_alias(self, *args):
|
||||||
"""Save a shortcut for special commands (+params)
|
"""Save a shortcut for special commands (+params)
|
||||||
usage: /alias sa shopping add
|
|
||||||
Means: /sa will now be treated as input /shopping add"""
|
|
||||||
|
|
||||||
return "Does this look finished to you?"
|
Usage: alias <alias-name> {<alias-name> <command>}
|
||||||
|
Keywords:
|
||||||
|
* action - all, add, delete or clear (deleta all)
|
||||||
|
* alias-name - short name
|
||||||
|
* command - command to be executed, can contain arguments for the command
|
||||||
|
Example usage:
|
||||||
|
* alias sa list shopping add
|
||||||
|
* alias sp list shopping print
|
||||||
|
Means that '/sa ...' will now be treated as if typed '/list shopping add ...'
|
||||||
|
"""
|
||||||
|
# args = list(args)
|
||||||
|
if len(args) == 0:
|
||||||
|
return "Missing parameters"
|
||||||
|
try:
|
||||||
|
if args[0] == "clear":
|
||||||
|
self.persistence["bot"]["aliases"] = {}
|
||||||
|
return "All aliases cleared"
|
||||||
|
elif args[0] == "all":
|
||||||
|
try:
|
||||||
|
output = "Existing aliases are:\n"
|
||||||
|
for j, k in self.persistence["bot"]["aliases"].items():
|
||||||
|
output += j + " -> " + k + "\n"
|
||||||
|
return output
|
||||||
|
except:
|
||||||
|
return "No aliases created."
|
||||||
|
|
||||||
|
if len(args) < 2:
|
||||||
|
return "Missing parameters"
|
||||||
|
if args[0] == "delete":
|
||||||
|
ak = args[1]
|
||||||
|
self.persistence["bot"]["aliases"].pop(ak, None) # no error if key doesnt exist
|
||||||
|
return "Deleted alias " + ak
|
||||||
|
|
||||||
|
if len(args) < 3:
|
||||||
|
return "Missing parameters"
|
||||||
|
if args[0] == "add":
|
||||||
|
ak = args[1]
|
||||||
|
cmd = " ".join(args[2:])
|
||||||
|
self.persistence["bot"]["aliases"][ak] = cmd
|
||||||
|
return "Created alias for " + ak
|
||||||
|
|
||||||
|
except:
|
||||||
|
return "Could not handle your request. Maybe check the keys?"
|
||||||
|
return "Bad input..."
|
||||||
|
@ -13,6 +13,8 @@ class ClockFace(object):
|
|||||||
"""Actual functions one might need for a clock"""
|
"""Actual functions one might need for a clock"""
|
||||||
|
|
||||||
def __init__(self, text_speed=18, prst=""):
|
def __init__(self, text_speed=18, prst=""):
|
||||||
|
""""""
|
||||||
|
self.persistence = prst
|
||||||
self.IO = led.OutputHandler(32,16)
|
self.IO = led.OutputHandler(32,16)
|
||||||
self.tspeed = text_speed
|
self.tspeed = text_speed
|
||||||
|
|
||||||
@ -21,6 +23,12 @@ class ClockFace(object):
|
|||||||
self.output_queue = []
|
self.output_queue = []
|
||||||
# Threads to execute next
|
# Threads to execute next
|
||||||
|
|
||||||
|
self.commands = {
|
||||||
|
"blink" : self.alarm_blink,
|
||||||
|
"wakeup" : self.wake_light,
|
||||||
|
"showmessage" : self.show_message,
|
||||||
|
}
|
||||||
|
|
||||||
self.weather = ""
|
self.weather = ""
|
||||||
self.brightness_overwrite = {"value" : 1, "duration" : 0}
|
self.brightness_overwrite = {"value" : 1, "duration" : 0}
|
||||||
|
|
||||||
@ -82,6 +90,11 @@ class ClockFace(object):
|
|||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
### Higher level commands, accessible from the chat-bot
|
### Higher level commands, accessible from the chat-bot
|
||||||
|
def external_action(self, command, params):
|
||||||
|
""""""
|
||||||
|
self.commands[command](*params)
|
||||||
|
|
||||||
|
|
||||||
def wake_light(self, duration=600):
|
def wake_light(self, duration=600):
|
||||||
"""Simulates a sunris, takes one optional parameter: the duration"""
|
"""Simulates a sunris, takes one optional parameter: the duration"""
|
||||||
def output(duration):
|
def output(duration):
|
||||||
@ -99,11 +112,10 @@ class ClockFace(object):
|
|||||||
self.IO.set_matrix(ones,colors=[col])
|
self.IO.set_matrix(ones,colors=[col])
|
||||||
time.sleep(int(duration) / 20)
|
time.sleep(int(duration) / 20)
|
||||||
|
|
||||||
|
|
||||||
self.run(output,(duration,))
|
self.run(output,(duration,))
|
||||||
|
|
||||||
|
|
||||||
def alarm_blink(self, duration, frequency):
|
def alarm_blink(self, duration=0, frequency=0):
|
||||||
"""Blinks the whole screen (red-black). Duration in seconds, frequency in Hertz"""
|
"""Blinks the whole screen (red-black). Duration in seconds, frequency in Hertz"""
|
||||||
def output(duration, frequency):
|
def output(duration, frequency):
|
||||||
self.set_brightness(value=1)
|
self.set_brightness(value=1)
|
||||||
@ -118,8 +130,8 @@ class ClockFace(object):
|
|||||||
time.sleep(1/frequency)
|
time.sleep(1/frequency)
|
||||||
self.IO.set_matrix(empty)
|
self.IO.set_matrix(empty)
|
||||||
time.sleep(1/frequency)
|
time.sleep(1/frequency)
|
||||||
|
if not(duration == 0 or frequency == 0):
|
||||||
self.run(output,(duration, frequency))
|
self.run(output,(duration, frequency))
|
||||||
|
|
||||||
|
|
||||||
def image_show(self, image, duration):
|
def image_show(self, image, duration):
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import wrapper
|
|
||||||
|
|
||||||
from threading import Thread
|
|
||||||
import time
|
|
||||||
|
|
||||||
class DashBoardWrapper(wrapper.Wrapper):
|
|
||||||
def __init__(self, own_module, *other_modules):
|
|
||||||
""""""
|
|
||||||
super().__init__(own_module, other_modules)
|
|
||||||
print("Initializing DASHBOARD-functionality")
|
|
||||||
# mainloop
|
|
||||||
|
|
||||||
def mainloop(self):
|
|
||||||
super(DashBoardWrapper, self).mainloop(sleep_delta = 3600*3) #3hours refresh-cycle
|
|
||||||
self.set_weather()
|
|
||||||
self.set_shopping_list()
|
|
||||||
self.set_bot_logs()
|
|
||||||
self.set_joke()
|
|
||||||
self.bot.refresh()
|
|
||||||
|
|
||||||
def set_weather(self):
|
|
||||||
weather = self.bot.bot_show_weather("zurich")
|
|
||||||
...
|
|
||||||
self.own.set_weather(weather)
|
|
17
launcher.py
17
launcher.py
@ -2,7 +2,6 @@
|
|||||||
import bot.main
|
import bot.main
|
||||||
import clock.main
|
import clock.main
|
||||||
import dashboard.main
|
import dashboard.main
|
||||||
|
|
||||||
# wrapper
|
# wrapper
|
||||||
import wrapper
|
import wrapper
|
||||||
|
|
||||||
@ -10,17 +9,17 @@ import wrapper
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
import shelve
|
import shelve
|
||||||
|
|
||||||
|
|
||||||
class Launcher():
|
class Launcher():
|
||||||
"""Launches all other submodules"""
|
"""Launches all other submodules"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
""""""
|
""""""
|
||||||
self.persistence = shelve.open('persistence/prst.db', writeback=True)
|
self.persistence = shelve.DbfilenameShelf("persistence/prst.db", writeback = True)
|
||||||
self.init_persistence()
|
if len(self.persistence) == 0:
|
||||||
# TODO populate the persistence
|
self.init_persistence()
|
||||||
self.bot_module = bot.main.ChatBot(name="ChatterBot", version="2.1", prst=self.persistence)
|
self.persistence["global"]["reboots"] += 1
|
||||||
self.clock_module = clock.main.ClockFace(prst=self.persistence)
|
self.clock_module = clock.main.ClockFace(prst=self.persistence)
|
||||||
|
self.bot_module = bot.main.ChatBot(name="ChatterBot", version="2.1", prst=self.persistence, hw_commands=self.clock_module.commands)
|
||||||
|
|
||||||
self.threads = []
|
self.threads = []
|
||||||
self.threads.append(Thread(target=self.chatbot))
|
self.threads.append(Thread(target=self.chatbot))
|
||||||
@ -42,6 +41,7 @@ class Launcher():
|
|||||||
self.dashboard = wrapper.DashBoardWrapper(self.dashboard_module, self.bot_module)
|
self.dashboard = wrapper.DashBoardWrapper(self.dashboard_module, self.bot_module)
|
||||||
|
|
||||||
def init_persistence(self):
|
def init_persistence(self):
|
||||||
|
print("New Persistence created")
|
||||||
self.persistence["bot"] = {
|
self.persistence["bot"] = {
|
||||||
"messages_read": 0,
|
"messages_read": 0,
|
||||||
"messages_sent": 0,
|
"messages_sent": 0,
|
||||||
@ -49,12 +49,13 @@ class Launcher():
|
|||||||
"photos_sent": 0,
|
"photos_sent": 0,
|
||||||
"log": [],
|
"log": [],
|
||||||
"chat_members": {},
|
"chat_members": {},
|
||||||
"reboots": 0
|
"aliases" : {}
|
||||||
}
|
}
|
||||||
self.persistence["clock"] = {}
|
self.persistence["clock"] = {}
|
||||||
self.persistence["dashboard"] = {}
|
self.persistence["dashboard"] = {}
|
||||||
self.persistence["global"] = {
|
self.persistence["global"] = {
|
||||||
"lists" : {}
|
"lists" : {},
|
||||||
|
"reboots": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
1
persistence/__init__.py
Normal file
1
persistence/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
#placeholder
|
56
wrapper.py
56
wrapper.py
@ -19,6 +19,8 @@ class Wrapper():
|
|||||||
while True:
|
while True:
|
||||||
action()
|
action()
|
||||||
time.sleep(sleep_delta)
|
time.sleep(sleep_delta)
|
||||||
|
self.own.persistence.sync()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -87,34 +89,44 @@ class BotWrapper(Wrapper):
|
|||||||
self.bot = own_module
|
self.bot = own_module
|
||||||
self.clock = other_modules[0]
|
self.clock = other_modules[0]
|
||||||
|
|
||||||
|
|
||||||
# available hw-commands. Must be updated manually!
|
|
||||||
self.hw_commands = {
|
|
||||||
"blink" : self.clock.alarm_blink,
|
|
||||||
"wakeup" : self.clock.wake_light,
|
|
||||||
"showmessage" : self.clock.show_message,
|
|
||||||
|
|
||||||
}
|
|
||||||
self.bot.add_commands(self.hw_commands)
|
|
||||||
|
|
||||||
self.mainloop(10)
|
self.mainloop(10)
|
||||||
|
|
||||||
|
|
||||||
def mainloop(self, sleep_delta):
|
def mainloop(self, sleep_delta):
|
||||||
"""Calls the telegram entity regularly to check for activity"""
|
"""Calls the telegram entity regularly to check for activity"""
|
||||||
def perform_loop():
|
def perform_loop():
|
||||||
result = self.bot.telegram.fetch_updates()
|
self.bot.react_chats()
|
||||||
if len(result) != 0:
|
# num = self.bot.telegram.fetch_updates()
|
||||||
command, params = self.bot.telegram.handle_result(result)
|
# for message in range(num):
|
||||||
if command != "nothing":
|
# command, params = self.bot.react_command() # returns None if handled internally
|
||||||
if command in self.hw_commands:
|
# if command != None:
|
||||||
self.react_hw_command(command,params) # hw-level
|
# self.clock.external_action(command, params)
|
||||||
else:
|
super().mainloop(sleep_delta, perform_loop)
|
||||||
self.bot.react_command(command,params) # sw-level
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
class DashBoardWrapper(Wrapper):
|
||||||
|
def __init__(self, own_module, *other_modules):
|
||||||
|
"""Wrapper for the dashboard functionality"""
|
||||||
|
super().__init__(own_module, other_modules)
|
||||||
|
# self.mainloop(2 * 3600) # 2 hours refresh-cycle
|
||||||
|
|
||||||
|
|
||||||
|
def mainloop(self, sleep_delta):
|
||||||
|
def perform_loop():
|
||||||
|
self.set_weather()
|
||||||
|
self.set_shopping_list()
|
||||||
|
self.set_bot_logs()
|
||||||
|
self.set_joke()
|
||||||
|
self.bot.refresh()
|
||||||
|
|
||||||
super().mainloop(sleep_delta, perform_loop)
|
super().mainloop(sleep_delta, perform_loop)
|
||||||
|
|
||||||
def react_hw_command(self, command, params):
|
|
||||||
""""""
|
def set_weather(self):
|
||||||
# so params is a list, and so, to pass the commands, we need to unpack it:
|
weather = self.bot.bot_show_weather("zurich")
|
||||||
self.hw_commands[command](*params)
|
...
|
||||||
|
self.own.set_weather(weather)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user