From 31ccd5c2322788c8fce0cc1aa58f7631a9e777ca Mon Sep 17 00:00:00 2001 From: Blake DeMarcy Date: Mon, 10 Apr 2017 09:02:08 -0500 Subject: [PATCH] add color options --- clients/network_client.py | 16 +++- clients/urwid/main.py | 180 +++++++++++++++++++++++++++++++------- src/db.py | 5 +- src/formatting.py | 29 ++++-- 4 files changed, 183 insertions(+), 47 deletions(-) diff --git a/clients/network_client.py b/clients/network_client.py index 11fa228..951c292 100644 --- a/clients/network_client.py +++ b/clients/network_client.py @@ -67,7 +67,7 @@ class BBJ(object): self.user_name = self.user_auth = None self.send_auth = True try: - self("get_me") + self.user = self("get_me")["data"] except URLError: raise URLError("Cannot connect to %s (is the server down?)" % self.base[0:-2]) @@ -275,6 +275,7 @@ class BBJ(object): self.user_auth = user_auth self.user_name = user_name + self.user = self("get_me")["data"] return True @@ -331,7 +332,6 @@ class BBJ(object): return response["data"] - def user_register(self, user_name, user_auth, hash_auth=True, set_as_user=True): """ Register user_name into the system with user_auth. Unless hash_auth @@ -368,12 +368,20 @@ class BBJ(object): ]) if set_as_user: - self.user_name = user_name - self.user_auth = user_auth + self.set_credentials(user_name, user_auth, False) return response + def user_update(self, **params): + """ + Update the user's data on the server. + """ + response = self("user_update", **params) + self.user = self("get_me")["data"] + return response["data"] + + def thread_index(self): """ Returns a tuple where [0] is a list of all threads ordered by diff --git a/clients/urwid/main.py b/clients/urwid/main.py index b77280d..88e39c8 100644 --- a/clients/urwid/main.py +++ b/clients/urwid/main.py @@ -46,11 +46,10 @@ colors = [ ] -editors = ["nano", "emacs", "vim", "micro", "ed", "joe"] +editors = ["nano", "vim", "emacs", "vim -u NONE", "emacs -Q", "micro", "ed", "joe"] # defaults to internal editor, integrates the above as well default_prefs = { - # well, it WILL default to the internal editor when I write it =) - "editor": "vim", + "editor": None, "integrate_external_editor": True, "dramatic_exit": True, "date": "%Y/%m/%d", @@ -63,24 +62,26 @@ default_prefs = { class App(object): def __init__(self): self.bars = { - "index": "[C]ompose [R]efresh [/]Search [O]ptions [?]Help/More [Q]uit", - "thread": "[C]ompose [Q]Back [R]efresh [E]dit [/]Search [B/T]End [?]Help/More" + "index": "[C]ompose [R]efresh [O]ptions [?]Help/More [Q]uit", + "thread": "[C]ompose [Q]Back [R]efresh [Enter]Post Options [/]Search [B/T]End [?]Help/More" } colors = [ ("default", "default", "default"), ("bar", "light magenta", "default"), ("button", "light red", "default"), + ("hover", "light cyan", "default"), ("dim", "dark gray", "default"), # map the bbj api color values for display ("0", "default", "default"), - ("1", "light red", "default"), - ("2", "yellow", "default"), - ("3", "light green", "default"), - ("4", "light blue", "default"), - ("5", "light cyan", "default"), - ("6", "light magenta", "default") + ("1", "dark red", "default"), + # sounds ugly but brown is as close as we can get to yellow without being bold + ("2", "brown", "default"), + ("3", "dark green", "default"), + ("4", "dark blue", "default"), + ("5", "dark cyan", "default"), + ("6", "dark magenta", "default") ] self.mode = None @@ -124,6 +125,33 @@ class App(object): self.loop.widget.footer = urwid.AttrMap(urwid.Text(string), "bar") + def set_default_header(self): + """ + Sets the header to the default for the current screen. + """ + if self.mode == "thread": + name = self.usermap[self.thread["author"]]["user_name"] + self.set_header("~{}: {}", name, self.thread["title"]) + else: + self.set_header("{} threads", len(self.walker)) + + + def set_default_footer(self): + """ + Sets the footer to the default for the current screen. + """ + self.set_footer(self.bars[self.mode]) + + + def set_bars(self): + """ + Sets both the footer and header to their default values + for the current mode. + """ + self.set_default_header() + self.set_default_footer() + + def close_editor(self): """ Close whatever editing widget is open and restore proper @@ -133,10 +161,9 @@ class App(object): self.window_split = False self.loop.widget.focus_position = "body" self.set_footer(self.bars["thread"]) - name = self.usermap[self.thread["author"]]["user_name"] - self.set_header("~{}: {}", name, self.thread["title"]) else: self.loop.widget = self.loop.widget[0] + self.set_default_header() def switch_editor(self): @@ -188,6 +215,10 @@ class App(object): def make_message_body(self, message): + """ + Returns the widgets that comprise a message in a thread, including the + text body, author info and the action button + """ info = "@ " + self.timestring(message["created"]) if message["edited"]: info += " [edited]" @@ -195,7 +226,7 @@ class App(object): name = urwid.Text("~{}".format(self.usermap[message["author"]]["user_name"])) post = str(message["post_id"]) head = urwid.Columns([ - (2 + len(post), urwid.AttrMap(cute_button(">" + post), "button")), + (2 + len(post), urwid.AttrMap(cute_button(">" + post), "button", "hover")), (len(name._text) + 1, urwid.AttrMap(name, str(self.usermap[message["author"]]["color"]))), urwid.AttrMap(urwid.Text(info), "dim") @@ -212,6 +243,9 @@ class App(object): def timestring(self, epoch, mode="both"): + """ + Returns a string of time representing a given epoch and mode. + """ if mode == "delta": return self.readable_delta(epoch) @@ -226,19 +260,24 @@ class App(object): def make_thread_body(self, thread): + """ + Returns the pile widget that comprises a thread in the index. + """ button = cute_button(">>", self.thread_load, thread["thread_id"]) title = urwid.Text(thread["title"]) - dateline = "by ~{} @ {}".format( - self.usermap[thread["author"]]["user_name"], - self.timestring(thread["created"]) - ) + user = self.usermap[thread["author"]] + dateline = [ + ("default", "by "), + (str(user["color"]), "~%s " % user["user_name"]), + ("dim", "@ %s" % self.timestring(thread["created"])) + ] infoline = "%d replies; active %s" % ( thread["reply_count"], self.timestring(thread["last_mod"], "delta")) pile = urwid.Pile([ - urwid.Columns([(3, urwid.AttrMap(button, "button")), title]), - urwid.AttrMap(urwid.Text(dateline), "dim"), + urwid.Columns([(3, urwid.AttrMap(button, "button", "hover")), title]), + urwid.Text(dateline), urwid.AttrMap(urwid.Text(infoline), "dim"), urwid.AttrMap(urwid.Divider("-"), "dim") ]) @@ -256,11 +295,10 @@ class App(object): self.window_split = False threads, usermap = network.thread_index() self.usermap.update(usermap) - self.set_header("{} threads", len(threads)) - self.set_footer(self.bars["index"]) self.walker.clear() for thread in threads: self.walker.append(self.make_thread_body(thread)) + self.set_bars() try: self.loop.widget.body.base_widget.set_focus(self.last_pos) except IndexError: pass @@ -277,14 +315,9 @@ class App(object): self.usermap.update(usermap) self.thread = thread self.walker.clear() - self.set_footer(self.bars["thread"]) - - self.set_header("~{}: {}", - usermap[thread["author"]]["user_name"], - thread["title"]) - for message in thread["messages"]: self.walker += self.make_message_body(message) + self.set_bars() def refresh(self, bottom=False): @@ -303,6 +336,9 @@ class App(object): def set_new_editor(self, button, value, key): + """ + Callback for the option radio buttons to set the the text editor. + """ if value == False: return elif key == "internal": @@ -312,13 +348,78 @@ class App(object): def set_editor_mode(self, button, value): + """ + Callback for the editor mode radio buttons in the options. + """ self.prefs["integrate_external_editor"] = value bbjrc("update", **self.prefs) + def relog(self, *_, **__): + """ + Options menu callback to log the user in again. + Drops back to text mode because im too lazy to + write a responsive urwid thing for this. + """ + self.loop.widget = self.loop.widget[0] + self.loop.stop() + run("clear", shell=True) + print(welcome) + log_in() + self.loop.start() + self.set_default_header() + self.options_menu() + + + def unlog(self, *_, **__): + """ + Options menu callback to anonymize the user and + then redisplay the options menu. + """ + network.user_name = network.user_auth = None + self.loop.widget = self.loop.widget[0] + self.set_default_header() + self.options_menu() + + + def set_color(self, button, value, color): + if value == False: + return + network.user_update(color=color) + + def options_menu(self): + """ + Create a popup for the user to configure their account and + display settings. + """ editor_buttons = [] edit_mode = [] + user_colors = [] + + if network.user_auth: + account_message = "Logged in as %s." % network.user_name + colors = ["None", "Red", "Yellow", "Green", "Blue", "Cyan", "Magenta"] + for index, color in enumerate(colors): + urwid.RadioButton( + user_colors, color, + network.user["color"] == index, + self.set_color, index) + + account_stuff = [ + urwid.Button("Change login.", on_press=self.relog), + urwid.Button("Go anonymous.", on_press=self.unlog), + urwid.Divider(), + urwid.Text(("button", "Your color:")), + *user_colors + + ] + else: + account_message = "You're browsing anonymously, and cannot set account preferences." + account_stuff = [ + urwid.Button("Login/Register", on_press=self.relog) + ] + urwid.RadioButton( editor_buttons, "Internal", @@ -328,7 +429,7 @@ class App(object): for editor in editors: urwid.RadioButton( - editor_buttons, editor.title(), + editor_buttons, editor, state=self.prefs["editor"] == editor, on_state_change=self.set_new_editor, user_data=editor) @@ -345,11 +446,16 @@ class App(object): widget = OptionsMenu( urwid.Filler( urwid.Pile([ - urwid.AttrMap(urwid.Text("Text editor:"), "button"), + urwid.Text(("button", "Your account:")), + urwid.Text(account_message), + urwid.Divider(), + *account_stuff, + urwid.Divider("-"), + urwid.Text(("button", "Text editor:")), urwid.Divider(), *editor_buttons, urwid.Divider("-"), - urwid.AttrMap(urwid.Text("External text editor mode:"), "button"), + urwid.Text(("button", "External text editor mode:")), urwid.Text("If you have problems using an external text editor, " "set this to Overthrow."), urwid.Divider(), @@ -654,6 +760,15 @@ def cute_button(label, callback=None, data=None): return button +def urwid_rainbows(string): + """ + Same as below, but instead of printing rainbow text, returns + a markup list suitable for urwid's Text contructor. + """ + colors = [str(x) for x in range(1, 7)] + return urwid.Text([(choice(colors), char) for char in string]) + + def motherfucking_rainbows(string, inputmode=False, end="\n"): """ I cANtT FeELLE MyYE FACECsEE ANYrrMOROeeee @@ -777,6 +892,7 @@ def log_in(): password = password_loop("Enter a password. It can be empty if you want") network.user_register(name, password) motherfucking_rainbows("~~welcome to the party, %s!~~" % network.user_name) + sleep(0.8) # let that confirmation message shine def frame_theme(mode="default"): @@ -824,10 +940,10 @@ def main(): motherfucking_rainbows(obnoxious_logo) print(welcome) log_in() - sleep(0.8) # let that confirmation message shine if __name__ == "__main__": # global app main() app = App() app.loop.run() + # app.loop.widget.keypress(app.loop.screen_size, "up") diff --git a/src/db.py b/src/db.py index 38d8cb0..5e86ff5 100644 --- a/src/db.py +++ b/src/db.py @@ -268,7 +268,8 @@ def user_update(connection, user_object, parameters): user_id = user_object["user_id"] for key in ("user_name", "auth_hash", "quip", "bio", "color"): value = parameters.get(key) - if value: + # bool(0) == False hur hur hurrrrrr ::drools:: + if value == 0 or value: validate([(key, value)]) user_object[key] = value @@ -383,7 +384,7 @@ def validate(keys_and_values): elif key == "color": - if value in range(0, 6): + if value in range(0, 7): continue raise BBJParameterError( "Color specification out of range (int 0-6)") diff --git a/src/formatting.py b/src/formatting.py index 755ef94..07250e8 100644 --- a/src/formatting.py +++ b/src/formatting.py @@ -16,13 +16,11 @@ markup = [ "bold", "italic", "underline", "strike" ] -tokens = re.compile( - r"\[({}): (.+?)]".format( - "|".join(colors + markup)), - flags=re.DOTALL) +tokens = re.compile(r"\[(%s): (.+?)]" % "|".join(colors + markup), + flags=re.DOTALL) -quotes = re.compile(">>([0-9]+)") -linequotes = re.compile("^(>.+)$", +quotes = re.compile(">>([0-9]+)") +linequotes = re.compile("^(>.+)$", flags=re.MULTILINE) @@ -31,9 +29,8 @@ def apply_formatting(msg_obj, formatter): Receives a messages object from a thread and returns it with all the message bodies passed through FORMATTER. """ - for x in msg_obj["messages"].keys(): - msg_obj["messages"][x]["body"] = \ - formatter(msg_obj["messages"][x]["body"]) + for x in range(len(msg_obj)): + msg_obj[x]["body"] = formatter(msg_obj[x]["body"]) return msg_obj @@ -44,6 +41,20 @@ def raw(text): return text +def strip(text): + """ + Returns the text with all formatting directives removed. + Not to be confused with `raw`. + """ + + + +def entities(text): + """ + Returns a tuple where [0] is raw text + """ + + def html(text): """ Returns messages in html format, after being sent through markdown.