feat: Add nostr

This commit is contained in:
Nathan Woodburn 2024-03-27 16:59:22 +11:00
parent 754aada478
commit 1b6536d8ae
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
12 changed files with 331 additions and 5 deletions

139
main.py
View File

@ -8,6 +8,7 @@ import render
import secrets
import nginx
import threading
import nostr as nostr_module
app = Flask(__name__)
dotenv.load_dotenv()
@ -109,7 +110,8 @@ def site():
"btn_bg": "#2c54cf",
"btn_fg": "#ffffff",
"socials": [],
"address": []
"address": [],
"nostrs": []
}
@ -298,6 +300,104 @@ def publish():
response.set_cookie('auth', '', expires=0)
return response
@app.route('/nostr')
def nostr():
if 'auth' not in request.cookies:
return redirect('/')
auth = request.cookies['auth']
for i in cookies:
if i['cookie'] == auth:
# Load site content
if os.path.isfile(f'sites/{i["name"]}.json'):
with open(f'sites/{i["name"]}.json') as file:
data = json.load(file)
nostr = []
if 'nostr' in data:
nostr = data['nostr']
return render_template('nostr.html',year=datetime.datetime.now().year, domain=i['name'],nostr=nostr)
response = make_response(redirect('/'))
response.set_cookie('auth', '', expires=0)
return response
@app.route('/nostr', methods=['POST'])
def nostr_post():
if 'auth' not in request.cookies:
return redirect('/')
auth = request.cookies['auth']
for i in cookies:
if i['cookie'] == auth:
# Get site content
if os.path.isfile(f'sites/{i["name"]}.json'):
with open(f'sites/{i["name"]}.json') as file:
data = json.load(file)
else:
return redirect('/site')
nostr = []
if 'nostr' in data:
nostr = data['nostr']
# Check for new nostr links
if 'new-name' in request.form and 'new-pub' in request.form:
name = request.form['new-name']
pub = request.form['new-pub']
id = len(nostr)
for link in nostr:
if link['name'] == name:
link['pub'] = pub
data['nostr'] = nostr
with open(f'sites/{i["name"]}.json', 'w') as file:
json.dump(data, file)
return redirect('/nostr')
if link['id'] >= id:
id = link['id'] + 1
nostr.append({'name': name, 'pub': pub, 'id': id})
data['nostr'] = nostr
with open(f'sites/{i["name"]}.json', 'w') as file:
json.dump(data, file)
return redirect('/nostr')
response = make_response(redirect('/'))
response.set_cookie('auth', '', expires=0)
return response
@app.route('/nostr/delete/<int:id>')
def nostr_delete(id):
if 'auth' not in request.cookies:
return redirect('/')
auth = request.cookies['auth']
for i in cookies:
if i['cookie'] == auth:
# Get site content
if os.path.isfile(f'sites/{i["name"]}.json'):
with open(f'sites/{i["name"]}.json') as file:
data = json.load(file)
else:
return redirect('/site')
nostr = []
if 'nostr' in data:
nostr = data['nostr']
nostr = [i for i in nostr if i['id'] != id]
data['nostr'] = nostr
with open(f'sites/{i["name"]}.json', 'w') as file:
json.dump(data, file)
return redirect('/nostr')
response = make_response(redirect('/'))
response.set_cookie('auth', '', expires=0)
return response
@app.route('/.well-known/wallets/<path:path>')
def wallets(path):
# Check if host is in domains
@ -332,6 +432,43 @@ def wallets(path):
response.headers['Content-Type'] = 'text/plain'
return response
return render_template('404.html', year=datetime.datetime.now().year), 404
@app.route('/.well-known/nostr.json')
def nostr_account():
# Check if host is in domains
if request.host in DOMAINS:
# Check if user is logged in
if 'auth' not in request.cookies:
return redirect(f'https://{DOMAINS[0]}')
auth = request.cookies['auth']
for i in cookies:
if i['cookie'] == auth:
# Load site content
if os.path.isfile(f'sites/{i["name"]}.json'):
with open(f'sites/{i["name"]}.json') as file:
data = json.load(file)
if 'nostr' in data:
nostr = data['nostr']
# Return as plain text
response = make_response(nostr_module.json(nostr))
response.headers['Content-Type'] = 'text/plain'
response.headers.add('Access-Control-Allow-Origin', '*')
return response
# Get wallet from domain
host = request.host.split(':')[0]
if os.path.isfile(f'sites/{host}.json'):
with open(f'sites/{host}.json') as file:
data = json.load(file)
if 'nostr' in data:
nostr = data['nostr']
# Return as plain text
response = make_response(nostr_module.json(nostr))
response.headers['Content-Type'] = 'text/plain'
response.headers.add('Access-Control-Allow-Origin', '*')
return response
return render_template('404.html', year=datetime.datetime.now().year), 404
@app.route('/publishing')

29
nostr.py Normal file
View File

@ -0,0 +1,29 @@
from base64 import b32decode
from base64 import b16encode
from bech32 import bech32_decode
from bech32 import bech32_encode
B32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
def add_padding(base32_len):
bits = base32_len * 5
padding_size = (8 - (bits % 8)) % 8
return padding_size
def npub_to_hex(npub):
hrd, data = bech32_decode(npub)
b32_data = [B32[index] for index in data]
data_str = "".join(b32_data)
data_length = len(data_str)
data_str += "=" * add_padding(data_length)
decoded_data = b32decode(data_str)
b16_encoded_data = b16encode(decoded_data)
hex_str = b16_encoded_data.decode("utf-8").lower()
return hex_str
def json(links):
names = {}
for link in links:
names[link['name']] = npub_to_hex(link['pub'])
return {'names':names}

View File

@ -63,7 +63,7 @@ def address_links(addresses,foreground):
html = ''
for address in addresses:
token = address['token'].upper()
html += f'<a href=".well-known/wallets/{token}" target="_blank">{tokenImage(token,foreground)}</a>'
html += f'<a style="margin:5px;" href=".well-known/wallets/{token}" target="_blank">{tokenImage(token,foreground)}</a>'
return html
def tokenImage(token,foreground):

View File

@ -2,4 +2,5 @@ flask
python-dotenv
gunicorn
requests
apscheduler
apscheduler
bech32

View File

@ -5,7 +5,15 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>HNS Links</title>
<meta name="twitter:title" content="HNS Links">
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:title" content="HNS Links">
<meta name="description" content="Create a links page for your domain">
<meta property="og:description" content="Create a links page for your domain">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:description" content="Create a links page for your domain">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">

View File

@ -1 +1 @@
.video-container{position:relative;padding-bottom:56.25%;padding-top:0;height:0;overflow:hidden}.video-container embed,.video-container iframe,.video-container object{position:absolute;top:0;left:0;width:100%;height:100%}.social-icons{color:#313437;background-color:#fff;padding:70px 0}@media (max-width:767px){.social-icons{padding:50px 0}}.social-div{display:flex;justify-content:center;align-items:center}.social-list{max-width:100%;display:flex;list-style:none;gap:2.5rem}@media (max-width:500px){.social-list{gap:1.5rem}}@media (max-width:420px){.social-list{gap:0}}.custom-button{max-width:500px}.addresses{margin-bottom:20px;display:flex;justify-content:center;align-items:center}.social-icons i{color:#757980;margin:0 10px;width:60px;height:60px;border:1px solid #c8ced7;text-align:center;border-radius:50%;line-height:60px;display:inline-block}.social-link a{text-decoration:none;width:4.8rem;height:4.8rem;background-color:#f0f9fe;border-radius:50%;display:flex;justify-content:center;align-items:center;position:relative;z-index:1;border:3px solid #f0f9fe;overflow:hidden}.social-link a::before{content:"";position:absolute;width:100%;height:100%;background:#000;z-index:0;scale:1 0;transform-origin:bottom;transition:scale .5s}.social-link:hover a::before{scale:1 1}.icon{font-size:2rem;color:#011827;transition:.5s;z-index:2}.social-link a:hover .icon{color:#fff;transform:rotateY(360deg)}
.video-container{position:relative;padding-bottom:56.25%;padding-top:0;height:0;overflow:hidden}.video-container embed,.video-container iframe,.video-container object{position:absolute;top:0;left:0;width:100%;height:100%}.nostr{margin-top:1em}.social-icons{color:#313437;background-color:#fff;padding:70px 0}@media (max-width:767px){.social-icons{padding:50px 0}}.social-div{display:flex;justify-content:center;align-items:center}.social-list{max-width:100%;display:flex;list-style:none;gap:2.5rem}@media (max-width:500px){.social-list{gap:1.5rem}}@media (max-width:420px){.social-list{gap:0}}.custom-button{max-width:500px}.addresses{margin-bottom:20px;display:flex;justify-content:center;align-items:center}.social-icons i{color:#757980;margin:0 10px;width:60px;height:60px;border:1px solid #c8ced7;text-align:center;border-radius:50%;line-height:60px;display:inline-block}.social-link a{text-decoration:none;width:4.8rem;height:4.8rem;background-color:#f0f9fe;border-radius:50%;display:flex;justify-content:center;align-items:center;position:relative;z-index:1;border:3px solid #f0f9fe;overflow:hidden}.social-link a::before{content:"";position:absolute;width:100%;height:100%;background:#000;z-index:0;scale:1 0;transform-origin:bottom;transition:scale .5s}.social-link:hover a::before{scale:1 1}.icon{font-size:2rem;color:#011827;transition:.5s;z-index:2}.social-link a:hover .icon{color:#fff;transform:rotateY(360deg)}

View File

@ -5,7 +5,23 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>HNS Links</title>
<meta name="twitter:title" content="HNS Links">
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:title" content="HNS Links">
<meta name="description" content="Create a links page for your domain">
<meta property="og:description" content="Create a links page for your domain">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:description" content="Create a links page for your domain">
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "WebSite",
"name": "HNS Links",
"url": "https://links.hns.au"
}
</script>
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">

111
templates/nostr.html Normal file
View File

@ -0,0 +1,111 @@
<!DOCTYPE html>
<html data-bs-theme="light" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Nostr - HNS Links</title>
<meta name="twitter:title" content="HNS Links">
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:title" content="HNS Links">
<meta name="description" content="Create a links page for your domain">
<meta property="og:description" content="Create a links page for your domain">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:description" content="Create a links page for your domain">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&amp;display=swap">
<link rel="stylesheet" href="/assets/css/styles.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<script async src="https://umami.woodburn.au/script.js" data-website-id="a165bb1c-eef8-4737-8066-fcd8b4a380bd"></script>
</head>
<body>
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/favicon.png" width="45px"><span>&nbsp;HNS Links</span></a><button data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navcol-1"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navcol-1">
<ul class="navbar-nav mx-auto"></ul><a class="btn btn-primary shadow" role="button" href="/preview" style="margin-right: 10px;">Preview</a><a class="btn btn-primary shadow" role="button" href="/logout">Logout</a>
</div>
</div>
</nav>
<header class="bg-dark">
<div class="container pt-4 pt-xl-5">
<div class="row pt-5">
<div class="col-md-8 col-xl-6 text-center text-md-start mx-auto">
<div class="text-center">
<p class="fw-bold text-success mb-2">{{domain}}/</p>
<h1 class="fw-bold">Manage your site</h1>
</div>
</div>
</div>
</div>
</header>
<section>
<div style="padding: 1em;padding-bottom: 25px;max-width: 800px;margin: auto;">
<form method="post" enctype="multipart/form-data">
<h1>Nostr</h1>
<div style="margin-bottom: 25px;">
<h4>Nostr Links</h4>{% for link in nostr %}
<div class='nostr'>
<input class="form-control" type="text" style="display: inline-block;width: auto;" value="{{link.name}}" readonly/>
<input class="form-control" type="text" style="display: inline-block;width: auto;" value="{{link.pub}}" readonly/>
<a class="btn btn-primary" role="button" href="/nostr/delete/{{link.id}}"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor">
<path d="M0 0h24v24H0z" fill="none"></path>
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"></path>
</svg></a>
</div>
{% endfor %}
<div class='nostr'>
<span>Link a new nostr profile</span><br>
<input class="form-control" type="text" style="display: inline-block;width: auto;" name="new-name" placeholder="User Name" />
<input class="form-control" type="text" style="display: inline-block;width: auto;" name="new-pub" placeholder="npub..." />
<br><br>
<input class="btn btn-primary" type="submit" value="Link" style="margin-right: 20px;" />
</div>
</div>
</form>
</div>
</section>
<section></section>
<footer class="bg-dark">
<div class="container py-4 py-lg-5">
<div class="row justify-content-center">
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
<h3 class="fs-6 fw-bold">Services</h3>
<ul class="list-unstyled">
<li><a href="https://hnshosting.au" target="_blank">Wordpress Hosting</a></li>
<li><a href="https://firewallet.au" target="_blank">FireWallet</a></li>
</ul>
</div>
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
<h3 class="fs-6 fw-bold">About</h3>
<ul class="list-unstyled">
<li><a href="https://hns.au" target="_blank">HNSAU</a></li>
<li><a href="https://nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a></li>
</ul>
</div>
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
<div class="fw-bold d-flex align-items-center mb-2"><span>HNS Links</span></div>
<p class="text-muted">Easy to manage links page on Handshake domains</p>
</div>
</div>
<hr>
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
<p class="mb-0">Copyright © {{year}} Nathan.Woodburn/</p>
</div>
</div>
</footer>
<script src="/assets/js/jquery.min.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="https://auth.varo.domains/v1"></script>
<script src="/assets/js/script.min.js"></script>
</body>
</html>

View File

@ -5,6 +5,10 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>{{title}}</title>
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:title" content="{{title}}">
<meta property="og:title" content="{{title}}">
<meta name="description" content="{{title}}">

View File

@ -5,6 +5,10 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>{{title}}</title>
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:title" content="{{title}}">
<meta property="og:title" content="{{title}}">
<meta name="description" content="{{title}}">

View File

@ -5,7 +5,15 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>HNS Links</title>
<meta name="twitter:title" content="HNS Links">
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:title" content="HNS Links">
<meta name="description" content="Create a links page for your domain">
<meta property="og:description" content="Create a links page for your domain">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:description" content="Create a links page for your domain">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">

View File

@ -5,7 +5,15 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Home - HNS Links</title>
<meta name="twitter:title" content="HNS Links">
<meta name="twitter:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="website">
<meta property="og:title" content="HNS Links">
<meta name="description" content="Create a links page for your domain">
<meta property="og:description" content="Create a links page for your domain">
<meta property="og:image" content="https://links.hns.au/assets/img/dashboard.png">
<meta name="twitter:description" content="Create a links page for your domain">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
<link rel="icon" type="image/png" sizes="600x627" href="/assets/img/favicon.png">
@ -88,7 +96,7 @@
</div><input class="btn btn-primary" type="submit" value="Save" style="margin-right: 20px;"><a class="btn btn-primary" role="button" href="/publish">Publish</a>
</form>
</div>
<div class="col-md-6" style="text-align: center;">{{preview|safe}}</div>
<div class="col-md-6" style="text-align: center;">{{preview|safe}}<a class="btn btn-primary" role="button" href="/nostr" style="margin-top: 1em;">Link Nostr</a></div>
</div>
</div>
</div>