Compare commits

...

27 Commits

Author SHA1 Message Date
f4d937ff0f feat: Add API only mode
All checks were successful
Build Docker / Build Image (push) Successful in 36s
2025-04-05 14:51:12 +11:00
3d00eef43c feat: Add some IP limiting
All checks were successful
Build Docker / Build Image (push) Successful in 39s
2025-04-04 16:07:08 +11:00
823b348248 fix: key error in api response
All checks were successful
Build Docker / Build Image (push) Successful in 31s
2025-03-31 13:28:23 +11:00
4adf82be49 fix: Update some logic to make gifting smother
All checks were successful
Build Docker / Build Image (push) Successful in 34s
2025-03-31 13:25:49 +11:00
67840e04e5 fix: Only send domains expiry after threshold not before
All checks were successful
Build Docker / Build Image (push) Successful in 33s
2025-03-31 13:19:00 +11:00
35628bdacf fix: Make sure not to gift expiring domains
All checks were successful
Build Docker / Build Image (push) Successful in 33s
2025-03-31 13:16:45 +11:00
4e613b8c27 fix: Update address on other pages
All checks were successful
Build Docker / Build Image (push) Successful in 56s
2025-03-14 10:30:28 +11:00
2e307f90da fix: Update style for mobile
All checks were successful
Build Docker / Build Image (push) Successful in 1m0s
2025-03-14 10:23:21 +11:00
4e8ca03aed fix: Only update address every 24 hrs and increase the receiveWindow
All checks were successful
Build Docker / Build Image (push) Successful in 50s
2024-07-23 10:03:30 +10:00
9604f08c4e feat: Add link to stats from home page
All checks were successful
Build Docker / Build Image (push) Successful in 32s
2023-12-29 20:05:48 +11:00
a400586b05 feat: Add umami
All checks were successful
Build Docker / Build Image (push) Successful in 36s
2023-12-20 17:03:09 +11:00
0a47dd7cbe feat: Add analytics
All checks were successful
Build Docker / Build Image (push) Successful in 19s
2023-12-13 14:53:24 +11:00
2b744a7cf8 fix: Discord api
All checks were successful
Build Docker / Build Image (push) Successful in 19s
2023-12-12 15:13:34 +11:00
e60ae30668 feat: Alert via discord if the nb token expires
All checks were successful
Build Docker / Build Image (push) Successful in 31s
2023-12-12 15:10:31 +11:00
96afc29328 feat: Add shakecities to Faucet
All checks were successful
Build Docker / Build Image (push) Successful in 19s
2023-11-18 15:57:50 +11:00
9c0534ee2f fix: Typo in rate limit time
All checks were successful
Build Docker / Build Image (push) Successful in 20s
2023-11-09 11:06:50 +11:00
a271ce9793 fix: Rate limit discord users
All checks were successful
Build Docker / Build Image (push) Successful in 26s
2023-11-08 17:59:27 +11:00
6e2f4bd814 fix: API rate limits
All checks were successful
Build Docker / Build Image (push) Successful in 23s
2023-11-08 15:55:38 +11:00
30c723cebb feat: Add api endpoint
All checks were successful
Build Docker / Build Image (push) Successful in 20s
2023-11-08 15:25:47 +11:00
5e6af2bcc8 feat: Add email validation
All checks were successful
Build Docker / Build Image (push) Successful in 31s
2023-11-08 14:49:08 +11:00
93f45eec12 feat: Send gift to discord webhook
All checks were successful
Build Docker / Build Image (push) Successful in 20s
2023-11-08 14:31:52 +11:00
7bb07dbb9e feat: Limit to x gifts per interval
All checks were successful
Build Docker / Build Image (push) Successful in 27s
2023-11-08 14:25:39 +11:00
27af90fcc6 docs: Added readme info
All checks were successful
Build Docker / Build Image (push) Successful in 20s
2023-11-08 14:05:55 +11:00
3b7c587e54 feat: Update address every hour from NB api
All checks were successful
Build Docker / Build Image (push) Successful in 29s
2023-11-08 14:01:08 +11:00
aa2e5a1c4d fix: Cleared up stats balance 2023-11-08 13:35:21 +11:00
784455fe1d feat: Add cookie for referral
All checks were successful
Build Docker / Build Image (push) Successful in 18s
2023-11-08 13:33:17 +11:00
7c3d101782 feat: Add path referral 2023-11-08 13:30:13 +11:00
13 changed files with 451 additions and 119 deletions

3
.env.example Normal file
View File

@@ -0,0 +1,3 @@
local=true
admin_ip=127.0.0.1
cookie=s%3A...

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@
__pycache__/
gifts.json
.venv/

View File

@@ -1 +1,26 @@
# faucet
# Woodburn Faucet
A simple faucet for Handshake domains
Running on [faucet.woodburn.au](https://faucet.woodburn.au)
## Docker deployment
```
docker run -d \
--name=faucet \
-e admin_ip=<your-IP> \
-e cookie=<your-nb-main-cookie>
-p 5000:5000 \
git.woodburn.au/nathanwoodburn/faucet:latest
```
## Optional environment variables
| Variable | Description | Default |
| --- | --- | --- |
| admin_ip | IP address to allow multiple gifts sent to |
| cookie | Cookie to use for nb-main |
| max_price | Maximum price to pay for a domain (in HNS) | 5 |
| max_gifts_per_interval | Maximum number of gifts to send per interval | 24 |
| interval | Interval to send gifts (in seconds) | 86400 (24 hours) |
| discord_webhook | Discord webhook to send notifications to | None |
| api_key | API key for api gifts | None |

115
gift.py
View File

@@ -14,11 +14,38 @@ cookies = {"namebase-main": nbcookie}
nb_endpoint = "https://www.namebase.io/"
max_price = 5 # Max price to buy a domain at (in HNS)
previous_gifts = []
max_gifts_per_interval = 24 # Max gifts per interval
interval = 60*60*24 # 24 hours
EXPIRY_THRESHOLD = 144 * 35 # 35 days
if os.getenv('max_price') == 'true':
max_price = int(os.getenv('max_price'))
if os.getenv('max_gifts_per_interval') == 'true':
max_gifts_per_interval = int(os.getenv('max_gifts_per_interval'))
if os.getenv('interval') == 'true':
interval = int(os.getenv('interval'))
def gift(name,email,referer, ip):
ONLY_API = False
if os.getenv("ONLY_API") == 'true':
ONLY_API = True
def gift(name,email,referer, ip,api=False):
global loaded
global gifts
global previous_gifts
if ONLY_API and not api:
return "Sorry, the faucet is currently only available through the Discord bot<br>Check back in a few minutes or contact Nathan.Woodburn/"
recent_gifts = 0
for gift in previous_gifts:
if gift['time'] > time.time() - interval:
recent_gifts += 1
if recent_gifts > max_gifts_per_interval and ip != os.getenv('admin_ip'):
return "Too many gifts recently<br>Check back in a few minutes"
print("Name: " + name,flush=True)
print("Email: " + email,flush=True)
@@ -41,19 +68,40 @@ def gift(name,email,referer, ip):
loaded = True
# Check if the user has already submitted
if ip != os.getenv('admin_ip'):
if ip != os.getenv('admin_ip') and not api:
ip_first = ip.split('.')[0]
ip_block = 0
for gift in gifts:
if gift['email'] == email:
return "You have already submitted a gift request"
if gift['ip'] == ip:
return "You have already submitted a gift request"
if gift['ip'].startswith(ip_first):
if 'time' in gift and gift['time'] > (time.time() - interval*4):
ip_block += 1
if 'time' not in gift:
ip_block += 1
if ip_block > 2:
return "You have been rate limited<br>Contact Nathan.Woodburn if you think this is a mistake"
if api:
for gift in gifts:
if gift['email'] == email:
return "You have already submitted a gift request"
if gift['name'] == name:
return "You have already submitted a gift request"
# Add the user to the list
gifts.append({
'name': name,
'email': email,
'referer': referer,
'ip': ip
'ip': ip,
'time': time.time()
})
previous_gifts.append({
'time': time.time()
})
# Save the file
@@ -69,8 +117,15 @@ def gift(name,email,referer, ip):
if names.status_code != 200:
return "Error getting names:<br>" + names.text
names = names.json()
if len(names['domains']) == 0:
tmpnames = names.json()
domain = None
for name in tmpnames['domains']:
if (name['expireBlock'] - tmpnames['currentHeight']) > EXPIRY_THRESHOLD:
domain = name['name']
break
if domain == None:
domains_market = requests.get(nb_endpoint + "/api/domains/marketplace?offset=0&buyNowOnly=true&sortKey=price&sortDirection=asc&exclude=%2Cnumbers%2Chyphens%2Cunderscores&maxLength=10&offersOnly=false"
,headers=headers, cookies=cookies)
if domains_market.status_code != 200:
@@ -80,24 +135,33 @@ def gift(name,email,referer, ip):
if len(domains_market['domains']) == 0:
return "No domains available to gift<br>Check back in a few minutes"
domain = domains_market['domains'][0]['name']
print("Buying: " + domain,flush=True)
price = int(domains_market['domains'][0]['amount'])
if price > max_price*1000000:
return "Domain price too high<br>Check back in a few minutes"
listing = None
for d in domains_market['domains']:
if int(d['amount']) > max_price*1000000:
continue
data = requests.post("https://www.namebase.io/api/domains/search",headers=headers,json={"domains":[d['name']]}, cookies=cookies)
if data.status_code != 200:
return "Error getting names:<br>" + data.text
data = data.json()
if data['domains'][0]['domainInfo']['name'] != d['name']:
return "Something weird happened<br>Check back in a few minutes"
if (data['domains'][0]['domainInfo']['expireBlock'] - data['height']) > EXPIRY_THRESHOLD:
domain = d['name']
listing = d['id']
break
if domain == None:
return "No domains available to gift<br>Check back in a few minutes"
# Buy the domain
print("Buying: " + domain,flush=True)
payload = {
"listingId": domains_market['domains'][0]['id']
"listingId": listing
}
buy = requests.post(nb_endpoint + "/api/v0/marketplace/"+domain+"/buynow",headers=headers,data=json.dumps(payload), cookies=cookies)
if buy.status_code != 200:
return "Error buying name:<br>" + buy.text
else:
domain = names['domains'][0]['name']
print("Gifting: " + domain,flush=True)
@@ -115,6 +179,7 @@ def gift(name,email,referer, ip):
if send_name.status_code != 200:
return "Error sending gift:<br>" + send_name.text
discord(domain,email,ip,referer)
return True
@@ -127,3 +192,21 @@ def balance():
hns_balance = user_info['hns_balance']
hns_balance = int(hns_balance)/1000000
return hns_balance
def discord(domain, email,ip,referer):
url = os.getenv('discord_webhook')
if url == None:
return "No webhook set"
payload = {
"content": "New gift request: " + domain + "\nSent to " + email + "\nIP: " + ip + "\nReferer: " + referer
}
response = requests.post(url, data=json.dumps(payload), headers={'Content-Type': 'application/json'})
# Check if the message was sent successfully
if response.status_code == 204:
print("Message sent successfully!")
else:
print(f"Failed to send message. Status code: {response.status_code}")
print(response.text)

225
main.py
View File

@@ -1,110 +1,219 @@
from flask import Flask, make_response, redirect, request, jsonify, render_template, send_from_directory
from flask import (
Flask,
make_response,
redirect,
request,
jsonify,
render_template,
send_from_directory,
)
import os
import dotenv
import requests
import gift
import json
import schedule
import time
from email_validator import validate_email, EmailNotValidError
app = Flask(__name__)
dotenv.load_dotenv()
address = 'hs1qr7d0xqsyatls47jf28gvm97twe8k606gspfpsz'
address = "hs1qr7d0xqsyatls47jf28gvm97twe8k606gspfpsz"
if os.getenv('address') != None:
address = os.getenv('address')
if os.getenv("address") != None:
address = os.getenv("address")
#Assets routes
@app.route('/assets/<path:path>')
# Assets routes
@app.route("/assets/<path:path>")
def send_report(path):
return send_from_directory('templates/assets', path)
return send_from_directory("templates/assets", path)
@app.route('/')
@app.route("/")
def index():
params = request.args
if 'r' in params:
print("Referer: " + params['r'])
return render_template('index.html', hidden=params['r'],address=address)
if "r" in request.cookies:
print("Referer: " + request.cookies["r"])
return render_template(
"index.html", hidden=request.cookies["r"], address=address
)
return render_template('index.html',address=address)
if "r" in params:
print("Referer: " + params["r"])
# Set cookie
resp = make_response(
render_template("index.html", hidden=params["r"], address=address)
)
resp.set_cookie("r", params["r"], max_age=60 * 60 * 24)
return resp
return render_template("index.html", address=address)
@app.route('/', methods=['POST'])
@app.route("/", methods=["POST"])
def submit():
name = request.form['name']
email = request.form['email']
hidden = request.form['hi']
name = request.form["name"]
email = request.form["email"]
hidden = request.form["hi"]
ip = request.remote_addr
if 'X-REAL-IP' in request.headers:
ip = request.headers['X-REAL-IP']
if "X-REAL-IP" in request.headers:
ip = request.headers["X-REAL-IP"]
if 'X-Real-Ip' in request.headers:
ip = request.headers['X-Real-Ip']
if "X-Real-Ip" in request.headers:
ip = request.headers["X-Real-Ip"]
if hidden == '':
hidden = 'None'
if hidden == "":
hidden = "None"
if "r" in request.cookies:
hidden = request.cookies["r"]
# Validate email
try:
emailinfo = validate_email(email, check_deliverability=False)
email = emailinfo.normalized
except EmailNotValidError as e:
return render_template(
"error.html", error="Invalid email address", address=address
)
status = gift.gift(name, email, hidden, ip)
print(status,flush=True)
print(status, flush=True)
if status == True:
return render_template('success.html',address=address)
return render_template("success.html", address=address)
else:
return render_template('error.html',error=status,address=address)
return render_template("error.html", error=status, address=address)
@app.route("/api", methods=["POST"])
def api():
# Get from params
params = request.args
name = params["name"]
email = params["email"]
key = params["key"]
if key != os.getenv("api_key"):
return jsonify({"error": "Invalid API key", "success": False})
if "X-REAL-IP" in request.headers:
ip = request.headers["X-REAL-IP"]
if "X-Real-Ip" in request.headers:
ip = request.headers["X-Real-Ip"]
# Validate email
try:
emailinfo = validate_email(email, check_deliverability=False)
email = emailinfo.normalized
except EmailNotValidError as e:
return jsonify({"error": "Invalid email address", "success": False})
status = gift.gift(name, email, "api", ip, True)
print(status, flush=True)
if status == True:
return jsonify({"success": True})
else:
return jsonify({"error": status, "success": False})
# Special routes
@app.route('/.well-known/wallets/<token>')
@app.route("/.well-known/wallets/<token>")
def send_wallet(token):
address = requests.get('https://nathan.woodburn.au/.well-known/wallets/'+token).text
return make_response(address, 200, {'Content-Type': 'text/plain'})
address = requests.get(
"https://nathan.woodburn.au/.well-known/wallets/" + token
).text
return make_response(address, 200, {"Content-Type": "text/plain"})
@app.route('/stats')
@app.route("/stats")
def stats():
# Read the file
path = '/data/gifts.json'
if os.getenv('local') == 'true':
path = './gifts.json'
path = "/data/gifts.json"
if os.getenv("local") == "true":
path = "./gifts.json"
# Load file
gifts = []
if os.path.isfile(path):
with open(path, 'r') as f:
gifts = json.load(f)
with open(path, "r") as f:
gifts = json.load(f)
# Loop through gifts
referals = {}
for gift_item in gifts:
if gift_item['referer'] not in referals:
referals[gift_item['referer']] = 1
if gift_item["referer"] not in referals:
referals[gift_item["referer"]] = 1
else:
referals[gift_item['referer']] += 1
statsHTML = 'Total gifts: ' + str(len(gifts)) + '<br><br>'
statsHTML += 'Referals:<br>'
referals[gift_item["referer"]] += 1
statsHTML = "Total gifts: " + str(len(gifts)) + "<br><br>"
statsHTML += "Referals:<br>"
for referal in referals:
statsHTML += referal + ': ' + str(referals[referal]) + '<br>'
statsHTML += referal + ": " + str(referals[referal]) + "<br>"
statsHTML += "<br>Remaining balance: " + str(gift.balance()) + " HNS<br>"
return render_template("stats.html", address=address, stats=statsHTML)
statsHTML += '<br>Remaining HNS: ' + str(gift.balance()) + '<br>'
return render_template('stats.html',address=address,stats=statsHTML)
@app.route('/<path:path>')
@app.route("/<path:path>")
def catch_all(path):
# # If file exists, load it
# if os.path.isfile('templates/' + path):
# return render_template(path)
# # Try with .html
# if os.path.isfile('templates/' + path + '.html'):
# return render_template(path + '.html')
return redirect('/')
return redirect("/?r=" + path)
# 404 catch all
@app.errorhandler(404)
def not_found(e):
return redirect('/')
return redirect("/")
if __name__ == '__main__':
app.run(debug=False, port=5000, host='0.0.0.0')
def update_address():
global address
payload = {
"asset": "HNS",
"timestamp": int(round(time.time() * 1000)),
"receiveWindow": 20000,
}
nbcookie = os.getenv("cookie")
cookies = {"namebase-main": nbcookie}
headers = {"Accept": "application/json", "Content-Type": "application/json"}
r = requests.post(
"https://www.namebase.io/api/v0/deposit/address",
data=json.dumps(payload),
headers=headers,
cookies=cookies,
)
if "address" not in r.json():
print("Error: " + r.text, flush=True)
# Send alert via discord
url = os.getenv("discord_webhook")
if url == None:
return "No webhook set"
payload = {"content": "NB cookie has expired\n@everyone"}
response = requests.post(
url, data=json.dumps(payload), headers={"Content-Type": "application/json"}
)
# Check if the message was sent successfully
if response.status_code == 204:
print("Message sent successfully!")
else:
print(f"Failed to send message. Status code: {response.status_code}")
print(response.text)
return
address = r.json()["address"]
print("Address updated: " + address, flush=True)
update_address()
if __name__ == "__main__":
app.run(debug=False, port=5000, host="0.0.0.0")

View File

@@ -2,4 +2,8 @@ flask
python-dotenv
gunicorn
requests
pyotp
pyotp
schedule
email-validator
py3dns
apscheduler

View File

@@ -7,6 +7,7 @@ import os
import dotenv
import sys
import json
from apscheduler.schedulers.background import BackgroundScheduler
class GunicornApp(BaseApplication):
@@ -24,6 +25,12 @@ class GunicornApp(BaseApplication):
return self.application
if __name__ == '__main__':
# Every hour
scheduler = BackgroundScheduler()
scheduler.add_job(main.update_address, 'cron', hour='0')
scheduler.start()
workers = os.getenv('WORKERS')
threads = os.getenv('THREADS')
if workers is None:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -10,13 +10,13 @@
<meta property="og:description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta name="twitter:title" content="Woodburn Faucet">
<meta property="og:image" content="https://faucet.woodburn.au/assets/img/android-chrome-512x512.png">
<meta property="og:type" content="website">
<meta name="description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta property="og:title" content="Woodburn Faucet">
<meta name="twitter:description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta name="description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta property="og:image" content="https://faucet.woodburn.au/assets/img/android-chrome-512x512.png">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
@@ -29,11 +29,27 @@ Claim a free Handshake domain">
<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/fonts/material-icons.min.css">
<link rel="stylesheet" href="assets/css/styles.min.css">
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://analytics.woodburn.au/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '5']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
<script async src="https://umami.woodburn.au/script.js" data-website-id="f9357bd9-88b0-473d-9484-b1d37b1b5c87"></script>
</head>
<body style="background: rgb(39,38,46);">
<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.svg" width="50em"><span>&nbsp;Woodburn Faucet</span></a><button class="navbar-toggler" data-bs-toggle="collapse"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button></div>
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="assets/img/favicon.svg" width="50em"><span>&nbsp;Woodburn Faucet</span></a></div>
</nav>
<header class="bg-dark">
<div class="container pt-4 pt-xl-5">
@@ -54,10 +70,10 @@ Claim a free Handshake domain">
</div>
</div>
</header>
<section style="margin-top: 50px;">
<section style="margin: auto;margin-top: 50px;max-width: 90%;">
<div style="text-align: center;">
<h3>Donate to fund the faucet</h3>
<h4>{{address}}</h4>
<h4 class="text-truncate">{{address}}</h4>
</div>
</section>
<section style="background: #27262e;">
@@ -84,7 +100,16 @@ Claim a free Handshake domain">
<div class="card-body px-4 py-5 px-md-5">
<div class="bs-icon-lg d-flex justify-content-center align-items-center mb-3 bs-icon" style="top: 1rem;right: 1rem;position: absolute;"><i class="material-icons text-success">dns</i></div>
<h5 class="fw-bold card-title">Woodburn Registry</h5>
<p class="text-muted card-text mb-4">Free Handshake compatible DNS server. Also provides free SLDs for testing</p><a class="btn btn-primary shadow" role="button" href="https://reg.woodburn.au" target="_blank">Learn more</a>
<p class="text-muted card-text mb-4">Free Handshake DNS service. Also provides free SLDs on select TLDs</p><a class="btn btn-primary shadow" role="button" href="https://reg.woodburn.au" target="_blank">Learn more</a>
</div>
</div>
</div>
<div class="col mb-5">
<div class="card shadow-sm">
<div class="card-body px-4 py-5 px-md-5">
<div class="bs-icon-lg d-flex justify-content-center align-items-center mb-3 bs-icon" style="top: 1rem;right: 1rem;position: absolute;"><i class="material-icons text-success">web</i></div>
<h5 class="fw-bold card-title">ShakeCities</h5>
<p class="text-muted card-text mb-4">Unlock web ownership's future with ShakeCities! Create your free, secure Handshake domain site. Integrate crypto payments, explore HNSChat, and join us in shaping the decentralized web!</p><a class="btn btn-primary shadow" role="button" href="https://shakecities.com" target="_blank">Learn more</a>
</div>
</div>
</div>
@@ -100,7 +125,7 @@ Claim a free Handshake domain">
<p class="w-lg-50">Thanks to these donors for supporting the faucet</p>
</div>
</div>
<div class="row gy-4 row-cols-2 row-cols-md-4">
<div class="row gy-4 row-cols-1 row-cols-sm-1 row-cols-md-2 row-cols-lg-2 row-cols-xl-4 row-cols-xxl-4">
<div class="col">
<div class="card border-0 shadow-none">
<div class="card-body text-center d-flex flex-column align-items-center p-0"><img class="rounded-circle mb-3 fit-cover" width="130" height="130" src="assets/img/team/nathanwoodburn.webp" style="margin-top: 10px;">
@@ -140,7 +165,7 @@ Claim a free Handshake domain">
<div class="container py-4 py-lg-5">
<hr>
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
<p class="mb-0 copyright">Copyright © 2023 Nathan.Woodburn/</p>
<p class="mb-0 copyright">Copyright © 2023 Nathan.Woodburn/</p><a href="/stats">Stats</a>
</div>
</div>
</footer>

View File

@@ -10,13 +10,13 @@
<meta property="og:description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta name="twitter:title" content="Woodburn Faucet">
<meta property="og:image" content="https://faucet.woodburn.au/assets/img/android-chrome-512x512.png">
<meta property="og:type" content="website">
<meta name="description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta property="og:title" content="Woodburn Faucet">
<meta name="twitter:description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta name="description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta property="og:image" content="https://faucet.woodburn.au/assets/img/android-chrome-512x512.png">
<script type="application/ld+json">
{
"@context": "http://schema.org",
@@ -37,11 +37,27 @@ Claim a free Handshake domain">
<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/fonts/material-icons.min.css">
<link rel="stylesheet" href="assets/css/styles.min.css">
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://analytics.woodburn.au/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '5']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
<script async src="https://umami.woodburn.au/script.js" data-website-id="f9357bd9-88b0-473d-9484-b1d37b1b5c87"></script>
</head>
<body style="background: rgb(39,38,46);">
<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.svg" width="50em"><span>&nbsp;Woodburn Faucet</span></a><button class="navbar-toggler" data-bs-toggle="collapse"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button></div>
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="assets/img/favicon.svg" width="50em"><span>&nbsp;Woodburn Faucet</span></a></div>
</nav>
<header class="bg-dark">
<div class="container pt-4 pt-xl-5">
@@ -56,16 +72,16 @@ Claim a free Handshake domain">
<div class="text-center position-relative" style="display: block;flex-wrap: wrap;justify-content: flex-end;">
<h2 style="width: 100%;">Send a free domain to your email</h2>
<p>Powered by <a href="https://www.namebase.io/register/kdh57i" target="_blank">Namebase</a> instant transfers</p>
<form class="text-center" style="width: 100%;" method="post"><input class="form-control" type="text" style="margin: auto;max-width: 450px;margin-top: 10px;margin-bottom: 10px;" name="name" placeholder="Name" required="" minlength="3"><input class="form-control" type="email" style="margin: auto;max-width: 450px;margin-top: 10px;margin-bottom: 10px;" placeholder="your@email.com" name="email" required=""><input class="btn btn-primary" type="submit"><input class="form-control" type="hidden" name="hi" value="{{hidden}}"></form>
<form class="text-center" style="width: 100%;" method="post"><input class="form-control" type="text" style="margin: auto;max-width: 450px;margin-top: 10px;margin-bottom: 10px;" name="name" placeholder="Name" required="" minlength="3"><input class="form-control" type="email" style="margin: auto;max-width: 450px;margin-top: 10px;margin-bottom: 10px;" placeholder="your@email.com" name="email" required=""><input class="btn btn-primary" type="submit" data-umami-event="Gift Button"><input class="form-control" type="hidden" name="hi" value="{{hidden}}"></form>
</div>
</div>
</div>
</div>
</header>
<section style="margin-top: 50px;">
<section style="margin: auto;margin-top: 50px;max-width: 90%;">
<div style="text-align: center;">
<h3>Donate to fund the faucet</h3>
<h4>{{address}}</h4>
<h4 class="text-truncate">{{address}}</h4>
</div>
</section>
<section style="background: #27262e;">
@@ -92,7 +108,16 @@ Claim a free Handshake domain">
<div class="card-body px-4 py-5 px-md-5">
<div class="bs-icon-lg d-flex justify-content-center align-items-center mb-3 bs-icon" style="top: 1rem;right: 1rem;position: absolute;"><i class="material-icons text-success">dns</i></div>
<h5 class="fw-bold card-title">Woodburn Registry</h5>
<p class="text-muted card-text mb-4">Free Handshake compatible DNS server. Also provides free SLDs for testing</p><a class="btn btn-primary shadow" role="button" href="https://reg.woodburn.au" target="_blank">Learn more</a>
<p class="text-muted card-text mb-4">Free Handshake DNS service. Also provides free SLDs on select TLDs</p><a class="btn btn-primary shadow" role="button" href="https://reg.woodburn.au" target="_blank">Learn more</a>
</div>
</div>
</div>
<div class="col mb-5">
<div class="card shadow-sm">
<div class="card-body px-4 py-5 px-md-5">
<div class="bs-icon-lg d-flex justify-content-center align-items-center mb-3 bs-icon" style="top: 1rem;right: 1rem;position: absolute;"><i class="material-icons text-success">web</i></div>
<h5 class="fw-bold card-title">ShakeCities</h5>
<p class="text-muted card-text mb-4">Unlock web ownership's future with ShakeCities! Create your free, secure Handshake domain site. Integrate crypto payments, explore HNSChat, and join us in shaping the decentralized web!</p><a class="btn btn-primary shadow" role="button" href="https://shakecities.com" target="_blank">Learn more</a>
</div>
</div>
</div>
@@ -108,7 +133,7 @@ Claim a free Handshake domain">
<p class="w-lg-50">Thanks to these donors for supporting the faucet</p>
</div>
</div>
<div class="row gy-4 row-cols-2 row-cols-md-4">
<div class="row gy-4 row-cols-1 row-cols-sm-1 row-cols-md-2 row-cols-lg-2 row-cols-xl-4 row-cols-xxl-4">
<div class="col">
<div class="card border-0 shadow-none">
<div class="card-body text-center d-flex flex-column align-items-center p-0"><img class="rounded-circle mb-3 fit-cover" width="130" height="130" src="assets/img/team/nathanwoodburn.webp" style="margin-top: 10px;">
@@ -148,7 +173,7 @@ Claim a free Handshake domain">
<div class="container py-4 py-lg-5">
<hr>
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
<p class="mb-0 copyright">Copyright © 2023 Nathan.Woodburn/</p>
<p class="mb-0 copyright">Copyright © 2025 Nathan.Woodburn/</p><a href="/stats">Stats</a>
</div>
</div>
</footer>

View File

@@ -10,13 +10,13 @@
<meta property="og:description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta name="twitter:title" content="Woodburn Faucet">
<meta property="og:image" content="https://faucet.woodburn.au/assets/img/android-chrome-512x512.png">
<meta property="og:type" content="website">
<meta name="description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta property="og:title" content="Woodburn Faucet">
<meta name="twitter:description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta name="description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta property="og:image" content="https://faucet.woodburn.au/assets/img/android-chrome-512x512.png">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
@@ -29,11 +29,27 @@ Claim a free Handshake domain">
<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/fonts/material-icons.min.css">
<link rel="stylesheet" href="assets/css/styles.min.css">
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://analytics.woodburn.au/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '5']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
<script async src="https://umami.woodburn.au/script.js" data-website-id="f9357bd9-88b0-473d-9484-b1d37b1b5c87"></script>
</head>
<body style="background: rgb(39,38,46);">
<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.svg" width="50em"><span>&nbsp;Woodburn Faucet</span></a><button class="navbar-toggler" data-bs-toggle="collapse"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button></div>
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="assets/img/favicon.svg" width="50em"><span>&nbsp;Woodburn Faucet</span></a></div>
</nav>
<header class="bg-dark" style="background: rgb(39, 38, 46);">
<div class="container pt-4 pt-xl-5">
@@ -53,10 +69,10 @@ Claim a free Handshake domain">
</div>
</div>
</header>
<section style="margin-top: 50px;">
<section style="margin: auto;margin-top: 50px;max-width: 90%;">
<div style="text-align: center;">
<h3>Donate to fund the faucet</h3>
<h4>{{address}}</h4>
<h4 class="text-truncate">{{address}}</h4>
</div>
</section>
<section style="background: #27262e;">
@@ -83,7 +99,16 @@ Claim a free Handshake domain">
<div class="card-body px-4 py-5 px-md-5">
<div class="bs-icon-lg d-flex justify-content-center align-items-center mb-3 bs-icon" style="top: 1rem;right: 1rem;position: absolute;"><i class="material-icons text-success">dns</i></div>
<h5 class="fw-bold card-title">Woodburn Registry</h5>
<p class="text-muted card-text mb-4">Free Handshake compatible DNS server. Also provides free SLDs for testing</p><a class="btn btn-primary shadow" role="button" href="https://reg.woodburn.au" target="_blank">Learn more</a>
<p class="text-muted card-text mb-4">Free Handshake DNS service. Also provides free SLDs on select TLDs</p><a class="btn btn-primary shadow" role="button" href="https://reg.woodburn.au" target="_blank">Learn more</a>
</div>
</div>
</div>
<div class="col mb-5">
<div class="card shadow-sm">
<div class="card-body px-4 py-5 px-md-5">
<div class="bs-icon-lg d-flex justify-content-center align-items-center mb-3 bs-icon" style="top: 1rem;right: 1rem;position: absolute;"><i class="material-icons text-success">web</i></div>
<h5 class="fw-bold card-title">ShakeCities</h5>
<p class="text-muted card-text mb-4">Unlock web ownership's future with ShakeCities! Create your free, secure Handshake domain site. Integrate crypto payments, explore HNSChat, and join us in shaping the decentralized web!</p><a class="btn btn-primary shadow" role="button" href="https://shakecities.com" target="_blank">Learn more</a>
</div>
</div>
</div>
@@ -99,7 +124,7 @@ Claim a free Handshake domain">
<p class="w-lg-50">Thanks to these donors for supporting the faucet</p>
</div>
</div>
<div class="row gy-4 row-cols-2 row-cols-md-4">
<div class="row gy-4 row-cols-1 row-cols-sm-1 row-cols-md-2 row-cols-lg-2 row-cols-xl-4 row-cols-xxl-4">
<div class="col">
<div class="card border-0 shadow-none">
<div class="card-body text-center d-flex flex-column align-items-center p-0"><img class="rounded-circle mb-3 fit-cover" width="130" height="130" src="assets/img/team/nathanwoodburn.webp" style="margin-top: 10px;">
@@ -139,7 +164,7 @@ Claim a free Handshake domain">
<div class="container py-4 py-lg-5">
<hr>
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
<p class="mb-0 copyright">Copyright © 2023 Nathan.Woodburn/</p>
<p class="mb-0 copyright">Copyright © 2025 Nathan.Woodburn/</p>
</div>
</div>
</footer>

View File

@@ -10,13 +10,13 @@
<meta property="og:description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta name="twitter:title" content="Woodburn Faucet">
<meta property="og:image" content="https://faucet.woodburn.au/assets/img/android-chrome-512x512.png">
<meta property="og:type" content="website">
<meta name="description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta property="og:title" content="Woodburn Faucet">
<meta name="twitter:description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta name="description" content="Woodburn Faucet
Claim a free Handshake domain">
<meta property="og:image" content="https://faucet.woodburn.au/assets/img/android-chrome-512x512.png">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
@@ -29,11 +29,27 @@ Claim a free Handshake domain">
<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/fonts/material-icons.min.css">
<link rel="stylesheet" href="assets/css/styles.min.css">
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://analytics.woodburn.au/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '5']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
<script async src="https://umami.woodburn.au/script.js" data-website-id="f9357bd9-88b0-473d-9484-b1d37b1b5c87"></script>
</head>
<body style="background: rgb(39,38,46);">
<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.svg" width="50em"><span>&nbsp;Woodburn Faucet</span></a><button class="navbar-toggler" data-bs-toggle="collapse"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button></div>
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="assets/img/favicon.svg" width="50em"><span>&nbsp;Woodburn Faucet</span></a></div>
</nav>
<header class="bg-dark" style="background: rgb(39, 38, 46);">
<div class="container pt-4 pt-xl-5">
@@ -53,10 +69,10 @@ Claim a free Handshake domain">
</div>
</div>
</header>
<section style="margin-top: 50px;">
<section style="margin: auto;margin-top: 50px;max-width: 90%;">
<div style="text-align: center;">
<h3>Donate to fund the faucet</h3>
<h4>{{address}}</h4>
<h4 class="text-truncate">{{address}}</h4>
</div>
</section>
<section style="background: #27262e;">
@@ -83,7 +99,16 @@ Claim a free Handshake domain">
<div class="card-body px-4 py-5 px-md-5">
<div class="bs-icon-lg d-flex justify-content-center align-items-center mb-3 bs-icon" style="top: 1rem;right: 1rem;position: absolute;"><i class="material-icons text-success">dns</i></div>
<h5 class="fw-bold card-title">Woodburn Registry</h5>
<p class="text-muted card-text mb-4">Free Handshake compatible DNS server. Also provides free SLDs for testing</p><a class="btn btn-primary shadow" role="button" href="https://reg.woodburn.au" target="_blank">Learn more</a>
<p class="text-muted card-text mb-4">Free Handshake DNS service. Also provides free SLDs on select TLDs</p><a class="btn btn-primary shadow" role="button" href="https://reg.woodburn.au" target="_blank">Learn more</a>
</div>
</div>
</div>
<div class="col mb-5">
<div class="card shadow-sm">
<div class="card-body px-4 py-5 px-md-5">
<div class="bs-icon-lg d-flex justify-content-center align-items-center mb-3 bs-icon" style="top: 1rem;right: 1rem;position: absolute;"><i class="material-icons text-success">web</i></div>
<h5 class="fw-bold card-title">ShakeCities</h5>
<p class="text-muted card-text mb-4">Unlock web ownership's future with ShakeCities! Create your free, secure Handshake domain site. Integrate crypto payments, explore HNSChat, and join us in shaping the decentralized web!</p><a class="btn btn-primary shadow" role="button" href="https://shakecities.com" target="_blank">Learn more</a>
</div>
</div>
</div>
@@ -99,7 +124,7 @@ Claim a free Handshake domain">
<p class="w-lg-50">Thanks to these donors for supporting the faucet</p>
</div>
</div>
<div class="row gy-4 row-cols-2 row-cols-md-4">
<div class="row gy-4 row-cols-1 row-cols-sm-1 row-cols-md-2 row-cols-lg-2 row-cols-xl-4 row-cols-xxl-4">
<div class="col">
<div class="card border-0 shadow-none">
<div class="card-body text-center d-flex flex-column align-items-center p-0"><img class="rounded-circle mb-3 fit-cover" width="130" height="130" src="assets/img/team/nathanwoodburn.webp" style="margin-top: 10px;">
@@ -139,7 +164,7 @@ Claim a free Handshake domain">
<div class="container py-4 py-lg-5">
<hr>
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
<p class="mb-0 copyright">Copyright © 2023 Nathan.Woodburn/</p>
<p class="mb-0 copyright">Copyright © 2025 Nathan.Woodburn/</p><a href="/stats">Stats</a>
</div>
</div>
</footer>