diff --git a/README.md b/README.md index 1a18073..af24ca7 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ You may want to set data.json as a volume to be able to make changes on the fly ![matrix room screenshot](screenshots/example2.png) + +![Adding questions from matrix](screenshots/adding-questions.gif) ## Features in the works * Alerts * Dynamic configuration diff --git a/commands.py b/commands.py index 804f8c6..212387b 100644 --- a/commands.py +++ b/commands.py @@ -1,161 +1,233 @@ import random +from turtle import update import features -jokes = [ - ["Testing","Testing you!"], - ["singing pokemon","Jiggalypuff! 🎙️"] -] -#jokes = [] -def handle_command(state,sender,message): +api = features.API() +debug = False + +def handle_command(sender, message): + state = api.get_user_state(sender) for command in commands: - if message.find(command)==0: - result = commands[command](state,message,sender) - return result + if message.find(command) == 0: + result = commands[command](state, message, sender) + return result if len(state) > 0: + if debug == 1: + return commands[state[0]](state, message, sender) try: - result = commands[state[0]](state,message,sender) + result = commands[state[0]](state, message, sender) return result except Exception as e: return { - "state":[], - "response":f"⚠️Error⚠️ \n {e}" + "response": f"⚠️Error⚠️" } - + return { - "response":None, - "state":state + "response": "", } - """ - Knock-knock implimentation - - when you receive message, read state. - user: knock knock - bot: who's there? -> bot responds and registers state - (user.state=[knock-knock]) - user: {statement} - bot: {statement} who? -> bot responds and stores statement - (user.stage=[knock-knock,{statement}]) - user: {punchline} - bot: Nice one! -> bot saves knock-knock joke - """ - -def nevermind(state,message,sender): + +def get_faq_string(): + return api.get_faq_string() + + +def nevermind(state, message, sender): + api.update_user_state(sender, []) return { - "response":"starting over.", - "state":[] + "response": "starting over." } -def knock_knock(state,message,sender): - #return {response:"",state:""} - try: - if state[0] != "knock knock": - #clear the state - state = [] - except: - pass +# faq + + +def get_faq(state, message, sender): if len(state) == 0: - state.append("knock knock") - return { - "response":"Who's there?", - "state": state - } - if len(state) == 1: - state.append(message) return { - "response":message + " who?", - "state":state + "response": api.get_faq_string() } - if len(state) == 2: - state.append(message) - jokes.append([ - state[1],state[2] - ]) - print("joke registered.") - return { - "response":"Hah! very funny!", - "state":[] - } - return { - "response":"Cleared.", - "state":[] + "response": api.get_faq_string() } -"""'joke' command - ------------- - user:!joke - bot:Knock knock -> bot initializes state - (user.state=[joke]) - user: Who's there? - bot: {statement} -> bot responds with chosen joke - (user.state=[joke,chosenjoke]) - user: {statement} who? - bot: - - """ -def joke(state,message,sender): - if len(jokes) == 0: - return { - "response":"I don't know any jokes...", - "state":[] - } - + +def add_question(state, message, sender): if len(state) == 0: + api.update_user_state(sender, ["!add question"]) return { - "response":"Knock knock", - "state":["joke"] + 'response': "Please enter the question title." } elif len(state) == 1: - if message.find("there?") > 0 : - #get random joke index - state.append(random.randint(0,len(jokes)-1)) - return { - "response":jokes[state[1]][0], - "state":state - } - else: + state.append(message) + api.update_user_state(sender, state) + return { + 'response': 'Please enter the answer to the question.' + } + elif len(state) == 2: + state.append(message) + api.update_user_state(sender, state) + state = api.get_user_state(sender) + return { + 'response': f"Your question is:\n---\n# {state[1]}\n{state[2]}\n\n---\nIf this is what you want respond with `confirm`\n If you'd like to start over respond with `restart`\n Respond with `nevermind` to cancel entirely" + } + elif len(state) == 3: + if message == 'confirm': + api.add_question({ + 'question': state[1], + 'answer': state[2] + }) + api.update_user_state(sender, []) return { - "response":"ask, \"who's there?\"", - "state":state + 'response': 'Question added.' } - elif len(state) == 2: - if message.find("who?") > 0: + elif message == 'restart': + api.update_user_state(sender, ['!add question']) return { - "response":jokes[state[1]][1], - "state":[] + 'response': 'Please enter the question title.' } else: return { - "response":"ask, \""+ jokes[state[1]][0] +" who?\"", - "state":state + 'response': f"Your question is:\n# {state[1]}\n{state[2]}\n\nIf this is what you want respond with `confirm`\n If you'd like to start over respond with `restart`\n Respond with `nevermind` to cancel entirely" } - + return { - "response":None, - "state":[] + 'response': None } - - """ - """ - def appointments(): - + +def update_question(state, message, sender): + if len(api.data['faq']['questions']) == 0: + api.update_user_state(sender, []) return { - "response":None, - state:[] + 'response': "No questions registered." } + elif len(state)==0: + api.update_user_state(sender,"!update question") + return { + "response":f"Choose a question to update:\n {api.get_faq_questions}" + } + return { + 'response': None, + + } + + +def remove_question(state, message, sender): + if len(api.data['faq']['questions']) == 0: + api.update_user_state(sender, []) + return { + 'response': "No questions registered." + } + elif len(state)==0: + api.update_user_state(sender,["!remove question"]) + return { + "response":f"Choose a question to remove:\n {api.get_faq_questions()}" + } + elif len(state) == 1: + try: + int(message) + except: + return {"response":"Please enter a number."} + state.append(message) + if int(state[1]) < len(api.data['faq']['questions']) and int(state[1]) >=0: + if debug == True: + api.remove_question(int(state[1])) + api.update_user_state(sender,[]) + return { + 'response':'Question removed' + } + else: + try: + api.update_user_state(sender,[]) + api.remove_question(int(state[1])) + return { + 'response':'Question removed' + } + except: + pass + + api.update_user_state(sender,["!remove question"]) + return { + 'response':'Please enter the correct question #' + } + + + return { + 'response': None, + + } + + +def update_header(state, message, sender): + return { + 'response': None, + + } + + +def remove_header(state, message, sender): + return { + 'response': None, + + } + + +def update_footer(state, message, sender): + return { + 'response': None, + + } + + +def remove_footer(state, message, sender): + return { + 'response': None, + + } + + +def add_admin(state, message, sender): + return { + 'response': None, + + } + + +def remove_admin(state, message, sender): + return { + 'response': None, + + } + + +def appointments(): + + return { + "response": None, + state: [] + } + + commands = { - "nevermind":nevermind, - "knock knock":knock_knock, - "joke":joke -} + "nevermind": nevermind, + "!faq": get_faq, + "!add question": add_question, + "!remove question": remove_question, + "!update question": update_question, + "!update header": update_header, + "!remove header": remove_header, + "!update footer": update_footer, + "!remove footer": remove_footer, + "!add admin": add_admin, + "!remove admin": remove_admin, +} if __name__ == '__main__': + user = "admin" msg = "" + debug = True while msg != "exit": msg = input("user:") - user = handle_command(user,msg) - #print("state:",user) \ No newline at end of file + print("bot:"+handle_command(user, msg)['response']) + # print("state:",user) diff --git a/data-example.json b/data-example.json index 67fbcaf..08d3fb0 100644 --- a/data-example.json +++ b/data-example.json @@ -5,17 +5,17 @@ "faq": { "header": "# Simple example FAQ", "questions": [{ - "question": "# What is the meaning of life?", + "question": "What is the meaning of life?", "answer": "42.", "key_phrases": ["meaning", "life"] }, { - "question": "# Who is the coolest pokemon?", + "question": "Who is the coolest pokemon?", "answer": "Mewtwo! 🐈", "key_phrases": ["coolest", "pokemon"] }, { - "question": "# What is the coolest programming language?", + "question": "What is the coolest programming language?", "answer": "🐍 python!", "key_phrases": ["coolest", "programming", "language"] } diff --git a/features.py b/features.py index 6f4a12f..cdb6adf 100644 --- a/features.py +++ b/features.py @@ -11,20 +11,23 @@ class API: with open('data.json') as f: self.data = json.loads(f.read()) # Only refresh data when a change is made - + def update_data(self): + with open('data.json','r') as f: + self.data = json.loads(f.read()) def save(self): with open('data.json','w') as f: f.write(json.dumps(self.data,indent=2)) return True return False - #Administration - + #Administration def is_admin(self,handle): + self.update_data() #return bool pass def add_admin(self,handle): + self.update_data() #return bool if self.is_admin(handle): return True @@ -33,6 +36,7 @@ class API: self.save() return True def remove_admin(self,handle): + self.update_data() #return bool if self.is_admin(handle): i = self.data["admins"].index(handle) @@ -42,38 +46,83 @@ class API: else: return True + + #state management + def get_user_state(self,handle): + self.update_data() + if handle not in self.data["users"]: + self.data["users"][handle]={"state":[]} + self.save() + return self.data["users"][handle]["state"] + + def update_user_state(self,handle,state): + self.update_data() + current_state = self.get_user_state(handle) + self.data["users"][handle]["state"] = state + self.save() + return True + + #FAQ + def get_faq_string(self): + self.update_data() + faq = self.data["faq"] + return faq["header"] + "".join( + ["\n# "+q["question"]+"\n"+q["answer"]+"\n" for q in faq["questions"]] + ) + "\n"+ faq["footer"] + + def get_faq_questions(self): + self.update_data() + questions = self.data['faq']['questions'] + response = "" + index = 0 + for question in questions: + response += f"[{index}]:{question['question']}\n" + index += 1 + return response + + + def get_header(self): + self.update_data() return self.data["faq"]["header"] def set_header(self,hdr): + self.update_data() #return bool self.data["faq"]["header"] = hdr self.save() return True def get_questions(self): + self.update_data() #return questions return self.data["faq"]["questions"] def add_question(self,qtn): + self.update_data() #return bool self.data["faq"]["questions"].append(qtn) self.save() return True def remove_question(self,qtn_i): + self.update_data() #return bool self.data["faq"]["questions"].pop(qtn_i) self.save() return True def update_question(self,qtn_i,qtn): + self.update_data() #return bool self.data["faq"]["questions"][qtn_i]=qtn return True #Meetings - def get_meetings(): + def get_meetings(self): + self.update_data() pass - def request_meeting(): + def request_meeting(self): + self.update_data() pass - def accept_meeting(): + def accept_meeting(self): + self.update_data() pass diff --git a/main.py b/main.py index b721f89..54bd85e 100644 --- a/main.py +++ b/main.py @@ -3,58 +3,22 @@ import nio import simplematrixbotlib as botlib import commands -#TODO move state functionality entirely to features.py - data = json.loads(open('data.json').read()) -dfaq = data["faq"] -userstate = data['users'] -faq_text = dfaq["header"] + "".join(["\n"+q["question"]+"\n"+q["answer"] - for q in dfaq["questions"]]) + "\n\n"+dfaq["footer"] - creds = botlib.Creds( - data["homeserver"], - data['username'], - data['password'] - ) + data["homeserver"], + data['username'], + data['password'] +) bot = botlib.Bot(creds) -PREFIX = '!' - - -def load_faq(): - dfaq = json.loads(open('data.json').read())["faq"] - return dfaq["header"] + "".join(["\n"+q["question"]+"\n"+q["answer"] for q in dfaq["questions"]]) + "\n\n"+dfaq["footer"] - -def save_userstate(): - with open('data.json','w') as f: - f.write(json.dumps(data,indent=4)) @bot.listener.on_message_event async def faq(room, message): - if message.sender not in userstate: - userstate[message.sender] = {"state":[]} - save_userstate() - - - state = userstate[message.sender]['state'] - result = commands.handle_command(state,message.sender,message.body) - if result['response']!= None: - state = result['state'] - userstate[message.sender]['state'] = result['state'] - save_userstate() + result = commands.handle_command(message.sender, message.body) + if result['response'] != None and result['response'] != "": await bot.api.send_markdown_message( room.room_id, result['response'] ) - - - match = botlib.MessageMatch(room, message, bot, PREFIX) - #print(f"{message.sender} : {message.body}") - if match.is_not_from_this_bot() and match.prefix() and match.command( - "faq"): - await bot.api.send_markdown_message( - room.room_id, - load_faq()) - @bot.listener.on_custom_event(nio.InviteMemberEvent) @@ -62,31 +26,7 @@ async def example(room, event): if event.membership == "join": await bot.api.send_markdown_message( room.room_id, - load_faq() + commands.get_faq_string() ) - -def get_questions(): - return json.loads(open('data.json').read())["faq"]["questions"] - - -@bot.listener.on_message_event -async def faqresponse(room, message): - match = botlib.MessageMatch(room, message, bot, PREFIX) - if match.is_not_from_this_bot(): - response = "" - questions = get_questions() - for q in questions: - if sum([ 1 for kp in q["key_phrases"] if kp in message.body]) == len(q["key_phrases"]): - response += q["question"] + "\n" + q["answer"]+"\n" - if response != "": - await bot.api.send_markdown_message( - room.room_id, - response + dfaq["footer"] - ) - - - - - bot.run() diff --git a/screenshots/adding-questions.gif b/screenshots/adding-questions.gif new file mode 100644 index 0000000..925e413 Binary files /dev/null and b/screenshots/adding-questions.gif differ