From 1538aad56e0c0732e4c8e652ce4e47cb6ed92d6b Mon Sep 17 00:00:00 2001
From: Ben Harris
Date: Sat, 15 Jan 2022 22:54:16 -0500
Subject: [PATCH] fix some typos and warnings
---
README.md | 36 +++-
clients/network_client.py | 95 ++++------
clients/urwid/main.py | 316 +++++++++++++-------------------
contrib/bbj.service | 16 ++
contrib/bbj.sh | 2 +
docs/docs/api_overview.md | 14 +-
docs/docs/errors.md | 2 +-
docs/site/errors/index.html | 2 +-
docs/site/index.html | 2 +-
docs/site/mkdocs/js/text.js | 6 +-
gendocs.sh | 4 +-
prototype/clients/urwid/main.py | 2 +-
prototype/src/endpoints.py | 35 ++--
requirements.txt | 3 +
server.py | 42 ++++-
setup.sh | 44 ++---
src/db.py | 46 +++--
src/formatting.py | 16 +-
src/schema.py | 4 +-
19 files changed, 333 insertions(+), 354 deletions(-)
create mode 100644 contrib/bbj.service
create mode 100644 contrib/bbj.sh
create mode 100644 requirements.txt
diff --git a/README.md b/README.md
index a27f519..8f7b629 100644
--- a/README.md
+++ b/README.md
@@ -5,11 +5,41 @@ miraculously shit out a fully functional, text-driven community bulletin board.
Requires Python 3.4 and up for the server and the official TUI client (clients/urwid/).

-Look Ma, it boots !!11!
+Look Ma, it boots !!11!
-It's all driven by an API sitting on top of CherryPy. Currently it does not
+It's all driven by an API sitting on top of CherryPy. Currently, it does not
serve HTML but this is planned for the (distant?) future.
-The two official client implementations are a stand alone TUI client for
+The two official client implementations are a standalone TUI client for
the unix terminal, and GNU Emacs. The API is simple and others are welcome
to join the party at some point.
+
+## Setup Instructions
+
+1. Make a virtual env
+```
+python3 -m venv venv
+source venv/bin/activate
+```
+
+2. Run setup.sh
+```
+./setup.sh venv/bin/python3
+```
+
+3. Add systemd service (optional)
+```
+cp contrib/bbj.service /etc/systemd/system/
+$EDITOR /etc/systemd/system/bbj.service
+systemctl enable --now bbj
+```
+Be sure to edit bbj.service with your venv and paths.
+
+4. Make a client script
+
+Create a script somewhere in your `$PATH` (I used `/usr/local/bin/bbj`) with the following contents,
+adapting the path to your install:
+```shell
+#!/bin/sh
+exec /srv/bbj/venv/bin/python3 /srv/bbj/clients/urwid/main.py
+```
diff --git a/clients/network_client.py b/clients/network_client.py
index 6cd4883..4dbd594 100644
--- a/clients/network_client.py
+++ b/clients/network_client.py
@@ -1,15 +1,15 @@
-from urllib.error import URLError
+import json
import urllib.request as url
from hashlib import sha256
from time import time
-import json
+from urllib.error import URLError
class BBJ(object):
- # this module isnt exactly complete. The below description claims
+ # this module isn't exactly complete. The below description claims
# `all of its endpoints are mapped to native methods` though this
# is not yet true. The documentation for the API is not yet
- # complete, and neither is this client. Currently this module is
+ # complete, and neither is this client. Currently, this module is
# being adapted to fit the needs of the urwid client. As it evolves,
# and the rest of the project evolves, this client will be completed
# and well documented.
@@ -44,11 +44,12 @@ class BBJ(object):
except UserWarning as e:
assert e.code == 4
print(e.description)
- # want the raw error object? thats weird, but whatever.
+ # want the raw error object? that's weird, but whatever.
return e.body
- See the offical API error documentation for more details.
+ See the official API error documentation for more details.
"""
+
def __init__(self, host="127.0.0.1", port=7099, https=False):
"""
Optionally takes port and host as kwargs. It will immediately
@@ -80,22 +81,20 @@ def __init__(self, host="127.0.0.1", port=7099, https=False):
except URLError:
raise URLError("Cannot connect to %s (is the server down?)" % self.base[0:-2])
-
def __call__(self, *args, **kwargs):
"""
Calling the network object itself is exactly the same as calling
- it's .request() method.
+ its .request() method.
"""
return self.request(*args, **kwargs)
-
- def _hash(self, string):
+ @staticmethod
+ def _hash(string):
"""
Handy function to hash a password and return it.
"""
return sha256(bytes(string, "utf8")).hexdigest()
-
def request(self, endpoint, **params):
"""
Takes the string endpoint, and a variable number of kwargs
@@ -104,7 +103,7 @@ def request(self, endpoint, **params):
raised.
However, one kwarg is magical here: no_auth. If you include
- this, its not sent with the request, it just disables the
+ this, it's not sent with the request, it just disables the
sending of auth info when it is available (for more info,
read __init__'s documentation).
@@ -139,7 +138,6 @@ def request(self, endpoint, **params):
return value
-
def raise_exception(self, error_object):
"""
Takes an API error object and raises the appropriate exception,
@@ -158,7 +156,7 @@ def raise_exception(self, error_object):
except UserWarning as e:
assert e.code == 4
print(e.description)
- # want the raw error object? thats weird, but whatever.
+ # want the raw error object? that's weird, but whatever.
return e.body
"""
description = error_object["description"]
@@ -178,7 +176,6 @@ def raise_exception(self, error_object):
e.code, e.description, e.body = code, description, error_object
raise e
-
def update_instance_info(self):
"""
Stores configuration info for the connected BBJ server.
@@ -192,10 +189,9 @@ def update_instance_info(self):
response = self("instance_info")
self.instance_info = response["data"]
-
def validate(self, key, value, exception=AssertionError):
"""
- Uses the server's db_validate method to verify the validty
+ Uses the server's db_validate method to verify the validity
of `value` by `key`. If it is invalid, kwarg exception (default
AssertionError) is raised with the exception containing the
attribute .description as the server's reason. Exception can
@@ -203,7 +199,7 @@ def validate(self, key, value, exception=AssertionError):
Examples:
- # this will fail bacause the server wont allow newlines in usernames.
+ # this will fail because the server won't allow newlines in usernames.
try:
bbj.validate("user_name", "des\nvox")
except AssertionError as e:
@@ -229,7 +225,6 @@ def validate(self, key, value, exception=AssertionError):
return True
-
def validate_all(self, keys_and_values, exception=AssertionError):
"""
Takes a single iterable object as its argument, containing
@@ -263,12 +258,11 @@ def validate_all(self, keys_and_values, exception=AssertionError):
self.validate(key, value, exception) for key, value in keys_and_values
]
-
def set_credentials(self, user_name, user_auth, hash_auth=True, check_validity=True):
"""
Internalizes user_name and user_auth. Unless hash_auth=False is
specified, user_auth is assumed to be an unhashed password
- string and it gets hashed with sha256. If you want to handle
+ string, and it gets hashed with sha256. If you want to handle
hashing yourself, make sure to disable that.
Unless check_validity is set to false, the new credentials are
@@ -290,7 +284,7 @@ def set_credentials(self, user_name, user_auth, hash_auth=True, check_validity=T
except ConnectionRefusedError:
# bad auth info
except ValueError:
- # paramter validation failed or the user is not registered
+ # parameter validation failed or the user is not registered
# you can handle hashing yourself if you want
password = input("Enter your password:")
@@ -312,7 +306,6 @@ def set_credentials(self, user_name, user_auth, hash_auth=True, check_validity=T
self.user = self("get_me")["data"]
return True
-
def validate_credentials(self, user_name, user_auth, exception=True):
"""
Pings the server to check that user_name can be authenticated with
@@ -335,15 +328,15 @@ def validate_credentials(self, user_name, user_auth, exception=True):
is_okay = bbj.validate_credentials("desvox", hashed_password, exception=False)
"""
self.validate_all([
- ("user_name", user_name),
- ("auth_hash", user_auth)
- ], ValueError)
+ ("user_name", user_name),
+ ("auth_hash", user_auth)
+ ], ValueError)
try:
response = self("check_auth",
- no_auth=True,
- target_user=user_name,
- target_hash=user_auth
- )
+ no_auth=True,
+ target_user=user_name,
+ target_hash=user_auth
+ )
return response["data"]
except ConnectionRefusedError as e:
@@ -351,7 +344,6 @@ def validate_credentials(self, user_name, user_auth, exception=True):
raise e
return False
-
def user_is_registered(self, user_name):
"""
Returns True or False whether user_name is registered
@@ -365,7 +357,6 @@ def user_is_registered(self, user_name):
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
@@ -391,10 +382,10 @@ def user_register(self, user_name, user_auth, hash_auth=True, set_as_user=True):
user_auth = sha256(bytes(user_auth, "utf8")).hexdigest()
response = self("user_register",
- no_auth=True,
- user_name=user_name,
- auth_hash=user_auth
- )["data"]
+ no_auth=True,
+ user_name=user_name,
+ auth_hash=user_auth
+ )["data"]
assert all([
user_auth == response["auth_hash"],
@@ -406,7 +397,6 @@ def user_register(self, user_name, user_auth, hash_auth=True, set_as_user=True):
return response
-
def user_update(self, **params):
"""
Update the user's data on the server. The new parameters
@@ -422,7 +412,6 @@ def user_update(self, **params):
self.user = self("get_me")["data"]
return response["data"]
-
def user_get(self, user_id_or_name):
"""
Return a full user object by their id or username.
@@ -432,13 +421,12 @@ def user_get(self, user_id_or_name):
same objects. You shouldn't use this method when a usermap
is provided.
- If the user element isnt found, ValueError is raised.
+ If the user element isn't found, ValueError is raised.
See also `user_is_registered`
"""
response = self("user_get", target_user=user_id_or_name)
return response["data"]
-
def thread_index(self, include_op=False):
"""
Returns a tuple where [0] is a list of all threads ordered by
@@ -453,7 +441,6 @@ def thread_index(self, include_op=False):
response = self("thread_index", include_op=include_op)
return response["data"], response["usermap"]
-
def thread_load(self, thread_id, format=None, op_only=False):
"""
Returns a tuple where [0] is a thread object and [1] is a usermap object.
@@ -466,10 +453,9 @@ def thread_load(self, thread_id, format=None, op_only=False):
print(message["body"])
"""
response = self("thread_load",
- format=format, thread_id=thread_id, op_only=op_only)
+ format=format, thread_id=thread_id, op_only=op_only)
return response["data"], response["usermap"]
-
def thread_create(self, title, body):
"""
Submit a new thread, and return its new object. Requires the
@@ -480,7 +466,6 @@ def thread_create(self, title, body):
response = self("thread_create", title=title, body=body)
return response["data"]
-
def thread_reply(self, thread_id, body):
"""
Submits a new reply to a thread and returns the new object.
@@ -489,10 +474,9 @@ def thread_reply(self, thread_id, body):
response = self("thread_reply", thread_id=thread_id, body=body)
return response["data"]
-
def fake_message(self, body="!!", format="sequential", author=None, post_id=0):
"""
- Produce a a valid message object with `body`. Useful for
+ Produce a valid message object with `body`. Useful for
testing and can also be used mimic server messages in a
client.
"""
@@ -506,7 +490,6 @@ def fake_message(self, body="!!", format="sequential", author=None, post_id=0):
"thread_id": "gibberish"
}
-
def format_message(self, body, format="sequential"):
"""
Send `body` to the server to be formatted according to `format`,
@@ -515,7 +498,6 @@ def format_message(self, body, format="sequential"):
response = self("format_message", body=body, format=format)
return response["data"]
-
def message_delete(self, thread_id, post_id):
"""
Delete message `post_id` from `thread_id`. The same rules apply
@@ -526,11 +508,10 @@ def message_delete(self, thread_id, post_id):
response = self("delete_post", thread_id=thread_id, post_id=post_id)
return response["data"]
-
def edit_query(self, thread_id, post_id):
"""
- Queries ther server database to see if a post can
- be edited by the logged in user. thread_id and
+ Queries the database to see if a post can
+ be edited by the logged-in user. thread_id and
post_id are required.
Returns a message object on success, or raises
@@ -539,11 +520,10 @@ def edit_query(self, thread_id, post_id):
response = self("edit_query", thread_id=thread_id, post_id=int(post_id))
return response["data"]
-
def can_edit(self, thread_id, post_id):
"""
Return bool True/False that the post at thread_id | post_id
- can be edited by the logged in user. Will not raise UserWarning.
+ can be edited by the logged-in user. Will not raise UserWarning.
"""
try:
result = bool(self.edit_query(thread_id, post_id))
@@ -551,7 +531,6 @@ def can_edit(self, thread_id, post_id):
result = False
return result
-
def edit_message(self, thread_id, post_id, new_body):
"""
Requires the thread_id and post_id. The edit flag is then
@@ -566,7 +545,6 @@ def edit_message(self, thread_id, post_id, new_body):
post_id=post_id, body=new_body)
return response["data"]
-
def set_post_raw(self, thread_id, post_id, value):
"""
This is a subset of `edit_message` that retains the old
@@ -581,7 +559,6 @@ def set_post_raw(self, thread_id, post_id, value):
value=bool(value))
return response["data"]
-
def user_is_admin(self, user_name_or_id):
"""
Return boolean True or False whether the given user identifier
@@ -591,18 +568,16 @@ def user_is_admin(self, user_name_or_id):
response = self("is_admin", target_user=user_name_or_id)
return response["data"]
-
def thread_set_pin(self, thread_id, new_status):
"""
Set whether a thread should be pinned or not. new_status
- is evaluated as a boolean, and given that the logged in
+ is evaluated as a boolean, and given that the logged-in
user is an admin, the thread is set to this status on
the server, and the boolean is returned.
"""
response = self("thread_set_pin", thread_id=thread_id, value=new_status)
return response["data"]
-
def message_feed(self, time, format=None):
"""
Returns a special object representing all activity on the board since
@@ -628,7 +603,7 @@ def message_feed(self, time, format=None):
objects from the usermap object.
The "messages" array is already sorted by submission time, newest
- first. The order in the threads object is undefined and you should
+ first. The order in the threads object is undefined, and you should
instead use their `last_mod` attribute if you intend to list them
out visually.
diff --git a/clients/urwid/main.py b/clients/urwid/main.py
index 0227bd1..ccef6f0 100644
--- a/clients/urwid/main.py
+++ b/clients/urwid/main.py
@@ -3,51 +3,55 @@
If you're looking for help on how to use the program, just press
? while its running. This mess will not help you.
-Urwid aint my speed. Hell, making complex, UI-oriented programs
-aint my speed. So some of this code is pretty messy. I stand by
+Urwid ain't my speed. Hell, making complex, UI-oriented programs
+ain't my speed. So some of this code is pretty messy. I stand by
it though, and it seems to be working rather well.
Most of the functionality is crammed in the App() class. Key
handling is found in the other subclasses for urwid widgets.
-An instantiation of App() is casted as `app` globally and
+An instantiation of App() is cast as `app` globally and
the keypress methods will call into this global `app` object.
There are few additional functions that are defined outside
-of the App class. They are delegated to the very bottom of
+the App class. They are delegated to the very bottom of
this file.
Please mail me (~desvox) for feedback and for any of your
"OH MY GOD WHY WOULD YOU DO THIS"'s or "PEP8 IS A THING"'s.
"""
-from network import BBJ, URLError
-from string import punctuation
+import json
+import os
+import re
+import readline
+import rlcompleter
+import tempfile
+from code import interact
from datetime import datetime
-from sys import argv, version
-from time import time, sleep
from getpass import getpass
-from subprocess import call
from random import choice
-from code import interact
-import rlcompleter
-import readline
-import tempfile
+from string import punctuation
+from subprocess import call
+from sys import argv, version
+from time import time, sleep
+
import urwid
-import json
-import os
-import re
+
+from network import BBJ, URLError
+
# XxX_N0_4rgP4rs3_XxX ###yoloswag
def get_arg(key, default=None, get_value=True):
try:
spec = argv.index("--" + key)
value = argv[spec + 1] if get_value else True
- except ValueError: # --key not specified
+ except ValueError: # --key not specified
value = default
- except IndexError: # flag given but no value
+ except IndexError: # flag given but no value
exit("invalid format for --" + key)
return value
+
if get_arg("help", False, False):
print("""BBJ Urwid Client
Available options:
@@ -124,10 +128,9 @@ def get_arg(key, default=None, get_value=True):
"long as you are not anonymous.",
"In previous versions of BBJ, linebreaks were joined into sentences if they "
- "occured in the same paragraph, however this confused many users and has been "
+ "occurred in the same paragraph, however this confused many users and has been "
"reverted to just use whatever was submitted, as-is.",
-
"[red: Colors, Bold, Underline & Expressions]",
"You can use [rainbow: rainbow], [red: red], [yellow: yellow], [green: green], "
@@ -146,13 +149,13 @@ def get_arg(key, default=None, get_value=True):
"closing brackets need to be escaped within an expression. Any backslashes used "
"for escaping will not show in the body unless you use two slashes.",
- "This peculiar syntax elimiates false positives. You never have to escape [normal] "
+ "This peculiar syntax eliminates false positives. You never have to escape [normal] "
"brackets when using the board. Only expressions with **valid and defined** directives "
"will be affected. [so: this is totally valid and requires no escapes] because 'so' is "
"not a directive. [red this will pass too] because the colon is missing.",
"The following directives may be used in this form: red, yellow, green, blue, cyan, "
- "magenta, bold, underline, dim, and rainbow. Nesting expressions into eachother will "
+ "magenta, bold, underline, dim, and rainbow. Nesting expressions into each other will "
"override the parent directives until the innermost expression closes. Thus, nesting "
"is valid but doesn't produce layered results on the command line client.",
@@ -161,7 +164,7 @@ def get_arg(key, default=None, get_value=True):
"You can refer to a post number using two angle brackets pointing into a number. >>432 "
"like this. You can color a whole line green by proceeding it with a '>'. Note that "
"this violates the sentence structure outlined in the **Whitespace** section above, "
- "so you may introduce >greentext without splicing into seperate paragraphs. The '>' "
+ "so you may introduce >greentext without splicing into separate paragraphs. The '>' "
"must be the first character on the line with no whitespace before it.\n>it looks like this\n"
"and the paragraph doesnt have to break on either side. The formatter is smart enough to "
"differentiate between >>greentext with multiple arrows and numeric quotes (outlined below) "
@@ -179,29 +182,30 @@ def get_arg(key, default=None, get_value=True):
("bold", "use the arrow keys, j/k, or n/p to scroll down this menu\n\n"),
("bold", "use q or escape to close dialogs and menus (including this one)\n\n"),
("10", "use q, escape, or a left directional key to go back at any point"
- " from just about anywhere.\n\n"),
+ " from just about anywhere.\n\n"),
("20", "use the o key to change your settings when this dialog is closed\n\n"),
"You may use the arrow keys, or use ", ("button", "jk/np/Control-n|p"),
" to move up and down by "
"an element. If an element is overflowing the screen, it will scroll only one line. "
"To make scrolling faster, ", ("button", "hold shift"), " when using a control: it "
- "will repeat 5 times by default, and you can change this number in your settings.\n\n"
+ "will repeat 5 times by default, and you can change this number in your settings.\n\n"
- "In threads, The ", ("button", "<"), " and ", ("button", ">"), " keys will jump by "
- "a chosen number of post headers. You can see the count inside of the footer line at "
- "the far right side: press ", ("button", "x"), " to cycle it upwards or ",
+ "In threads, The ", ("button", "<"), " and ",
+ ("button", ">"), " keys will jump by "
+ "a chosen number of post headers. You can see the count inside of the footer line at "
+ "the far right side: press ", ("button", "x"), " to cycle it upwards or ",
("button", "X"), " to cycle it downwards.\n\n"
- "In the thread index and any open thread, the ", ("button", "b"), " and ", ("button", "t "),
+ "In the thread index and any open thread, the ", ("button", "b"), " and ", ("button", "t "),
"keys may be used to go to very top or bottom.\n\n"
"To go back and forth between threads, you may also use the left/right arrow keys, "
"or ", ("button", "h"), "/", ("button", "l"), " to do it vi-style.\n\n"
- "Aside from those, primary controls are shown on the very bottom of the screen "
- "in the footer line, or may be placed in window titles for other actions like "
- "dialogs or composers."
+ "Aside from those, primary controls are shown on the very bottom of the screen "
+ "in the footer line, or may be placed in window titles for other actions like "
+ "dialogs or composers."
]
colors = [
@@ -275,8 +279,8 @@ def get_arg(key, default=None, get_value=True):
escape_map = {
key: urwid.vterm.ESC + sequence
- for sequence, key in urwid.escape.input_sequences
- if len(key) > 1
+ for sequence, key in urwid.escape.input_sequences
+ if len(key) > 1
}
themes = {
@@ -287,10 +291,10 @@ def get_arg(key, default=None, get_value=True):
"trcorner": "@",
"blcorner": "@",
"brcorner": "@",
- "tline": "=",
- "bline": "=",
- "lline": "|",
- "rline": "|",
+ "tline": "=",
+ "bline": "=",
+ "lline": "|",
+ "rline": "|",
}
},
@@ -301,10 +305,10 @@ def get_arg(key, default=None, get_value=True):
"trcorner": "┐",
"blcorner": "└",
"brcorner": "┘",
- "tline": "─",
- "bline": "─",
- "lline": "│",
- "rline": "│",
+ "tline": "─",
+ "bline": "─",
+ "lline": "│",
+ "rline": "│",
}
},
@@ -315,10 +319,10 @@ def get_arg(key, default=None, get_value=True):
"trcorner": "",
"blcorner": "",
"brcorner": "",
- "tline": "",
- "bline": "",
- "lline": "",
- "rline": "",
+ "tline": "",
+ "bline": "",
+ "lline": "",
+ "rline": "",
}
}
}
@@ -327,6 +331,7 @@ def get_arg(key, default=None, get_value=True):
markpath = os.path.join(os.getenv("HOME"), ".bbjmarks")
pinpath = os.path.join(os.getenv("HOME"), ".bbjpins")
+
class App(object):
def __init__(self):
self.prefs = bbjrc("load")
@@ -368,7 +373,6 @@ def __init__(self):
palette=colormap,
handle_mouse=self.prefs["mouse_integration"])
-
def frame_theme(self, title=""):
"""
Return the kwargs for a frame theme.
@@ -379,10 +383,9 @@ def frame_theme(self, title=""):
theme.update({"title": title})
return theme
-
def set_header(self, text, *format_specs):
"""
- Update the header line with the logged in user, a seperator,
+ Update the header line with the logged-in user, a seperator,
then concat text with format_specs applied to it. Applies
bar formatting to it.
"""
@@ -392,7 +395,6 @@ def set_header(self, text, *format_specs):
)
self.loop.widget.header = urwid.AttrMap(urwid.Text(header), "bar")
-
def set_footer(self, string):
"""
Sets the footer to display `string`, applying bar formatting.
@@ -405,7 +407,6 @@ def set_footer(self, string):
# self.loop.widget.footer[0].set_text(widget)
# else:
-
def set_default_header(self):
"""
Sets the header to the default for the current screen.
@@ -416,7 +417,6 @@ def set_default_header(self):
else:
self.set_header("{} threads", len(self.walker))
-
def set_default_footer(self, clobber_composer=False):
"""
Sets the footer to the default for the current screen.
@@ -434,7 +434,6 @@ def set_default_footer(self, clobber_composer=False):
self.set_footer(footer)
-
def set_bars(self, clobber_composer=False):
"""
Sets both the footer and header to their default values
@@ -443,7 +442,6 @@ def set_bars(self, clobber_composer=False):
self.set_default_header()
self.set_default_footer(clobber_composer)
-
def close_editor(self):
"""
Close whatever editing widget is open and restore proper
@@ -457,14 +455,12 @@ def close_editor(self):
self.loop.widget = self.loop.widget[0]
self.set_default_header()
-
def overlay_p(self):
"""
Return True or False if the current widget is an overlay.
"""
return isinstance(self.loop.widget, urwid.Overlay)
-
def remove_overlays(self, *_):
"""
Remove ALL urwid.Overlay objects which are currently covering the base
@@ -476,7 +472,6 @@ def remove_overlays(self, *_):
except:
break
-
def switch_editor(self):
"""
Switch focus between the thread viewer and the open editor
@@ -511,7 +506,6 @@ def switch_editor(self):
self.loop.widget.header.attr_map = {None: attr[1]}
self.body.attr_map = {None: attr[1]}
-
def readable_delta(self, modified):
"""
Return a human-readable string representing the difference
@@ -530,7 +524,6 @@ def readable_delta(self, modified):
return "%d minutes ago" % minutes
return "less than a minute ago"
-
def quote_view_action(self, button, message):
"""
Callback function to view a quote from the message object menu.
@@ -548,7 +541,6 @@ def quote_view_action(self, button, message):
height=("relative", 60)
)
-
def quote_view_menu(self, button, post_ids):
"""
Receives a list of quote ids and makes a frilly menu to pick one to view.
@@ -571,7 +563,7 @@ def quote_view_menu(self, button, post_ids):
]
buttons.append(cute_button(label, self.quote_view_action, message))
except IndexError:
- continue # users can submit >>29384234 garbage references
+ continue # users can submit >>29384234 garbage references
widget = OptionsMenu(
urwid.ListBox(urwid.SimpleFocusListWalker(buttons)),
@@ -586,7 +578,6 @@ def quote_view_menu(self, button, post_ids):
width=30
)
-
def edit_post(self, button, message):
post_id = message["post_id"]
thread_id = message["thread_id"]
@@ -600,12 +591,10 @@ def edit_post(self, button, message):
self.remove_overlays()
self.compose(init_body=message["body"], edit=message)
-
def reply(self, button, message):
self.remove_overlays()
self.compose(init_body=">>%d\n\n" % message["post_id"])
-
def deletion_dialog(self, button, message):
"""
Prompts the user to confirm deletion of an item.
@@ -615,7 +604,7 @@ def deletion_dialog(self, button, message):
buttons = [
urwid.Text(("bold", "Delete this %s?" % ("whole thread" if op else "post"))),
urwid.Divider(),
- cute_button(("10" , ">> Yes"), lambda _: [
+ cute_button(("10", ">> Yes"), lambda _: [
network.message_delete(message["thread_id"], message["post_id"]),
self.remove_overlays(),
self.index() if op else self.refresh()
@@ -634,14 +623,12 @@ def deletion_dialog(self, button, message):
valign=("relative", 50),
width=30, height=6)
-
def toggle_formatting(self, button, message):
self.remove_overlays()
raw = not message["send_raw"]
network.set_post_raw(message["thread_id"], message["post_id"], raw)
return self.refresh()
-
def on_post(self, button, message):
quotes = self.get_quotes(message)
author = self.usermap[message["author"]]
@@ -656,11 +643,12 @@ def on_post(self, button, message):
self.quote_view_menu, quotes))
if network.can_edit(message["thread_id"], message["post_id"]) \
- and not self.window_split:
+ and not self.window_split:
if message["post_id"] == 0:
msg = "Thread"
- else: msg = "Post"
+ else:
+ msg = "Post"
raw = message["send_raw"]
buttons.insert(0, urwid.Button("Delete %s" % msg, self.deletion_dialog, message))
@@ -681,7 +669,7 @@ def on_post(self, button, message):
size = self.loop.screen_size
self.loop.widget = urwid.Overlay(
- urwid.AttrMap(widget, str(author["color"]*10)),
+ urwid.AttrMap(widget, str(author["color"] * 10)),
self.loop.widget,
align=("relative", 50),
valign=("relative", 50),
@@ -689,7 +677,6 @@ def on_post(self, button, message):
height=len(buttons) + 2
)
-
def get_quotes(self, msg_object, value_type=int):
"""
Returns the post_ids that msg_object is quoting.
@@ -704,7 +691,6 @@ def get_quotes(self, msg_object, value_type=int):
[quotes.append(cdr) for car, cdr in paragraph if car == "quote"]
return [value_type(q) for q in quotes]
-
def make_thread_body(self, thread, pinned=False):
"""
Returns the pile widget that comprises a thread in the index.
@@ -746,7 +732,6 @@ def make_thread_body(self, thread, pinned=False):
pile.thread = thread
return pile
-
def make_message_body(self, message, no_action=False):
"""
Returns the widgets that comprise a message in a thread, including the
@@ -769,11 +754,11 @@ def make_message_body(self, message, no_action=False):
post = str(message["post_id"])
head = urwid.Columns([
- (2 + len(post), urwid.AttrMap(
- cute_button(">" + post, callback, message), "button", "hover")),
- (len(name._text) + 1, urwid.AttrMap(name, color)),
- urwid.AttrMap(urwid.Text(info), "dim")
- ])
+ (2 + len(post), urwid.AttrMap(
+ cute_button(">" + post, callback, message), "button", "hover")),
+ (len(name._text) + 1, urwid.AttrMap(name, color)),
+ urwid.AttrMap(urwid.Text(info), "dim")
+ ])
head.message = message
return [
@@ -786,7 +771,6 @@ def make_message_body(self, message, no_action=False):
urwid.AttrMap(urwid.Divider(self.theme["divider"]), "dim")
]
-
def timestring(self, epoch, mode="both"):
"""
Returns a string of time representing a given epoch and mode.
@@ -800,10 +784,9 @@ def timestring(self, epoch, mode="both"):
elif mode == "date":
directive = self.prefs["date"]
else:
- directive = "%s %s" % ( self.prefs["time"], self.prefs["date"])
+ directive = "%s %s" % (self.prefs["time"], self.prefs["date"])
return date.strftime(directive)
-
def index(self, *_, threads=None):
"""
Browse or return to the index.
@@ -818,7 +801,7 @@ def index(self, *_, threads=None):
self.window_split = False
if threads:
# passing in an argument for threads implies that we are showing a
- # narrowed selection of content, so we dont want to resume last_index_pos
+ # narrowed selection of content, so we don't want to resume last_index_pos
self.last_index_pos = False
else:
threads, usermap = network.thread_index()
@@ -852,8 +835,6 @@ def index(self, *_, threads=None):
# checks to make sure there are any posts to focus
self.box.set_focus(0)
-
-
def thread_load(self, button, thread_id):
"""
Open a thread.
@@ -878,7 +859,6 @@ def thread_load(self, button, thread_id):
self.set_default_footer()
self.goto_post(mark(thread_id))
-
def toggle_client_pin(self):
if self.mode != "index":
return
@@ -886,7 +866,6 @@ def toggle_client_pin(self):
self.client_pinned_threads = toggle_client_pin(thread_id)
self.index()
-
def toggle_server_pin(self):
if self.mode != "index" or not network.user["is_admin"]:
return
@@ -894,14 +873,13 @@ def toggle_server_pin(self):
network.thread_set_pin(thread["thread_id"], not thread["pinned"])
self.index()
-
def search_index_callback(self, query):
simple_query = query.lower().strip()
threads, usermap = network.thread_index()
self.usermap.update(usermap)
results = [
thread for thread in threads
- if simple_query in thread["title"].lower().strip()
+ if simple_query in thread["title"].lower().strip()
]
if results:
self.index(threads=results)
@@ -910,14 +888,13 @@ def search_index_callback(self, query):
else:
self.temp_footer_message("No results for '{}'".format(query))
-
def search_thread_callback(self, query):
- # normally i would just use self.thread["messages"] but I need the visbile text post-formatted
+ # normally i would just use self.thread["messages"] but I need the visible text post-formatted
query = query.lower().strip()
self.match_data["matches"] = [
self.thread["messages"][widget.base_widget.post_id] for widget in self.walker
- if isinstance(widget.base_widget, MessageBody)
- and query in widget.base_widget.text.lower().strip()
+ if isinstance(widget.base_widget, MessageBody)
+ and query in widget.base_widget.text.lower().strip()
]
if self.match_data["matches"]:
self.match_data["query"] = query
@@ -927,7 +904,6 @@ def search_thread_callback(self, query):
else:
self.temp_footer_message("No results for '{}'".format(query))
-
def do_search_result(self, forward=True):
if not self.match_data["matches"]:
return
@@ -945,7 +921,6 @@ def do_search_result(self, forward=True):
self.match_data["position"] + 1, length, self.match_data["query"]
), 5)
-
# XXX: Try to find a way to overlay properties onto an existing widget instead of this trainwreck.
# def highlight_query(self):
# # pass
@@ -967,7 +942,6 @@ def do_search_result(self, forward=True):
# index += length
# new_attrs.append((prop, length))
-
def search_prompt(self):
if self.mode == "index":
callback = self.search_index_callback
@@ -991,7 +965,6 @@ def search_prompt(self):
valign=("relative", 25 if self.window_split else 50),
width=("relative", 40), height=6)
-
def refresh(self):
self.remove_overlays()
if self.mode == "index":
@@ -1006,7 +979,6 @@ def refresh(self):
self.goto_post(mark(thread))
self.temp_footer_message("Refreshed content!", 1)
-
def back(self, terminate=False):
if app.mode == "index" and terminate:
frilly_exit()
@@ -1019,7 +991,7 @@ def back(self, terminate=False):
buttons = [
urwid.Text(("bold", "Discard current post?")),
urwid.Divider(),
- cute_button(("10" , ">> Yes"), lambda _: [
+ cute_button(("10", ">> Yes"), lambda _: [
self.remove_overlays(),
self.index()
]),
@@ -1041,14 +1013,12 @@ def back(self, terminate=False):
mark()
self.index()
-
def get_focus_post(self, return_widget=False):
pos = self.box.get_focus_path()[0]
if self.mode == "thread":
return (pos - (pos % 5)) // 5
return pos if not return_widget else self.walker[pos]
-
def header_jump_next(self):
if self.mode == "index":
return self.box.keypress(self.loop.screen_size, "down")
@@ -1056,8 +1026,8 @@ def header_jump_next(self):
post = self.get_focus_post()
if post != self.thread["reply_count"]:
self.goto_post(post + 1)
- else: break
-
+ else:
+ break
def header_jump_previous(self):
if self.mode == "index":
@@ -1066,8 +1036,8 @@ def header_jump_previous(self):
post = self.get_focus_post()
if post != 0:
self.goto_post(post - 1)
- else: break
-
+ else:
+ break
def goto_post(self, number):
if self.mode != "thread":
@@ -1084,7 +1054,6 @@ def goto_post(self, number):
except IndexError:
self.temp_footer_message("OUT OF BOUNDS")
-
def goto_post_prompt(self, init):
if self.mode != "thread":
return
@@ -1113,7 +1082,6 @@ def goto_post_prompt(self, init):
valign=("relative", 25 if self.window_split else 50),
width=20, height=6)
-
def jump_peek(self, editor, value, display):
if not value:
return display.set_text("")
@@ -1121,24 +1089,22 @@ def jump_peek(self, editor, value, display):
author = self.usermap[msg["author"]]
display.set_text((str(author["color"]), ">>%s %s" % (value, author["user_name"])))
-
def set_theme(self, button, new_state):
"""
Callback for the theme radio buttons in the options.
"""
- if new_state == True:
+ if new_state:
self.theme = themes[button.label].copy()
if self.prefs["custom_divider_char"]:
self.theme["divider"] = self.prefs["custom_divider_char"]
self.prefs["frame_theme"] = button.label
bbjrc("update", **self.prefs)
-
def set_new_editor(self, button, value, arg):
"""
- Callback for the option radio buttons to set the the text editor.
+ Callback for the option radio buttons to set the text editor.
"""
- if value == False:
+ if not value:
return
elif isinstance(value, str):
[button.set_state(False) for button in arg]
@@ -1151,7 +1117,6 @@ def set_new_editor(self, button, value, arg):
self.prefs.update({"editor": key})
bbjrc("update", **self.prefs)
-
def set_editor_mode(self, button, value):
"""
Callback for the editor mode radio buttons in the options.
@@ -1159,11 +1124,9 @@ def set_editor_mode(self, button, value):
self.prefs["integrate_external_editor"] = value
bbjrc("update", **self.prefs)
-
def toggle_thread_pin(self, thread_id):
pass
-
def relog(self, *_, **__):
"""
Options menu callback to log the user in again.
@@ -1182,7 +1145,6 @@ def relog(self, *_, **__):
self.set_default_header()
self.options_menu()
-
def unlog(self, *_, **__):
"""
Options menu callback to anonymize the user and
@@ -1194,10 +1156,9 @@ def unlog(self, *_, **__):
self.set_default_header()
self.options_menu()
-
def general_help(self):
"""
- Show a general help dialog. In all honestly, its not
+ Show a general help dialog. In all honestly, it's not
very useful and will only help people who have never
really used terminal software before =)
"""
@@ -1224,7 +1185,6 @@ def general_help(self):
height=("relative", 60)
)
-
def formatting_help(self, *_):
"""
Pops a help window for formatting directives.
@@ -1249,35 +1209,29 @@ def formatting_help(self, *_):
height=("relative", vh)
)
-
def set_color(self, button, value, color):
- if value == False:
+ if not value:
return
network.user_update(color=color)
-
def toggle_exit(self, button, value):
self.prefs["dramatic_exit"] = value
bbjrc("update", **self.prefs)
-
def toggle_anon_warn(self, button, value):
self.prefs["confirm_anon"] = value
bbjrc("update", **self.prefs)
-
def toggle_mouse(self, button, value):
self.prefs["mouse_integration"] = value
self.loop.handle_mouse = value
self.loop.screen.set_mouse_tracking(value)
bbjrc("update", **self.prefs)
-
def toggle_spacing(self, button, value):
self.prefs["index_spacing"] = value
bbjrc("update", **self.prefs)
-
def change_username(self, *_):
self.loop.stop()
call("clear", shell=True)
@@ -1293,7 +1247,6 @@ def change_username(self, *_):
except (KeyboardInterrupt, InterruptedError):
self.loop.start()
-
def change_password(self, *_):
self.loop.stop()
call("clear", shell=True)
@@ -1309,7 +1262,6 @@ def change_password(self, *_):
except (KeyboardInterrupt, InterruptedError):
self.loop.start()
-
def live_time_render(self, editor, text, args):
widget, key = args
try:
@@ -1320,25 +1272,22 @@ def live_time_render(self, editor, text, args):
rendered = ("1", "Invalid Input")
widget.set_text(rendered)
-
def edit_width(self, editor, content):
value = int(content) if content else 0
- if value < 10: value = 10
+ if value < 10:
+ value = 10
self.prefs["max_text_width"] = value
bbjrc("update", **self.prefs)
-
def edit_shift(self, editor, content):
self.prefs["shift_multiplier"] = \
int(content) if content else 0
bbjrc("update", **self.prefs)
-
def save_escape_key(self, value, mode):
self.prefs["edit_escapes"].update({mode[0]: value})
bbjrc("update", **self.prefs)
-
def set_escape_key(self, button, args):
mode = args[0]
widget = OptionsMenu(
@@ -1360,7 +1309,6 @@ def set_escape_key(self, button, args):
width=25, height=5
)
-
def incr_jump(self):
if self.mode != "thread":
return
@@ -1373,7 +1321,6 @@ def incr_jump(self):
self.set_default_footer()
bbjrc("update", **self.prefs)
-
def decr_jump(self):
if self.mode != "thread":
return
@@ -1386,7 +1333,6 @@ def decr_jump(self):
self.set_default_footer()
bbjrc("update", **self.prefs)
-
def options_menu(self):
"""
Create a popup for the user to configure their account and
@@ -1405,7 +1351,7 @@ def options_menu(self):
urwid.Divider(),
urwid.Text(("button", "Your color:")),
urwid.Text(("default", "This color will show on your "
- "post headers and when people quote you.")),
+ "post headers and when people quote you.")),
urwid.Divider()
]
@@ -1570,7 +1516,6 @@ def options_menu(self):
height=("relative", 75)
)
-
def footer_prompt(self, text, callback, *callback_args, extra_text=None):
text = "(%s)> " % text
widget = urwid.Columns([
@@ -1587,17 +1532,15 @@ def footer_prompt(self, text, callback, *callback_args, extra_text=None):
self.loop.widget.footer = widget
self.loop.widget.focus_position = "footer"
-
def reset_footer(self, *_):
if self.window_split:
return
self.set_default_footer()
# try:
- # self.loop.widget.focus_position = "body"
+ # self.loop.widget.focus_position = "body"
# except:
- # just keep trying until the focus widget can handle it
- # return self.loop.set_alarm_in(0.25, self.reset_footer)
-
+ # just keep trying until the focus widget can handle it
+ # return self.loop.set_alarm_in(0.25, self.reset_footer)
def temp_footer_message(self, string, duration=3):
self.loop.remove_alarm(self.last_alarm)
@@ -1607,7 +1550,6 @@ def temp_footer_message(self, string, duration=3):
else:
self.set_footer(string)
-
def overthrow_ext_edit(self, init_body=""):
"""
Opens the external editor, but instead of integreating it into the app,
@@ -1625,7 +1567,6 @@ def overthrow_ext_edit(self, init_body=""):
self.loop.start()
return body.strip()
-
def compose(self, title=None, init_body="", edit=False):
"""
Dispatches the appropriate composure mode and widget based on application
@@ -1635,7 +1576,8 @@ def compose(self, title=None, init_body="", edit=False):
return self.footer_prompt("Title", self.compose)
elif title:
- try: network.validate("title", title)
+ try:
+ network.validate("title", title)
except AssertionError as e:
return self.footer_prompt(
"Title", self.compose, extra_text=e.description)
@@ -1711,7 +1653,7 @@ def compose(self, title=None, init_body="", edit=False):
self.loop.screen_size[1] // 2)])
self.set_header(*header)
- self.window_split=True
+ self.window_split = True
self.switch_editor()
@@ -1719,6 +1661,7 @@ class MessageBody(urwid.Text):
"""
An urwid.Text object that works with the BBJ formatting directives.
"""
+
def __init__(self, message):
if message["send_raw"]:
return super(MessageBody, self).__init__(message["body"])
@@ -1744,9 +1687,9 @@ def __init__(self, message):
result.append(("3", "%s" % body.strip()))
# TEN MILLION YEARS DUNGEON NO TRIAL
# try:
- # this /naughty/ hack is supposed to keep spacing consistent....needs tweaking
- # if result[-1][-1][-1] != "\n":
- # result.append(("default", "\n"))
+ # this /naughty/ hack is supposed to keep spacing consistent....needs tweaking
+ # if result[-1][-1][-1] != "\n":
+ # result.append(("default", "\n"))
# except IndexError:
# pass
@@ -1776,7 +1719,7 @@ def __init__(self, message):
color += "0"
else:
display = "[%s]" % user["user_name"]
- except: # the quote may be garbage and refer to a nonexistant post
+ except: # the quote may be garbage and refer to a nonexistent post
display = ""
result.append((color, ">>%s%s" % (body, display)))
@@ -1792,7 +1735,7 @@ def __init__(self, message):
result.append(("default", body))
last_directive = directive
result.append("\n\n")
- result.pop() # lazily ensure \n\n between paragraphs but not at the end
+ result.pop() # lazily ensure \n\n between paragraphs but not at the end
super(MessageBody, self).__init__(result)
@@ -1802,6 +1745,7 @@ class KeyPrompt(urwid.Edit):
keybinding that is pressed. Is used to customize
keybinds across the client.
"""
+
def __init__(self, initkey, callback, *callback_args):
super(KeyPrompt, self).__init__()
self.set_edit_text(initkey)
@@ -1823,6 +1767,7 @@ class Prompt(urwid.Edit):
character-wise (not word-wise) movements are
implemented.
"""
+
def keypress(self, size, key):
if not super(Prompt, self).keypress(size, key):
return
@@ -1854,7 +1799,7 @@ def keypress(self, size, key):
self.set_edit_pos(len(text))
elif key == "d":
- self.set_edit_text(text[0:column] + text[column+1:])
+ self.set_edit_text(text[0:column] + text[column + 1:])
return key
@@ -1865,7 +1810,6 @@ def __init__(self, callback, *callback_args):
self.callback = callback
self.args = callback_args
-
def keypress(self, size, key):
super(FootPrompt, self).keypress(size, key)
if key == "enter":
@@ -1884,7 +1828,6 @@ def __init__(self, callback, *callback_args):
self.callback = callback
self.args = callback_args
-
def keypress(self, size, key):
keyl = key.lower()
if key == "enter":
@@ -1905,7 +1848,6 @@ def __init__(self, max_length, callback, *callback_args):
self.callback = callback
self.args = callback_args
-
def valid_char(self, char):
if not (len(char) == 1 and char in "0123456789"):
return False
@@ -1919,11 +1861,10 @@ def valid_char(self, char):
text.set_text((attr, body))
app.loop.draw_screen()
sleep(0.05)
- except: # fuck it who cares
+ except: # fuck it who cares
pass
return False
-
def incr(self, direction):
value = self.value()
if direction == "down" and value > 0:
@@ -1939,7 +1880,6 @@ def incr(self, direction):
self.set_edit_pos(len(value))
-
def keypress(self, size, key):
keyl = key.lower()
if key == "enter":
@@ -1955,7 +1895,7 @@ def keypress(self, size, key):
elif keyl in ("up", "ctrl p", "p", "k"):
self.incr("up")
- else: # dont use super because we want to allow zeros in this box
+ else: # don't use super because we want to allow zeros in this box
urwid.Edit.keypress(self, (size[0],), key)
@@ -1982,7 +1922,6 @@ def __init__(self, endpoint, **params):
super(ExternalEditor, self).__init__(command, env, app.loop, app.prefs["edit_escapes"]["abort"])
urwid.connect_signal(self, "closed", self.exterminate)
-
# def confirm_anon(self, button, value):
# app.loop.widget = app.loop.widget[0]
# if not value:
@@ -1993,11 +1932,10 @@ def __init__(self, endpoint, **params):
# app.loop.start()
# self.exterminate(anon_confirmed=True)
-
def exterminate(self, *_, anon_confirmed=False):
if app.prefs["confirm_anon"] \
- and not anon_confirmed \
- and network.user["user_name"] == "anonymous":
+ and not anon_confirmed \
+ and network.user["user_name"] == "anonymous":
# TODO fixoverlay: urwid terminal widgets have been mucking
# up overlay dialogs since the wee days of bbj, i really
# need to find a real solution instead of dodging the issue
@@ -2055,7 +1993,6 @@ def exterminate(self, *_, anon_confirmed=False):
else:
app.temp_footer_message("EMPTY POST DISCARDED")
-
def keypress(self, size, key):
"""
The majority of the things the parent keypress method will do is
@@ -2108,7 +2045,6 @@ def keypress(self, size, key):
os.write(self.master, key.encode("utf8"))
-
def __del__(self):
"""
Make damn sure we scoop up after ourselves here...
@@ -2151,7 +2087,6 @@ def keypress(self, size, key):
elif keyl == "ctrl l":
wipe_screen()
-
def mouse_event(self, size, event, button, x, y, focus):
if super(OptionsMenu, self).mouse_event(size, event, button, x, y, focus):
return
@@ -2167,6 +2102,7 @@ class ActionBox(urwid.ListBox):
The listwalker used by all the browsing pages. Most of the application
takes place in an instance of this box. Handles many keybinds.
"""
+
def keypress(self, size, key):
super(ActionBox, self).keypress(size, key)
overlay = app.overlay_p()
@@ -2252,8 +2188,10 @@ def keypress(self, size, key):
elif key == "~":
# sssssshhhhhhhh
app.loop.stop()
- try: call("sl", shell=True)
- except: pass
+ try:
+ call("sl", shell=True)
+ except:
+ pass
app.loop.start()
elif keyl == "$":
@@ -2273,7 +2211,6 @@ def keypress(self, size, key):
elif keyl == "ctrl r":
app.reply(None, message)
-
def mouse_event(self, size, event, button, x, y, focus):
if super(ActionBox, self).mouse_event(size, event, button, x, y, focus):
return
@@ -2286,7 +2223,6 @@ def mouse_event(self, size, event, button, x, y, focus):
self._keypress_down(size)
-
def frilly_exit():
"""
Exit with some flair. Will fill the screen with rainbows
@@ -2294,15 +2230,17 @@ def frilly_exit():
setting, `dramatic_exit`
"""
# sometimes this gets called before the loop is set up properly
- try: app.loop.stop()
- except: pass
+ try:
+ app.loop.stop()
+ except:
+ pass
if app.prefs["dramatic_exit"] and app.loop.screen_size:
width, height = app.loop.screen_size
for x in range(height - 1):
motherfucking_rainbows(
"".join([choice([" ", choice(punctuation)])
- for x in range(width)]
- ))
+ for x in range(width)]
+ ))
out = " ~~CoMeE BaCkK SooOn~~ 0000000"
motherfucking_rainbows(out.zfill(width))
else:
@@ -2328,7 +2266,8 @@ def urwid_rainbows(string, bold=False):
a markup list suitable for urwid's Text contructor.
"""
colors = [str(x) for x in range(1, 7)]
- if bold: colors = [(c + "0") for c in colors]
+ if bold:
+ colors = [(c + "0") for c in colors]
return urwid.Text([(choice(colors), char) for char in string])
@@ -2344,7 +2283,7 @@ def motherfucking_rainbows(string, inputmode=False, end="\n"):
return print(end, end="")
-def paren_prompt(text, positive=True, choices=[], function=input, default=None):
+def paren_prompt(text, positive=True, choices=None, function=input, default=None):
"""
input(), but riced the fuck out. Changes color depending on
the value of positive (blue/green for good stuff, red/yellow
@@ -2352,12 +2291,14 @@ def paren_prompt(text, positive=True, choices=[], function=input, default=None):
system capable of rejecting unavailable choices and highlighting
their first characters.
"""
+ if choices is None:
+ choices = []
end = text[-1]
if end != "?" and end in punctuation:
text = text[0:-1]
mood = ("\033[1;36m", "\033[1;32m") if positive \
- else ("\033[1;31m", "\033[1;33m")
+ else ("\033[1;31m", "\033[1;33m")
if choices:
prompt = "%s{" % mood[0]
@@ -2389,7 +2330,8 @@ def sane_value(key, prompt, positive=True, return_empty=False):
response = paren_prompt(prompt, positive)
if return_empty and response == "":
return response
- try: network.validate(key, response)
+ try:
+ network.validate(key, response)
except AssertionError as e:
return sane_value(key, e.description, False)
return response
@@ -2424,8 +2366,8 @@ def log_in(relog=False):
name = sane_value("user_name", "Username", return_empty=True)
else:
name = get_arg("user") \
- or os.getenv("BBJ_USER") \
- or sane_value("user_name", "Username", return_empty=True)
+ or os.getenv("BBJ_USER") \
+ or sane_value("user_name", "Username", return_empty=True)
if name == "":
motherfucking_rainbows("~~W3 4R3 4n0nYm0u5~~")
else:
@@ -2435,7 +2377,7 @@ def log_in(relog=False):
network.set_credentials(
name,
os.getenv("BBJ_PASSWORD", default="")
- if not relog else ""
+ if not relog else ""
)
# make it easy for people who use an empty password =)
motherfucking_rainbows("~~welcome back {}~~".format(network.user_name))
@@ -2467,7 +2409,7 @@ def login_loop(prompt, positive):
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.5) # let that confirmation message shine
+ sleep(0.5) # let that confirmation message shine
def bbjrc(mode, **params):
@@ -2488,7 +2430,7 @@ def bbjrc(mode, **params):
# Also covers a previous encounter a user
# had with having a NoneType set in their
# config by accident, crashing the program.
- if key not in values or values[key] == None:
+ if key not in values or values[key] is None:
values[key] = default_value
# else make one
except FileNotFoundError:
@@ -2505,7 +2447,7 @@ def bbjrc(mode, **params):
def mark(directive=True):
"""
Set and retrieve positional marks for threads.
- This uses a seperate file from the preferences
+ This uses a separate file from the preferences
to keep it free from clutter.
"""
try:
@@ -2514,7 +2456,7 @@ def mark(directive=True):
except FileNotFoundError:
values = {}
- if directive == True and app.mode == "thread":
+ if directive and app.mode == "thread":
pos = app.get_focus_post()
values[app.thread["thread_id"]] = pos
with open(markpath, "w") as _out:
@@ -2564,8 +2506,8 @@ def ignore(*_, **__):
def wipe_screen(*_):
"""
- A crude hack to repaint the whole screen. I didnt immediately
- see anything to acheive this in the MainLoop methods so this
+ A crude hack to repaint the whole screen. I didn't immediately
+ see anything to achieve this in the MainLoop methods so this
will do, I suppose.
"""
app.loop.stop()
diff --git a/contrib/bbj.service b/contrib/bbj.service
new file mode 100644
index 0000000..446c1ce
--- /dev/null
+++ b/contrib/bbj.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=bbj daemon
+After=network-online.target
+
+[Service]
+Type=simple
+WorkingDirectory=/srv/bbj/bbj
+ExecStart=/srv/bbj/bbj/venv/bin/python3 server.py
+User=bbj
+Restart=always
+RestartSec=5
+StartLimitInterval=60s
+StartLimitBurst=3
+
+[Install]
+WantedBy=default.target
diff --git a/contrib/bbj.sh b/contrib/bbj.sh
new file mode 100644
index 0000000..4aa7b20
--- /dev/null
+++ b/contrib/bbj.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /srv/bbj/venv/bin/python3 /srv/bbj/clients/urwid/main.py
diff --git a/docs/docs/api_overview.md b/docs/docs/api_overview.md
index edbdc86..8027edf 100644
--- a/docs/docs/api_overview.md
+++ b/docs/docs/api_overview.md
@@ -12,7 +12,7 @@ at the root:
`http://server.com/api/endpoint_here`
-The body of your request contains all of it's argument fields, instead of
+The body of your request contains all of its argument fields, instead of
using URL parameters. As a demonstration, to call `thread_create`,
it requires two arguments: `title`, and `body`. We put those argument
names at the root of the json object, and their values are the info
@@ -33,13 +33,13 @@ GET these if you so choose.
For all endpoints, argument keys that are not consumed by the endpoint are
ignored. Posting an object with a key/value pair of `"sandwich": True` will
-not clog up any pipes :) In the same vein, endpoints who dont take arguments
+not clog up any pipes :) In the same vein, endpoints who don't take arguments
don't care if you supply them anyway.
## Output
BBJ returns data in a consistently formatted json object. The base object
-has three keys: `data`, `usermap`, and `error`. Visualizied:
+has three keys: `data`, `usermap`, and `error`. Visualized:
```javascript
{
@@ -74,7 +74,7 @@ objects. BBJ handles users entirely by an ID system, meaning any references
to them inside of response data will not include vital information like their
username, or their profile information. Instead, we fetch those values from
this usermap object. All of it's root keys are user_id's and their values
-are user objects. It should be noted that the anonymous user has it's own
+are user objects. It should be noted that the anonymous user has its own
ID and profile object as well.
### error
@@ -121,7 +121,7 @@ is correct for the given user.
Requires the arguments `thread_id` and `post_id`.
Delete a message from a thread. The same rules apply
-here as `edit_post` and `edit_query`: the logged in user
+here as `edit_post` and `edit_query`: the logged-in user
must either be the one who posted the message within 24hrs,
or have admin rights. The same error descriptions and code
are returned on falilure. Boolean true is returned on
@@ -215,7 +215,7 @@ you can access metadata for these threads by the `threads` object
which is also provided.
The `messages` array is already sorted by submission time, newest
-first. The order in the threads object is undefined and you should
+first. The order in the threads object is undefined, and you should
instead use their `last_mod` attribute if you intend to list them
out visually.
@@ -319,7 +319,7 @@ for the original post.
Returns the thread object with all of its messages loaded.
Requires the argument `thread_id`. `format` may also be
specified as a formatter to run the messages through.
-Currently only "sequential" is supported.
+Currently, only "sequential" is supported.
You may also supply the parameter `op_only`. When it's value
is non-nil, the messages array will only include post_id 0 (the first)
diff --git a/docs/docs/errors.md b/docs/docs/errors.md
index 4144fbd..2f41efc 100644
--- a/docs/docs/errors.md
+++ b/docs/docs/errors.md
@@ -23,7 +23,7 @@ users.
* **Code 0**: Malformed but non-empty json input. An empty json input where it is required is handled by code 3. This is just decoding errors. The exception text is returned as description.
- * **Code 1**: Internal server error. A short representation of the internal exception as well as the code the server logged it as is returned in the description. Your clients cannot recover from this class of error, and its probably not your fault if you encounter it. If you ever get one, file a bug report.
+ * **Code 1**: Internal server error. A short representation of the internal exception as well as the code the server logged it as is returned in the description. Your clients cannot recover from this class of error, and it's probably not your fault if you encounter it. If you ever get one, file a bug report.
* **Code 2**: Server HTTP error: This is similar to the above but captures errors for the HTTP server rather than BBJs own codebase. The description contains the HTTP error code and server description. This notably covers 404s and thus invalid endpoint names. The HTTP error code is left intact, so you may choose to let your HTTP library or tool of choice handle these for you.
diff --git a/docs/site/errors/index.html b/docs/site/errors/index.html
index edbb4b4..995a2a1 100644
--- a/docs/site/errors/index.html
+++ b/docs/site/errors/index.html
@@ -120,7 +120,7 @@ Handling Error Responses
Code 0: Malformed but non-empty json input. An empty json input where it is required is handled by code 3. This is just decoding errors. The exception text is returned as description.
-Code 1: Internal server error. A short representation of the internal exception as well as the code the server logged it as is returned in the description. Your clients cannot recover from this class of error, and its probably not your fault if you encounter it. If you ever get one, file a bug report.
+Code 1: Internal server error. A short representation of the internal exception as well as the code the server logged it as is returned in the description. Your clients cannot recover from this class of error, and It's probably not your fault if you encounter it. If you ever get one, file a bug report.
Code 2: Server HTTP error: This is similar to the above but captures errors for the HTTP server rather than BBJs own codebase. The description contains the HTTP error code and server description. This notably covers 404s and thus invalid endpoint names. The HTTP error code is left intact, so you may choose to let your HTTP library or tool of choice handle these for you.
diff --git a/docs/site/index.html b/docs/site/index.html
index ae27211..33d99d7 100644
--- a/docs/site/index.html
+++ b/docs/site/index.html
@@ -107,7 +107,7 @@
Being a command-line-oriented text board, BBJ has no avatars or file sharing
-capabilties, so its easier to administrate and can't be used to distribute illegal
+capabilties, so it's easier to administrate and can't be used to distribute illegal
content like imageboards. It has very few dependancies and is easy to set up.
The API is simple and doesn't use require complex authorization schemes or session management.
It is fully documented on this site (though the verbage is still being revised for friendliness)
diff --git a/docs/site/mkdocs/js/text.js b/docs/site/mkdocs/js/text.js
index 17921b6..0d8dd1b 100644
--- a/docs/site/mkdocs/js/text.js
+++ b/docs/site/mkdocs/js/text.js
@@ -13,10 +13,10 @@ define(['module'], function (module) {
var text, fs, Cc, Ci, xpcIsWindows,
progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
- xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,
+ xmlRegExp = /^\s*<\?xml(\s)+version=['"](\d)*.(\d)*['"](\s)*\?>/im,
bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im,
hasLocation = typeof location !== 'undefined' && location.href,
- defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''),
+ defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/:/, ''),
defaultHostName = hasLocation && location.hostname,
defaultPort = hasLocation && (location.port || undefined),
buildMap = {},
@@ -116,7 +116,7 @@ define(['module'], function (module) {
};
},
- xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/,
+ xdRegExp: /^((\w+):)?\/\/([^\/\\]+)/,
/**
* Is an URL on another domain. Only works for browser use, returns
diff --git a/gendocs.sh b/gendocs.sh
index 600fca0..e208338 100755
--- a/gendocs.sh
+++ b/gendocs.sh
@@ -4,6 +4,4 @@
# Nothing magical here.
python3 ./mkendpoints.py
-cd ./docs
-mkdocs build
-cd ..
+(cd docs; mkdocs build)
diff --git a/prototype/clients/urwid/main.py b/prototype/clients/urwid/main.py
index d29bcac..2b9335c 100644
--- a/prototype/clients/urwid/main.py
+++ b/prototype/clients/urwid/main.py
@@ -12,7 +12,7 @@ def geterr(obj):
error = obj.get("error")
if not error:
return False
- return (error["code"], error["description"])
+ return error["code"], error["description"]
def register_prompt(user, initial=True):
diff --git a/prototype/src/endpoints.py b/prototype/src/endpoints.py
index 973a528..381170d 100644
--- a/prototype/src/endpoints.py
+++ b/prototype/src/endpoints.py
@@ -1,26 +1,25 @@
-from src import formatting
-from src import schema
from time import time
-from src import db
+from src import db
+from src import formatting
+from src import schema
endpoints = {
- "check_auth": ["user", "auth_hash"],
- "is_registered": ["target_user"],
- "is_admin": ["target_user"],
- "thread_index": [],
- "thread_load": ["thread_id"],
- "thread_create": ["title", "body", "tags"],
- "thread_reply": ["thread_id", "body"],
- "edit_post": ["thread_id", "post_id", "body"],
- "edit_query": ["thread_id", "post_id"],
- "can_edit": ["thread_id", "post_id"],
- "user_register": ["user", "auth_hash", "quip", "bio"],
- "user_get": ["target_user"],
+ "check_auth": ["user", "auth_hash"],
+ "is_registered": ["target_user"],
+ "is_admin": ["target_user"],
+ "thread_index": [],
+ "thread_load": ["thread_id"],
+ "thread_create": ["title", "body", "tags"],
+ "thread_reply": ["thread_id", "body"],
+ "edit_post": ["thread_id", "post_id", "body"],
+ "edit_query": ["thread_id", "post_id"],
+ "can_edit": ["thread_id", "post_id"],
+ "user_register": ["user", "auth_hash", "quip", "bio"],
+ "user_get": ["target_user"],
"user_name_to_id": ["target_user"]
}
-
authless = [
"is_registered",
"user_register"
@@ -32,7 +31,7 @@
def create_usermap(thread, index=False):
if index:
return {user: db.user_get(user) for user in
- {i["author"] for i in thread}}
+ {i["author"] for i in thread}}
result = {reply["author"] for reply in thread["replies"]}
result.add(thread["author"])
@@ -58,7 +57,7 @@ def is_registered(json):
def check_auth(json):
- "Returns true or false whether auth_hashes matches user."
+ """Returns true or false whether auth_hashes matches user."""
return bool(db.user_auth(json["user"], json["auth_hash"]))
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..05cf51c
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+CherryPy~=18.6.1
+Markdown~=3.3.6
+urwid~=2.1.2
diff --git a/server.py b/server.py
index 9d509b9..f37748a 100644
--- a/server.py
+++ b/server.py
@@ -1,16 +1,18 @@
-from src.exceptions import BBJException, BBJParameterError, BBJUserError
-from src import db, schema, formatting
+import json
+import sqlite3
+import traceback
from functools import wraps
-from uuid import uuid1
from sys import argv
-import traceback
+from uuid import uuid1
+
import cherrypy
-import sqlite3
-import json
+
+from src import db, schema, formatting
+from src.exceptions import BBJException, BBJParameterError, BBJUserError
dbname = "data.sqlite"
-# any values here may be overrided in the config.json. Any values not listed
+# any values here may be overridden in the config.json. Any values not listed
# here will have no effect on the server.
default_config = {
"admins": [],
@@ -29,7 +31,7 @@
# The application will never store a config value
# as the NoneType, so users may set an option as
# null in their file to reset it to default
- if key not in app_config or app_config[key] == None:
+ if key not in app_config or app_config[key] is None:
app_config[key] = default_value
# else just use the defaults
except FileNotFoundError:
@@ -171,7 +173,7 @@ def validate(json, args):
raise BBJParameterError(
"Required parameter {} is absent from the request. "
"This method requires the following arguments: {}"
- .format(arg, ", ".join(args)))
+ .format(arg, ", ".join(args)))
def no_anon_hook(user, message=None, user_error=True):
@@ -217,6 +219,7 @@ def user_register(self, args, database, user, **kwargs):
validate(args, ["user_name", "auth_hash"])
return db.user_register(
database, args["user_name"], args["auth_hash"])
+
user_register.doctype = "Users"
user_register.arglist = (
("user_name", "string: the desired display name"),
@@ -240,6 +243,7 @@ def user_update(self, args, database, user, **kwargs):
no_anon_hook(user, "Anons cannot modify their account.")
validate(args, []) # just make sure its not empty
return db.user_update(database, user, args)
+
user_update.doctype = "Users"
user_update.arglist = (
("Any of the following may be submitted", ""),
@@ -257,6 +261,7 @@ def get_me(self, args, database, user, **kwargs):
including your `auth_hash`.
"""
return user
+
get_me.doctype = "Users"
get_me.arglist = (("", ""),)
@@ -281,6 +286,7 @@ def user_map(self, args, database, user, **kwargs):
for user in users
}
return list(users)
+
user_map.doctype = "Tools"
user_map.arglist = (("", ""),)
@@ -292,6 +298,7 @@ def user_get(self, args, database, user, **kwargs):
validate(args, ["target_user"])
return db.user_resolve(
database, args["target_user"], return_false=False, externalize=True)
+
user_get.doctype = "Users"
user_get.arglist = (
("target_user", "string: either a user_name or a user_id"),
@@ -305,6 +312,7 @@ def user_is_registered(self, args, database, user, **kwargs):
"""
validate(args, ["target_user"])
return bool(db.user_resolve(database, args["target_user"]))
+
user_is_registered.doctype = "Users"
user_is_registered.arglist = (
("target_user", "string: either a user_name or a user_id"),
@@ -320,6 +328,7 @@ def check_auth(self, args, database, user, **kwargs):
user = db.user_resolve(
database, args["target_user"], return_false=False)
return args["target_hash"].lower() == user["auth_hash"].lower()
+
check_auth.doctype = "Authorization"
check_auth.arglist = (
("target_user", "string: either a user_name or a user_id"),
@@ -338,6 +347,7 @@ def thread_index(self, args, database, user, **kwargs):
threads = db.thread_index(database, include_op=args.get("include_op"))
cherrypy.thread_data.usermap = create_usermap(database, threads, True)
return threads
+
thread_index.doctype = "Threads & Messages"
thread_index.arglist = (
("OPTIONAL: include_op", "boolean: Include a `messages` object containing the original post"),
@@ -382,6 +392,7 @@ def message_feed(self, args, database, user, **kwargs):
do_formatting(args.get("format"), feed["messages"])
return feed
+
message_feed.doctype = "Threads & Messages"
message_feed.arglist = (
("time", "int/float: epoch/unix time of the earliest point of interest"),
@@ -405,6 +416,7 @@ def thread_create(self, args, database, user, **kwargs):
cherrypy.thread_data.usermap = \
create_usermap(database, thread["messages"])
return thread
+
thread_create.doctype = "Threads & Messages"
thread_create.arglist = (
("body", "string: The body of the first message"),
@@ -426,6 +438,7 @@ def thread_reply(self, args, database, user, **kwargs):
return db.thread_reply(
database, user["user_id"], args["thread_id"],
args["body"], args.get("send_raw"))
+
thread_reply.doctype = "Threads & Messages"
thread_reply.arglist = (
("thread_id", "string: the id for the thread this message should post to."),
@@ -451,6 +464,7 @@ def thread_load(self, args, database, user, **kwargs):
create_usermap(database, thread["messages"])
do_formatting(args.get("format"), thread["messages"])
return thread
+
thread_load.doctype = "Threads & Messages"
thread_load.arglist = (
("thread_id", "string: the thread to load."),
@@ -484,6 +498,7 @@ def edit_post(self, args, database, user, **kwargs):
return db.message_edit_commit(
database, user["user_id"], args["thread_id"],
args["post_id"], args["body"], args.get("send_raw"))
+
edit_post.doctype = "Threads & Messages"
edit_post.arglist = (
("thread_id", "string: the thread the message was posted in."),
@@ -510,6 +525,7 @@ def delete_post(self, args, database, user, **kwargs):
validate(args, ["thread_id", "post_id"])
return db.message_delete(
database, user["user_id"], args["thread_id"], args["post_id"])
+
delete_post.doctype = "Threads & Messages"
delete_post.arglist = (
("thread_id", "string: the id of the thread this message was posted in."),
@@ -539,6 +555,7 @@ def set_post_raw(self, args, database, user, **kwargs):
database, user["user_id"],
args["thread_id"], args["post_id"],
None, args["value"], None)
+
set_post_raw.doctype = "Threads & Messages"
set_post_raw.arglist = (
("thread_id", "string: the id of the thread the message was posted in."),
@@ -556,6 +573,7 @@ def is_admin(self, args, database, user, **kwargs):
user = db.user_resolve(
database, args["target_user"], return_false=False)
return user["is_admin"]
+
is_admin.doctype = "Users"
is_admin.arglist = (
("target_user", "string: user_id or user_name to check against."),
@@ -575,6 +593,7 @@ def edit_query(self, args, database, user, **kwargs):
validate(args, ["thread_id", "post_id"])
return db.message_edit_query(
database, user["user_id"], args["thread_id"], args["post_id"])
+
edit_query.doctype = "Threads & Messages"
edit_query.arglist = (
("thread_id", "string: the id of the thread the message was posted in."),
@@ -592,6 +611,7 @@ def format_message(self, args, database, user, **kwargs):
message = [{"body": args["body"]}]
do_formatting(args["format"], message)
return message[0]["body"]
+
format_message.doctype = "Tools"
format_message.arglist = (
("body", "string: the message body to apply formatting to."),
@@ -613,6 +633,7 @@ def thread_set_pin(self, args, database, user, **kwargs):
if not user["is_admin"]:
raise BBJUserError("Only admins can set thread pins")
return db.thread_set_pin(database, args["thread_id"], args["value"])
+
thread_set_pin.doctype = "Threads & Messages"
thread_set_pin.arglist = (
("thread_id", "string: the id of the thread to modify."),
@@ -656,12 +677,13 @@ def db_validate(self, args, database, user, **kwargs):
response["bool"] = False
response["description"] = e.description
return response
+
db_validate.doctype = "Tools"
db_validate.arglist = (
("key", "string: the identifier for the ruleset to check."),
("value", "VARIES: the object for which `key` will check for."),
("OPTIONAL: error", "boolean: when `true`, will return an API error "
- "response instead of a special object.")
+ "response instead of a special object.")
)
diff --git a/setup.sh b/setup.sh
index 496940f..f781022 100755
--- a/setup.sh
+++ b/setup.sh
@@ -1,13 +1,13 @@
-#!/bin/bash
+#!/bin/sh
-DEPS=(
- cherrypy
- urwid
-)
+create_db() {
+ sqlite3 data.sqlite < schema.sql
+ chmod 600 data.sqlite
+}
case $1 in
- --help )
- cat < ?", (time,))
+ connection.execute(
+ "SELECT * FROM threads WHERE last_mod > ?", (time,))
}
messages = list()
for thread in threads.values():
messages += [
schema.message(*obj) for obj in
- connection.execute("""
+ connection.execute("""
SELECT * FROM messages WHERE thread_id = ?
AND created > ? """, (thread["thread_id"], time))
]
@@ -97,10 +96,9 @@ def thread_get(connection, thread_id, messages=True, op_only=False):
thread = schema.thread(*thread)
if messages or op_only:
- query = "SELECT * FROM messages WHERE thread_id = ? %s"
- c.execute(query % (
- "AND post_id = 0" if op_only else "ORDER BY post_id"
- ), (thread_id,))
+ query = "SELECT * FROM messages WHERE thread_id = ? "
+ query += "AND post_id = 0" if op_only else "ORDER BY post_id"
+ c.execute(query, (thread_id,))
# create a list where each post_id matches its list[index]
thread["messages"] = [schema.message(*values) for values in c.fetchall()]
@@ -121,7 +119,7 @@ def thread_index(connection, include_op=False):
threads = [
thread_get(connection, obj[0], False, include_op)
- for obj in c.fetchall()
+ for obj in c.fetchall()
]
return threads
@@ -146,7 +144,7 @@ def thread_create(connection, author_id, body, title, send_raw=False):
Create a new thread and return it.
"""
validate([
- ("body", body),
+ ("body", body),
("title", title)
])
@@ -154,7 +152,7 @@ def thread_create(connection, author_id, body, title, send_raw=False):
thread_id = uuid1().hex
scheme = schema.thread(
thread_id, author_id, title,
- now, now, -1, # see below for why i set -1 instead of 0
+ now, now, -1, # see below for why i set -1 instead of 0
False, author_id)
connection.execute("""
@@ -230,7 +228,7 @@ def message_delete(connection, author, thread_id, post_id):
WHERE thread_id = ?
AND post_id = ?
""", (anon["user_id"], "[deleted]", False, thread_id, post_id))
- # DONT deincrement the reply_count of this thread,
+ # DON'T decrement the reply_count of this thread,
# or even delete the message itself. This breaks
# balance between post_id and the post's index when
# the thread is served with the messages in an array.
@@ -250,7 +248,8 @@ def message_edit_query(connection, author, thread_id, post_id):
user = user_resolve(connection, author)
thread = thread_get(connection, thread_id)
- try: message = thread["messages"][post_id]
+ try:
+ message = thread["messages"][post_id]
except IndexError:
raise BBJParameterError("post_id out of bounds for requested thread")
@@ -364,7 +363,7 @@ def user_resolve(connection, name_or_id, externalize=False, return_false=True):
SELECT * FROM users
WHERE user_name = ?
OR user_id = ? """,
- (name_or_id, name_or_id)).fetchone()
+ (name_or_id, name_or_id)).fetchone()
if user:
user = schema.user_internal(*user)
@@ -397,8 +396,8 @@ def user_update(connection, user_object, parameters):
user_object[key] = value
values = ordered_keys(user_object,
- "user_name", "quip", "auth_hash",
- "bio", "color", "user_id")
+ "user_name", "quip", "auth_hash",
+ "bio", "color", "user_id")
connection.execute("""
UPDATE users SET
@@ -418,7 +417,6 @@ def set_admins(connection, users):
not included in `users` will have their privledge
revoked.
"""
- connection.execute("UPDATE users SET is_admin = 0")
for user in users:
connection.execute(
"UPDATE users SET is_admin = 1 WHERE user_name = ?",
diff --git a/src/formatting.py b/src/formatting.py
index 5e9e7fa..b086520 100644
--- a/src/formatting.py
+++ b/src/formatting.py
@@ -62,11 +62,10 @@
they are only removed when they occur before a valid expression.
"""
-from string import punctuation
import re
colors = [
-#0, 1 2 3 4 5 6 dim is not used in color api
+ # 0, 1 2 3 4 5 6 dim is not used in color api
"red", "yellow", "green", "blue", "cyan", "magenta", "dim"
]
@@ -74,7 +73,6 @@
"bold", "underline", "linequote", "quote", "rainbow"
]
-
# quotes being references to other post_ids, like >>34 or >>0 for OP
quotes = re.compile(">>([0-9]+)")
bold = re.compile(r"(?