From 1ddf49224942253bf383ebffeeba2b1880f4a9a8 Mon Sep 17 00:00:00 2001 From: Blake DeMarcy Date: Wed, 1 Mar 2017 15:54:56 -0600 Subject: [PATCH] too many things and I suck at git --- apidoc | 147 +++++++++++++++++++++++++---------------------- bbj.el | 77 ++++++++++++++++++++----- src/db.py | 2 +- src/endpoints.py | 4 +- src/schema.py | 11 ++-- src/server.py | 41 +++++-------- 6 files changed, 163 insertions(+), 119 deletions(-) diff --git a/apidoc b/apidoc index ab36823..72af728 100644 --- a/apidoc +++ b/apidoc @@ -4,7 +4,7 @@ Text Entities The `entities` attribute is an array of objects that represent blocks of text within a post that have special properties. Clients may safely ignore these things without losing too much meaning, but in a rich -application like an Emacs or GUI implementation, they can provide +implementation like an Emacs or GUI, they can provide some highlighting and navigation perks. The array object may be empty. If its not, its populated with arrays representing the modifications to be made. @@ -19,31 +19,35 @@ property in the body string. The way clients are to access these indices is beyond the scope of this document; accessing a subsequence varies a lot between programming languages. -Some objects will provide further arguments beyond those 3. - -|--------------|----------------------------------------------------------| -| Name | Description | -|--------------|----------------------------------------------------------| -| `quote` | This is a string that refers to a previous post number. | -| | These are formatted like >>5, which means it is a | -| | reference to `post_id` 5. These are not processed in | -| | thread OPs. >>0 may be used to refer to the OP. | -|--------------|----------------------------------------------------------| -| `blockquote` | This is a block of text, denoted by a newline during | -| | composure, representing text that is assumed to be | -| | a quote of someone else. | -|--------------|----------------------------------------------------------| -| `color` | This is a block of text, denoted by [[color: body]] | -| | during composure. The body may span across newlines. | -| | A fourth item is provided in the array: it is one of the | -| | following strings representing the color. | -| | `red`, `green`, `yellow`, `blue`, `magenta`, or `cyan`. | -|--------------|----------------------------------------------------------| -| `bold` | Like color, except that no additional attribute is | -| | provided. it is denoted as [[bold: body]] during | -| | composure. | -|--------------|----------------------------------------------------------| +Some objects will provide further arguments beyond those 3. They will +always be at the end of the array. +|-------------|----------------------------------------------------------| +| Name | Description | +|-------------|----------------------------------------------------------| +| `quote` | This is a string that refers to a previous post number. | +| | These are formatted like >>5, which means it is a | +| | reference to `post_id` 5. These are not processed in | +| | thread OPs. >>0 may be used to refer to the OP. In | +| | addition to the indices at i[1] and i[2], a fourth value | +| | is provided, which is an integer of the `post_id` being | +| | quoted. Note that the string indices include the >>'s. | +|-------------|----------------------------------------------------------| +| `linequote` | This is a line of text, denoted by a newline during | +| | composure, representing text that is assumed to be | +| | a quote of someone else. The indices span from the > | +| | until (not including) the newline. | +|-------------|----------------------------------------------------------| +| `color` | This is a block of text, denoted by [[color: body]] | +| | during composure. The body may span across newlines. | +| | A fourth item is provided in the array: it is one of the | +| | following strings representing the color. | +| | `red`, `green`, `yellow`, `blue`, `magenta`, or `cyan`. | +|-------------|----------------------------------------------------------| +| `bold` | Like color, except that no additional attribute is | +| `italic` | provided. it is denoted as [[directive: body]] during | +| `underline` | composure. | +|-------------|----------------------------------------------------------| Threads & Replies @@ -57,13 +61,13 @@ available on the parent object: |---------------|------------------------------------------------------| | Name | Description | |---------------|------------------------------------------------------| -| `author` | The ID of the author | +| `author` | The ID string of the author. | |---------------|------------------------------------------------------| -| `thread_id` | The ID of the thread. | +| `thread_id` | The ID string of the thread. | |---------------|------------------------------------------------------| -| `title` | The title string of the thread | +| `title` | The title string of the thread. | |---------------|------------------------------------------------------| -| `body` | The body of the post's text. | +| `body` | The body string of the post's text. | |---------------|------------------------------------------------------| | `entities` | A (possibly empty) array of entity objects for | | | the post `body`. | @@ -74,7 +78,7 @@ available on the parent object: |---------------|------------------------------------------------------| | `replies` | An array containing full reply objects in | | | the order they were posted. Your clients | -| | do not need to sort these. | +| | do not need to sort these. Array can be empty. | |---------------|------------------------------------------------------| | `reply_count` | An integer representing the number of replies | | | that have been posted in this thread. | @@ -82,8 +86,7 @@ available on the parent object: | `lastmod` | Unix timestamp of when the thread was last | | | posted in, or a message was edited. | |---------------|------------------------------------------------------| -| `edited` | Boolean of whether this post has been edited since | -| | it was made. | +| `edited` | Boolean of whether the post has been edited. | |---------------|------------------------------------------------------| | `created` | Unix timestamp of when the post was originally made. | |---------------|------------------------------------------------------| @@ -97,11 +100,11 @@ The following attributes are available on each reply object in `replies`: | `post_id` | An integer of the posts ID; unlike thread and user ids, | | | this is not a uuid but instead is incremental, starting | | | from 1 as the first reply and going up by one for each | -| | post. | +| | post. These may be referenced by `quote` entities. | |------------|---------------------------------------------------------| -| `author` | Author ID | +| `author` | Author ID string | |------------|---------------------------------------------------------| -| `body` | The body the reply's text. | +| `body` | The body string the reply's text. | |------------|---------------------------------------------------------| | `entities` | A (possibly empty) array of entity objects for | | | the reply `body`. | @@ -126,37 +129,43 @@ the problem. `description` is intended for human consumption; in your client code, use the error codes to handle conditions. The `presentable` column indicates whether the `description` should be shown to users verbatim. -|------|--------------|--------------------------------------------------| -| Code | Presentable | Documentation | -|------|--------------|--------------------------------------------------| -| 0 | Never, fix | Malformed json input. `description` is the error | -| | your client | string thrown by the server-side json decoder. | -|------|--------------|--------------------------------------------------| -| 1 | Not a good | Internal server error. Unaltered exception text | -| | idea, the | is returned as `description`. This shouldn't | -| | exceptions | happen, and if it does, make a bug report. | -| | are not | clients should not attempt to intelligently | -| | helpful | recover from any errors of this class. | -|------|--------------|--------------------------------------------------| -| 2 | Nadda. | Unknown `method` was requested. | -|------|--------------|--------------------------------------------------| -| 3 | Fix. Your. | Missing or malformed parameter values for the | -| | Client. | requested `method`. | -|------|--------------|--------------------------------------------------| -| 4 | Only during | Invalid or unprovided `user`. During | -| | registration | registration, this code is returned if the name | -| | | is already occupied or contains illegal chars. | -|------|--------------|--------------------------------------------------| -| 5 | Always | `user` is not registered. | -|------|--------------|--------------------------------------------------| -| 6 | Always | User `auth_hash` failed or was not provided. | -|------|--------------|--------------------------------------------------| -| 7 | Always | Requested thread does not exist. | -|------|--------------|--------------------------------------------------| -| 8 | Always | Requested thread does not allow posts. | -|------|--------------|--------------------------------------------------| -| 9 | Always | Message edit failed; there is a 24hr limit for | -| | | editing posts. | -|------|--------------|--------------------------------------------------| -| 10 | Always | User action requires `admin` privilege. | -|------|--------------|--------------------------------------------------| +|------|--------------|---------------------------------------------------| +| Code | Presentable | Documentation | +|------|--------------|---------------------------------------------------| +| 0 | Never, fix | Malformed json input. `description` is the error | +| | your client | string thrown by the server-side json decoder. | +|------|--------------|---------------------------------------------------| +| 1 | Not a good | Internal server error. Unaltered exception text | +| | idea, the | is returned as `description`. This shouldn't | +| | exceptions | happen, and if it does, make a bug report. | +| | are not | clients should not attempt to intelligently | +| | helpful | recover from any errors of this class. | +|------|--------------|---------------------------------------------------| +| 2 | Nadda. | Unknown `method` was requested. | +|------|--------------|---------------------------------------------------| +| 3 | Fix. Your. | Missing or malformed parameter values for the | +| | Client. | requested `method`. | +|------|--------------|---------------------------------------------------| +| 4 | Only during | Invalid or unprovided `user`. | +| | registration | | +| | | During registration, this code is returned with a | +| | | `description` that should be shown to the user. | +| | | It could indicate an invalid name input, an | +| | | occupied username, invalid/missing `auth_hash`, | +| | | etc. | +|------|--------------|---------------------------------------------------| +| 5 | Always | `user` is not registered. | +|------|--------------|---------------------------------------------------| +| 6 | Always | User `auth_hash` failed or was not provided. | +|------|--------------|---------------------------------------------------| +| 7 | Always | Requested thread does not exist. | +|------|--------------|---------------------------------------------------| +| 8 | Always | Requested thread does not allow posts. | +|------|--------------|---------------------------------------------------| +| 9 | Always | Message edit failed; there is a 24hr limit for | +| | | editing posts. | +|------|--------------|---------------------------------------------------| +| 10 | Always | User action requires `admin` privilege. | +|------|--------------|---------------------------------------------------| +| 11 | Always | Invalid formatting directives in text submission. | +|------|--------------|---------------------------------------------------| diff --git a/bbj.el b/bbj.el index b45d9d6..125e7b2 100644 --- a/bbj.el +++ b/bbj.el @@ -6,32 +6,79 @@ (defvar bbj:user nil) (defvar bbj:hash nil) +(make-variable-buffer-local + (defvar bbj:aux-callback #'ignore)) + +(define-derived-mode bbj-mode fundamental-mode "[BBJ]" + "Mode for browsing and posting to BBJ." + :group 'bbj-mode + (local-set-key (kbd "C-c C-c") 'bbj:aux) + (local-set-key (kbd "+") 'bbj:compose) + (local-set-key (kbd "RET") 'bbj:enter)) (defun bbj:request (&rest cells) + (push (cons 'user bbj:user) cells) + (push (cons 'auth_hash bbj:hash) cells) (with-temp-buffer (insert (json-encode cells)) (shell-command-on-region (point-min) (point-max) (format "nc %s %s" bbj:host bbj:port))) (with-current-buffer "*Shell Command Output*" - (json-read-from-string - (buffer-substring-no-properties - (point-min) (point-max))))) + (goto-char (point-min)) + (let (json-false json-null) + (json-read)))) -(bbj:request '(user . "desvox") - '(auth_hash . "nrr") - '(method . "check_auth")) +(defun bbj:sethash (&optional password) + (unless password (setq password + (read-from-minibuffer "(Password)> "))) + (setq bbj:hash (secure-hash 'sha256 password))) + (defun bbj:login () (interactive) (setq bbj:user (read-from-minibuffer "(BBJ Username)> ")) - (if (bbj:request '(method . "is_registered") - `(target_user . ,bbj:user)) - (setq bbj:hash (secure-hash 'sha256 (read-from-minibuffer "(BBJ Password)> "))) - (when (y-or-n-p (format "Register for BBJ as %s? " bbj:user)) - (message - (bbj:request (cons 'auth_hash bbj:hash) - (cons 'user bbj:user) - (cons 'avatar nil) - (cons 'bio (read-from-minibuffer "(Enter a short bio about youself!)> "))))))) + (cond + ((bbj:request '(method . "is_registered") + `(target_user . ,bbj:user)) + (bbj:sethash) + (unless (bbj:request '(method . "check_auth")) + (message "(Invalid Password!)"))) + ((y-or-n-p (format "Register for BBJ as %s? " bbj:user)) + (let ((response (bbj:request + (cons 'method "user_register") + (cons 'auth_hash (bbj:sethash)) + (cons 'user bbj:user) + (cons 'quip (read-from-minibuffer "(Quip)> ")) + (cons 'bio (read-from-minibuffer "(Bio)> "))))) + (if (alist-get 'error response) + (message "%s" (alist-get 'error response)) + (setq bbj:logged-in t) + (message "Logged in as %s!" bbj:user)))))) + + +(defun bbj:compose-in-window (title callback &rest cbargs) + (let ((buffer (get-buffer-create "*BBJ: Compose*"))) + (pop-to-buffer buffer) + (with-current-buffer buffer + (erase-buffer) + (bbj-mode) + (setq header-line-format title + bbj:aux-callback callback)))) + + +(defun bbj:consume-window (buffer) + (interactive) + (with-current-buffer buffer + (let ((content (buffer-substring-no-properties + (point-min) (point-max)))) + (quit-window t) + content))) + + +(defun bbj:browse-index () + (interactive) + (let ((response (bbj:request '(method . "thread_index")))) + (cl-loop for thread across (alist-get 'threads response) do + (message "%s" thread)))) diff --git a/src/db.py b/src/db.py index a7a1b37..0e8bd33 100644 --- a/src/db.py +++ b/src/db.py @@ -30,7 +30,7 @@ def thread_index(key="lastmod"): thread = thread_load(ID) thread.pop("replies") result.append(thread) - return sorted(result, key=lambda i: i[key]) + return sorted(result, key=lambda i: i[key], reverse=True) def thread_create(author, body, title, tags): diff --git a/src/endpoints.py b/src/endpoints.py index c433899..bc51087 100644 --- a/src/endpoints.py +++ b/src/endpoints.py @@ -31,11 +31,11 @@ def create_usermap(thread, index=False): def is_registered(json): - return dumps(bool(db.USERDB["mapname"].get(json["target_user"]))) + return bool(db.USERDB["mapname"].get(json["target_user"])) def check_auth(json): - return dumps(bool(db.user_auth(json["user"], json["auth_hash"]))) + return bool(db.user_auth(json["user"], json["auth_hash"])) def user_register(json): diff --git a/src/schema.py b/src/schema.py index ddfcb41..1d5c87e 100644 --- a/src/schema.py +++ b/src/schema.py @@ -1,3 +1,4 @@ +from src import formatting from time import time def base(): @@ -48,12 +49,13 @@ def user_external(ID, name, quip, bio, admin): def thread(ID, author, body, title, tags): + text, entities = formatting.parse(body, doquotes=False) now = time() return { "thread_id": ID, "author": author, - "body": body, - "entities": list(), + "body": text, + "entities": entities, # of type list() "title": title, "tags": tags, "replies": list(), @@ -65,12 +67,13 @@ def thread(ID, author, body, title, tags): def reply(ID, author, body): + text, entities = formatting.parse(body) now = time() return { "post_id": ID, "author": author, - "body": body, - "entities": list(), + "body": text, + "entities": entities, # of type list() "lastmod": now, "edited": False, "created": now diff --git a/src/server.py b/src/server.py index a7ce3dc..26474de 100644 --- a/src/server.py +++ b/src/server.py @@ -8,7 +8,7 @@ import json class RequestHandler(StreamRequestHandler): """ Receieves and processes json input; dispatches input to the - approproate endpoint, or responds with error objects. + requested endpoint, or responds with error objects. """ @@ -22,52 +22,37 @@ class RequestHandler(StreamRequestHandler): endpoint = request.get("method") if endpoint not in endpoints.endpoints: - raise IndexError("Invalid endpoint") + return self.reply(schema.error(2, "Invalid endpoint")) # check to make sure all the arguments for endpoint are provided elif any([key not in request for key in endpoints.endpoints[endpoint]]): - raise ValueError("{} requires: {}".format( - endpoint, ", ".join(endpoints.endpoints[endpoint]))) + return self.reply(schema.error(3, "{} requires: {}".format( + endpoint, ", ".join(endpoints.endpoints[endpoint])))) elif endpoint not in endpoints.authless: if not request.get("user"): - raise ConnectionError("No username provided.") + return self.reply(schema.error(4, "No username provided.")) user = db.user_resolve(request["user"]) request["user"] = user if not user: - raise ConnectionAbortedError("User not registered") + return self.reply(schema.error(5, "User not registered")) - elif endpoint != "check_auth" and not db.user_auth(user, request.get("auth_hash")): - raise ConnectionRefusedError("Authorization failed.") + elif endpoint != "check_auth" and not \ + db.user_auth(user, request.get("auth_hash")): + return self.reply(schema.error(6, "Authorization failed.")) + + # exception handling is now passed to the endpoints; + # anything unhandled beyond here is a code 1 + self.reply(eval("endpoints." + endpoint)(request)) except json.decoder.JSONDecodeError as E: return self.reply(schema.error(0, str(E))) - except IndexError as E: - return self.reply(schema.error(2, str(E))) - - except ValueError as E: - return self.reply(schema.error(3, str(E))) - - except ConnectionError as E: - return self.reply(schema.error(4, str(E))) - - except ConnectionAbortedError as E: - return self.reply(schema.error(5, str(E))) - - except ConnectionRefusedError as E: - return self.reply(schema.error(6, str(E))) - except Exception as E: return self.reply(schema.error(1, str(E))) - try: - self.reply(eval("endpoints." + endpoint)(request)) - except Exception as E: - self.reply(schema.error(1, str(E))) - def run(host, port): server = TCPServer((host, port), RequestHandler)