From f658a78c7629340a76ed863536e5bf736b03659d Mon Sep 17 00:00:00 2001 From: Carlos Sanchez Date: Mon, 1 May 2023 13:02:29 -0400 Subject: [PATCH] Userlist printing --- contentapi.py | 21 ++++++++++++++++ main.py | 68 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/contentapi.py b/contentapi.py index 30d3a2a..4c19924 100644 --- a/contentapi.py +++ b/contentapi.py @@ -1,6 +1,7 @@ import requests import logging +import json class AuthenticationError(Exception): """ Exception for 401 errors, meaning your token was bad (expired maybe?) """ @@ -13,6 +14,14 @@ class NotFoundError(Exception): """Exception for 404 errors, meaning whatever you were looking for wasn't found (This is rare from the API) """ +# Given a normal result set for users, find the user by id or return some reasonable default +def get_user_or_default(users, id): + for u in users: + if "id" in u and u["id"] == id: + return u + return { "id" : id, "username" : "???", "avatar": "0" } + + # Your gateway to the static endpoints for contentapi. It's a context because it needs # to track stuff like "which api am I contacting" and "which user am I authenticating as (if any)" class ApiContext: @@ -84,6 +93,18 @@ class ApiContext: self.logger.debug("Error from endpoint: %s" % ex) return False + # Generate a completely ready websocket request (for data). You can write the result directly to the websocket + def gen_ws_request(self, type, data = None, id = None): + request = { + "type" : type + } + if data: + request["data"] = data + if id: + request["id"] = id + return json.dumps(request) + + # Return info about the current user based on the token. Useful to see if your token is valid # and who you are diff --git a/main.py b/main.py index b95b562..555d935 100644 --- a/main.py +++ b/main.py @@ -31,13 +31,15 @@ config = { "tokenfile" : ".qcstoken" } +# The command dictionary (only used to display help) commands = OrderedDict([ - ("h" , "Help, prints this menu!"), - ("s" , "Search, find and/or set a room to listen to (one at a time!)"), - ("g" , "Global userlist, print users using contentapi in general"), - ("u" , "Userlist, print users in the current room"), - ("i" , "Insert mode, allows you to send a message (pauses messages!)"), - ("q" , "Quit, no warning!") + ("h", "Help, prints this menu!"), + ("s", "Search, find and/or set a room to listen to (one at a time!)"), + ("g", "Global userlist, print users using contentapi in general"), + ("u", "Userlist, print users in the current room"), + ("i", "Insert mode, allows you to send a message (pauses messages!)"), + ("t", "Statistics, see info about runtime"), + ("q", "Quit, no warning!") ]) @@ -76,6 +78,7 @@ def main(): ws.main_config = config ws.current_room = 0 ws.current_room_data = False + ws.ignored = {} # Go out and get the default room if one was provided. if config["default_room"]: @@ -91,7 +94,6 @@ def main(): ws.on_close = ws_onclose ws.on_message = ws_onmessage - # connect to the WebSocket server and block until connected ws.run_forever() print("Program end") @@ -104,19 +106,20 @@ def ws_onclose(ws): def ws_onopen(ws): def main_loop(): - printstatus = True printr(Fore.GREEN + Style.BRIGHT + "\n-- Connected to live updates! --") if not ws.current_room: printr(Fore.YELLOW + "* You are not connected to any room! Press 'S' to search for a room! *") + printstatus = True + # The infinite input loop! Or something! while True: if printstatus: print_statusline(ws) - printstatus = True # Allow printing the statusline next time + printstatus = False # Assume we are not printing the status every time (it's kinda annoying) ws.pause_output = False # Allow arbitrary output again key = readchar.readkey() @@ -132,32 +135,65 @@ def ws_onopen(ws): print(" " + Style.BRIGHT + key + Style.NORMAL + " - " + value) elif key == "s": search(ws) + printstatus = True elif key == "g": - print("not yet") + ws.send(ws.context.gen_ws_request("userlist", id = "userlist_global")) elif key == "u": if not ws.current_room: print("You're not in a room! Can't check userlist!") else: - print("not yet") + # Just send it out, we have to wait for the websocket handler to get the response + ws.send(ws.context.gen_ws_request("userlist", id = "userlist_room_%d" % ws.current_room)) elif key == "i": if not ws.current_room: print("You're not in a room! Can't send messages!") else: print("not yet") + printstatus = True + elif key == "t": + print(" -- Ignored WS Data (normal) --") + for key,value in ws.ignored.items(): + printr(Style.BRIGHT + ("%16s" % key) + (" : %d" % value)) elif key == "q": print("Quitting (may take a bit for the websocket to close)") ws.close() break - else: - printstatus = False # create a thread to run the blocking task thread = threading.Thread(target=main_loop) thread.start() - +# Message handler for our websocket; will handle live messages for the room you're listening to and +# userlist updates request results, but not much else (for now) def ws_onmessage(ws, message): - pass + logging.debug("WSRCV: " + message) + result = json.loads(message) + + # Someone asked for the userlist, check the id to figure out what to print and which list to see + if result["type"] == "userlist": + all_statuses = result["data"]["statuses"] + if result["id"] == "userlist_global": + usermessage = " -- Global userlist --" + statuses = all_statuses["0"] if "0" in all_statuses else {} + else: # This is a bad assumption, it should parse the room id out of the id instead (maybe?) + usermessage = " -- Userlist for %s -- " % ws.current_room_data["name"] + statuses = all_statuses[str(ws.current_room)] if str(ws.current_room) in all_statuses else {} + print(usermessage) + print_userlist(statuses, result["data"]["objects"]["user"]) + + # Track ignored data + if result["type"] not in ws.ignored: + ws.ignored[result["type"]] = 0 + ws.ignored[result["type"]] += 1 + +# Print the plain userlist given a list of statuses (in a room or otherwise) and a list of user data +# (usually provided by whatever gave you the statuses) +def print_userlist(statuses, users): + for key,value in statuses.items(): + key = int(key) + user = contentapi.get_user_or_default(users,key) + # Weird parenthesis are because I was aligning printed data before + printr(Style.BRIGHT + " " + ("%s" % (user["username"] + Style.DIM + " #%d" % key)) + Style.RESET_ALL + " - " + value) # Loads the config from file into the global config var. If the file @@ -245,7 +281,7 @@ def print_statusline(ws): room = "'" + (name[:(MAXTITLE - 3)] + '...' if len(name) > MAXTITLE else name) + "'" else: room = Fore.RED + Style.DIM + "NONE" + Style.NORMAL + Fore.BLACK - print(Back.GREEN + Fore.BLACK + "\n " + ws.user_info["username"] + " - " + room + " CTRL: h s g u i q " + Style.RESET_ALL) + print(Back.GREEN + Fore.BLACK + "\n " + ws.user_info["username"] + " - " + room + " CTRL: h s g u i t q " + Style.RESET_ALL) # Print and then reset the style def printr(msg):