begin urwid client
parent
b99c7a1f0f
commit
19af814a93
|
@ -42,8 +42,7 @@ class BBJ:
|
|||
"""
|
||||
def __init__(self, host="127.0.0.1", port=8080):
|
||||
self.base = "http://{}:{}/api/%s".format(host, port)
|
||||
self.user_name = None
|
||||
self.user_auth = None
|
||||
self.user_name = self.user_auth = None
|
||||
self.send_auth = True
|
||||
|
||||
|
||||
|
@ -101,7 +100,8 @@ class BBJ:
|
|||
Uses the server's db_sanity_check method to verify the validty
|
||||
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.
|
||||
attribute .description as the server's reason. Exception can
|
||||
be a False value to just rturn boolean False.
|
||||
"""
|
||||
response = self(
|
||||
"db_sanity_check",
|
||||
|
@ -111,6 +111,8 @@ class BBJ:
|
|||
)
|
||||
|
||||
if not response["data"]["bool"]:
|
||||
if not exception:
|
||||
return False
|
||||
description = response["data"]["description"]
|
||||
error = exception(description)
|
||||
error.description = description
|
||||
|
@ -148,8 +150,7 @@ class BBJ:
|
|||
user_auth = sha256(bytes(user_auth, "utf8")).hexdigest()
|
||||
|
||||
if check_validity and not self.validate_credentials(user_name, user_auth):
|
||||
self.user_auth = None
|
||||
self.user_name = None
|
||||
self.user_auth = self.user_name = None
|
||||
raise ConnectionRefusedError("Auth and User do not match")
|
||||
|
||||
self.user_auth = user_auth
|
||||
|
@ -180,22 +181,44 @@ class BBJ:
|
|||
Returns True or False whether user_name is registered
|
||||
into the system.
|
||||
"""
|
||||
return self(
|
||||
response = self(
|
||||
"user_is_registered",
|
||||
no_auth=True,
|
||||
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
|
||||
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):
|
||||
"""
|
||||
|
@ -204,3 +227,11 @@ class BBJ:
|
|||
"""
|
||||
response = self("thread_index")
|
||||
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"]
|
||||
|
|
|
@ -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()
|
|
@ -0,0 +1 @@
|
|||
../network_client.py
|
|
@ -1,3 +0,0 @@
|
|||
sorry
|
||||
|
||||
emacs is cooler anyway 8)
|
Loading…
Reference in New Issue