import json from flask import Flask, make_response, redirect, request, jsonify, render_template, send_from_directory, send_file from flask_cors import CORS import os import dotenv import requests import CloudFlare import datetime import qrcode import re from ansi2html import Ansi2HTMLConverter app = Flask(__name__) CORS(app) dotenv.load_dotenv() address = '' handshake_scripts = '' restricted = ['ascii'] sites = [] if os.path.isfile('data/sites.json'): with open('data/sites.json') as file: sites = json.load(file) # Remove any sites that are not enabled sites = [site for site in sites if 'enabled' not in site or site['enabled'] == True] projects = [] projectsUpdated = 0 def getAddress(): global address if address == '': address = 'hs1qv3uu4amv87g7p7h49xez2pmzwjf92am0wzpnh4' return address #Assets routes @app.route('/assets/') def send_report(path): if path.endswith('.json'): return send_from_directory('templates/assets', path, mimetype='application/json') if os.path.isfile('templates/assets/' + path): return send_from_directory('templates/assets', path) # Try looking in one of the directories filename:str = path.split('/')[-1] if filename.endswith('.png') or filename.endswith('.jpg') \ or filename.endswith('.jpeg') or filename.endswith('.svg'): if os.path.isfile('templates/assets/img/' + filename): return send_from_directory('templates/assets/img', filename) if os.path.isfile('templates/assets/img/favicon/' + filename): return send_from_directory('templates/assets/img/favicon', filename) return render_template('404.html'), 404 # Special routes @app.route('/links') def links(): return render_template('link.html') @app.route('/sitemap') @app.route('/sitemap.xml') def sitemap(): # Remove all .html from sitemap with open('templates/sitemap.xml') as file: sitemap = file.read() sitemap = sitemap.replace('.html', '') return make_response(sitemap, 200, {'Content-Type': 'application/xml'}) @app.route('/favicon.png') def faviconPNG(): return send_from_directory('templates/assets/img/favicon', 'favicon.png') @app.route('/favicon.svg') def faviconSVG(): return send_from_directory('templates/assets/img/favicon', 'favicon.svg') @app.route('/https.js') @app.route('/handshake.js') @app.route('/redirect.js') def handshake(): # return request.path return send_from_directory('templates/assets/js', request.path.split('/')[-1]) @app.route('/generator/') def removeTrailingSlash(): return render_template(request.path.split('/')[-2] + '.html') @app.route('/.well-known/wallets/') def wallet(path): # If HNS, redirect to HNS wallet if path == "HNS": # Get from 100.66.107.77:8080 then return result # Check for cookie if request.cookies.get('HNS'): return make_response(request.cookies.get('HNS'), 200, {'Content-Type': 'text/plain'}) address = getAddress() # Set cookie resp = make_response(address, 200, {'Content-Type': 'text/plain'}) # Cookie should last 1 week resp.set_cookie('HNS', address, max_age=604800) return resp if path[0] == ".": return send_from_directory('.well-known/wallets', path, mimetype='application/json') elif os.path.isfile('.well-known/wallets/' + path): address = '' with open('.well-known/wallets/' + path) as file: address = file.read() address = address.strip() return make_response(address, 200, {'Content-Type': 'text/plain'}) if os.path.isfile('.well-known/wallets/' + path.upper()): return redirect('/.well-known/wallets/' + path.upper(), code=302) return render_template('404.html'), 404 @app.route('/.well-known/nostr.json') def nostr(): # Get name parameter name = request.args.get('name') if not name: return jsonify({'error': 'No name provided'}) return jsonify({ 'names': { name: 'b57b6a06fdf0a4095eba69eee26e2bf6fa72bd1ce6cbe9a6f72a7021c7acaa82' } }) @app.route('/manifest.json') def manifest(): host = request.host if host == 'nathan.woodburn.au': return send_from_directory('templates', 'manifest.json') # Read as json with open('templates/manifest.json') as file: manifest = json.load(file) scheme = request.scheme manifest['start_url'] = f'{scheme}://{host}/' return jsonify(manifest) # region Main routes @app.route('/') def index(): # Check if host if podcast.woodburn.au if "podcast.woodburn.au" in request.host: return render_template('podcast.html') loaded = False if request.referrer: # Check if referrer includes nathan.woodburn.au if "nathan.woodburn.au" in request.referrer: loaded = True # Check if cookie is set if not request.cookies.get('loaded') and not loaded: # Set cookie resp = make_response(render_template('loading.html'), 200, {'Content-Type': 'text/html'}) resp.set_cookie('loaded', 'true', max_age=604800) return resp global address global handshake_scripts global projects global projectsUpdated try: git=requests.get('https://git.woodburn.au/api/v1/users/nathanwoodburn/activities/feeds?only-performed-by=true&limit=1', headers={'Authorization': os.getenv('git_token')}) git = git.json() git = git[0] repo_name=git['repo']['name'] repo_name=repo_name.lower() repo_description=git['repo']['description'] except: repo_name = "nathanwoodburn.github.io" repo_description = "Personal website" git = {'repo': {'html_url': 'https://nathan.woodburn.au', 'name': 'nathanwoodburn.github.io', 'description': 'Personal website'}} print("Error getting git data") custom = "" # Get only repo names for the newest updates if projects == [] or projectsUpdated < datetime.datetime.now() - datetime.timedelta(hours=2): projectsreq = requests.get('https://git.woodburn.au/api/v1/users/nathanwoodburn/repos') projects = projectsreq.json() # Check for next page pageNum = 1 while 'rel="next"' in projectsreq.headers['link']: projectsreq = requests.get('https://git.woodburn.au/api/v1/users/nathanwoodburn/repos?page=' + str(pageNum)) projects += projectsreq.json() pageNum += 1 for project in projects: if project['avatar_url'] == 'https://git.woodburn.au/': project['avatar_url'] = '/favicon.png' project['name'] = project['name'].replace('_', ' ').replace('-', ' ') # Sort by last updated projectsList = sorted(projects, key=lambda x: x['updated_at'], reverse=True) projects = [] projectNames = [] projectNum = 0 while len(projects) < 3: if projectsList[projectNum]['name'] not in projectNames: projects.append(projectsList[projectNum]) projectNames.append(projectsList[projectNum]['name']) projectNum += 1 projectsUpdated = datetime.datetime.now() # Check for downtime uptime = requests.get('https://uptime.woodburn.au/api/status-page/main/badge') uptime = uptime.content.count(b'Up') > 1 if uptime: custom += "" else: custom += "" # Special names if repo_name == "nathanwoodburn.github.io": repo_name = "Nathan.Woodburn/" html_url=git['repo']['html_url'] repo = "" + repo_name + "" # If localhost, don't load handshake if request.host == "localhost:5000" or request.host == "127.0.0.1:5000" or os.getenv('dev') == "true" or request.host == "test.nathan.woodburn.au": handshake_scripts = "" if request.cookies.get('HNS'): resp = make_response(render_template('index.html', handshake_scripts=handshake_scripts, HNS=request.cookies.get('HNS'), repo=repo, repo_description=repo_description, custom=custom,sites=sites, projects=projects), 200, {'Content-Type': 'text/html'}) resp.set_cookie('loaded', 'true', max_age=604800) return resp if address == '': address = getAddress() # Set cookie resp = make_response(render_template('index.html', handshake_scripts=handshake_scripts, HNS=address, repo=repo, repo_description=repo_description, custom=custom,sites=sites,projects=projects), 200, {'Content-Type': 'text/html'}) # Cookie should last 1 week resp.set_cookie('HNS', address, max_age=604800) if loaded: resp.set_cookie('loaded', 'true', max_age=604800) return resp # region Now Pages @app.route('/now') @app.route('/now/') def now(): global handshake_scripts # If localhost, don't load handshake if request.host == "localhost:5000" or request.host == "127.0.0.1:5000" or os.getenv('dev') == "true" or request.host == "test.nathan.woodburn.au": handshake_scripts = "" # Get latest now page files = os.listdir('templates/now') # Remove template files = [file for file in files if file != 'template.html' and file != 'old.html'] files.sort(reverse=True) date = files[0].strip('.html') # Convert to date date = datetime.datetime.strptime(date, '%y_%m_%d') date = date.strftime('%A, %B %d, %Y') return render_template('now/' + files[0], handshake_scripts=handshake_scripts, DATE=date) @app.route('/now/') def now_path(path): global handshake_scripts # If localhost, don't load handshake if request.host == "localhost:5000" or request.host == "127.0.0.1:5000" or os.getenv('dev') == "true" or request.host == "test.nathan.woodburn.au": handshake_scripts = "" date = path date = date.strip('.html') try: # Convert to date date = datetime.datetime.strptime(date, '%y_%m_%d') date = date.strftime('%A, %B %d, %Y') except: date = "" if path.lower().replace('.html','') == 'template': return render_template('404.html'), 404 # If file exists, load it if os.path.isfile('templates/now/' + path): return render_template('now/' + path, handshake_scripts=handshake_scripts, DATE=date) if os.path.isfile('templates/now/' + path + '.html'): return render_template('now/' + path + '.html', handshake_scripts=handshake_scripts, DATE=date) return render_template('404.html'), 404 @app.route('/old') @app.route('/old/') @app.route('/now/old') @app.route('/now/old/') def now_old(): global handshake_scripts # If localhost, don't load handshake if request.host == "localhost:5000" or request.host == "127.0.0.1:5000" or os.getenv('dev') == "true" or request.host == "test.nathan.woodburn.au": handshake_scripts = "" now_pages = os.listdir('templates/now') now_pages = [page for page in now_pages if page != 'template.html' and page != 'old.html'] now_pages.sort(reverse=True) html = '
    ' latest = " (Latest)" for page in now_pages: link = page.strip('.html') date = datetime.datetime.strptime(link, '%y_%m_%d') date = date.strftime('%A, %B %d, %Y') html += f'
  • {date}{latest}
  • ' latest = "" html += '
' return render_template('now/old.html', handshake_scripts=handshake_scripts,now_pages=html) # endregion @app.route('/donate') def donate(): global handshake_scripts # If localhost, don't load handshake if request.host == "localhost:5000" or request.host == "127.0.0.1:5000" or os.getenv('dev') == "true" or request.host == "test.nathan.woodburn.au": handshake_scripts = "" coinList = os.listdir('.well-known/wallets') coinList = [file for file in coinList if file[0] != '.'] coinList.sort() tokenList = [] with open('.well-known/wallets/.tokens') as file: tokenList = file.read() tokenList = json.loads(tokenList) coinNames = {} with open('.well-known/wallets/.coins') as file: coinNames = file.read() coinNames = json.loads(coinNames) coins = '' default_coins = ['btc', 'eth', 'hns','sol','bnb','xrp','ada'] for file in coinList: if file in coinNames: coins += f'{coinNames[file]}' else: coins += f'{file}' for token in tokenList: if token["chain"] != 'null': coins += f'' else: coins += f'' crypto = request.args.get('c') if not crypto: instructions = '
Donate with cryptocurrency:
Select a coin from the dropdown above.' return render_template('donate.html', handshake_scripts=handshake_scripts, coins=coins,default_coins=default_coins, crypto=instructions) crypto = crypto.upper() token = request.args.get('t') if token: token = token.upper() for t in tokenList: if t['symbol'].upper() == token and t['chain'].upper() == crypto: token = t break if not isinstance(token, dict): token = { "name": "Unknown token", "symbol": token, "chain": crypto } address = '' domain = '' cryptoHTML = '' if os.path.isfile(f'.well-known/wallets/{crypto}'): with open(f'.well-known/wallets/{crypto}') as file: address = file.read() if not token: cryptoHTML += f'
Donate with {coinNames[crypto] if crypto in coinNames else crypto}:' else: cryptoHTML += f'
Donate with {token["name"]} {"("+token["symbol"]+") " if token["symbol"] != token["name"] else ""}on {crypto}:' cryptoHTML += f'{address}' elif token: if 'address' in token: address = token['address'] cryptoHTML += f'
Donate with {token["name"]} {"("+token["symbol"]+")" if token["symbol"] != token["name"] else ""}{" on "+crypto if crypto != "NULL" else ""}:' cryptoHTML += f'{address}' else: cryptoHTML += f'
Invalid coin: {crypto}
' else: cryptoHTML += f'
Invalid coin: {crypto}
' if os.path.isfile(f'.well-known/wallets/.domains'): # Get json of all domains with open(f'.well-known/wallets/.domains') as file: domains = file.read() domains = json.loads(domains) if crypto in domains: domain = domains[crypto] cryptoHTML += '
Or send to this domain on compatible wallets:
' cryptoHTML += f'{domain}' if address: cryptoHTML += 'QR Code' copyScript = '' cryptoHTML += copyScript return render_template('donate.html', handshake_scripts=handshake_scripts, crypto=cryptoHTML, coins=coins,default_coins=default_coins) @app.route('/qrcode/') def addressQR(data): qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4, ) qr.add_data(data) qr.make(fit=True) qr_image = qr.make_image(fill_color="#110033", back_color="white") # Save the QR code image to a temporary file qr_image_path = "/tmp/qr_code.png" qr_image.save(qr_image_path) # Return the QR code image as a response return send_file(qr_image_path, mimetype="image/png") @app.route('/supersecretpath') def supersecretpath(): ascii_art = '' if os.path.isfile('data/ascii.txt'): with open('data/ascii.txt') as file: ascii_art = file.read() converter = Ansi2HTMLConverter() ascii_art_html = converter.convert(ascii_art) return render_template('ascii.html', ascii_art=ascii_art_html) @app.route('/') def catch_all(path): global handshake_scripts # If localhost, don't load handshake if request.host == "localhost:5000" or request.host == "127.0.0.1:5000" or os.getenv('dev') == "true" or request.host == "test.nathan.woodburn.au": handshake_scripts = "" if path.lower().replace('.html','') in restricted: return render_template('404.html'), 404 # If file exists, load it if os.path.isfile('templates/' + path): return render_template(path, handshake_scripts=handshake_scripts,sites=sites) # Try with .html if os.path.isfile('templates/' + path + '.html'): return render_template(path + '.html', handshake_scripts=handshake_scripts,sites=sites) if os.path.isfile('templates/' + path.strip('/') + '.html'): return render_template(path.strip('/') + '.html', handshake_scripts=handshake_scripts,sites=sites) return render_template('404.html'), 404 # endregion #region ACME @app.route('/hnsdoh-acme', methods=['POST']) def hnsdoh_acme(): # Get the TXT record from the request if not request.json: return jsonify({'status': 'error', 'error': 'No JSON data provided'}) if 'txt' not in request.json or 'auth' not in request.json: return jsonify({'status': 'error', 'error': 'Missing required data'}) txt = request.json['txt'] auth = request.json['auth'] if auth != os.getenv('CF_AUTH'): return jsonify({'status': 'error', 'error': 'Invalid auth'}) cf = CloudFlare.CloudFlare(token=os.getenv('CF_TOKEN')) zone = cf.zones.get(params={'name': 'hnsdoh.com'}) zone_id = zone[0]['id'] existing_records = cf.zones.dns_records.get(zone_id, params={'type': 'TXT', 'name': '_acme-challenge.hnsdoh.com'}) # Delete existing TXT records for record in existing_records: print(record) record_id = record['id'] cf.zones.dns_records.delete(zone_id, record_id) record = cf.zones.dns_records.post(zone_id, data={'type': 'TXT', 'name': '_acme-challenge', 'content': txt}) print(record) return jsonify({'status': 'success'}) #endregion #region Podcast @app.route('/ID1') def ID1(): # Proxy to ID1 url req = requests.get('https://id1.woodburn.au/ID1') return make_response(req.content, 200, {'Content-Type': req.headers['Content-Type']}) @app.route('/ID1/') def ID1_slash(): # Proxy to ID1 url req = requests.get('https://id1.woodburn.au/ID1/') return make_response(req.content, 200, {'Content-Type': req.headers['Content-Type']}) @app.route('/ID1/') def ID1_path(path): # Proxy to ID1 url req = requests.get('https://id1.woodburn.au/ID1/' + path) return make_response(req.content, 200, {'Content-Type': req.headers['Content-Type']}) @app.route('/ID1.xml') def ID1_xml(): # Proxy to ID1 url req = requests.get('https://id1.woodburn.au/ID1.xml') return make_response(req.content, 200, {'Content-Type': req.headers['Content-Type']}) @app.route('/podsync.opml') def podsync(): req = requests.get('https://id1.woodburn.au/podsync.opml') return make_response(req.content, 200, {'Content-Type': req.headers['Content-Type']}) #endregion #region Error Catching # 404 catch all @app.errorhandler(404) def not_found(e): return render_template('404.html'), 404 #endregion if __name__ == '__main__': app.run(debug=True, port=5000, host='0.0.0.0')