feat: Initial code drop
12
.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
.env
|
||||||
|
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
avatars/
|
||||||
|
|
||||||
|
certs/
|
||||||
|
|
||||||
|
cookies.json
|
||||||
|
|
||||||
|
sites/
|
369
main.py
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
from flask import Flask, make_response, redirect, request, jsonify, render_template, send_from_directory, render_template_string
|
||||||
|
import os
|
||||||
|
import dotenv
|
||||||
|
import requests
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import render
|
||||||
|
import secrets
|
||||||
|
import nginx
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
|
# Get site domains
|
||||||
|
DOMAINS = os.getenv('DOMAINS')
|
||||||
|
DOMAINS = json.loads(DOMAINS)
|
||||||
|
|
||||||
|
# Add local domains
|
||||||
|
# DOMAINS.append('localhost:5000')
|
||||||
|
DOMAINS.append('127.0.0.1:5000')
|
||||||
|
IP = "0.0.0.0"
|
||||||
|
|
||||||
|
try:
|
||||||
|
IP = requests.get('https://ipinfo.io/ip').text.strip()
|
||||||
|
except:
|
||||||
|
IP = "Error"
|
||||||
|
|
||||||
|
# Load cookies
|
||||||
|
cookies = []
|
||||||
|
|
||||||
|
if os.path.isfile('cookies.json'):
|
||||||
|
with open('cookies.json') as file:
|
||||||
|
cookies = json.load(file)
|
||||||
|
else:
|
||||||
|
with open('cookies.json', 'w') as file:
|
||||||
|
json.dump(cookies, file)
|
||||||
|
|
||||||
|
if not os.path.isdir('avatars'):
|
||||||
|
os.mkdir('avatars')
|
||||||
|
|
||||||
|
if not os.path.isdir('sites'):
|
||||||
|
os.mkdir('sites')
|
||||||
|
|
||||||
|
if not os.path.isdir('certs'):
|
||||||
|
os.mkdir('certs')
|
||||||
|
|
||||||
|
#Assets routes
|
||||||
|
@app.route('/assets/<path:path>')
|
||||||
|
def send_report(path):
|
||||||
|
return send_from_directory('templates/assets', path)
|
||||||
|
|
||||||
|
@app.route('/favicon.png')
|
||||||
|
def faviconPNG():
|
||||||
|
return send_from_directory('templates/assets/img', 'favicon.png')
|
||||||
|
|
||||||
|
|
||||||
|
# Main routes
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
if request.host in DOMAINS:
|
||||||
|
if 'auth' in request.cookies:
|
||||||
|
auth = request.cookies['auth']
|
||||||
|
for i in cookies:
|
||||||
|
if i['cookie'] == auth:
|
||||||
|
return render_template('index.html',varo="window.location = '/site';", year=datetime.datetime.now().year)
|
||||||
|
return render_template('index.html',varo=render.varo_login(), year=datetime.datetime.now().year)
|
||||||
|
# Remove any ports
|
||||||
|
host = request.host.split(':')[0]
|
||||||
|
# Get content from site
|
||||||
|
if os.path.isfile(f'sites/{host}.json'):
|
||||||
|
with open(f'sites/{host}.json') as file:
|
||||||
|
data = json.load(file)
|
||||||
|
|
||||||
|
return render.site(data)
|
||||||
|
return redirect(f'https://{DOMAINS[0]}')
|
||||||
|
|
||||||
|
@app.route('/site')
|
||||||
|
def site():
|
||||||
|
# Get auth domain
|
||||||
|
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)
|
||||||
|
preview = render.preview(data)
|
||||||
|
else:
|
||||||
|
with open(f'sites/example.json') as file:
|
||||||
|
data = json.load(file)
|
||||||
|
preview = render.preview(data)
|
||||||
|
data = {
|
||||||
|
'title': '',
|
||||||
|
'link_0': '',
|
||||||
|
'link_1': '',
|
||||||
|
'link_2': '',
|
||||||
|
'link_3': '',
|
||||||
|
'link_0_url': '',
|
||||||
|
'link_1_url': '',
|
||||||
|
'link_2_url': '',
|
||||||
|
'link_3_url': '',
|
||||||
|
'image': '',
|
||||||
|
"bg_0": "#001665",
|
||||||
|
"bg_1": "#000000",
|
||||||
|
"fg_0": "#ffffff",
|
||||||
|
"btn_bg": "#2c54cf",
|
||||||
|
"btn_fg": "#ffffff",
|
||||||
|
"socials": []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
title = data['title']
|
||||||
|
link_0 = data['link_0']
|
||||||
|
link_1 = data['link_1']
|
||||||
|
link_2 = data['link_2']
|
||||||
|
link_3 = data['link_3']
|
||||||
|
link_0_url = data['link_0_url']
|
||||||
|
link_1_url = data['link_1_url']
|
||||||
|
link_2_url = data['link_2_url']
|
||||||
|
link_3_url = data['link_3_url']
|
||||||
|
fg_0 = data['fg_0']
|
||||||
|
bg_0 = data['bg_0']
|
||||||
|
bg_1 = data['bg_1']
|
||||||
|
btn_bg = data['btn_bg']
|
||||||
|
btn_fg = data['btn_fg']
|
||||||
|
socials = data['socials']
|
||||||
|
address = data['address']
|
||||||
|
|
||||||
|
# Convert socials to dict
|
||||||
|
socials = {i['name']: i['url'] for i in socials}
|
||||||
|
address = {i['token']: i['address'] for i in address}
|
||||||
|
tlsa = data['tlsa'] if 'tlsa' in data else ''
|
||||||
|
ip = IP
|
||||||
|
|
||||||
|
return render_template('site.html', year=datetime.datetime.now().year, domain=i['name'],
|
||||||
|
title=title, link_0=link_0, link_1=link_1, link_2=link_2, link_3=link_3,
|
||||||
|
link_0_url=link_0_url, link_1_url=link_1_url, link_2_url=link_2_url,
|
||||||
|
link_3_url=link_3_url, fg_0=fg_0, bg_0=bg_0, bg_1=bg_1, btn_bg=btn_bg, btn_fg=btn_fg,
|
||||||
|
socials=socials,address=address,preview=preview,tlsa=tlsa,ip=ip)
|
||||||
|
response = make_response(redirect('/'))
|
||||||
|
response.set_cookie('auth', '', expires=0)
|
||||||
|
return response
|
||||||
|
|
||||||
|
@app.route('/site', methods=['POST'])
|
||||||
|
def site_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:
|
||||||
|
with open(f'sites/example.json') as file:
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
|
||||||
|
# Save site content
|
||||||
|
data['title'] = request.form['title']
|
||||||
|
data['link_0'] = request.form['link_0']
|
||||||
|
data['link_1'] = request.form['link_1']
|
||||||
|
data['link_2'] = request.form['link_2']
|
||||||
|
data['link_3'] = request.form['link_3']
|
||||||
|
data['link_0_url'] = request.form['link_0_url']
|
||||||
|
data['link_1_url'] = request.form['link_1_url']
|
||||||
|
data['link_2_url'] = request.form['link_2_url']
|
||||||
|
data['link_3_url'] = request.form['link_3_url']
|
||||||
|
data['bg_0'] = request.form['bg_0']
|
||||||
|
data['bg_1'] = request.form['bg_1']
|
||||||
|
data['fg_0'] = request.form['fg_0']
|
||||||
|
data['btn_bg'] = request.form['btn_bg']
|
||||||
|
data['btn_fg'] = request.form['btn_fg']
|
||||||
|
|
||||||
|
socials = []
|
||||||
|
socials.append({'name': 'email', 'url': request.form['email']})
|
||||||
|
socials.append({'name': 'twitter', 'url': request.form['twitter']})
|
||||||
|
socials.append({'name': 'github', 'url': request.form['github']})
|
||||||
|
socials.append({'name': 'youtube', 'url': request.form['youtube']})
|
||||||
|
|
||||||
|
address = []
|
||||||
|
address.append({'token': 'hns', 'address': request.form['hns']})
|
||||||
|
address.append({'token': 'eth', 'address': request.form['eth']})
|
||||||
|
address.append({'token': 'btc', 'address': request.form['btc']})
|
||||||
|
address.append({'token': 'sol', 'address': request.form['sol']})
|
||||||
|
|
||||||
|
# Remove empty socials and addresses
|
||||||
|
socials = [i for i in socials if i['url'] != '']
|
||||||
|
data['socials'] = socials
|
||||||
|
address = [i for i in address if i['address'] != '']
|
||||||
|
data['address'] = address
|
||||||
|
|
||||||
|
if 'image' in request.files:
|
||||||
|
if request.files['image'].filename != '' and request.files['image'].filename != None:
|
||||||
|
# Make sure the file is an image
|
||||||
|
file = request.files['image']
|
||||||
|
extension = file.filename.split('.')[-1]
|
||||||
|
|
||||||
|
file.save(f'avatars/{i["name"]}.' + extension)
|
||||||
|
data['image'] = f'{i["name"]}.' + extension
|
||||||
|
|
||||||
|
with open(f'sites/{i["name"]}.json', 'w') as file:
|
||||||
|
json.dump(data, file)
|
||||||
|
return redirect('/site')
|
||||||
|
|
||||||
|
response = make_response(redirect('/'))
|
||||||
|
response.set_cookie('auth', '', expires=0)
|
||||||
|
return response
|
||||||
|
|
||||||
|
@app.route('/preview')
|
||||||
|
def site_preview():
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
with open(f'sites/example.json') as file:
|
||||||
|
data = json.load(file)
|
||||||
|
return render.site(data)
|
||||||
|
|
||||||
|
response = make_response(redirect('/'))
|
||||||
|
response.set_cookie('auth', '', expires=0)
|
||||||
|
return response
|
||||||
|
|
||||||
|
@app.route('/publish')
|
||||||
|
def publish():
|
||||||
|
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)
|
||||||
|
if 'tlsa' in data:
|
||||||
|
# Site is already published
|
||||||
|
return redirect('/site')
|
||||||
|
|
||||||
|
tlsa = nginx.generate_ssl(i['name'])
|
||||||
|
data['tlsa'] = tlsa
|
||||||
|
with open(f'sites/{i["name"]}.json', 'w') as file:
|
||||||
|
json.dump(data, file)
|
||||||
|
nginx.write_nginx_conf(i['name'])
|
||||||
|
return redirect('/site')
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
for i in data['address']:
|
||||||
|
if i['token'].upper() == path:
|
||||||
|
# Return as plain text
|
||||||
|
response = make_response(i['address'])
|
||||||
|
response.headers['Content-Type'] = 'text/plain'
|
||||||
|
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)
|
||||||
|
for i in data['address']:
|
||||||
|
if i['token'].upper() == path:
|
||||||
|
# Return as plain text
|
||||||
|
response = make_response(i['address'])
|
||||||
|
response.headers['Content-Type'] = 'text/plain'
|
||||||
|
return response
|
||||||
|
return render_template('404.html', year=datetime.datetime.now().year), 404
|
||||||
|
|
||||||
|
|
||||||
|
# region Auth
|
||||||
|
@app.route('/auth', methods=['POST'])
|
||||||
|
def auth():
|
||||||
|
global cookies
|
||||||
|
auth = login(request)
|
||||||
|
if auth == False:
|
||||||
|
return render_template('index.html',varo=render.varo_login(), year=datetime.datetime.now().year, error="Failed to login")
|
||||||
|
resp = make_response(render_template_string("Success"))
|
||||||
|
# Gen cookie
|
||||||
|
auth_cookie = secrets.token_hex(12 // 2)
|
||||||
|
cookies.append({'name': auth, 'cookie': auth_cookie})
|
||||||
|
|
||||||
|
with open('cookies.json', 'w') as file:
|
||||||
|
json.dump(cookies, file)
|
||||||
|
|
||||||
|
resp.set_cookie('auth', auth_cookie)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
def logout():
|
||||||
|
global cookies
|
||||||
|
resp = make_response(redirect('/'))
|
||||||
|
resp.set_cookie('auth', '', expires=0)
|
||||||
|
|
||||||
|
cookies = [i for i in cookies if i['cookie'] != request.cookies['auth']]
|
||||||
|
with open('cookies.json', 'w') as file:
|
||||||
|
json.dump(cookies, file)
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def login(request):
|
||||||
|
dict = request.form.to_dict()
|
||||||
|
keys = dict.keys()
|
||||||
|
keys = list(keys)[0]
|
||||||
|
keys = json.loads(keys)
|
||||||
|
auth_request = keys['request']
|
||||||
|
# return login(auth_request)
|
||||||
|
r = requests.get(f'https://auth.varo.domains/verify/{auth_request}')
|
||||||
|
r = r.json()
|
||||||
|
if r['success'] == False:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if 'data' in r:
|
||||||
|
data = r['data']
|
||||||
|
if 'name' in data:
|
||||||
|
return data['name']
|
||||||
|
return False
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
@app.route('/avatar/<path:path>')
|
||||||
|
def avatar(path):
|
||||||
|
return send_from_directory('avatars', path)
|
||||||
|
|
||||||
|
@app.route('/token/<path:path>')
|
||||||
|
def tokens(path):
|
||||||
|
# Colour is last char
|
||||||
|
colour = path[-1]
|
||||||
|
token = path[:-1]
|
||||||
|
if colour.lower() == 'w':
|
||||||
|
return send_from_directory('templates/assets/img/tokens', f'{token}W.png')
|
||||||
|
return send_from_directory('templates/assets/img/tokens', f'{token}.png')
|
||||||
|
|
||||||
|
# 404 catch all
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def not_found(e):
|
||||||
|
return render_template('404.html', year=datetime.datetime.now().year), 404
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True, port=5000, host='0.0.0.0')
|
41
nginx.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
def write_nginx_conf(domain):
|
||||||
|
|
||||||
|
ssl = f'''
|
||||||
|
listen 443 ssl;
|
||||||
|
ssl_certificate /root/hns-links/certs/{domain}/cert.crt;
|
||||||
|
ssl_certificate_key /root/hns-links/certs/{domain}/cert.key;
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
conf = f'''
|
||||||
|
server {{
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name {domain} *.{domain};
|
||||||
|
|
||||||
|
location / {{
|
||||||
|
proxy_pass http://127.0.0.1:5000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
{ssl}
|
||||||
|
}}
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
with open(f'/etc/nginx/sites-enabled/{domain}.conf', 'w') as file:
|
||||||
|
file.write(conf)
|
||||||
|
|
||||||
|
# Restart nginx
|
||||||
|
os.system('systemctl restart nginx')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def generate_ssl(domain):
|
||||||
|
tlsa = os.popen(f'./tlsa.sh {domain}').read().strip()
|
||||||
|
return tlsa
|
115
render.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
from flask import Flask, render_template, request, jsonify
|
||||||
|
|
||||||
|
def varo_login():
|
||||||
|
return '''
|
||||||
|
varo.auth().then(auth => {
|
||||||
|
if (auth.success) {
|
||||||
|
// handle success by calling your api to update the users session
|
||||||
|
$.post("/auth", JSON.stringify(auth.data), (response) => {
|
||||||
|
window.location = '/site';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});'''
|
||||||
|
|
||||||
|
|
||||||
|
def preview(data):
|
||||||
|
title = data['title']
|
||||||
|
link_0 = data['link_0']
|
||||||
|
link_1 = data['link_1']
|
||||||
|
link_2 = data['link_2']
|
||||||
|
link_3 = data['link_3']
|
||||||
|
link_0_url = data['link_0_url']
|
||||||
|
link_1_url = data['link_1_url']
|
||||||
|
link_2_url = data['link_2_url']
|
||||||
|
link_3_url = data['link_3_url']
|
||||||
|
fg_0 = data['fg_0']
|
||||||
|
bg_0 = data['bg_0']
|
||||||
|
bg_1 = data['bg_1']
|
||||||
|
btn_bg = data['btn_bg']
|
||||||
|
btn_fg = data['btn_fg']
|
||||||
|
socials = data['socials']
|
||||||
|
image = data['image']
|
||||||
|
|
||||||
|
if image != "":
|
||||||
|
image = f'<img class="rounded" src="/avatar/{image}" width="128px" height="128px" style="margin-top: 25px;" />'
|
||||||
|
|
||||||
|
links = ""
|
||||||
|
links += link(link_0,link_0_url,btn_bg,btn_fg)
|
||||||
|
links += link(link_1,link_1_url,btn_bg,btn_fg)
|
||||||
|
links += link(link_2,link_2_url,btn_bg,btn_fg)
|
||||||
|
links += link(link_3,link_3_url,btn_bg,btn_fg)
|
||||||
|
|
||||||
|
socials = social_links(socials)
|
||||||
|
addresss = address_links(data['address'],fg_0)
|
||||||
|
|
||||||
|
return render_template('preview_card.html', title=title, links=links, image=image, bg_0=bg_0, bg_1=bg_1, fg_0=fg_0, btn_bg=btn_bg, btn_fg=btn_fg, socials=socials, addresses=addresss)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def link(name,url,btn_bg,btn_fg):
|
||||||
|
if url == "" or name == "":
|
||||||
|
return ""
|
||||||
|
colour = f'background-color: {btn_bg}; color: {btn_fg};'
|
||||||
|
return f'<a class="btn btn-primary custom-button" role="button" style="display: block;margin: auto;margin-bottom: 25px;width: 95%; {colour}" target="_blank" href="{url}">{name}</a>'
|
||||||
|
|
||||||
|
|
||||||
|
def social_links(socials):
|
||||||
|
html = ''
|
||||||
|
for social in socials:
|
||||||
|
html += render_template(social['name'] + '.html', url=social['url'])
|
||||||
|
return html
|
||||||
|
|
||||||
|
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>'
|
||||||
|
return html
|
||||||
|
|
||||||
|
def tokenImage(token,foreground):
|
||||||
|
if is_closer_to_black(foreground):
|
||||||
|
return f'<img src="/token/{token}B" width="32px" height="32px" style="margin-top: 25px;" />'
|
||||||
|
return f'<img src="/token/{token}W" width="32px" height="32px" style="margin-top: 25px;" />'
|
||||||
|
|
||||||
|
def site(data):
|
||||||
|
title = data['title']
|
||||||
|
link_0 = data['link_0']
|
||||||
|
link_1 = data['link_1']
|
||||||
|
link_2 = data['link_2']
|
||||||
|
link_3 = data['link_3']
|
||||||
|
link_0_url = data['link_0_url']
|
||||||
|
link_1_url = data['link_1_url']
|
||||||
|
link_2_url = data['link_2_url']
|
||||||
|
link_3_url = data['link_3_url']
|
||||||
|
image = f'/avatar/{data["image"]}'
|
||||||
|
links = link(link_0,link_0_url,data['btn_bg'],data['btn_fg'])
|
||||||
|
links += link(link_1,link_1_url,data['btn_bg'],data['btn_fg'])
|
||||||
|
links += link(link_2,link_2_url,data['btn_bg'],data['btn_fg'])
|
||||||
|
links += link(link_3,link_3_url,data['btn_bg'],data['btn_fg'])
|
||||||
|
socials = social_links(data['socials'])
|
||||||
|
addresses = address_links(data['address'],data['fg_0'])
|
||||||
|
|
||||||
|
html = render_template('page.html', title=title, links=links, image=image,
|
||||||
|
bg_0=data['bg_0'], bg_1=data['bg_1'], fg_0=data['fg_0'],
|
||||||
|
btn_bg=data['btn_bg'], btn_fg=data['btn_fg'],
|
||||||
|
socials=socials, addresses=addresses)
|
||||||
|
html = html.replace('/assets/img/favicon.png',f'/avatar/{data["image"]}')
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def hex_to_rgb(hex_color):
|
||||||
|
hex_color = hex_color.lstrip('#')
|
||||||
|
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
||||||
|
|
||||||
|
def color_distance(color1, color2):
|
||||||
|
return sum((c1 - c2) ** 2 for c1, c2 in zip(color1, color2)) ** 0.5
|
||||||
|
|
||||||
|
def is_closer_to_black(hex_color):
|
||||||
|
white = (255, 255, 255)
|
||||||
|
black = (0, 0, 0)
|
||||||
|
color = hex_to_rgb(hex_color)
|
||||||
|
distance_to_white = color_distance(color, white)
|
||||||
|
distance_to_black = color_distance(color, black)
|
||||||
|
return distance_to_black < distance_to_white
|
||||||
|
|
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
flask
|
||||||
|
python-dotenv
|
||||||
|
gunicorn
|
||||||
|
requests
|
||||||
|
apscheduler
|
52
templates/404.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en-au">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<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="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&display=swap">
|
||||||
|
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||||
|
<link rel="stylesheet" href="/assets/css/404.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body><!-- partial:index.partial.html -->
|
||||||
|
<p>HTTP: <span>404</span></p>
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<div style="display: inline-block; text-align: left;">
|
||||||
|
<code><span>this_page</span>.<em>not_found</em> = true;</code>
|
||||||
|
<code><span>if</span> (<b>you_spelt_it_wrong</b>){<br><span>autocorrect</span>.<em>enable</em>();<br>}</code>
|
||||||
|
<code><span>else if</span> (<b>we_screwed_up</b>){<br><em>alert</em>(<i>"Strewth mate, you're lost! Fair dinkum, why are you here?"</i>);<br><span>window</span>.<em>location</em> = home;<br>}</code>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- partial -->
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function callback(){
|
||||||
|
return function(){
|
||||||
|
alert("Strewth mate, you're lost! Fair dinkum, why are you here?");
|
||||||
|
window.location = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var a = "world";
|
||||||
|
setTimeout(callback(), 5000);
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<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>
|
||||||
|
<script src="/assets/js/404.min.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
6
templates/assets/bootstrap/css/bootstrap.min.css
vendored
Normal file
6
templates/assets/bootstrap/js/bootstrap.min.js
vendored
Normal file
1
templates/assets/css/404.min.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*{padding:0;margin:0;box-sizing:border-box}body{background:#282828;overflow-y:auto}p{font-family:Bevan,cursive;font-size:130px;margin:10vh 0 0;text-align:center;overflow-y:auto;letter-spacing:5px;color:#000;text-shadow:2px 2px 3px rgba(255,255,255,.1);-webkit-background-clip:text;-moz-background-clip:text;background-clip:text}p span{font-size:1.2em}code{color:#bdbdbd;text-align:left;display:block;overflow-y:auto;font-size:16px;margin:0 30px 25px}code span{color:#f0c674}code i{color:#b5bd68}code em{color:#b294bb;font-style:unset}code b{color:#81a2be;font-weight:500;overflow-y:auto}a{color:#8abeb7;font-family:monospace;font-size:20px;text-decoration:underline;overflow-y:auto;margin-top:10px;display:inline-block}@media screen and (max-width:880px){p{font-size:14vw;overflow-y:auto}}
|
1
templates/assets/css/styles.min.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.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)}
|
BIN
templates/assets/fonts/ionicons.eot
Normal file
11
templates/assets/fonts/ionicons.min.css
vendored
Normal file
2230
templates/assets/fonts/ionicons.svg
Normal file
After Width: | Height: | Size: 326 KiB |
BIN
templates/assets/fonts/ionicons.ttf
Normal file
BIN
templates/assets/fonts/ionicons.woff
Normal file
BIN
templates/assets/img/16.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
templates/assets/img/180.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
templates/assets/img/192.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
templates/assets/img/32.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
templates/assets/img/512.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
templates/assets/img/brands/apple.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
templates/assets/img/brands/facebook.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
templates/assets/img/brands/google.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
templates/assets/img/brands/microsoft.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
templates/assets/img/brands/twitter.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
templates/assets/img/favicon.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
templates/assets/img/products/1.jpg
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
templates/assets/img/products/2.jpg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
templates/assets/img/products/3.jpg
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
templates/assets/img/team/avatar2.jpg
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
templates/assets/img/team/avatar4.jpg
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
templates/assets/img/team/avatar5.jpg
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
templates/assets/img/tokens/BTC.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
templates/assets/img/tokens/BTCW.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
templates/assets/img/tokens/ETH.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
templates/assets/img/tokens/ETHW.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
templates/assets/img/tokens/HNS.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
templates/assets/img/tokens/HNSW.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
templates/assets/img/tokens/SOL.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
templates/assets/img/tokens/SOLW.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
templates/assets/img/transparent.png
Normal file
After Width: | Height: | Size: 70 KiB |
1
templates/assets/js/404.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
function type(e,t){var n=document.getElementsByTagName("code")[e].innerHTML.toString(),a=0;document.getElementsByTagName("code")[e].innerHTML="",setTimeout((function(){var t=setInterval((function(){a++,document.getElementsByTagName("code")[e].innerHTML=n.slice(0,a)+"|",a==n.length&&(clearInterval(t),document.getElementsByTagName("code")[e].innerHTML=n)}),10)}),t)}type(0,0),type(1,600),type(2,1300);
|
2
templates/assets/js/jquery.min.js
vendored
Normal file
1
templates/assets/js/script.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
!function(){"use strict";!function(){if("requestAnimationFrame"in window&&!/Mobile|Android/.test(navigator.userAgent)){var e=document.querySelectorAll("[data-bss-parallax]");if(e.length){var n,t=[];window.addEventListener("scroll",a),window.addEventListener("resize",a),a()}}function a(){t.length=0;for(var a=0;a<e.length;a++){var o=e[a].getBoundingClientRect(),i=parseFloat(e[a].getAttribute("data-bss-parallax-speed"),10)||.5;o.bottom>0&&o.top<window.innerHeight&&t.push({speed:i,node:e[a]})}cancelAnimationFrame(n),t.length&&(n=requestAnimationFrame(r))}function r(){for(var e=0;e<t.length;e++){var n=t[e].node,a=t[e].speed;n.style.transform="translate3d(0, "+-window.scrollY*a+"px, 0)"}}}()}();var varo=new Varo;
|
1
templates/email.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<li class="social-link"><a href="{{url}}" target="_blank"><i class="fas fa-envelope icon"></i></a></li>
|
3
templates/github.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<li class="social-link"><a href="{{url}}" target="_blank"><svg class="bi bi-github icon" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8"></path>
|
||||||
|
</svg></a></li>
|
147
templates/index.html
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<!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>HNS Links</title>
|
||||||
|
<meta name="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&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">
|
||||||
|
</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> 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">
|
||||||
|
<li class="nav-item"><a class="nav-link active" href="/index.html">Home</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link active" href="/#contact">Contact</a></li>
|
||||||
|
</ul><button class="btn btn-primary shadow" type="button" onclick="{{varo}}">Create/edit site</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<header class="bg-dark">
|
||||||
|
<div class="container pt-4 pt-xl-5">
|
||||||
|
<h1 class="text-center" style="color: rgb(255,0,0);">{{error}}</h1>
|
||||||
|
<div class="row pt-5">
|
||||||
|
<div class="col-md-8 col-xl-6 text-center text-md-start mx-auto">
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="fw-bold">The best solution for your HNS Domain</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-10 mx-auto">
|
||||||
|
<div class="position-relative" style="display: flex;flex-wrap: wrap;justify-content: flex-end;">
|
||||||
|
<div style="position: relative;flex: 0 0 45%;transform: translate3d(-15%, 35%, 0);"><img class="img-fluid" data-bss-parallax="" data-bss-parallax-speed="0.8" src="/assets/img/products/3.jpg"></div>
|
||||||
|
<div style="position: relative;flex: 0 0 45%;transform: translate3d(-5%, 20%, 0);"><img class="img-fluid" data-bss-parallax="" data-bss-parallax-speed="0.4" src="/assets/img/products/2.jpg"></div>
|
||||||
|
<div style="position: relative;flex: 0 0 60%;transform: translate3d(0, 0%, 0);"><img class="img-fluid" data-bss-parallax="" data-bss-parallax-speed="0.25" src="/assets/img/products/1.jpg"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<section>
|
||||||
|
<div class="container bg-dark py-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 col-xl-6 text-center mx-auto">
|
||||||
|
<p class="fw-bold text-success mb-2">Features</p>
|
||||||
|
<h3 class="fw-bold">What we can do for you</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="py-5 p-lg-5">
|
||||||
|
<div class="row row-cols-1 row-cols-md-2 mx-auto" style="max-width: 900px;">
|
||||||
|
<div class="col mb-5">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body px-4 py-5 px-md-5">
|
||||||
|
<h5 class="fw-bold card-title">Easy to create site</h5>
|
||||||
|
<p class="text-muted card-text mb-4">We use varo auth to allow you to login with any Handshake domain to allow building your page.<br>Once you login just fill in some info and publish your site in minutes.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col mb-5">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body px-4 py-5 px-md-5">
|
||||||
|
<h5 class="fw-bold card-title">Open Source</h5>
|
||||||
|
<p class="text-muted card-text mb-4">Want full control over your website? Deploy this entire system on your server for free</p><a class="btn btn-primary shadow" role="button" target="_blank" href="https://git.woodburn.au/nathanwoodburn/hns-links">Learn more</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="contact" class="py-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-5">
|
||||||
|
<div class="col-md-8 col-xl-6 text-center mx-auto">
|
||||||
|
<p class="fw-bold text-success mb-2">Contact</p>
|
||||||
|
<h2 class="fw-bold">How you can reach us</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row d-flex justify-content-center">
|
||||||
|
<div class="col-md-4 col-xl-4 d-flex justify-content-center justify-content-xl-start">
|
||||||
|
<div class="d-flex flex-wrap flex-md-column justify-content-md-start align-items-md-start h-100">
|
||||||
|
<div class="d-flex align-items-center p-3">
|
||||||
|
<div class="bs-icon-md bs-icon-circle bs-icon-primary shadow d-flex flex-shrink-0 justify-content-center align-items-center d-inline-block bs-icon bs-icon-md"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-discord">
|
||||||
|
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"></path>
|
||||||
|
</svg></div>
|
||||||
|
<div class="px-2">
|
||||||
|
<h6 class="fw-bold mb-0">Discord</h6><a href="https://discord.hns.au" target="_blank">Join our server</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center p-3">
|
||||||
|
<div class="bs-icon-md bs-icon-circle bs-icon-primary shadow d-flex flex-shrink-0 justify-content-center align-items-center d-inline-block bs-icon bs-icon-md"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="color: #ffffff;">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"></path>
|
||||||
|
</svg></div>
|
||||||
|
<div class="px-2">
|
||||||
|
<h6 class="fw-bold mb-0">Email</h6><a href="mailto:links@hns.au">links@hns.au</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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>
|
57
templates/page.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en" style="text-align: center;">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<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:title" content="{{title}}">
|
||||||
|
<meta property="og:title" content="{{title}}">
|
||||||
|
<meta name="description" content="{{title}}">
|
||||||
|
<meta property="og:description" content="{{title}}">
|
||||||
|
<meta name="twitter:description" content="{{title}}">
|
||||||
|
<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&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">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="background: transparent;">
|
||||||
|
<div style="border-radius: 20px;"><img class="rounded" src="{{image}}" width="128px" height="128px" style="margin-top: 25px;">
|
||||||
|
<h1 style="margin-top: 10px;">{{title}}</h1> <div class="addresses">
|
||||||
|
{{addresses|safe}}
|
||||||
|
</div>
|
||||||
|
<!-- <div style="height:25px;"></div> --> <div class="social-div">
|
||||||
|
<ul class="list-unstyled social-list">
|
||||||
|
{{socials|safe}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div style="height:25px;"></div><style>
|
||||||
|
.custom-button:hover {
|
||||||
|
background-color: {{btn_fg}} !important;
|
||||||
|
color: {{btn_bg}} !important;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
background: linear-gradient({{bg_0}} 0%, {{bg_1}} 100%); color:{{fg_0}};"
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{{links|safe}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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>
|
24
templates/preview_card.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<div
|
||||||
|
style="border-radius: 20px;border-width: 2px;border-style: solid;background: linear-gradient({{bg_0}} 0%, {{bg_1}} 100%); color:{{fg_0}};">
|
||||||
|
{{image|safe}}
|
||||||
|
<h1>{{title}}</h1>
|
||||||
|
<style>
|
||||||
|
.custom-button:hover {
|
||||||
|
background-color: {{btn_fg}} !important;
|
||||||
|
color: {{btn_bg}} !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="addresses">
|
||||||
|
{{addresses|safe}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="social-div">
|
||||||
|
<ul class="list-unstyled social-list">
|
||||||
|
{{socials|safe}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{links|safe}}
|
||||||
|
</div>
|
||||||
|
</div>
|
157
templates/site.html
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<!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>Home - HNS Links</title>
|
||||||
|
<meta name="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&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">
|
||||||
|
</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> 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-top: 25px;padding-bottom: 25px;">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<h1>Content</h1><label class="form-label" style="font-size: 24px;margin-right: 10px;">Image</label><input class="form-control" type="file" style="margin-bottom: 25px;width: auto;display: inline-block;" name="image"><button class="btn btn-primary" type="button" style="display: inline;padding-top: 8px;padding-right: 12px;padding-bottom: 8px;padding-left: 12px;margin-left: 5px;"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-trash3-fill">
|
||||||
|
<path d="M11 1.5v1h3.5a.5.5 0 0 1 0 1h-.538l-.853 10.66A2 2 0 0 1 11.115 16h-6.23a2 2 0 0 1-1.994-1.84L2.038 3.5H1.5a.5.5 0 0 1 0-1H5v-1A1.5 1.5 0 0 1 6.5 0h3A1.5 1.5 0 0 1 11 1.5m-5 0v1h4v-1a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5M4.5 5.029l.5 8.5a.5.5 0 1 0 .998-.06l-.5-8.5a.5.5 0 1 0-.998.06Zm6.53-.528a.5.5 0 0 0-.528.47l-.5 8.5a.5.5 0 0 0 .998.058l.5-8.5a.5.5 0 0 0-.47-.528ZM8 4.5a.5.5 0 0 0-.5.5v8.5a.5.5 0 0 0 1 0V5a.5.5 0 0 0-.5-.5"></path>
|
||||||
|
</svg></button><input class="form-control" type="text" style="margin-bottom: 25px;" name="title" value="{{title}}" placeholder="Title">
|
||||||
|
<div style="margin-bottom: 25px;">
|
||||||
|
<h4>Socials</h4><input class="form-control" type="text" style="display: inline-block;width: auto;" name="email" value="{{socials['email']}}" placeholder="Email"><input class="form-control" type="text" style="display: inline-block;width: auto;" name="twitter" value="{{socials['twitter']}}" placeholder="Twitter"><input class="form-control" type="text" style="display: inline-block;width: auto;" name="github" value="{{socials['github']}}" placeholder="Github"><input class="form-control" type="text" style="display: inline-block;width: auto;" name="youtube" value="{{socials['youtube']}}" placeholder="YouTube">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 25px;">
|
||||||
|
<h4>Addresses</h4><input class="form-control" type="text" style="display: inline-block;width: auto;" name="hns" value="{{address['hns']}}" placeholder="HNS Address"><input class="form-control" type="text" style="display: inline-block;width: auto;" name="eth" value="{{address['eth']}}" placeholder="ETH Address"><input class="form-control" type="text" style="display: inline-block;width: auto;" name="btc" value="{{address['btc']}}" placeholder="BTC Address"><input class="form-control" type="text" style="display: inline-block;width: auto;" name="sol" value="{{address['sol']}}" placeholder="SOL Address">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 25px;">
|
||||||
|
<h4>Link</h4><input class="form-control" type="text" style="display: inline-block;width: 45%;" name="link_0" value="{{link_0}}" placeholder="Link Name"><input class="form-control" type="text" style="display: inline-block;width: auto;" name="link_0_url" value="{{link_0_url}}" placeholder="Url">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 25px;">
|
||||||
|
<h4>Link</h4><input class="form-control" type="text" style="display: inline-block;width: 45%;" name="link_1" value="{{link_1}}" placeholder="Link Name"><input class="form-control" type="text" style="display: inline-block;width: auto;" name="link_1_url" value="{{link_1_url}}" placeholder="Url">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 25px;">
|
||||||
|
<h4>Link</h4><input class="form-control" type="text" style="display: inline-block;width: 45%;" name="link_2" value="{{link_2}}" placeholder="Link Name"><input class="form-control" type="text" style="display: inline-block;width: auto;" name="link_2_url" value="{{link_2_url}}" placeholder="Url">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 25px;">
|
||||||
|
<h4>Link</h4><input class="form-control" type="text" style="display: inline-block;width: 45%;" name="link_3" value="{{link_3}}" placeholder="Link Name"><input class="form-control" type="text" style="display: inline-block;width: auto;" name="link_3_url" value="{{link_3_url}}" placeholder="Url">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 25px;">
|
||||||
|
<p>Main Colours</p>
|
||||||
|
<div>
|
||||||
|
<p style="display: inline-block;">Background Top</p><input class="form-control form-control-color" type="color" style="display: inline-block;margin-left: 25px;height: 2em;" name="bg_0" value="{{bg_0}}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style="display: inline-block;">Background Bottom</p><input class="form-control form-control-color" type="color" style="display: inline-block;margin-left: 25px;height: 2em;" value="{{bg_1}}" name="bg_1">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style="display: inline-block;">Text</p><input class="form-control form-control-color" type="color" style="display: inline-block;margin-left: 25px;height: 2em;" name="fg_0" value="{{fg_0}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 25px;">
|
||||||
|
<p>Button Colours</p>
|
||||||
|
<div>
|
||||||
|
<p style="display: inline-block;">Background</p><input class="form-control form-control-color" type="color" style="display: inline-block;margin-left: 25px;height: 2em;" name="btn_bg" value="{{btn_bg}}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style="display: inline-block;">Text</p><input class="form-control form-control-color" type="color" style="display: inline-block;margin-left: 25px;height: 2em;" name="btn_fg" value="{{btn_fg}}">
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<div style="max-width: 1000px;margin: auto;">
|
||||||
|
<h2 class="text-center">DNS Records</h2>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-dark">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>A</td>
|
||||||
|
<td>@</td>
|
||||||
|
<td>{{ip}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>TLSA</td>
|
||||||
|
<td>_443._tcp</td>
|
||||||
|
<td>{{tlsa}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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>
|
8
templates/twitter.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<li class="social-link"><a href="{{url}}" target="_blank"><svg class="bi bi-twitter-x icon"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865l8.875 11.633Z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
3
templates/youtube.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<li class="social-link"><a href="{{url}}" target="_blank"><svg class="bi bi-youtube icon" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408z"></path>
|
||||||
|
</svg></a></li>
|
20
tlsa.sh
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Get domain name from arguments
|
||||||
|
domain=$1
|
||||||
|
|
||||||
|
mkdir certs/$domain
|
||||||
|
|
||||||
|
openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \
|
||||||
|
-keyout certs/$domain/cert.key -out certs/$domain/cert.crt \
|
||||||
|
-extensions ext -config \
|
||||||
|
<(echo "[req]";
|
||||||
|
echo distinguished_name=req;
|
||||||
|
echo "[ext]";
|
||||||
|
echo "keyUsage=critical,digitalSignature,keyEncipherment";
|
||||||
|
echo "extendedKeyUsage=serverAuth";
|
||||||
|
echo "basicConstraints=critical,CA:FALSE";
|
||||||
|
echo "subjectAltName=DNS:$domain,DNS:*.$domain";
|
||||||
|
) -subj "/CN=*.$domain"
|
||||||
|
|
||||||
|
echo -n "3 1 1 " && openssl x509 -in certs/$domain/cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | xxd -p -u -c 32
|