begin urwid client

pull/4/head
Blake DeMarcy 2017-04-05 13:09:38 -05:00
parent b99c7a1f0f
commit 19af814a93
4 changed files with 292 additions and 15 deletions

View File

@ -42,8 +42,7 @@ class BBJ:
""" """
def __init__(self, host="127.0.0.1", port=8080): def __init__(self, host="127.0.0.1", port=8080):
self.base = "http://{}:{}/api/%s".format(host, port) self.base = "http://{}:{}/api/%s".format(host, port)
self.user_name = None self.user_name = self.user_auth = None
self.user_auth = None
self.send_auth = True self.send_auth = True
@ -101,7 +100,8 @@ class BBJ:
Uses the server's db_sanity_check method to verify the validty Uses the server's db_sanity_check method to verify the validty
of value by key. If it is invalid, kwarg exception (default of value by key. If it is invalid, kwarg exception (default
AssertionError) is raised with the exception containing the AssertionError) is raised with the exception containing the
attribute .description as the server's reason. attribute .description as the server's reason. Exception can
be a False value to just rturn boolean False.
""" """
response = self( response = self(
"db_sanity_check", "db_sanity_check",
@ -111,6 +111,8 @@ class BBJ:
) )
if not response["data"]["bool"]: if not response["data"]["bool"]:
if not exception:
return False
description = response["data"]["description"] description = response["data"]["description"]
error = exception(description) error = exception(description)
error.description = description error.description = description
@ -148,8 +150,7 @@ class BBJ:
user_auth = sha256(bytes(user_auth, "utf8")).hexdigest() user_auth = sha256(bytes(user_auth, "utf8")).hexdigest()
if check_validity and not self.validate_credentials(user_name, user_auth): if check_validity and not self.validate_credentials(user_name, user_auth):
self.user_auth = None self.user_auth = self.user_name = None
self.user_name = None
raise ConnectionRefusedError("Auth and User do not match") raise ConnectionRefusedError("Auth and User do not match")
self.user_auth = user_auth self.user_auth = user_auth
@ -180,22 +181,44 @@ class BBJ:
Returns True or False whether user_name is registered Returns True or False whether user_name is registered
into the system. into the system.
""" """
return self( response = self(
"user_is_registered", "user_is_registered",
no_auth=True, no_auth=True,
target_user=user_name target_user=user_name
)["data"] )
return response["data"]
def user_register(self, user_name, user_auth, hash_auth=True): 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 Register user_name into the system with user_auth. Unless hash_auth
is set to false, user_auth should be a password string. is set to false, user_auth should be a password string.
"""
pass
# return self(
# ) When set_as_user is True, the newly registered user is internalizedn
and subsequent uses of the object will be authorized for them.
"""
if hash_auth:
user_auth = sha256(bytes(user_auth, "utf8")).hexdigest()
response = self(
"user_register",
no_auth=True,
user_name=user_name,
auth_hash=user_auth
)["data"]
assert all([
user_auth == response["auth_hash"],
user_name == response["user_name"]
])
if set_as_user:
self.user_name = user_name
self.user_auth = user_auth
return response
def thread_index(self): def thread_index(self):
""" """
@ -204,3 +227,11 @@ class BBJ:
""" """
response = self("thread_index") response = self("thread_index")
return response["data"], response["usermap"] return response["data"], response["usermap"]
def thread_load(self, thread_id):
"""
Returns a tuple where [0] is a thread object and [1] is a usermap object.
"""
response = self("thread_load", thread_id=thread_id)
return response["data"], response["usermap"]

View File

@ -0,0 +1,248 @@
from time import sleep, localtime
from string import punctuation
from subprocess import run
from random import choice
from network import BBJ
import urwid
network = BBJ(host="127.0.0.1", port=8080)
obnoxious_logo = """
OwO 8 888888888o 8 888888888o 8 8888 1337
% 8 8888 `88. 8 8888 `88. ! 8 8888 >><>
!! 8 8888 `88 8 8888 `88 * 8 8888
$ 8 8888 ,88 8 8888 ,88 8 8888 <><><><>
<3 8 8888. ,88' 8 8888. ,88' ! 8 8888 ^ >|
^ 8 8888888888 8 8888888888 8 8888 ----||
( 8 8888 `88. 8 8888 `88. 88. 8 8888 |
8 8888 88 8 8888 88 `88. | 8 888' !??!
g ? 8 8888. ,88' 8 8888. ,88' `88o. .8 88' ----_
8 888888888P 8 888888888P `Y888888 ' |
"""
welcome = """
>>> Welcome to Bulletin Butter & Jelly! ---------------------------------------@
| BBJ is a persistent, chronologically ordered discussion board for tilde.town.|
| You may log in, register as a new user, or participate anonymously. |
| \033[1;31mTo go anon, just press enter. Otherwise, give me a name (registered or not)\033[0m |
@______________________________________________________________________________@
"""
colors = [
"\033[1;31m", "\033[1;33m", "\033[1;33m",
"\033[1;32m", "\033[1;34m", "\033[1;35m"
]
class App:
def __init__(self):
colors = [
("bar", "light magenta", "default", "underline"),
("button", "light red", "default")
]
self.loop = urwid.MainLoop(urwid.Frame(
urwid.LineBox(ActionBox(urwid.SimpleFocusListWalker([]))),
), colors)
self.date_format = "{1}/{2}/{0}"
self.index()
def set_header(self, text, *format_specs):
self.loop.widget.header = urwid.AttrMap(urwid.Text(
("%s@bbj | " % network.user_name)
+ text.format(*format_specs)
), "bar")
def set_footer(self, *controls):
text = str()
for control in controls:
text += "[{}]{} ".format(control[0], control[1:])
self.loop.widget.footer = urwid.AttrMap(urwid.Text(text), "bar")
def readable_delta(self, created, modified):
delta = modified - created
minutes = delta // 60
if not minutes:
return "less than a minute ago"
elif minutes < 60:
return "%d minutes ago" % minutes
hours = delta // 60
if hours == 1:
return "about an hour ago"
elif hours < 48:
return "%d hours ago" % hours
return self.date_format.format(*localtime(modified))
def index(self):
threads, usermap = network.thread_index()
self.set_header("{} threads", len(threads))
self.set_footer("Compose")
walker = self.loop.widget.body.base_widget.body
for thread in threads:
button = urwid.Button("", self.thread_load, thread["thread_id"])
super(urwid.Button, button).__init__(
urwid.SelectableIcon(">>"))
title = urwid.Text(thread["title"])
last_mod = thread["last_mod"]
created = thread["created"]
infoline = "by ~{} @ {} | last active {}".format(
usermap[thread["author"]]["user_name"],
self.date_format.format(*localtime(created)),
self.readable_delta(created, last_mod)
)
walker.append(urwid.Columns([(3, urwid.AttrMap(button, "button")), title]))
walker.append(urwid.Text(infoline))
walker.append(urwid.Divider("-"))
def thread_load(self, button, thread_id):
thread, usermap = network.thread_load(thread_id)
walker = self.loop.widget.body.base_widget.body
walker.clear()
self.set_header("~{}: {}",
usermap[thread["author"]]["user_name"], thread["title"])
for message in thread["messages"]:
pass
class ActionBox(urwid.ListBox):
pass
def motherfucking_rainbows(string, inputmode=False, end="\n"):
"""
I cANtT FeELLE MyYE FACECsEE ANYrrMOROeeee!!
"""
for character in string:
print(choice(colors) + character, end="")
print('\033[0m', end="")
if inputmode:
return input("")
return print(end, end="")
def paren_prompt(text, positive=True, choices=[]):
"""
input(), but riced the fuck out. Changes color depending on
the value of positive (blue/green for good stuff, red/yellow
for bad stuff like invalid input), and has a multiple choice
system capable of rejecting unavailable choices and highlighting
their first characters.
"""
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")
if choices:
prompt = "%s{" % mood[0]
for choice in choices:
prompt += "{0}[{1}{0}]{2}{3} ".format(
"\033[1;35m", choice[0], mood[1], choice[1:])
formatted_choices = prompt[:-1] + ("%s}" % mood[0])
else:
formatted_choices = ""
try:
response = input("{0}({1}{2}{0}){3}> \033[0m".format(
*mood, text, formatted_choices))
if not choices:
return response
elif response == "":
response = " "
char = response.lower()[0]
if char in [c[0] for c in choices]:
return char
return paren_prompt("Invalid choice", False, choices)
except EOFError:
print("")
return ""
except KeyboardInterrupt:
exit("\nNevermind then!")
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)
except AssertionError as e:
return sane_value(key, e.description, False)
return response
def log_in():
"""
Handles login or registration using the oldschool input()
method. The user is run through this before starting the
curses app.
"""
name = sane_value("user_name", "Username", return_empty=True)
if name == "":
motherfucking_rainbows("~~W3 4R3 4n0nYm0u5~~")
else:
# ConnectionRefusedError means registered but needs a
# password, ValueError means we need to register the user.
try:
network.set_credentials(name, "")
# make it easy for people who use an empty password =)
motherfucking_rainbows("~~logged in as {}~~".format(network.user_name))
except ConnectionRefusedError:
def login_loop(prompt, positive):
try:
password = paren_prompt(prompt, positive)
network.set_credentials(name, password)
except ConnectionRefusedError:
login_loop("// R E J E C T E D //.", False)
login_loop("Enter your password", True)
motherfucking_rainbows("~~logged in as {}~~".format(network.user_name))
except ValueError:
motherfucking_rainbows("Nice to meet'cha, %s!" % name)
response = paren_prompt(
"Register as %s?" % name,
choices=["yes!", "change name"]
)
if response == "c":
name = sane_value("user_name", "Pick a new name")
def password_loop(prompt, positive=True):
response1 = paren_prompt(prompt, positive)
if response1 == "":
confprompt = "Confirm empty password"
else:
confprompt = "Confirm it"
response2 = paren_prompt(confprompt)
if response1 != response2:
return password_loop("Those didnt match. Try again", False)
return response1
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)
def main():
run("clear", shell=True)
motherfucking_rainbows(obnoxious_logo)
print(welcome)
log_in()
# sleep(1) # let that confirmation message shine
if __name__ == "__main__":
global app
main()
app = App()
app.loop.run()

View File

@ -0,0 +1 @@
../network_client.py

View File

@ -1,3 +0,0 @@
sorry
emacs is cooler anyway 8)