commit 39881ccf6a1bd0e90f314c8db28a55ce3e195384 Author: trinkey <97406176+trinkey@users.noreply.github.com> Date: Tue Feb 20 14:55:22 2024 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b733e91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +save/ +.vscode/ diff --git a/_server.py b/_server.py new file mode 100644 index 0000000..e722d18 --- /dev/null +++ b/_server.py @@ -0,0 +1,321 @@ +import hashlib +import shutil +import flask +import json +import os + +from flask import request +from typing import Union, Callable + +CONTENT_DIRECTORY = "./public/" +SAVING_DIRECTORY = "./save/" + +app = flask.Flask(__name__) + +def sort_list(l: list[list[str]]) -> list[list[str]]: + output = [] + for i in l: + if i[0] and i[1] in ["1", "2", "3", "4"]: + output.append(i) + + return sorted(output, key=lambda x: {"1": "d", "2": "c", "3": "b", "4": "a"}[x[1]] + x[0]) + +def return_dynamic_content_type(content: Union[str, bytes], content_type: str="text/html") -> flask.Response: + response = flask.make_response(content) + response.headers["Content-Type"] = content_type + return response + +def sha(string: Union[str, bytes]) -> str: + if type(string) == str: + return hashlib.sha256(str.encode(string)).hexdigest() + elif type(string) == bytes: + return hashlib.sha256(string).hexdigest() + return "" + +def ensure_file(path: str, *, default_value: str="", folder: bool=False) -> None: + if os.path.exists(path): + if folder and not os.path.isdir(path): + os.remove(path) + os.makedirs(path) + elif not folder and os.path.isdir(path): + shutil.rmtree(path, ignore_errors=True) + f = open(path, "w") + f.write(default_value) + f.close() + else: + if folder: + os.makedirs(path) + else: + f = open(path, "w") + f.write(default_value) + f.close() + +def escape_html(string: str) -> str: + return string.replace("&", "&").replace("<", "<").replace("\"", "&quo;") + +def create_file_serve(file) -> Callable: + x = lambda: flask.send_file(f"{CONTENT_DIRECTORY}{file}") + x.__name__ = file + return x + +def generate_token(username: str, passhash: str) -> str: + return sha(sha(f"{username}:{passhash}") + "among us in real life, sus, sus") + +def create_folder_serve(directory) -> Callable: + x = lambda file: flask.send_from_directory(f"{CONTENT_DIRECTORY}{directory}", file) + x.__name__ = directory + return x + +def api_account_login(): + try: + x = json.loads(request.data) + username = x["username"].replace(" ", "").lower() + passhash = x["password"] + except json.JSONDecodeError: + flask.abort(400) + except KeyError: + flask.abort(400) + + if len(username) > 24 or len(username) < 1: + flask.abort(400) + + for i in username: + if i not in "abcdefghijklmnopqrstuvwxyz0123456789_-": + return return_dynamic_content_type(json.dumps({ + "valid": False, + "reason": "User doesn't exist." + }), "application/json") + + try: + open(f"{SAVING_DIRECTORY}{username}.json", "r") + except FileNotFoundError: + return return_dynamic_content_type(json.dumps({ + "valid": False, + "reason": "User doesn't exist." + }), "application/json") + + token = generate_token(username, passhash) + + try: + enforced_username = open(f"{SAVING_DIRECTORY}tokens/{token}.txt", "r").read() + except FileNotFoundError: + return return_dynamic_content_type(json.dumps({ + "valid": False, + "reason": "Invalid password" + }), "application/json") + + if enforced_username != username: + return return_dynamic_content_type(json.dumps({ + "valid": False, + "reason": "Invalid password" + }), "application/json") + + return return_dynamic_content_type(json.dumps({ + "valid": True, + "token": token + }), "application/json") + +def api_account_signup(): + try: + x = json.loads(request.data) + username = x["username"].replace(" ", "") + passhash = x["password"] + except json.JSONDecodeError: + flask.abort(400) + except KeyError: + flask.abort(400) + + if len(username) > 24 or len(username) < 1: + flask.abort(400) + + if len(passhash) != 64: + flask.abort(400) + + for i in passhash: + if i not in "abcdef0123456789": + flask.abort(400) + + for i in username: + if i not in "abcdefghijklmnopqrstuvwxyz0123456789_-": + return return_dynamic_content_type(json.dumps({ + "valid": False, + "reason": "Username can only contain a-z, 0-9, underscores, and hyphens." + }), "application/json") + + try: + open(f"{SAVING_DIRECTORY}{username}.json", "r") + return return_dynamic_content_type(json.dumps({ + "valid": False, + "reason": "Username taken." + }), "application/json") + except FileNotFoundError: + pass + + token = generate_token(username, passhash) + ensure_file(f"{SAVING_DIRECTORY}tokens/{token}.txt", default_value=username) + ensure_file(f"{SAVING_DIRECTORY}{username}.json", default_value=json.dumps({ + "username": username, + "display_name": username, + "description": "", + "colors": { + "accent": "#ff0000", + "text": "#ffffff", + "background": "#111122" + }, + "names": [ + [username, "4"] + ], + "pronouns": [ + ["he/him", "3"], + ["it/its", "3"], + ["she/her", "3"], + ["they/them", "3"] + ], + "honorifics": [ + ["ma'am", "3"], + ["madam", "3"], + ["mir", "3"], + ["mr.", "3"], + ["ms.", "3"], + ["mx.", "3"], + ["sai", "3"], + ["shazam", "3"], + ["sir", "3"], + ["zam", "3"] + ], + "compliments": [ + ["cute", "3"], + ["handsome", "3"], + ["hot", "3"], + ["pretty", "3"], + ["sexy", "3"] + ], "relationship": [ + ["beloved", "3"], + ["boyfriend", "3"], + ["darling", "3"], + ["enbyfriend", "3"], + ["friend", "3"], + ["girlfriend", "3"], + ["husband", "3"], + ["partner", "3"], + ["wife", "3"] + ] + })) + + return return_dynamic_content_type(json.dumps({ + "valid": True, + "token": token + })) + +def api_account_info_(user): + try: + return return_dynamic_content_type( + open(f"{SAVING_DIRECTORY}{user}.json").read(), + "application/json" + ) + except FileNotFoundError: + flask.abort(404) + +def api_account_self(): + try: + return return_dynamic_content_type( + open(SAVING_DIRECTORY + open(f'{SAVING_DIRECTORY}tokens/{request.cookies["token"]}.txt', 'r').read() + ".json", 'r').read(), + "application/json" + ) + except FileNotFoundError: + flask.abort(404) + +def api_save(): + # TODO - ADD SORTING + + username = open(f'{SAVING_DIRECTORY}tokens/{request.cookies["token"]}.txt', 'r').read() + x = json.loads(request.data) + + user_data = json.loads(open(f"{SAVING_DIRECTORY}{username}.json", "r").read()) + + if "display_name" in x and len(x["display_name"]) < 64 and len(x["display_name"]) > 0: + user_data["display_name"] = x["display_name"] + + if "description" in x and len(x["description"]) < 512: + user_data["description"] = x["description"] + + if "colors" in x: + if "accent" in x["colors"]: + user_data["colors"]["accent"] = x["colors"]["accent"] + + if "background" in x["colors"]: + user_data["colors"]["background"] = x["colors"]["background"] + + if "text" in x["colors"]: + user_data["colors"]["text"] = x["colors"]["text"] + + if "names" in x: + names = [] + for i in x["names"]: + if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48: + names.append(i) + user_data["names"] = sort_list(names) + + if "pronouns" in x: + pronouns = [] + for i in x["pronouns"]: + if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48: + pronouns.append(i) + user_data["pronouns"] = sort_list(pronouns) + + if "honorifics" in x: + honorifics = [] + for i in x["honorifics"]: + if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48: + honorifics.append(i) + user_data["honorifics"] = sort_list(honorifics) + + if "compliments" in x: + compliments = [] + for i in x["compliments"]: + if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48: + compliments.append(i) + user_data["compliments"] = sort_list(compliments) + + if "relationship" in x: + relationship = [] + for i in x["relationship"]: + if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48: + relationship.append(i) + user_data["relationship"] = sort_list(relationship) + + f = open(f"{SAVING_DIRECTORY}{username}.json", "w") + f.write(json.dumps(user_data)) + f.close() + + return "200 OK" + +def u_(username): + return flask.send_file(f"{CONTENT_DIRECTORY}user.html") + +# levels: +# 4 - love +# 3 - good +# 2 - okay +# 1 - bad + +ensure_file(SAVING_DIRECTORY, folder=True) +ensure_file(f"{SAVING_DIRECTORY}tokens/", folder=True) + +app.route("/")(create_file_serve("index.html")) +app.route("/login")(create_file_serve("login.html")) +app.route("/signup")(create_file_serve("signup.html")) +app.route("/logout")(create_file_serve("logout.html")) +app.route("/editor")(create_file_serve("editor.html")) +app.route("/u/")(u_) +app.route("/js/")(create_folder_serve("js")) +app.route("/css/")(create_folder_serve("css")) + +app.route("/api/account/login", methods=["POST"])(api_account_login) +app.route("/api/account/signup", methods=["POST"])(api_account_signup) +app.route("/api/account/info/", methods=["GET"])(api_account_info_) +app.route("/api/account/self", methods=["GET"])(api_account_self) +app.route("/api/save", methods=["PATCH"])(api_save) + +if __name__ == "__main__": + app.run(debug=True, port=8080) diff --git a/public/css/base.css b/public/css/base.css new file mode 100644 index 0000000..3eb8cc5 --- /dev/null +++ b/public/css/base.css @@ -0,0 +1,132 @@ +@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,700;1,400;1,700&display=swap'); + +@font-face { + font-family: 'FontAwesome Solid'; + src: url('https://site-assets.fontawesome.com/releases/v6.5.1/webfonts/fa-solid-900.woff2'); +} + +@font-face { + font-family: 'FontAwesome Solid'; + src: url('https://site-assets.fontawesome.com/releases/v6.5.1/webfonts/fa-solid-900.woff2'); +} + +.fa-s { + font-family: 'FontAwesome Solid', sans-serif; +} + +.fa-r { + font-family: 'FontAwesome Regular', sans-serif; +} + +:root { + --text: #aaaaaa; + --background: #333333; + --primary: #ffffff; + --secondary: #666666; + --accent: #444444; + + --text-low-opacity: #aaaaaa88; + --background-low-opacity: #33333333; + --primary-low-opacity: #88888833; + --secondary-low-opacity: #66666633; + --accent-low-opacity: #44444466; +} + +::selection { + background-color: var(--accent-low-opacity); + color: var(--text); +} + +body::-webkit-scrollbar { display: none; } + +body { + background-color: var(--background); + color: var(--text); + font-family: 'Poppins', "FontAwesome Regular", 'Arial'; + padding: 5vh 0; + text-align: center; + font-size: 14px; + margin: 0px; + overflow-x: hidden; + word-wrap: break-word; +} + +@media screen and (min-width: 1025px) { + body { font-size: 16px; } +} + +@media screen and (max-width: 565px) { + body { font-size: 16px; padding: 5vh 3vw; } +} + +button { + background-color: var(--secondary-low-opacity); + color: var(--primary); + border: none; + padding: 5px 7px; + border-radius: 10px; + cursor: pointer; + transition-property: color; + font-size: 0.8em; +} + +button:disabled { opacity: 60%; pointer-events: none; } +button:active { scale: 95%; } + +input, textarea, option, select{ + background-color: var(--secondary-low-opacity); + color: var(--primary); + border: none; + border-radius: 2px; + padding: 5px 7px; + margin: 3px; + font-size: 0.8em; +} + +input, textarea, select { + font-family: 'Poppins'; +} + +option, select { + background-color: var(--secondary-low-opacity); +} + +option { + font-size: 1.1em; +} + +textarea { background-color: var(--secondary-low-opacity); } +textarea:disabled { opacity: 70%; pointer-events: none; } + +a:link { color: var(--primary); text-decoration: none; } +a:visited { color: var(--primary); text-decoration: none; } +a:hover { text-decoration: underline; } +a::-moz-selection { text-decoration-color: var(--text); } +a::selection { -webkit-text-decoration-color: var(--text); text-decoration-color: var(--text); } + +i { opacity: 80%; } + +p { + margin: 0px; + padding: 2px; +} + +.text:not(svg) { color: var(--text); } +.background:not(svg) { color: var(--background); } +.primary:not(svg) { color: var(--primary); } +.secondary:not(svg) { color: var(--secondary); } +.accent:not(svg) { color: var(--accent); } + +svg.text { fill: var(--text); } +svg.background { fill: var(--background); } +svg.primary { fill: var(--primary); } +svg.secondary { fill: var(--secondary); } +svg.accent { fill: var(--accent); } + +.left { text-align: left; } +.center { text-align: center; } +.right { text-align: right; } + +.invis { opacity: 0%; } +.hidden { display: none; } +.no-underline { text-decoration: none !important; } diff --git a/public/css/editor.css b/public/css/editor.css new file mode 100644 index 0000000..fc690f6 --- /dev/null +++ b/public/css/editor.css @@ -0,0 +1,12 @@ +input:not([type]), input[type="text"] { + width: 10em; +} + +#input-display-name { + width: 20em; +} + +textarea { + width: 30em; + resize: vertical; +} diff --git a/public/css/page.css b/public/css/page.css new file mode 100644 index 0000000..c4cee04 --- /dev/null +++ b/public/css/page.css @@ -0,0 +1,26 @@ +svg { + width: 0.9em; + height: 0.9em; + display: inline-block; + fill: var(--text); +} + +#word-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + row-gap: 5em; + column-gap: 2em; +} + +.added { + display: inline-block; + text-align: left; + max-width: 16em; + white-space: break-word; +} + +.added h2 { + text-align: center; +} diff --git a/public/editor.html b/public/editor.html new file mode 100644 index 0000000..cd01aa9 --- /dev/null +++ b/public/editor.html @@ -0,0 +1,19 @@ + + + + + Editor + + + + + + + + + + + + Log Out + + \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..c1ddef7 --- /dev/null +++ b/public/index.html @@ -0,0 +1,25 @@ + + + + + InfoPage + + + + + + + + + +

InfoPage

+ To share information about yourself

+ Sign up - + Log in + + diff --git a/public/js/base.js b/public/js/base.js new file mode 100644 index 0000000..f511225 --- /dev/null +++ b/public/js/base.js @@ -0,0 +1,100 @@ +const dom = (id) => (document.getElementById(id)); + +function setCookie(name, value) { + let date = new Date(); + date.setTime(date.getTime() + (356 * 24 * 60 * 60 * 1000)); + document.cookie = `${name}=${value};Path=/;Expires=${date.toUTCString()}`; +} + +function eraseCookie(name) { + document.cookie = name +'=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; +} + +function sha256(ascii) { + function rightRotate(value, amount) { + return (value >>> amount) | (value << (32 - amount)); + }; + + let maxWord = Math.pow(2, 32); + let i, j; + let result = ''; + + let words = []; + let asciiBitLength = ascii["length"]*8; + + let hash = sha256.h = sha256.h || []; + let k = sha256.k = sha256.k || []; + let primeCounter = k["length"]; + + let isComposite = {}; + for (let candidate = 2; primeCounter < 64; candidate++) { + if (!isComposite[candidate]) { + for (i = 0; i < 313; i += candidate) { + isComposite[i] = candidate; + } + hash[primeCounter] = (Math.pow(candidate, .5) * maxWord) | 0; + k[primeCounter++] = (Math.pow(candidate, 1 / 3) * maxWord) | 0; + } + } + + ascii += '\x80' + while (ascii["length"]%64 - 56) ascii += '\x00' + for (i = 0; i < ascii["length"]; i++) { + j = ascii.charCodeAt(i); + if (j >> 8) return; + words[i >> 2] |= j << ((3 - i) % 4) * 8; + } + words[words["length"]] = ((asciiBitLength / maxWord) | 0); + words[words["length"]] = (asciiBitLength) + + for (j = 0; j < words["length"];) { + let w = words.slice(j, j += 16); + let oldHash = hash; + hash = hash.slice(0, 8); + + let w15, a, temp1, temp2; + + for (i = 0; i < 64; i++) { + w15 = w[i - 15], w2 = w[i - 2]; + a = hash[0], e = hash[4]; + temp1 = hash[7] + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) + ((e & hash[5])^((~e) & hash[6])) + k[i] + (w[i] = (i < 16) ? w[i] : (w[i - 16] + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) + w[i - 7] + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10))) | 0); + temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2])); + hash = [(temp1 + temp2) | 0].concat(hash); + hash[4] = (hash[4] + temp1) | 0; + } + + for (i = 0; i < 8; i++) { + hash[i] = (hash[i] + oldHash[i]) | 0; + } + } + + for (i = 0; i < 8; i++) { + for (j = 3; j + 1; j--) { + let b = (hash[i] >> (j * 8)) & 255; + result += ((b < 16) ? 0 : '') + b.toString(16); + } + } + return result; +}; + +function escapeHTML(text, forInput=false) { + if (forInput) { + return text.replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll("\"", """) + .replaceAll("\n", " "); + } + return text.replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll("\"", """); +} + +const icons = { + 1: '', + 2: '', + 3: '', + 4: '', + add: '', + x: '', + arrow: '' +} diff --git a/public/js/editor.js b/public/js/editor.js new file mode 100644 index 0000000..248a4c8 --- /dev/null +++ b/public/js/editor.js @@ -0,0 +1,159 @@ +function log(str) { + c++; + document.getElementById("log").innerText = str; + setTimeout( + function() { + --c; + if (!c) { + document.getElementById("log").innerText = " "; + } + }, 3000 + ); +} + +function addToOutput(starting, json, key, title) { + starting += `

${title}

`; // Title + + for (let i = 0; i < json[key].length; i++) { + starting += ` +
+ + + + ${icons.x} +
`; + } + starting += `
`; + return starting; +} + +function add_input(key) { + let x = document.createElement("div") + + let q = [...document.querySelectorAll(`#${key} div[id^="${key}-"]`)]; + let i = Number(q[q.length - 1].dataset.id) + 1; + + x.id = `${key}-${i}`; + x.setAttribute("data-id", i); + x.innerHTML = ` + + + + ${icons.x}`; + + dom(key).append(x); +} + +function updateColors() { + document.body.setAttribute("style", `--background: ${colors.background}; --background-low-opacity: ${colors.background}33; --accent: ${colors.accent}; --accent-low-opacity: ${colors.accent}66; --text: ${colors.text}; --text-low-opacity: ${colors.text}88;`); +} + +function get_list(key) { + let output = []; + [...document.querySelectorAll(`#${key} div[id^="${key}-"]`)].forEach((val, index) => { + output.push([val.querySelector("input").value, val.querySelector("select").value]); + }); + return output; +} + +if (localStorage.getItem("token")) { + setCookie("token", localStorage.getItem("token")); +} else { + window.location.href = "/logout"; +} + +let colors, c; + +fetch("/api/account/self", { + "method": "GET" +}).then((response) => (response.json())) + .then((json) => { + colors = json.colors; + updateColors(); + + let x = document.createElement("div"); + let inner = ` +

+
+
Text color:
+
Background color:
+
Accent color:

+
+
+ `; + + inner = addToOutput(inner, json, "names", "Names"); + inner = addToOutput(inner, json, "pronouns", "Pronouns"); + inner = addToOutput(inner, json, "honorifics", "Honorifics"); + inner = addToOutput(inner, json, "compliments", "Compliments"); + inner = addToOutput(inner, json, "relationship", "Relationship
Descriptions"); + + inner += "
"; + + x.id = "container"; + x.innerHTML = inner; + document.body.append(x); + + dom("input-description").addEventListener("input", function() { + let cursorPosition = this.selectionStart; + if (this.value.indexOf("\n") !== -1) { + --cursorPosition; + } + this.value = this.value.replaceAll("\n", "").replaceAll("\r", ""); + this.setSelectionRange(cursorPosition, cursorPosition); + }); + + dom("save").addEventListener("click", function() { + fetch("/api/save", { + "method": "PATCH", + "body": JSON.stringify({ + colors: colors, + display_name: dom("input-display-name").value, + description: dom("input-description").value, + names: get_list("names"), + pronouns: get_list("pronouns"), + honorifics: get_list("honorifics"), + compliments: get_list("compliments"), + relationship: get_list("relationship") + }) + }).then((response) => (response.text())) + .then((text) => { + if (text === "200 OK") { + log("Saved!"); + } else { + log("Something went wrong when saving!"); + } + }) + .catch((err) => { + log("Something went wrong when saving!"); + }) + }); + + dom("input-col-text").addEventListener("input", function() { + colors.text = this.value; + updateColors(); + }); + + dom("input-col-accent").addEventListener("input", function() { + colors.accent = this.value; + updateColors(); + }); + + dom("input-col-background").addEventListener("input", function() { + colors.background = this.value; + updateColors(); + }); + }) + .catch((err) => { + window.location.href = "/logout"; + }); diff --git a/public/js/login.js b/public/js/login.js new file mode 100644 index 0000000..7899932 --- /dev/null +++ b/public/js/login.js @@ -0,0 +1,54 @@ +let inc = 0, req = 0; + +showlog = (str, time=3000) => { + inc++; + dom("error").innerText = str; + setTimeout(() => { req++; if (req == inc) { dom("error").innerText = ""; }}, time); +}; + +dom("toggle-password").addEventListener("click", function() { + if (dom("password").getAttribute("type") == "password") { + dom("password").setAttribute("type", "text"); + } else { + dom("password").setAttribute("type", "password"); + } +}); + +dom("submit").addEventListener("click", function() { + this.setAttribute("disabled", ""); + username = dom("username").value; + password = sha256(dom("password").value) + fetch("/api/account/login", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + "username": username, + "password": password + }) + }) + .then((response) => { + if (response.status == 429) { + dom("post").removeAttribute("disabled"); + dom("post-text").removeAttribute("disabled"); + showlog("You are being ratelimited! Try again in a few moments..."); + } else { + response.json().then((json) => { + if (json.valid) { + setCookie("token", json.token); + localStorage.setItem("token", json.token); + window.location.href = "/editor"; + } else { + dom("submit").removeAttribute("disabled"); + showlog(`Unable to login! Reason: ${json.reason}`); + } + }) + } + }) + .catch((err) => { + dom("submit").removeAttribute("disabled"); + showlog("Something went wrong! Try again in a few moments..."); + throw(err); + }); +}); \ No newline at end of file diff --git a/public/js/signup.js b/public/js/signup.js new file mode 100644 index 0000000..e4fe014 --- /dev/null +++ b/public/js/signup.js @@ -0,0 +1,54 @@ +let inc = 0, req = 0; + +showlog = (str, time=3000) => { + inc++; + dom("error").innerText = str; + setTimeout(() => { req++; if (req == inc) { dom("error").innerText = ""; }}, time); +}; + +dom("toggle-password").addEventListener("click", function() { + if (dom("password").getAttribute("type") == "password") { + dom("password").setAttribute("type", "text"); + } else { + dom("password").setAttribute("type", "password"); + } +}); + +dom("submit").addEventListener("click", function() { + this.setAttribute("disabled", ""); + username = dom("username").value; + password = sha256(dom("password").value) + fetch("/api/account/signup", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + "username": username, + "password": password + }) + }) + .then((response) => { + if (response.status == 429) { + dom("post").removeAttribute("disabled"); + dom("post-text").removeAttribute("disabled"); + showlog("You are being ratelimited! Try again in a few moments..."); + } else { + response.json().then((json) => { + if (json.valid) { + setCookie("token", json.token); + localStorage.setItem("token", json.token); + window.location.href = "/editor"; + } else { + dom("submit").removeAttribute("disabled"); + showlog(`Unable to create account! Reason: ${json.reason}`); + } + }) + } + }) + .catch((err) => { + dom("submit").removeAttribute("disabled"); + showlog("Something went wrong! Try again in a few moments..."); + throw(err); + }); +}); \ No newline at end of file diff --git a/public/js/user.js b/public/js/user.js new file mode 100644 index 0000000..35ae931 --- /dev/null +++ b/public/js/user.js @@ -0,0 +1,43 @@ +function addToOutput(starting, json, key, title) { + starting += `

${title}

`; + for (let i = 0; i < json[key].length; i++) { + starting += `
${icons[json[key][i][1]]} ${escapeHTML(json[key][i][0])}
`; + } + starting += "
"; + return starting; +} + +let x2 = window.location.href.split("?")[0].split("/"); + +fetch("/api/account/info/" + x2[x2.length - 1], { + "method": "GET" +}).then((response) => (response.json())) + .then((json) => { + document.body.setAttribute("style", `--background: ${json.colors.background}; --background-low-opacity: ${json.colors.background}33; --accent: ${json.colors.accent}; --accent-low-opacity: ${json.colors.accent}66; --text: ${json.colors.text}; --text-low-opacity: ${json.colors.text}88;`); + let x = document.createElement("div"); + let inner = ` +

${escapeHTML(json.display_name)}

+
${escapeHTML(json.description)}
+
+ `; + + document.title = `${json.display_name} (@${x2[x2.length - 1]})`; + + inner = addToOutput(inner, json, "names", "Names"); + inner = addToOutput(inner, json, "pronouns", "Pronouns"); + inner = addToOutput(inner, json, "honorifics", "Honorifics"); + inner = addToOutput(inner, json, "compliments", "Compliments"); + inner = addToOutput(inner, json, "relationship", "Relationship
Descriptions"); + + inner += "
"; + + x.id = "container"; + x.innerHTML = inner; + document.body.append(x); + }) + .catch((err) => { + document.body.innerHTML = "

User not found!

Sign up - Log in"; + }); diff --git a/public/login.html b/public/login.html new file mode 100644 index 0000000..3fe27d3 --- /dev/null +++ b/public/login.html @@ -0,0 +1,30 @@ + + + + + Log In + + + + + + + + + +

Log In

+
+

+

+

+ Sign up instead... +
+ + + + diff --git a/public/logout.html b/public/logout.html new file mode 100644 index 0000000..7b74f5f --- /dev/null +++ b/public/logout.html @@ -0,0 +1,20 @@ + + + + + Log In + + + + + + + + Click here if you aren't redirected shortly... + + + diff --git a/public/signup.html b/public/signup.html new file mode 100644 index 0000000..1d3630a --- /dev/null +++ b/public/signup.html @@ -0,0 +1,30 @@ + + + + + Sign Up + + + + + + + + + +

Sign Up

+
+

+

+

+ Log in instead... +
+ + + + diff --git a/public/user.html b/public/user.html new file mode 100644 index 0000000..412e686 --- /dev/null +++ b/public/user.html @@ -0,0 +1,16 @@ + + + + + sussy among us imposter + + + + + + + + + + + \ No newline at end of file