From 4287c6c5d66bbf7d636875efc4e480417cb1aea2 Mon Sep 17 00:00:00 2001 From: trinkey <97406176+trinkey@users.noreply.github.com> Date: Thu, 28 Mar 2024 21:36:08 -0400 Subject: [PATCH] social links and key on `/u/...` --- README.md | 2 - _server.py | 127 ++++++++++++++++++++++++++++++++++++++---- public/css/base.css | 33 ++++------- public/css/editor.css | 16 ++++++ public/css/page.css | 54 +++++++++++++++--- public/editor.html | 2 +- public/js/base.js | 75 +++++++++++++++++++++++-- public/js/editor.js | 44 ++++++++++++--- public/js/login.js | 2 +- public/js/signup.js | 12 +++- public/js/user.js | 44 --------------- public/signup.html | 3 +- public/user.html | 18 ++++++ 13 files changed, 329 insertions(+), 103 deletions(-) delete mode 100644 public/js/user.js diff --git a/README.md b/README.md index 7288683..045fe68 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,4 @@ or https://infopg.web.app it's like pronouns.page but i made it ### todo -* social links (currently wip) * pride flags -* add key at bottom of /u/... pages diff --git a/_server.py b/_server.py index b8d8dba..26fdb56 100644 --- a/_server.py +++ b/_server.py @@ -9,6 +9,7 @@ import shutil import flask import json import os +import re from DotIndex import DotIndex from typing import Union, Callable @@ -18,6 +19,81 @@ from werkzeug.middleware.proxy_fix import ProxyFix app = flask.Flask(__name__) app.url_map.strict_slashes = False +SOCIALS_REGEX = { + "discord" : re.compile(r"^(?!.*\.\.)(?=.{2,32}$)[a-z0-9_.]+$"), + "twitter" : re.compile(r"^(?!.*twitter)(?!.*admin)[a-z0-9_]{1,15}$", re.IGNORECASE), + "github" : re.compile(r"^(?!.*--)[a-z0-9](?:[a-z0-9-]{0,37}[a-z0-9])?$", re.IGNORECASE), + "twitch" : re.compile(r"^[a-z0-9_]{4,25}$", re.IGNORECASE), + "reddit" : re.compile(r"^[a-z0-9_-]{3,20}$", re.IGNORECASE), + "snapchat" : re.compile(r"^(?=.{3,15}$)[a-z0-9]+(?:[_.-][a-z0-9]+)?$", re.IGNORECASE), + "instagram" : re.compile(r"^[a-z0-9_.]{1,30}$", re.IGNORECASE), + "facebook" : re.compile(r"^([a-z0-9].*){1,50}$", re.IGNORECASE), + "tiktok" : re.compile(r"^[a-z0-9_.]{1,25}$", re.IGNORECASE), + "smiggins" : re.compile(r"^[a-z0-9_-]{1,18}$"), + "tringl" : re.compile(r"^[a-z0-9_]{1,24}$") +} + +SOCIAL_ICONS = { + "discord": '', + "facebook": '', + "github": '', + "instagram": '', + "reddit": '', + "smiggins": '', + "snapchat": '', + "tiktok": '', + "tringl": '', + "twitch": '', + "twitter": '' +} + +SOCIAL_INFO = { + "discord": { + "link": None, + "prefix": "" + }, + "facebook": { + "link": "https://www.facebook.com/%q", + "prefix": "" + }, + "github": { + "link": "https://github.com/%q", + "prefix": "" + }, + "instagram": { + "link": "https://www.instagram.com/%q/", + "prefix": "" + }, + "reddit": { + "link": "https://www.reddit.com/u/%q", + "prefix": "/u/" + }, + "smiggins": { + "link": "https://trinkey.pythonanywhere.com/u/%q", + "prefix": "@" + }, + "snapchat": { + "link": "https://www.snapchat.com/add/%q", + "prefix": "" + }, + "tiktok": { + "link": "https://www.tiktok.com/@%q", + "prefix": "" + }, + "tringl": { + "link": "https://ngl.pythonanywhere.com/m/%q", + "prefix": "@" + }, + "twitch": { + "link": "https://www.twitch.tv/%q", + "prefix": "" + }, + "twitter": { + "link": "https://twitter.com/%q", + "prefix": "@" + } +} + def validate_color(color: str) -> bool: if len(color) != 7 or color[0] != "#": return False @@ -28,12 +104,14 @@ def validate_color(color: str) -> bool: return True -def sort_list(l: list[list[str]]) -> list[list[str]]: +def sort_list(l: list[list[str]], alphabetical=True) -> list[list[str]]: output = [] for i in l: - if i[0] and i[1] in ["1", "2", "3", "4"]: + if i[0] and (not alphabetical and (i[1] in ["1", "2", "3", "4"]) or (alphabetical and i[1])): output.append(i) + if alphabetical: + return sorted(output, key=lambda x: x[1] + x[0]) 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: @@ -73,7 +151,7 @@ def generate_token(username: str, passhash: str) -> str: return sha(sha(f"{username}:{passhash}") + "among us in real life, sus, sus") def list_public( - sort: str="alphabetical", + sort: str="random", page: int=0, limit: int=25 ) -> dict: @@ -148,6 +226,21 @@ def get_template(json, username): inner = add_to_output(inner, json, "compliments", "Compliments"); inner = add_to_output(inner, json, "relationship", "Relationship
Descriptions"); + try: + social = json.social # type: ignore + inner += '

Social Links

' + + for i in social: + if SOCIAL_INFO[i[1]]["link"]: + inner += f"
{SOCIAL_ICONS[i[1]]} {SOCIAL_INFO[i[1]]['prefix']}{escape_html(i[0])}
" + else: + inner += f"
{SOCIAL_ICONS[i[1]]} {SOCIAL_INFO[i[1]]['prefix']}{escape_html(i[0])}
" + + inner += "
" + + except AttributeError as e: + print(e) + inner += "" return title, inner, styles, embed @@ -155,6 +248,7 @@ def get_template(json, username): def get_user_page(user): user = user.lower() x = open(f"{CONTENT_DIRECTORY}user.html", "r").read() + try: user_json = json.loads(open(f"{SAVING_DIRECTORY}{user}.json", "r").read()) except FileNotFoundError: @@ -162,7 +256,11 @@ def get_user_page(user): title, inner, styles, embed = get_template(user_json, user) - return x.replace(" 0 and len(i[0]) < 48: + if len(i) == 2 and 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: + if len(i) == 2 and 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: + if len(i) == 2 and 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: + if len(i) == 2 and 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: + if len(i) == 2 and 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) + if "social" in x: + social = [] + for i in x["social"]: + if len(i) == 2 and i[1] in SOCIALS_REGEX and SOCIALS_REGEX[i[1]].match(i[0]): + social.append(i) + user_data["social"] = sort_list(social) + f = open(f"{SAVING_DIRECTORY}{username}.json", "w") f.write(json.dumps(user_data)) f.close() diff --git a/public/css/base.css b/public/css/base.css index 3eb8cc5..f04c9cd 100644 --- a/public/css/base.css +++ b/public/css/base.css @@ -1,23 +1,5 @@ @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; @@ -42,21 +24,21 @@ body::-webkit-scrollbar { display: none; } body { background-color: var(--background); color: var(--text); - font-family: 'Poppins', "FontAwesome Regular", 'Arial'; + font-family: 'Poppins', 'Arial'; padding: 5vh 0; text-align: center; - font-size: 14px; + font-size: 16px; margin: 0px; overflow-x: hidden; word-wrap: break-word; } @media screen and (min-width: 1025px) { - body { font-size: 16px; } + body { font-size: 18px; } } @media screen and (max-width: 565px) { - body { font-size: 16px; padding: 5vh 3vw; } + body { font-size: 18px; padding: 5vh 3vw; } } button { @@ -95,6 +77,13 @@ option { font-size: 1.1em; } +footer { + opacity: 50%; + position: fixed; + bottom: 10px; + right: 10px; +} + textarea { background-color: var(--secondary-low-opacity); } textarea:disabled { opacity: 70%; pointer-events: none; } diff --git a/public/css/editor.css b/public/css/editor.css index fc690f6..396a170 100644 --- a/public/css/editor.css +++ b/public/css/editor.css @@ -1,5 +1,21 @@ input:not([type]), input[type="text"] { width: 10em; + border: 2px solid #0000; +} + +#social input.bad, +#social input.bad:focus { + border-color: var(--accent); + outline-color: var(--accent); +} + +.added.wider input:not([type]), +.added.wider input[type="text"] { + width: 14em; +} + +.wider { + max-width: 22em; } #input-display-name { diff --git a/public/css/page.css b/public/css/page.css index c4cee04..ebd3c9b 100644 --- a/public/css/page.css +++ b/public/css/page.css @@ -1,3 +1,7 @@ +body { + padding-bottom: calc(20px + 8em); +} + svg { width: 0.9em; height: 0.9em; @@ -5,13 +9,14 @@ svg { fill: var(--text); } -#word-container { - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: center; - row-gap: 5em; - column-gap: 2em; +a, a:visited, a:link { + color: var(--text); + text-decoration-color: var(--text); +} + +footer a, footer a:visited, footer a:link { + color: var(--accent); + text-decoration-color: var(--accent); } .added { @@ -24,3 +29,38 @@ svg { .added h2 { text-align: center; } + +.added div { + margin-bottom: 0.2em; +} + +#word-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + row-gap: 5em; + column-gap: 2em; +} + +#key { + position: fixed; + text-align: left; + bottom: 10px; + opacity: 50%; +} + +@media screen and (min-width: 566px) { + #key { + left: 10px; + } +} + +@media screen and (max-width: 565px) { + #key { + position: fixed; + text-align: left; + bottom: calc(10px + 2em); + right: 10px; + } +} diff --git a/public/editor.html b/public/editor.html index a660dea..25ebc21 100644 --- a/public/editor.html +++ b/public/editor.html @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/public/js/base.js b/public/js/base.js index f511225..c37cbb8 100644 --- a/public/js/base.js +++ b/public/js/base.js @@ -38,7 +38,7 @@ function sha256(ascii) { } ascii += '\x80' - while (ascii["length"]%64 - 56) ascii += '\x00' + while (ascii["length"] % 64 - 56) ascii += '\x00' for (i = 0; i < ascii["length"]; i++) { j = ascii.charCodeAt(i); if (j >> 8) return; @@ -89,12 +89,79 @@ function escapeHTML(text, forInput=false) { .replaceAll("\"", """); } +const socialRegex = { + discord: { + regex: /^(?!.*\.\.)[a-z0-9_.]{2,32}$/, + link: null, + prefix: "", + name: "Discord" + }, + facebook: { + regex: /^([a-z0-9].*){1,50}$/i, + link: "https://www.facebook.com/%q", + prefix: "", + name: "Facebook" + }, + github: { + regex: /^(?!.*--)[a-z0-9](?:[a-z0-9-]{0,37}[a-z0-9])?$/i, + link: "https://github.com/%q", + prefix: "", + name: "GitHub" + }, + instagram: { + regex: /^[a-z0-9_.]{1,30}$/i, + link: "https://www.instagram.com/%q/", + prefix: "", + name: "Instagram" + }, + reddit: { + regex: /^[a-z0-9_-]{3,20}$/i, + link: "https://www.reddit.com/u/%q", + prefix: "/u/", + name: "Reddit" + }, + smiggins: { + regex: /^[a-z0-9_-]{1,18}$/, + link: "https://trinkey.pythonanywhere.com/u/%q", + prefix: "@", + name: "Smiggins" + }, + snapchat: { + regex: /^(?=.{3,15}$)[a-z0-9]+(?:[_.-][a-z0-9]+)?$/i, + link: "https://www.snapchat.com/add/%q", + prefix: "", + name: "Snapchat" + }, + tiktok: { + regex: /^[a-z0-9_.]{1,25}$/i, + link: "https://www.tiktok.com/@%q", + prefix: "", + name: "TikTok" + }, + tringl: { + regex: /^[a-z0-9_]{1,24}$/, + link: "https://ngl.pythonanywhere.com/m/%q", + prefix: "@", + name: "TriNGL" + }, + twitch: { + regex: /^[a-z0-9_]{4,25}$/i, + link: "https://www.twitch.tv/%q", + prefix: "", + name: "Twitch" + }, + twitter: { + regex: /^(?!.*twitter)(?!.*admin)[a-z0-9_]{1,15}$/i, + link: "https://twitter.com/%q", + prefix: "@", + name: "Twitter" + } +} + const icons = { 1: '', 2: '', 3: '', 4: '', - add: '', - x: '', - arrow: '' + x: '' } diff --git a/public/js/editor.js b/public/js/editor.js index cbe2a73..d2f7e0f 100644 --- a/public/js/editor.js +++ b/public/js/editor.js @@ -17,7 +17,7 @@ function addToOutput(starting, json, key, title) { for (let i = 0; i < json[key].length; i++) { starting += `
- @@ -32,7 +32,7 @@ function addToOutput(starting, json, key, title) { return starting; } -function add_input(key) { +function add_input(key, override) { let x = document.createElement("div") let q = [...document.querySelectorAll(`#${key} div[id^="${key}-"]`)]; @@ -40,8 +40,8 @@ function add_input(key) { x.id = `${key}-${i}`; x.setAttribute("data-id", i); - x.innerHTML = ` - @@ -49,7 +49,7 @@ function add_input(key) { - ${icons.x}`; + ${icons.x}`).replaceAll("%i", i); dom(key).append(x); } @@ -61,11 +61,25 @@ function updateColors() { 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]); + if (!val.classList.contains("bad")) { + output.push([val.querySelector("input").value, val.querySelector("select").value]); + } }); return output; } +function validate_input(el) { + el = document.querySelector(`#${el.dataset.id} input`); + platform = document.querySelector(`#${el.dataset.id} select`).value; + value = el.value; + + if (socialRegex[platform].regex.test(value)) { + el.classList.remove("bad"); + } else { + el.classList.add("bad"); + } +} + if (localStorage.getItem("token")) { setCookie("token", localStorage.getItem("token")); } else { @@ -74,6 +88,12 @@ if (localStorage.getItem("token")) { let colors, c; +let socialInput = "
${icons.x}`; + fetch("/api/account/self", { "method": "GET" }).then((response) => (response.json())) @@ -102,7 +122,14 @@ fetch("/api/account/self", { inner = addToOutput(inner, json, "compliments", "Compliments"); inner = addToOutput(inner, json, "relationship", "Relationship
Descriptions"); - inner += ""; + inner += `

Social Links

` + let i = 0; + for (const link of (json.social || [])) { + inner += `
${socialInput.split("${icons.x}
`; + i++; + } + + inner += `
`; x.id = "container"; x.innerHTML = inner; @@ -129,6 +156,7 @@ fetch("/api/account/self", { honorifics: get_list("honorifics"), compliments: get_list("compliments"), relationship: get_list("relationship"), + social: get_list("social"), public: dom("public").checked }) }).then((response) => (response.text())) @@ -160,5 +188,5 @@ fetch("/api/account/self", { }); }) .catch((err) => { - window.location.href = "/logout"; + document.body.innerHTML = `Something went wrong loading the page! Maybe try reloading?
Error: ${err}`; }); diff --git a/public/js/login.js b/public/js/login.js index d01a48f..5a71f00 100644 --- a/public/js/login.js +++ b/public/js/login.js @@ -51,4 +51,4 @@ dom("submit").addEventListener("click", function() { 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 index 797772d..1e2dcf8 100644 --- a/public/js/signup.js +++ b/public/js/signup.js @@ -15,9 +15,15 @@ dom("toggle-password").addEventListener("click", function() { }); dom("submit").addEventListener("click", function() { - this.setAttribute("disabled", ""); username = dom("username").value; - password = sha256(dom("password").value) + password = sha256(dom("password").value); + + if (sha256(dom("confirm").value) != password) { + showlog("Passwords don't match!"); + return; + } + + this.setAttribute("disabled", ""); fetch("/api/account/signup", { method: "POST", headers: { @@ -45,4 +51,4 @@ dom("submit").addEventListener("click", function() { 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 deleted file mode 100644 index 435c292..0000000 --- a/public/js/user.js +++ /dev/null @@ -1,44 +0,0 @@ -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].toLowerCase(), { - "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.title = "User not found - InfoPage" - document.body.innerHTML = "

User not found!

Sign up - Log in"; - }); diff --git a/public/signup.html b/public/signup.html index 0d8947b..942aee0 100644 --- a/public/signup.html +++ b/public/signup.html @@ -19,7 +19,8 @@

Sign Up


-

+
+





Log in instead... diff --git a/public/user.html b/public/user.html index b32d4af..cdf4d66 100644 --- a/public/user.html +++ b/public/user.html @@ -12,5 +12,23 @@ {{TEMPLATE}} + +
+ Key:
+ - Great
+ - Good
+ - Okay
+ - Bad +
+ + + +