fake messages, formatting endpoint, new help menus

pull/4/head
Blake DeMarcy 2017-04-12 09:09:16 -05:00
parent 7eef803084
commit 09077baeac
4 changed files with 221 additions and 19 deletions

View File

@ -1,6 +1,7 @@
from urllib.error import URLError
import urllib.request as url
from hashlib import sha256
from time import time
import json
@ -423,6 +424,32 @@ class BBJ(object):
return response["data"], response["usermap"]
def fake_message(self, body="!!", format="sequential", author=None, post_id=0):
"""
Produce a a valid message object with `body`. Useful for
testing and can also be used mimic server messages in a
client.
"""
return {
"body": self.format_message(body, format),
"author": author or self.user["user_id"],
"post_id": post_id,
"created": time(),
"edited": False,
"thread_id": "gibberish"
}
def format_message(self, body, format="sequential"):
"""
Send `body` to the server to be formatted according to `format`,
defaulting to the sequential parser. Returns the body object.
"""
response = self("format_message", body=body, format=format)
return response["data"]
def edit_query(self, thread_id, post_id):
"""
Queries ther server database to see if a post can

View File

@ -20,7 +20,6 @@ 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
from datetime import datetime
@ -62,6 +61,86 @@ welcome = """>>> Welcome to Bulletin Butter & Jelly! ------------------@
@_________________________________________________________@
"""
format_help = [
"BBJ supports **bolding**, __underlining__, and [rainbow: coloring] text "
"using markdown-style symbols as well as tag-like expressions. Markdown "
"is **NOT** fully implemented, but several of the more obvious concepts "
"have been brought over. Additionally, we have chan-style greentext and "
"numeric post referencing, ala >>3 for the third reply.",
"[red: Whitespace]",
"When you're composing, it is desirable to introduce linebreaks into the "
"body to keep it from overflowing the screen. However, you __dont__ want "
"that same spacing to bleed over to other people's screens, because clients "
"will wrap the text themselves.",
"Single line breaks in the body join into eachother to form sentences, "
"putting a space where the break was. This works like html. When you want "
"to split it off into a paragraph, **use two line breaks.**",
"[red: Colors, Bold, Underline & Expressions]",
"You can use [rainbow: rainbow], [red: red], [yellow: yellow], [green: green], "
"[blue: blue], [cyan: cyan], [magenta: and magenta], **bold**, and __underline__ "
"inside of your posts. **bold\nworks like this**, __and\nunderlines like this__. "
"The symbolic, markdown form of these directives does NOT allow escaping, and "
"can only apply to up to 20 characters on the same line. They are best used on short "
"phrases. However, you can use a different syntax for it, which is also required to use "
"colors: these expressions \[bold: look like this] and are much more reliable. "
"The colon and the space following it are important. When you use these "
"expressions, the __first__ space is not part of the content, but any characters, "
"including spaces, that follow it are included in the body. The formatting will "
"apply until the closing ]. You can escape such an expression \\[cyan: like this]"
"and can also \\[blue: escape \\] other closing brackets] inside of it. Only "
"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] "
"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, and rainbow. Nesting expressions into eachother will "
"override the parent directives until it closes. Thus, nesting is valid but doesn't produce "
"layered results.",
"[red: Quotes & Greentext]",
"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 '>' "
"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.",
"When using numeric quotes, they are highlighted and the author's name will show "
"next to them in the thread. You can press enter when focused on a message to view "
"the parent posts. You may insert these directives manually or use the <Reply> function "
"on post menus.",
"Quoting directives cannot be escaped."
]
general_help = [
("bold", "use q or escape to close dialogs and menus (including this one)\n\n"),
"You may use the arrow keys, or use 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, hold shift when using a control: it will repeat 5 times.\n\n"
"To go back and forth between threads, you may also use the left/right arrow keys, "
"or h/l to do it vi-style.\n\n"
"In the thread index and any open thread, the b and t keys may be used to go to "
"very top or bottom.\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."
]
colornames = [
"none", "red", "yellow", "green", "blue",
"cyan", "magenta"
@ -87,7 +166,7 @@ default_prefs = {
class App(object):
def __init__(self):
self.bars = {
"index": "[Arrows|JK|NP]Navigate [RET]Open [C]ompose [R]efresh [O]ptions [?]Help [Q]uit",
"index": "[JKNP]Navigate [RET]Open [C]ompose [R]efresh [O]ptions [?]Help [Q]uit",
"thread": "[C]ompose [RET]Interact [Q]Back [R]efresh [B/T]End [?]Help/More"
}
@ -222,9 +301,8 @@ class App(object):
focus = "[focused on thread]"
attr = ("dim", "bar")
control = "[save/quit to send]" if self.prefs["editor"] else "[F3]Send"
self.loop.widget.footer[0].set_text(
"[F1]Abort [F2]Swap %s %s" % (control, focus))
"[F1]Abort [F2]Swap [F3]Formatting Help [save/quit to send] " + focus)
# this hideous and awful sinful horrid unspeakable shithack changes
# the color of the help/title lines and editor border to reflect which
@ -339,7 +417,7 @@ class App(object):
init_body=message["body"],
post_id=post_id,
thread_id=thread_id),
title="[F1]Abort (save/quit to commit)",
title="[F1]Abort [F3]Formatting Help (save/quit to commit)",
**frame_theme()),
self.loop.widget,
align="center",
@ -382,7 +460,7 @@ class App(object):
align=("relative", 50),
valign=("relative", 50),
width=30,
height=len(buttons) + 3
height=len(buttons) + 2
)
@ -399,7 +477,7 @@ class App(object):
return [value_type(q) for q in quotes]
def make_message_body(self, message):
def make_message_body(self, message, no_action=False):
"""
Returns the widgets that comprise a message in a thread, including the
text body, author info and the action button
@ -408,11 +486,12 @@ class App(object):
if message["edited"]:
info += " [edited]"
callback = self.on_post if not no_action else ignore
name = urwid.Text("~{}".format(self.usermap[message["author"]]["user_name"]))
post = str(message["post_id"])
head = urwid.Columns([
(2 + len(post), urwid.AttrMap(
cute_button(">" + post, self.on_post, message), "button", "hover")),
cute_button(">" + post, callback, message), "button", "hover")),
(len(name._text) + 1, urwid.AttrMap(
name, str(self.usermap[message["author"]]["color"]))),
urwid.AttrMap(urwid.Text(info), "dim")
@ -508,7 +587,7 @@ class App(object):
self.set_bars()
def refresh(self, bottom=False):
def refresh(self, bottom=True):
self.remove_overlays()
if self.mode == "index":
return self.index()
@ -578,6 +657,60 @@ class App(object):
self.options_menu()
def general_help(self):
"""
Show a general help dialog. In all honestly, its not
very useful and will only help people who have never
really used terminal software before =)
"""
widget = OptionsMenu(
urwid.ListBox(
urwid.SimpleFocusListWalker([
urwid_rainbows(
"This is BBJ, a client/server textboard made for tilde.town!",
True),
urwid.Text(("dim", "...by ~desvox")),
urwid.Divider("-"),
urwid.Button("Post Formatting Help", self.formatting_help),
urwid.Divider("-"),
urwid.Text(general_help)
])),
title="?????",
**frame_theme()
)
app.loop.widget = urwid.Overlay(
widget, app.loop.widget,
align=("relative", 50),
valign=("relative", 50),
width=30,
height=("relative", 60)
)
def formatting_help(self, *_):
"""
Pops a help window with formatting directives.
"""
message = network.fake_message("\n\n".join(format_help))
widget = OptionsMenu(
urwid.ListBox(
urwid.SimpleFocusListWalker([
*app.make_message_body(message, True)
])),
title="Formatting Help",
**frame_theme()
)
app.loop.widget = urwid.Overlay(
widget, app.loop.widget,
align=("relative", 50),
valign=("relative", 50),
width=("relative", 98),
height=("relative", 60)
)
def set_color(self, button, value, color):
if value == False:
return
@ -854,7 +987,7 @@ class App(object):
if self.mode == "index":
self.set_header('Composing "{}"', title)
self.set_footer("[F1]Abort [Save and quit to submit your thread]")
self.set_footer("[F1]Abort [F3]Formatting Help [Save and quit to submit your thread]")
self.loop.widget = urwid.Overlay(
urwid.LineBox(
ExternalEditor("thread_create", title=title),
@ -1049,15 +1182,17 @@ class ExternalEditor(urwid.Terminal):
else:
return app.temp_footer_message("EMPTY POST DISCARDED")
elif key not in ["f1", "f2"]:
elif key not in ["f1", "f2", "f3"]:
return super(ExternalEditor, self).keypress(size, key)
elif key == "f1":
self.terminate()
app.close_editor()
app.refresh()
# key == "f2"
elif key == "f2":
app.switch_editor()
elif key == "f3":
app.formatting_help()
def __del__(self):
@ -1077,6 +1212,12 @@ class OptionsMenu(urwid.LineBox):
# try to let the base class handle the key, if not, we'll take over
elif not super(OptionsMenu, self).keypress(size, key):
return
elif key in ["shift down", "J", "N"]:
for x in range(5):
self.keypress(size, "down")
elif key in ["shift up", "K", "P"]:
for x in range(5):
self.keypress(size, "up")
elif key.lower() == "q":
app.loop.widget = app.loop.widget[0]
elif key in ["ctrl n", "j", "n"]:
@ -1102,11 +1243,11 @@ class ActionBox(urwid.ListBox):
elif key in ["k", "p", "ctrl p"]:
self._keypress_up(size)
elif key in ["J", "N"]:
elif key in ["shift down", "J", "N"]:
for x in range(5):
self._keypress_down(size)
elif key in ["K", "P"]:
elif key in ["shift up", "K", "P"]:
for x in range(5):
self._keypress_up(size)
@ -1131,6 +1272,9 @@ class ActionBox(urwid.ListBox):
elif key == "o":
app.options_menu()
elif key == "?":
app.general_help()
elif key.lower() == "q":
app.back()
@ -1168,12 +1312,13 @@ def cute_button(label, callback=None, data=None):
return button
def urwid_rainbows(string):
def urwid_rainbows(string, bold=False):
"""
Same as below, but instead of printing rainbow text, returns
a markup list suitable for urwid's Text contructor.
"""
colors = [str(x) for x in range(1, 7)]
if bold: colors = [(c + "0") for c in colors]
return urwid.Text([(choice(colors), char) for char in string])
@ -1345,6 +1490,13 @@ def bbjrc(mode, **params):
return values
def ignore(*_, **__):
"""
The blackness of my soul.
"""
pass
def main():
run("clear", shell=True)

View File

@ -238,7 +238,9 @@ class API(object):
def thread_load(self, args, database, user, **kwargs):
"""
Returns the thread object with all of its messages loaded.
Requires the argument `thread_id`
Requires the argument `thread_id`. `format` may also be
specified as a formatter to run the messages through.
Currently only "sequential" is supported.
"""
validate(args, ["thread_id"])
thread = db.thread_get(database, args["thread_id"])
@ -300,6 +302,23 @@ class API(object):
database, user["user_id"], args["thread_id"], args["post_id"])
@api_method
def format_message(self, args, database, user, **kwargs):
"""
Requires the arguments `body` and `format`. Applies
`format` to `body` and returns the new object. See
`thread_load` for supported specifications for `format`.
"""
validate(args, ["format", "body"])
message = [{"body": args["body"]}]
if args["format"] == "sequential":
formatter = formatting.sequential_expressions
else:
raise BBJParameterError("invalid format directive.")
formatting.apply_formatting(message, formatter)
return message[0]["body"]
@api_method
def set_thread_pin(self, args, database, user, **kwargs):
"""

View File

@ -11,7 +11,7 @@ A B A N D O N ,:
/.' '
This module includes a couple '/'
of custom (GROAN) formatting +
specifications and parsers ' me irl
specifications and parsers '
for them. Why did i do this? `.
I have no idea! .-"-
( |
@ -70,7 +70,7 @@ colors = [
]
markup = [
"bold", "italic", "underline", "linequote", "quote", "rainbow"
"bold", "underline", "linequote", "quote", "rainbow"
]
# PS: regex parsing is no longer used for these, preserving anyways
@ -82,6 +82,8 @@ markup = [
# quotes being references to other post_ids, like >>34 or >>0 for OP
quotes = re.compile(">>([0-9]+)")
bold = re.compile(r"\*{2}(.{1,20})\*{2}")
underline = re.compile(r"__(.{1,20})__")
def parse_segments(text, sanitize_linequotes=True):
@ -98,6 +100,8 @@ def parse_segments(text, sanitize_linequotes=True):
if not segment:
continue
segment = quotes.sub(lambda m: "[quote: %s]" % m.group(1), segment)
segment = bold.sub(lambda m: "[bold: %s]" % m.group(1), segment)
segment = underline.sub(lambda m: "[underline: %s]" % m.group(1), segment)
if segment.startswith(">"):
if sanitize_linequotes:
inner = segment.replace("]", "\\]")