mirror of
https://git.ari.lt/ari.lt/ari.lt.git
synced 2025-02-04 09:39:25 +01:00
final beta
Signed-off-by: Ari Archer <ari@ari.lt>
This commit is contained in:
parent
3c218cd957
commit
dd15783241
15 changed files with 544 additions and 69 deletions
5
README.md
Normal file
5
README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# New era of ari.lt
|
||||
|
||||
Ari.lt has become quite messy over the years, so this is meant to replace the website.
|
||||
|
||||
It is still a work in progress, but it's better than a mess.
|
|
@ -1,2 +1,6 @@
|
|||
# export DB='mysql+pymysql://user:password@127.0.0.1/arilt?charset=utf8mb4'
|
||||
export DB='sqlite:///arilt.db'
|
||||
export EMAIL_USER='someone@ari.lt'
|
||||
export EMAIL_SERVER='mail.ari.lt'
|
||||
export EMAIL_PASSWORD='...'
|
||||
export ADMIN_KEY='...'
|
||||
|
|
|
@ -3,3 +3,8 @@ types-flask
|
|||
flask-sqlalchemy
|
||||
flask-ishuman
|
||||
crc4
|
||||
validators
|
||||
markdown
|
||||
jinja2
|
||||
MarkupSafe
|
||||
bleach
|
||||
|
|
|
@ -3,18 +3,20 @@
|
|||
"""ari.lt"""
|
||||
|
||||
import datetime
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
from base64 import b64encode
|
||||
from typing import Any
|
||||
|
||||
import flask
|
||||
|
||||
from . import util
|
||||
|
||||
|
||||
def create_app(name: str) -> flask.Flask:
|
||||
"""create ari.lt app"""
|
||||
|
||||
for var in ("DB",):
|
||||
for var in ("DB", "EMAIL_USER", "EMAIL_SERVER", "EMAIL_PASSWORD"):
|
||||
if var not in os.environ:
|
||||
print(f"Environment variable {var} is unset.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
@ -41,19 +43,17 @@ def create_app(name: str) -> flask.Flask:
|
|||
|
||||
app.config["USE_SESSION_FOR_NEXT"] = True
|
||||
|
||||
from .models import Admin, db
|
||||
from .models import Counter, db
|
||||
|
||||
with app.app_context():
|
||||
db.init_app(app)
|
||||
db.create_all()
|
||||
|
||||
# if db.session.query(Admin).count() < 1:
|
||||
# print("Creating an admin account...")
|
||||
if db.session.query(Counter).count() < 1:
|
||||
print("Creating a website counter...")
|
||||
db.session.add(Counter(int(input("Count: "))))
|
||||
|
||||
# full_name: str = input("Full name: ")
|
||||
# email: str = input("Email: ")
|
||||
# salt: bytes = os.urandom(64)
|
||||
# pwhash: bytes = hashlib.sha3_512(salt + input("Password: ")).digest()
|
||||
db.session.commit()
|
||||
|
||||
from .views import views
|
||||
|
||||
|
@ -63,6 +63,8 @@ def create_app(name: str) -> flask.Flask:
|
|||
|
||||
c.init_app(app)
|
||||
|
||||
app.jinja_env.filters["markdown"] = util.markdown_to_html
|
||||
|
||||
@app.context_processor # type: ignore
|
||||
def _() -> Any:
|
||||
"""Context processor"""
|
||||
|
@ -75,6 +77,7 @@ def create_app(name: str) -> flask.Flask:
|
|||
"programming_exp": y - 2016,
|
||||
"python_exp": y - 2016,
|
||||
"c_exp": y - 2020,
|
||||
"b64encode": b64encode,
|
||||
}
|
||||
|
||||
return app
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Constants"""
|
||||
|
||||
from typing import Final
|
||||
from typing import Dict, Final, FrozenSet
|
||||
|
||||
HUGEINT_MAX: Final[int] = (10**65) - 1
|
||||
|
||||
|
@ -12,3 +12,19 @@ NAME_SIZE: Final[int] = 256
|
|||
WEBSITE_SIZE: Final[int] = 256
|
||||
EMAIL_CT_SIZE: Final[int] = 256
|
||||
COMMENT_SIZE: Final[int] = 1024
|
||||
|
||||
ALLOWED_TAGS: Final[FrozenSet[str]] = frozenset(
|
||||
(
|
||||
"em",
|
||||
"strong",
|
||||
"a",
|
||||
"code",
|
||||
"pre",
|
||||
"blockquote",
|
||||
"p",
|
||||
"ul",
|
||||
"ol",
|
||||
"li",
|
||||
"br",
|
||||
)
|
||||
)
|
||||
|
|
27
src/aw/email.py
Normal file
27
src/aw/email.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""email"""
|
||||
|
||||
import os
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
|
||||
def sendmail(to: str, subject: str, content: str) -> None:
|
||||
"""send mail to an address"""
|
||||
|
||||
msg: MIMEText = MIMEText(content)
|
||||
|
||||
msg["Subject"] = f"[Ari-web] {subject}"
|
||||
msg["From"] = os.environ["EMAIL_USER"]
|
||||
msg["To"] = to
|
||||
|
||||
server: smtplib.SMTP = smtplib.SMTP(os.environ["EMAIL_SERVER"], 587)
|
||||
|
||||
server.ehlo()
|
||||
server.starttls()
|
||||
|
||||
server.login(os.environ["EMAIL_USER"], os.environ["EMAIL_PASSWORD"])
|
||||
|
||||
server.send_message(msg)
|
||||
server.quit()
|
|
@ -68,6 +68,17 @@ class Counter(db.Model):
|
|||
assert count >= 0 and count <= const.HUGEINT_MAX, "count out of range"
|
||||
self.count: int = count
|
||||
|
||||
def inc(self) -> "Counter":
|
||||
"""increment and return self"""
|
||||
self.count += 1
|
||||
db.session.commit()
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def first() -> "Counter":
|
||||
"""get counter"""
|
||||
return db.session.query(Counter).first() # type: ignore
|
||||
|
||||
|
||||
class Comment(db.Model):
|
||||
"""Comment"""
|
||||
|
@ -102,7 +113,7 @@ class Comment(db.Model):
|
|||
self.website: t.Optional[str] = website
|
||||
|
||||
self.key: bytes = rand.randbytes(32)
|
||||
self.email_ct: bytes = crc4.rc4(self.email.encode(), self.key) # type: ignore
|
||||
self.email_ct: bytes = crc4.rc4(email.encode(), self.key) # type: ignore
|
||||
|
||||
self.comment: str = comment
|
||||
self.confirmed: bool = False
|
||||
|
|
25
src/aw/util.py
Normal file
25
src/aw/util.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""utilities"""
|
||||
|
||||
import bleach
|
||||
from markdown import markdown
|
||||
from markupsafe import Markup
|
||||
|
||||
from . import const
|
||||
|
||||
|
||||
def markdown_to_html(text: str) -> Markup:
|
||||
"""Convert Markdown text to safe HTML"""
|
||||
|
||||
return Markup(
|
||||
bleach.clean(
|
||||
markdown(text, extensions=("extra", "smarty")),
|
||||
tags=const.ALLOWED_TAGS,
|
||||
attributes={
|
||||
"*": ["href", "title"],
|
||||
"a": ["href"],
|
||||
},
|
||||
protocols={"http", "https"},
|
||||
)
|
||||
)
|
263
src/aw/views.py
263
src/aw/views.py
|
@ -2,22 +2,171 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""views"""
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import typing as t
|
||||
|
||||
import flask
|
||||
import validators
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
from .routing import Bp
|
||||
from . import email, models
|
||||
from .c import c
|
||||
from .routing import Bp
|
||||
|
||||
views: Bp = Bp("views", __name__)
|
||||
status: t.Dict[str, t.Any] = {
|
||||
"status": "<i>No status</i>",
|
||||
"last_updated": datetime.datetime.now(datetime.timezone.utc),
|
||||
}
|
||||
|
||||
|
||||
@views.get("/status")
|
||||
def get_status() -> t.Any:
|
||||
"""Get status"""
|
||||
return flask.jsonify( # type: ignore
|
||||
{
|
||||
"status": status["status"],
|
||||
"last_updated": status["last_updated"].timestamp(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@views.post("/status")
|
||||
def set_status() -> Response:
|
||||
"""Set status"""
|
||||
|
||||
if (
|
||||
"status" in flask.request.form
|
||||
and flask.request.headers.get("X-Admin-Key", None) == os.environ["ADMIN_KEY"]
|
||||
):
|
||||
status["status"] = str(flask.request.form["status"]) # type: ignore
|
||||
status["last_updated"] = datetime.datetime.now(datetime.timezone.utc)
|
||||
return flask.jsonify( # type: ignore
|
||||
{
|
||||
"status": status["status"],
|
||||
"last_updated": status["last_updated"].timestamp(),
|
||||
}
|
||||
)
|
||||
else:
|
||||
flask.abort(401)
|
||||
|
||||
|
||||
@views.get("/index.html", alias=True)
|
||||
@views.get("/")
|
||||
def index() -> str:
|
||||
"""Home page"""
|
||||
return flask.render_template("index.j2")
|
||||
|
||||
return flask.render_template(
|
||||
"index.j2",
|
||||
visitor=models.Counter.first().inc().count,
|
||||
comments=models.Comment.query.filter_by(confirmed=True).order_by(
|
||||
models.Comment.posted.desc() # type: ignore
|
||||
),
|
||||
status=status,
|
||||
)
|
||||
|
||||
|
||||
@views.get("/confirm/<int:comment_id>/<string:token>/", alias=True)
|
||||
@views.get("/confirm/<int:comment_id>/<string:token>")
|
||||
def confirm(comment_id: int, token: str):
|
||||
"""confirm publishing of a comment"""
|
||||
|
||||
comment: models.Comment = models.Comment.query.filter_by(
|
||||
id=comment_id, token=token, confirmed=False
|
||||
).first_or_404()
|
||||
|
||||
comment.confirmed = True
|
||||
|
||||
models.db.session.commit()
|
||||
|
||||
email.sendmail(
|
||||
"ari@ari.lt",
|
||||
f"Comment #{comment.id} on the guestbook",
|
||||
f"""New comment on the guestbook for you to check out.
|
||||
|
||||
|
||||
URL: {flask.url_for("views.index")}#{comment.id}
|
||||
Name: {comment.name}
|
||||
Website: {comment.website}
|
||||
Comment:
|
||||
|
||||
```
|
||||
{comment.comment}
|
||||
```""",
|
||||
)
|
||||
|
||||
flask.flash(f"Comment #{comment_id} confirmed.")
|
||||
|
||||
return flask.redirect(flask.url_for("views.index"))
|
||||
|
||||
|
||||
@views.post("/")
|
||||
def comment():
|
||||
"""publish a comment"""
|
||||
|
||||
for field in "name", "email", "comment", "code":
|
||||
if field not in flask.request.form:
|
||||
flask.abort(400)
|
||||
|
||||
if not c.verify(flask.request.form["code"]): # type: ignore
|
||||
flask.abort(403)
|
||||
|
||||
if not validators.email(flask.request.form["email"]):
|
||||
flask.abort(400)
|
||||
|
||||
if (
|
||||
"website" in flask.request.form
|
||||
and flask.request.form["website"]
|
||||
and not validators.url(flask.request.form["website"])
|
||||
):
|
||||
flask.abort(400)
|
||||
|
||||
try:
|
||||
comment: models.Comment = models.Comment(
|
||||
flask.request.form["name"], # type: ignore
|
||||
flask.request.form.get("website", None), # type: ignore
|
||||
flask.request.form["email"], # type: ignore
|
||||
flask.request.form["comment"], # type: ignore
|
||||
)
|
||||
except Exception:
|
||||
flask.abort(400)
|
||||
|
||||
models.db.session.add(comment)
|
||||
models.db.session.commit()
|
||||
|
||||
try:
|
||||
email.sendmail(
|
||||
flask.request.form["email"], # type: ignore
|
||||
f"Email confirmation for guestbook comment #{comment.id}",
|
||||
f"""Hello!
|
||||
|
||||
Someone (or you) have commented on the https://ari.lt/ guestbook. If it was you, please confirm your email address below. Else - you may ignore it.
|
||||
|
||||
The comment is:
|
||||
|
||||
Name: {comment.name}
|
||||
Website: {comment.website or "<none>"}
|
||||
Comment:
|
||||
|
||||
```
|
||||
{comment.comment}
|
||||
```
|
||||
|
||||
Visit the following URL to *confirm* your email:
|
||||
|
||||
{flask.request.url.rstrip("/")}{flask.url_for("views.confirm", comment_id=comment.id, token=comment.token)}
|
||||
|
||||
...Or paste it into your browser.""",
|
||||
)
|
||||
except Exception:
|
||||
models.db.session.delete(comment)
|
||||
models.db.session.commit()
|
||||
flask.abort(400)
|
||||
|
||||
flask.flash("Check your mailbox.")
|
||||
|
||||
return flask.redirect(flask.url_for("views.index"))
|
||||
|
||||
|
||||
@views.get("/manifest.json")
|
||||
|
@ -56,14 +205,6 @@ def license() -> flask.Response:
|
|||
with open("LICENSE", "r") as fp:
|
||||
return flask.Response(fp.read(), mimetype="text/plain")
|
||||
|
||||
@views.post("/")
|
||||
def comment() -> flask.Response:
|
||||
"""publish a comment"""
|
||||
return flask.Response(
|
||||
c.new().rawpng(),
|
||||
mimetype="image/png"
|
||||
)
|
||||
|
||||
|
||||
@views.get("/git", defaults={"_": ""})
|
||||
@views.get("/git/", defaults={"_": ""})
|
||||
|
@ -71,7 +212,7 @@ def comment() -> flask.Response:
|
|||
def git(_: str) -> Response:
|
||||
"""Git source code"""
|
||||
return flask.redirect(
|
||||
f"https://ari.lt/lh/us.ari.lt/{flask.request.full_path[4:]}",
|
||||
f"/lh/ari.lt/{flask.request.full_path[4:]}",
|
||||
code=302,
|
||||
)
|
||||
|
||||
|
@ -87,32 +228,118 @@ def favicon() -> Response:
|
|||
)
|
||||
)
|
||||
|
||||
|
||||
@views.get("/captcha.png")
|
||||
def captcha() -> flask.Response:
|
||||
"""CAPTCHA"""
|
||||
return flask.Response(
|
||||
c.new().rawpng(),
|
||||
mimetype="image/png"
|
||||
)
|
||||
return flask.Response(c.new().rawpng(), mimetype="image/png")
|
||||
|
||||
|
||||
@views.get("/badge.png")
|
||||
def badge() -> Response:
|
||||
"""Website badge"""
|
||||
return flask.redirect(
|
||||
r: Response = flask.redirect(
|
||||
flask.url_for(
|
||||
"static",
|
||||
filename="badges/badge.png",
|
||||
)
|
||||
)
|
||||
|
||||
r.headers["Access-Control-Allow-Origin"] = "*"
|
||||
r.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS, HEAD"
|
||||
|
||||
return r
|
||||
|
||||
|
||||
@views.get("/badge-yellow.png")
|
||||
def badge_yellow() -> Response:
|
||||
"""Website badge (yellow)"""
|
||||
return flask.redirect(
|
||||
"""Website badge"""
|
||||
r: Response = flask.redirect(
|
||||
flask.url_for(
|
||||
"static",
|
||||
filename="badges/badge-yellow.png",
|
||||
)
|
||||
)
|
||||
|
||||
r.headers["Access-Control-Allow-Origin"] = "*"
|
||||
r.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS, HEAD"
|
||||
|
||||
return r
|
||||
|
||||
|
||||
@views.get("/btc")
|
||||
def btc() -> Response:
|
||||
"""Bitcoin address"""
|
||||
return flask.redirect(
|
||||
"https://www.blockchain.com/explorer/addresses/btc/bc1qn3k75kmyvpw9sc58t63hk4ej4pc0d0w52tvj7w"
|
||||
)
|
||||
|
||||
|
||||
@views.get("/xmr")
|
||||
def xmr() -> Response:
|
||||
"""Monero address"""
|
||||
return flask.redirect(
|
||||
"https://moneroexplorer.org/search?value=451VZy8FPDXCVvKWkq5cby3V24ApLnjaTdwDgKG11uqbUJYjxQWZVKiiefi4HvFd7haeUtGFRBaxgKNTr3vR78pkMzgJaAZ"
|
||||
)
|
||||
|
||||
|
||||
@views.get("/page/canary", alias=True)
|
||||
@views.get("/canary")
|
||||
def canary():
|
||||
"""Warrant Canary"""
|
||||
return "Unavailable due to migration reasons."
|
||||
|
||||
|
||||
@views.get("/page/casey", alias=True)
|
||||
@views.get("/casey")
|
||||
def casey():
|
||||
"""Open letter to my best friend"""
|
||||
return "Unavailable due to migration reasons."
|
||||
|
||||
|
||||
@views.get("/page/matrix", alias=True)
|
||||
@views.get("/matrix")
|
||||
def matrix():
|
||||
"""Matrix homeserver guidelines and Registration"""
|
||||
return "Unavailable due to migration reasons."
|
||||
|
||||
|
||||
@views.get("/mp")
|
||||
def mp():
|
||||
"""Music playlist"""
|
||||
return flask.redirect(
|
||||
"https://www.youtube.com/playlist?list=PL7UuKajElTaChff3BkcJE6620lSuSUaDC"
|
||||
)
|
||||
|
||||
|
||||
@views.get("/dotfiles", defaults={"_": ""})
|
||||
@views.get("/dotfiles/", defaults={"_": ""})
|
||||
@views.get("/dotfiles/<path:_>")
|
||||
def dotfiles(_: str) -> Response:
|
||||
"""Dotfiles"""
|
||||
return flask.redirect(
|
||||
f"https://github.com/TruncatedDinoSour/dotfiles-cleaned/{flask.request.full_path[9:]}",
|
||||
code=302,
|
||||
)
|
||||
|
||||
|
||||
@views.get("/gh", defaults={"_": ""})
|
||||
@views.get("/gh/", defaults={"_": ""})
|
||||
@views.get("/gh/<path:_>")
|
||||
def gh(_: str) -> Response:
|
||||
"""Main git account"""
|
||||
return flask.redirect(
|
||||
f"https://github.com/TruncatedDinoSour/{flask.request.full_path[3:]}",
|
||||
code=302,
|
||||
)
|
||||
|
||||
|
||||
@views.get("/lh", defaults={"_": ""})
|
||||
@views.get("/lh/", defaults={"_": ""})
|
||||
@views.get("/lh/<path:_>")
|
||||
def lh(_: str) -> Response:
|
||||
"""Main git organization account"""
|
||||
return flask.redirect(
|
||||
f"https://github.com/ari-lt/{flask.request.full_path[3:]}",
|
||||
code=302,
|
||||
)
|
||||
|
|
|
@ -94,7 +94,7 @@ li {
|
|||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
p, table, table * {
|
||||
table, table * {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,28 +5,41 @@
|
|||
}
|
||||
|
||||
.split {
|
||||
display: -ms-grid;
|
||||
display: grid;
|
||||
-ms-grid-columns: 3fr 0.5em 1fr;
|
||||
grid-template-columns: 3fr 1fr;
|
||||
grid-gap: 0.5em;
|
||||
-webkit-box-align: stretch;
|
||||
-ms-flex-align: stretch;
|
||||
align-items: stretch;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.split > * {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
padding: 2em;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.esplit {
|
||||
-ms-grid-columns: 1fr 1fr;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.split > :first-child {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.split > :first-child::before {
|
||||
|
@ -37,6 +50,7 @@
|
|||
top: 0;
|
||||
bottom: 0;
|
||||
border-right: 2px solid var(--red);
|
||||
-webkit-animation: grow 0.5s forwards;
|
||||
animation: grow 0.5s forwards;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
@ -68,9 +82,11 @@ form > button {
|
|||
}
|
||||
|
||||
.form-group {
|
||||
display: -ms-grid;
|
||||
display: grid;
|
||||
-ms-grid-columns: 8rem 1rem auto;
|
||||
grid-template-columns: 8em auto;
|
||||
grid-gap: 1em;
|
||||
grid-gap: 1rem;
|
||||
margin: 0.4em;
|
||||
}
|
||||
|
||||
|
@ -86,6 +102,7 @@ form > button {
|
|||
}
|
||||
|
||||
.captcha {
|
||||
display: -ms-grid;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin: 0.5em;
|
||||
|
@ -100,36 +117,59 @@ form > button {
|
|||
background-color: #111;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#comments > div button {
|
||||
border: none;
|
||||
background-color: #000;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--red);
|
||||
@-webkit-keyframes grow {
|
||||
from {
|
||||
-webkit-transform: scaleY(0);
|
||||
transform: scaleY(0);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: scaleY(1);
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes grow {
|
||||
from {
|
||||
-webkit-transform: scaleY(0);
|
||||
transform: scaleY(0);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: scaleY(1);
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1250px) {
|
||||
.split {
|
||||
-ms-grid-columns: 1fr;
|
||||
grid-template-columns: 1fr;
|
||||
-ms-grid-rows: auto 0.5em auto;
|
||||
grid-template-rows: auto auto;
|
||||
-webkit-box-align: unset;
|
||||
-ms-flex-align: unset;
|
||||
align-items: unset;
|
||||
}
|
||||
.split > *:nth-child(1) {
|
||||
-ms-grid-row: 1;
|
||||
-ms-grid-column: 1;
|
||||
}
|
||||
.split > *:nth-child(2) {
|
||||
-ms-grid-row: 3;
|
||||
-ms-grid-column: 1;
|
||||
}
|
||||
|
||||
.split > :first-child {
|
||||
-webkit-box-ordinal-group: 3;
|
||||
-ms-flex-order: 2;
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.split > :last-child {
|
||||
-webkit-box-ordinal-group: 2;
|
||||
-ms-flex-order: 1;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
canvas.height = window.innerHeight;
|
||||
|
||||
let particles = [];
|
||||
const particle_density = 1.5e-5;
|
||||
const particle_density = 2e-5;
|
||||
let num_particles;
|
||||
|
||||
const particle_size = 2;
|
62
src/static/js/rc4.js
Normal file
62
src/static/js/rc4.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* @licstart The following is the entire license notice for the JavaScript code in this file.
|
||||
*
|
||||
* Copyright (C) 2024 Ari Archer
|
||||
*
|
||||
* This file is part of ari.lt.
|
||||
*
|
||||
* The JavaScript code in this file is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* (AGPL) as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version. The code is distributed WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
*
|
||||
* As additional permission under AGPL version 3 section 7, you may distribute non-source
|
||||
* (e.g., minimized or compacted) forms of that code without the copy of the AGPL normally
|
||||
* required by section 4, provided you include this license notice and a URL through which
|
||||
* recipients can access the Corresponding Source.
|
||||
*
|
||||
* @licend The above is the entire license notice for the JavaScript code in this file.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
function rc4(b64_ct, b64_key) {
|
||||
let ciphertext = base64_to_array_buffer(b64_ct);
|
||||
let key = base64_to_array_buffer(b64_key);
|
||||
|
||||
let S = Array(256);
|
||||
let j = 0,
|
||||
temp;
|
||||
for (let i = 0; i < 256; ++i) S[i] = i;
|
||||
for (let i = 0; i < 256; ++i) {
|
||||
j = (j + S[i] + key[i % key.length]) % 256;
|
||||
temp = S[i];
|
||||
S[i] = S[j];
|
||||
S[j] = temp;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
j = 0;
|
||||
let decrypted = new Uint8Array(ciphertext.length);
|
||||
for (let y = 0; y < ciphertext.length; y++) {
|
||||
i = (i + 1) % 256;
|
||||
j = (j + S[i]) % 256;
|
||||
temp = S[i];
|
||||
S[i] = S[j];
|
||||
S[j] = temp;
|
||||
decrypted[y] = ciphertext[y] ^ S[(S[i] + S[j]) % 256];
|
||||
}
|
||||
|
||||
return new TextDecoder().decode(decrypted);
|
||||
}
|
||||
|
||||
function base64_to_array_buffer(base64) {
|
||||
let bin = window.atob(base64);
|
||||
let bytes = new Uint8Array(bin.length);
|
||||
|
||||
for (let idx = 0; idx < bin.length; ++idx) bytes[idx] = bin.charCodeAt(idx);
|
||||
|
||||
return bytes;
|
||||
}
|
|
@ -36,7 +36,19 @@
|
|||
{% block body %}{% endblock %}
|
||||
<article>
|
||||
<header>{% block header %}{% endblock %}</header>
|
||||
<main>{% block main %}{% endblock %}</main>
|
||||
<main>
|
||||
{% with messages = get_flashed_messages(with_categories=True) %}
|
||||
{% if messages %}
|
||||
<details open>
|
||||
<summary>messages from the server</summary>
|
||||
{% for category, message in messages %}
|
||||
<div data-category="{{ category }}">{{ message | escape }}</div>
|
||||
{% endfor %}
|
||||
</details>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block main %}{% endblock %}
|
||||
</main>
|
||||
<footer>
|
||||
<i>The <a target="_blank" href="{{ url_for('views.git') }}">source code</a> and all content, except the Nerd Hack font (see <a target="_blank" href="{{ url_for("static", filename="fonts/LICENSE") }}">Nerd Hack font license</a>), are licensed under the <a target="_blank" href="https://www.gnu.org/licenses/agpl-3.0.en.html">AGPL-3.0-or-later</a> by Ari Archer <<a target="_blank" href="mailto:ari@ari.lt">ari@ari.lt</a>> as a part of the <a href="{{ url_for("views.badge") }}" target="_blank">ari-web</a> project. Copyright 2019-{{ current_year }}.</i>
|
||||
</footer>
|
||||
|
|
|
@ -38,7 +38,8 @@
|
|||
|
||||
//--><!]]>
|
||||
</script>
|
||||
<script src="{{ url_for("static", filename="js/index.js") }}" referrerpolicy="no-referrer"></script>
|
||||
<script src="{{ url_for("static", filename="js/particles.js") }}" referrerpolicy="no-referrer"></script>
|
||||
<script src="{{ url_for("static", filename="js/rc4.js") }}" referrerpolicy="no-referrer"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
@ -54,6 +55,7 @@
|
|||
<summary>Navigation</summary>
|
||||
|
||||
<ul>
|
||||
<li>Pages and redirects: <a href="#pages">#pages</a></li>
|
||||
<li>Links: <a href="#links">#links</a></li>
|
||||
<li>Staff list: <a href="#staff">#staff</a></li>
|
||||
<li>Self-hosted services list: <a href="#services">#services</a></li>
|
||||
|
@ -72,7 +74,7 @@
|
|||
<blockquote>
|
||||
Before I start explaining who I am, if you're here on this page because of one of the users
|
||||
using ari-web services and you want to report abuse, please contact me using one of the
|
||||
contacts listed on this page (such as the <a href="mailto:ari@ari.lt" target="_blank">ari@ari.lt</a> email)
|
||||
contacts listed on this page (such as the <a href="mailto:ari@ari.lt">ari@ari.lt</a> email)
|
||||
or see <a href="#staff">the list of staff</a>. You may also leave a comment on <a href="#gb">the guestbook</a>.
|
||||
</blockquote>
|
||||
|
||||
|
@ -155,7 +157,7 @@
|
|||
<li>GitHub organization: <a target="_blank" href="https://github.com/ari-lt">ari-lt</a> (<a href="mailto:org@ari.lt">org@ari.lt</a>)</li>
|
||||
<li>Matrix: <a target="_blank" href="https://matrix.to/#/@ari:ari.lt">@ari:ari.lt</a></li>
|
||||
<li>Fediverse: <a target="_blank" href="https://ak.ari.lt/ari">@ari@ak.ari.lt</a></li>
|
||||
<li>E-Mail: <a target="_blank" href="mailto:ari@ari.lt">ari@ari.lt</a> (GPG: <a target="_blank" href="https://keys.openpgp.org/vks/v1/by-fingerprint/4FAD63E936B305906A6C4894A50D5B4B599AF8A2">4FAD63E936B305906A6C4894A50D5B4B599AF8A2</a>)</li>
|
||||
<li>E-Mail: <a href="mailto:ari@ari.lt">ari@ari.lt</a> (GPG: <a target="_blank" href="https://keys.openpgp.org/vks/v1/by-fingerprint/4FAD63E936B305906A6C4894A50D5B4B599AF8A2">4FAD63E936B305906A6C4894A50D5B4B599AF8A2</a>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -171,14 +173,42 @@
|
|||
this is a good introduction to what I do, what skills I posses.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Current status:
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<blockquote><pre>This is a status
|
||||
Hello world</pre>
|
||||
Last updated: 2024-06-06 00:17:11 UTC</blockquote>
|
||||
<div><b>*** Current status ***</b></div>
|
||||
<br />
|
||||
<div>{{ status["status"] }}</div>
|
||||
<br />
|
||||
<div>Last updated: <b>{{ status["last_updated"] }}</b></div>
|
||||
</div>
|
||||
|
||||
<h2 id="pages"><a href="#pages">#</a> Pages and redirects</h2>
|
||||
|
||||
<div class="split esplit">
|
||||
<div>
|
||||
<ul>
|
||||
<li><a href="/">The home page of Ari.lt</a></li>
|
||||
<li><a href="{{ url_for("views.canary") }}">Warrant canary of Ari.lt</a></li>
|
||||
<li><a href="{{ url_for("views.casey") }}">Open letter to my best friend, Casey</a></li>
|
||||
<li><a href="{{ url_for("views.matrix") }}">Matrix homeserver guidelines and registration</a></li>
|
||||
<li><a target="_blank" href="{{ url_for("views.mp") }}">Music playlist (YouTube)</a></li>
|
||||
<li><a target="_blank" href="{{ url_for("views.dotfiles") }}">My dotfiles (redirect)</a></li>
|
||||
<li><a target="_blank" href="{{ url_for("views.gh") }}">My main Git account (redirect)</a></li>
|
||||
<li><a target="_blank" href="{{ url_for("views.lh") }}">Main Ari.lt organization (redirect)</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
Ari.lt includes multiple pages and redirects which include various information. This is a manually collected list
|
||||
of some important pages. If you want a <i>full</i> list of all pages see the <a href="/sitemap.xml">sitemap.xml</a>
|
||||
which has all possible pages on this site in an XML format - mainly used by indexers.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Ari.lt is still in the middle of migration to full-featured self-hosting, so some stuff might break
|
||||
or change with time, although I try to keep backwards compatibility possible.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="links"><a href="#links">#</a> Links</h2>
|
||||
|
@ -194,10 +224,17 @@ Last updated: 2024-06-06 00:17:11 UTC</blockquote>
|
|||
<li>GitHub organization: <a target="_blank" href="https://github.com/ari-lt">ari-lt</a> (<a href="mailto:org@ari.lt">org@ari.lt</a>)</li>
|
||||
<li>Matrix: <a target="_blank" href="https://matrix.to/#/@ari:ari.lt">@ari:ari.lt</a></li>
|
||||
<li>Fediverse: <a target="_blank" href="https://ak.ari.lt/ari">@ari@ak.ari.lt</a></li>
|
||||
<li>E-Mail: <a target="_blank" href="mailto:ari@ari.lt">ari@ari.lt</a> (GPG: <a target="_blank" href="https://keys.openpgp.org/vks/v1/by-fingerprint/4FAD63E936B305906A6C4894A50D5B4B599AF8A2">4FAD63E936B305906A6C4894A50D5B4B599AF8A2</a>)</li>
|
||||
<li>E-Mail: <a href="mailto:ari@ari.lt">ari@ari.lt</a> (GPG: <a target="_blank" href="https://keys.openpgp.org/vks/v1/by-fingerprint/4FAD63E936B305906A6C4894A50D5B4B599AF8A2">4FAD63E936B305906A6C4894A50D5B4B599AF8A2</a>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3>Badges</h3>
|
||||
|
||||
<ul>
|
||||
<li>Normal: <a href="https://ari.lt/"> <img src="{{ url_for("views.badge") }}" loading="lazy" alt="ari-web badge" height="31px" width="88px" /> </a></li>
|
||||
<li>Yellow: <a href="https://ari.lt/"> <img src="{{ url_for("views.badge_yellow") }}" loading="lazy" alt="ari-web badge" height="31px" width="88px" /> </a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Activity</h3>
|
||||
|
||||
<ul>
|
||||
|
@ -223,6 +260,13 @@ Last updated: 2024-06-06 00:17:11 UTC</blockquote>
|
|||
sometimes.
|
||||
</p>
|
||||
|
||||
<h3>Support</h3>
|
||||
|
||||
<ul>
|
||||
<li>Monero wallet address: <a target="_blank" href="{{ url_for("views.xmr") }}">{{ url_for("views.xmr") | escape }}</a></li>
|
||||
<li>Bitcoin wallet address: <a target="_blank" href="{{ url_for("views.btc") }}">{{ url_for("views.btc") | escape }}</a></li>
|
||||
</ul>
|
||||
|
||||
<h2 id="staff"><a href="#staff">#</a> Staff</h2>
|
||||
|
||||
<p>
|
||||
|
@ -306,7 +350,7 @@ Last updated: 2024-06-06 00:17:11 UTC</blockquote>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>Email server hosting <a href="https://mailcow.email/" target="_blank">Mailcow</a></td>
|
||||
<td>Contact <a href="mailto:ari@ari.lt" target="_blank">ari@ari.lt</a> for custom domains (aggressive policy)</td>
|
||||
<td>Contact <a href="mailto:ari@ari.lt">ari@ari.lt</a> for custom domains (aggressive policy)</td>
|
||||
<td><a href="https://mail.ari.lt/" target="_blank">mail.ari.lt</a> (<a href="https://mail.ari.lt/signup" target="_blank">register here</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -355,10 +399,11 @@ Last updated: 2024-06-06 00:17:11 UTC</blockquote>
|
|||
<h2 id="gb"><a href="#gb">#</a> Guestbook</h2>
|
||||
|
||||
<p>
|
||||
If you want to interact with the community feel free to leave your comments here! You will require an
|
||||
email address to prevent spam and impersonation, it will be listed, although not as text as it will
|
||||
be encrypted server-side using <a target="_blank" href="https://en.wikipedia.org/wiki/RC4">RC4</a> (a fast, but insecure cipher)
|
||||
with a 32-byte (256 bit) key. Check your mailbox when you comment as you will need to verify the comment.
|
||||
If you want to interact with the community feel free to leave your comments here!
|
||||
You will require an email address so to prevent spam and scams, although, to protect
|
||||
<i>you</i> from spam and scam emails, I have implemented measures to not store your
|
||||
email address as plain text. Press 'show email' to show the email address on any given
|
||||
comment.
|
||||
</p>
|
||||
|
||||
<form method="post">
|
||||
|
@ -378,8 +423,8 @@ Last updated: 2024-06-06 00:17:11 UTC</blockquote>
|
|||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="comment">Comment:</label>
|
||||
<textarea required type="text" id="comment" name="comment" placeholder="Hello!"></textarea>
|
||||
<label for="comment">Comment (<a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" target="_blank">markdown</a>):</label>
|
||||
<textarea required type="text" id="comment" name="comment" placeholder="Hello! I like your website - <https://ari.lt/>."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="captcha">
|
||||
|
@ -389,30 +434,23 @@ Last updated: 2024-06-06 00:17:11 UTC</blockquote>
|
|||
|
||||
<div class="form-group">
|
||||
<label for="captcha">CAPTCHA:</label>
|
||||
<input required type="text" id="comment" name="comment" placeholder="Enter the CAPTCHA code above." />
|
||||
<input required type="text" id="code" name="code" placeholder="Enter the CAPTCHA code above." />
|
||||
</div>
|
||||
|
||||
<button type="submit">Comment</button>
|
||||
</form>
|
||||
|
||||
<hr />
|
||||
|
||||
<div align="center">
|
||||
<i>Report any impersonation to the owner of this website: Ari Archer.</i>
|
||||
</div>
|
||||
|
||||
<div id="comments">
|
||||
<div id="guestbook-1">
|
||||
<p><a href="#guestbook-1">#1:</a> Cool Person (<a target="_blank" href="#!">https://example.com/</a>) <<i><button onclick="rc4('data','key')">show email</button></i>> at 2024-06-05 11:11:11 UTC says...</p>
|
||||
<pre>Cool website, but you should kill yourself!
|
||||
Meow!</pre>
|
||||
</div>
|
||||
|
||||
<div id="guestbook-1">
|
||||
<p><a href="#guestbook-1">#1:</a> Cool Person (<a target="_blank" href="#!">https://example.com/</a>) <<i><button onclick="rc4('data','key')">show email</button></i>> at 2024-06-05 11:11:11 UTC says...</p>
|
||||
<pre>Cool website, but you should kill yourself!
|
||||
Meow!</pre>
|
||||
{% for comment in comments %}
|
||||
<div id="gb-{{ comment.id }}">
|
||||
<p><a href="#gb-{{ comment.id }}">#{{ comment.id }}:</a> <span>{{ comment.name | escape }}</span> {% if comment.website %} (<a target="_blank" href="{{ comment.website }}" rel="noopener noreferrer" target="_blank">{{ comment.website | escape }}</a>) {% endif %} <<i><a href="mailto:" style="color:#aaa" onclick="this.innerText=rc4('{{ b64encode(comment.email_ct).decode() }}','{{ b64encode(comment.key).decode() }}');this.href+=this.innerText;this.onclick=null;this.style='';return false">show email</a></i>> at <time>{{ comment.posted }} UTC</time> says...</p>
|
||||
<div>{{ comment.comment | markdown }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
Loading…
Add table
Reference in a new issue