diff --git a/.gitignore b/.gitignore index 1d9b6a4..0febb8e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ data.json session.txt -__pycache__ \ No newline at end of file +__pycache__ +easyappointments-flow.md \ No newline at end of file diff --git a/commands.py b/commands.py index 3aa721e..92fd8f3 100644 --- a/commands.py +++ b/commands.py @@ -1,5 +1,6 @@ import random import features +import json api = features.API() debug = False @@ -17,8 +18,11 @@ def handle_command(sender, message): result = commands[state[0]](state, message, sender) return result except Exception as e: + if __name__ != '__main__': + state = [] + api.update_user_state(sender, state) return { - "response": f"⚠️Error⚠️" + "response": f"⚠️Error⚠️\n{e}" } return { @@ -202,13 +206,202 @@ def meetings(state,message,sender): 'response':api.appointments.list_upcoming_appointments(sender) } -def appointments(): +def register_customer(state,message,sender): + if api.appointments.is_customer(sender): + return {'response':"Already registered."} + if state == ['!register2']: + return { + 'response':'What is your first name?' + } - return { - "response": None, - state: [] + if state == []: + state = ['!register'] + api.update_user_state(sender, state) + return { + 'response':'What is your first name?' + } + if len(state) == 1: + state.append(message) + api.update_user_state(sender, state) + return { + 'response':'What is your last name?' + } + if len(state) == 2: + state.append(message) + api.update_user_state(sender, state) + return { + 'response':'What is your e-mail?' + } + if len(state) == 3: + state.append(message) + api.update_user_state(sender, state) + return { + 'response':"What is your phone number?" + } + if len(state) == 4: + state.append(message) + api.update_user_state(sender, state) + resp = f"You've entered: `{state[1]} {state[2]} ({state[3]} / {state[4]})`\n**Is this correct?**\nIf so, respond with:Yes\nIf not, to restart respond with :No\nYou can always cancel with:nevermind" + return { + 'response':resp + } + if len(state) == 5: + if "yes" in message.lower(): + data = { + "firstName":state[1], + "lastName":state[2], + "email":state[3], + "phone":state[4], + "notes":f"matrix:{sender}" + } + if sender == "admin": + dat = json.dumps(data) + print(f"Data:\n{dat}") + if api.appointments.register_customer(data): + if(state[0]=='!register2'): + state = ['!book'] + api.update_user_state(sender, state) + return {'response':"Successfully registered.\n\n"+booking(['!book'],'',sender)['response']} + state = [] + api.update_user_state(sender, state) + return{'response':"Successfully registered."} + else: + return{'response':"Error, please try again."} + api.update_user_state(sender, []) + return { + 'response':'' + } + +def select_service(state,message,sender): + if len(state) >= 2: + return select_provider(state, message, sender) + # ['!book','select-service'] + if message == "!book": + return {'response':api.select_service()} + else: + try: + state.append(api.appointments.get_services()[int(message)]['id']) + api.update_user_state(sender, state) + return booking(state, "", sender) + except: + return {'response':'Error selecting service\nTo cancel type:nevermind'} + +def select_provider(state,message,sender): + if len(state) >= 3: + return select_time(state, message, sender) + if message == "": + return {'response':api.select_provider()} + try: + state.append(api.appointments.get_providers()[int(message)]['id']) + api.update_user_state(sender, state) + return booking(state,"",sender) + except: + return {'response':'Error selecting provider\nTo cancel type:nevermind'} + + +def select_time(state,message,sender): + if len(state) >= 5: + return booking(state, message, sender) + api.update_data() + # ['!book','select-time'] + if len(state) == 3: + msg = api.select_times(state[1],state[2],message) + if msg == False: + return {'response':"Please enter a date you'd like to book (\"YYYY-MM-DD\" format)"} + state.append(message) + api.update_user_state(sender, state) + return {'response':msg} + if len(state) == 4: + times = api.appointments.get_availabilities(state[1], state[2], state[3]) + if message in times: + state.append(message) + time = None + try: + time = times[int(message)-1] + state.append(time) + api.update_user_state(sender, state) + return booking(state, message, sender) + except: + return {'response':api.select_times(state[1],state[2],state[3])} + return {'response':error} + +def booking(state,message,sender): + if len(state) > 5: + state = [] + if state==[]: + state.append("!book") + api.update_user_state(sender, state) + """ + To book an appointment, you need (in order): + * the customer ID (now mapped to mxid) (0?) + * The service id (1) + * the provider id (2) + * the start date & time (3) + """ + #Quick checks + customer = api.appointments.is_customer(sender) + if customer == False: + return register_customer([], "", sender) + + #Service check + service = None + if len(api.appointments.services) == 0: + return {'response':"Error: there are no services"} + if len(api.appointments.services) == 1: + service = api.appointments.get_services()[0] + state.append(service) + api.update_user_state(sender, state) + return booking(state, message, sender) + if len(state) >= 2: + service = state[1] + if service == None: + return select_service(state, message, sender) + + + #Provider check + + provider = None + if len(api.appointments.providers) == 0: + return {'response':'error: there are no providers'} + if len(state) >= 3: + provider = state[2] + elif len(api.appointments.providers) == 1: + provider = api.appointments.get_providers()[0]['id'] + if service != None: + state.append(provider) + api.update_user_state(sender, state) + if provider == None: + return select_provider(state, message, sender) + + time = None + if len(state) == 5: + time = state[4] + if time == None: + return select_time(state, message, sender) + data = { + "start": state[3] + " "+ state[4]+":00", + "customerId":int(customer), + "providerId":int(provider), + "serviceId":int(service) } + result = api.appointments.register_appointment(data) + if result != False: + state = [] + api.update_user_state(sender, state) + return{'response':'Successfully registered appointment!'} + else: + state = [] + api.update_user_state(sender, state) + print(result) + return{'response':"Error: please try again"} + + return{'response':"Error: please try again"} + +def services(state,message,sender): + return { + 'response':api.list_services() + } commands = { "nevermind": nevermind, @@ -222,7 +415,11 @@ commands = { "!remove footer": remove_footer, "!add admin": add_admin, "!remove admin": remove_admin, - "meetings":meetings + "!meetings":meetings, + "!register":register_customer, + "!register2":register_customer, + "!book":booking, + "!services":services } @@ -235,3 +432,4 @@ if __name__ == '__main__': msg = input("user:") print("bot:"+handle_command(user, msg)['response']) # print("state:",user) + diff --git a/data-example.json b/data-example.json index 08d3fb0..35ef3fd 100644 --- a/data-example.json +++ b/data-example.json @@ -2,6 +2,10 @@ "username": "bot", "password": "hunter2", "homeserver": "http://localhost:8008", + "easyappointments": { + "token": "secrettoken", + "url": "http://localhost/easyappointments/index.php" + }, "faq": { "header": "# Simple example FAQ", "questions": [{ diff --git a/easyappointments.py b/easyappointments.py index d7245b8..b65a9d3 100644 --- a/easyappointments.py +++ b/easyappointments.py @@ -4,12 +4,15 @@ class easyappointments: def __init__(self,token,url): self.token = token self.url = url - self.headers = {"Authorization":"Bearer secrettoken"} + self.headers = {"Authorization":f"Bearer {token}"} self.appointments = {} self.services = {} self.providers = {} self.customers = {} - self.update_data() + try: + self.update_data() + except: + print("Failed to connect to easyappointments!") def get_appointments(self): data = requests.get(self.url+'/api/v1/appointments',headers=self.headers) @@ -24,6 +27,13 @@ class easyappointments: data = requests.get(self.url+'/api/v1/customers',headers=self.headers) return data.json() + def get_availabilities(self,service,provider,date): + try: + data = requests.get(self.url+f"/api/v1/availabilities?serviceId={service}&providerId={provider}&date={date}",headers=self.headers) + return data.json() + except: + return False + def update_data(self): appointments = self.get_appointments() for a in appointments: @@ -43,6 +53,7 @@ class easyappointments: for c in customers: if mxid in c['notes']: return c['id'] + return False def is_provider(self,mxid): @@ -50,7 +61,7 @@ class easyappointments: for p in providers: if mxid in p['notes']: return p['id'] - + return -1 def get_upcoming_appointments(self,mxid): #get services & customers id = self.is_provider(mxid) @@ -64,6 +75,9 @@ class easyappointments: else: return [] + + + def list_upcoming_appointments(self,mxid): apts = self.get_upcoming_appointments(mxid) if apts == []: @@ -75,6 +89,8 @@ class easyappointments: output += f"## {service} with {customer}\nTime:{a['start']}\n" return output + + def register_customer(self,customer_data): #customer data """ @@ -86,8 +102,50 @@ class easyappointments: "notes":"matrix:mxid" } """ - id = requests.post(self.url+'/api/v1/customers',headers=self.headers,data=json.dumps(customer_data)).json()['id'] - return id + try: + id = requests.post(self.url+'/api/v1/customers',headers=self.headers,data=json.dumps(customer_data)).json()['id'] + self.update_data() + return id + except: + return False + + def register_appointment(self,appointment_data): + #appointment data + """{ + "start":"YY-MM-DD HH:MM:SS", + "customerId":9, + "providerId":4, + "serviceId:4 + } + """ + print("Registering appointment:") + print(json.dumps(appointment_data)) + result = requests.post( + self.url+'/api/v1/appointments', + headers = self.headers, + data=json.dumps(appointment_data) + ) + id = result.json()['id'] + if id: + return id + else: + return False + +if __name__ == '__main__': + cdat = { + "firstName":"Test", + "lastName":"", + "email":"test@testing.xa", + "phone":"888-888-8888", + "notes":"matrix:@lol:testing" + } + adat = { + "start":"2022-09-28 09:00:00", + "customerId":8, + "providerId":4, + "serviceId":2 + } + easy = easyappointments('secrettoken', 'http://localhost:8787/easyappointments/index.php') diff --git a/error.log b/error.log new file mode 100644 index 0000000..dd079de --- /dev/null +++ b/error.log @@ -0,0 +1 @@ +Error:@gabriel:libresolutions.network: !bookError:@gabriel:libresolutions.network: !bookError:@gabriel:libresolutions.network: !register \ No newline at end of file diff --git a/features.py b/features.py index e2ca2c9..eae0c94 100644 --- a/features.py +++ b/features.py @@ -1,11 +1,18 @@ import json import requests import easyappointments +import datetime """ Features This is the main API for executing functions with data """ +def check_date(date): + try: + datetime.datetime.strptime(date,"%Y-%m-%d") + except: + return False + return True class API: def __init__(self): @@ -16,6 +23,7 @@ class API: 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)) @@ -25,11 +33,9 @@ class API: #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 @@ -38,7 +44,6 @@ 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) @@ -51,7 +56,6 @@ class API: #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() @@ -67,7 +71,6 @@ class API: #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"]] @@ -121,6 +124,57 @@ class API: self.update_data() self.appointments.list_upcoming_appointments(mxid) pass + def list_services(self): + msg = "## Services:" + for i in self.appointments.services: + service = self.appointments.services[i] + name = service['name'] + price = str(service['price']) + service['currency'] + description = service['description'] + msg += f"\n### {name}\nPrice:{price}\n{description}" + return msg + + + def select_service(self): + msg = "## Choose a service:" + counter = 1 + for i in self.appointments.services: + service = self.appointments.services[i] + name = f"#{counter}: " + service['name'] + price = str(service['price']) + service['currency'] + description = service['description'] + msg += f"\n### {name}\nPrice:{price}\n{description}" + counter += 1 + msg += "\nPlease enter the # of the service:" + return msg + + def select_provider(self): + msg = "## Choose a provider:" + counter = 1 + for p in self.appointments.get_providers(): + name = p['firstName']+" "+p['lastName'] + msg += f"\n* #{counter}: {name}" + counter += 1 + msg += "\nPlease enter the # of the provider" + return msg + + + + def select_times(self,service,provider,date): + data = self.appointments.get_availabilities(service, provider, date) + if data == False or check_date(date) == False: + return False + msg = f"## Choose a time for {date}:\n" + counter = 1 + for t in data: + msg += f"{counter}){t} " + counter +=1 + msg +="\nPlease enter the # of time" + return msg + + + + def request_meeting(self): self.update_data() pass @@ -130,5 +184,5 @@ class API: if __name__ == '__main__': - storeAPI = API() - print("Storage loaded.\n",storeAPI.data['username']) + api = API() + print("Storage loaded.\n",api.data['username']) diff --git a/main.py b/main.py index fb3256c..916277e 100644 --- a/main.py +++ b/main.py @@ -13,13 +13,16 @@ bot = botlib.Bot(creds) @bot.listener.on_message_event async def message(room, message): - 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'] - ) - + try: + 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'] + ) + except: + with open("error.log",'a') as f: + f.write(f"Error:{message}") @bot.listener.on_custom_event(nio.InviteMemberEvent) async def example(room, event):