diff --git a/contentapi.py b/contentapi.py index 86ecc90..d8a4582 100644 --- a/contentapi.py +++ b/contentapi.py @@ -28,6 +28,23 @@ def get_message(messages, id): return m return None +# Build a comment for writing on the API. Only required fields are text and content id, but PLEASE +# try to add the avatar and markup too! +def comment_builder(text, content_id, markup=None, avatar=None, nickname=None): + comment = { + "id": 0, + "contentId": content_id, + "text": text, + "values": {} + } + if markup: + comment["values"]["m"] = markup + if avatar: + comment["values"]["a"] = avatar + if nickname: + comment["values"]["n"] = nickname + return comment + # Constants for user actions CREATE = 1 READ = 2 @@ -173,4 +190,9 @@ class ApiContext: things = result["objects"][type] if not len(things): raise NotFoundError("Couldn't find %s with id %d" % (type, id)) - return things[0] \ No newline at end of file + return things[0] + + # Post the given message. Minimum fields are text and contentId, but you should also add the values + # which indicate the markup and avatar!! + def post_message(self, message): + return self.post("write/message", message) \ No newline at end of file diff --git a/main.py b/main.py index 6501245..ba43c1b 100644 --- a/main.py +++ b/main.py @@ -27,6 +27,7 @@ config = { "default_loglevel" : "WARNING", "websocket_trace" : False, "default_room" : 0, # Zero means it will ask you for a room + "default_markup" : "plaintext", "expire_seconds" : 31536000, # 365 days in seconds, expiration for token "appear_in_global" : False, "tokenfile" : ".qcstoken" @@ -58,14 +59,6 @@ def main(): logging.debug(json.dumps(context.api_status(), indent = 2)) authenticate(config, context) - # - Enter input loop, but check room number on "input" mode, don't let messages send in room 0 - # - h to help - # - s to search rooms, enter #1234 to connect directly, empty string to quit - # - g to list global users - # - u to list users in room - # - i to input - # - q to quit entirely - # Let users debug the websocket if they want I guess if config["websocket_trace"]: websocket.enableTrace(True) @@ -80,15 +73,7 @@ def main(): 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"]: - try: - ws.current_room_data = context.get_by_id("content", config["default_room"]) - ws.current_room = config["default_room"] - printr(Fore.GREEN + "Found default room %s" % ws.current_room_data["name"]) - except Exception as ex: - printr(Fore.YELLOW + "Error searching for default room %d: %s" % (config["default_room"], ex)) + # Note that the current_room stuff is set on open if you've supplied a default room in config # set the callback functions ws.on_open = ws_onopen @@ -101,9 +86,11 @@ def main(): def ws_onclose(ws): - print("Websocket closed! Program exit (FYI: you were in room %d)" % ws.current_room) + print("Websocket closed! Exiting (FYI: you were in room %d)" % ws.current_room) exit() + +# When the websocket is opened, we begin our thread to accept input forever def ws_onopen(ws): def main_loop(): @@ -132,11 +119,6 @@ def ws_onopen(ws): ws.pause_output = False # Allow arbitrary output again key = readchar.readkey() - # # Oops, websocket is not connected but you asked for a command that requires websocket! - # if not ws_context.connected and key in ["s", "g", "u", "i"]: - # print("No websocket connection") - # continue - ws.pause_output = True # Disable output for the duration of input handling if key == "h": @@ -158,7 +140,10 @@ def ws_onopen(ws): if not ws.current_room: print("You're not in a room! Can't send messages!") else: - print("not yet") + message = input("Post (empty = exit): ") + if message: + ws.context.post_message(contentapi.comment_builder(message, + ws.current_room, ws.main_config["default_markup"], ws.user_info["avatar"])) printstatus = True elif key == "t": print(" -- Ignored WS Data (normal) --") @@ -171,6 +156,11 @@ def ws_onopen(ws): elif key == " ": printstatus = True + # Set the main room; we want to wait until the websocket is open because this also sets your + # status in the userlist + if ws.main_config["default_room"]: + set_current_room(ws, ws.main_config["default_room"]) + # create a thread to run the blocking task thread = threading.Thread(target=main_loop) thread.start() @@ -213,12 +203,12 @@ def ws_onmessage(ws, message): for t in textwrap.wrap(message["text"], width = MAXMESSAGEWIDTH): ws_print(ws, t) - # Track ignored data if result["type"] not in ws.ignored: ws.ignored[result["type"]] = 0 ws.ignored[result["type"]] += 1 + # Printing websocket event output is tricky because we don't want to interrupt user input (we don't have # curses). As such, we must buffer our output IF we are asked to pause def ws_print(ws, output): @@ -247,6 +237,19 @@ def load_or_create_global_config(): myutils.set_logging_level(config["default_loglevel"]) + +# Set the room to listen to on the websocket. Will also update the userlist, if +# it's appropriate to do so +def set_current_room(ws, roomid): + try: + ws.current_room_data = ws.context.get_by_id("content", roomid) + ws.current_room = roomid + print(Fore.GREEN + "Set room to %s" % ws.current_room_data["name"] + Style.RESET_ALL) + return + except Exception as ex: + print(Fore.RED + "Couldn't find room with id %d" % roomid + Style.RESET_ALL) + + # Enter a search loop which will repeat until you quit. Output should be PAUSED here # (but someone else does it for us, we don't even know what 'pausing' is) def search(ws): @@ -257,13 +260,7 @@ def search(ws): match = re.match(r'#(\d+)', searchterm) if match: roomid = int(match.group(1)) - try: - ws.current_room_data = ws.context.get_by_id("content", roomid) - ws.current_room = roomid - print(Fore.GREEN + "Set room to %s" % ws.current_room_data["name"] + Style.RESET_ALL) - return - except Exception as ex: - print(Fore.RED + "Couldn't find room with id %d" % roomid + Style.RESET_ALL) + set_current_room(ws, roomid) elif searchterm: # Go search for rooms and display them result = ws.context.basic_search(searchterm)["objects"]["content"] @@ -273,6 +270,7 @@ def search(ws): else: printr(Style.DIM + " -- No results -- ") + # Either pull the token from a file, or get the login from the command # line if that doesn't work. WILL test your token against the real API # even if it's pulled from file! @@ -306,6 +304,7 @@ def authenticate(config, context: contentapi.ApiContext): print("ERROR: %s" % ex) message = "Please try logging in again:" + def print_statusline(ws): # if ws_context.connected: bg = Back.GREEN else: bg = Back.RED if ws.current_room: @@ -315,10 +314,12 @@ def print_statusline(ws): 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 t q " + Style.RESET_ALL) + # Print and then reset the style def printr(msg): print(msg + Style.RESET_ALL) + # Because python reasons if __name__ == "__main__": main() \ No newline at end of file