Compare commits
2 Commits
a78d999a61
...
copilot/im
| Author | SHA1 | Date | |
|---|---|---|---|
|
e489764ff8
|
|||
|
51c4416d4d
|
@@ -20,7 +20,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
|||||||
|
|
||||||
# Copy only app source files
|
# Copy only app source files
|
||||||
COPY blueprints blueprints
|
COPY blueprints blueprints
|
||||||
COPY main.py server.py curl.py tools.py mail.py ./
|
COPY main.py server.py curl.py tools.py mail.py cache_helper.py ./
|
||||||
COPY templates templates
|
COPY templates templates
|
||||||
COPY data data
|
COPY data data
|
||||||
COPY pwa pwa
|
COPY pwa pwa
|
||||||
@@ -55,6 +55,7 @@ COPY --from=build --chown=appuser:appgroup /app/server.py /app/
|
|||||||
COPY --from=build --chown=appuser:appgroup /app/curl.py /app/
|
COPY --from=build --chown=appuser:appgroup /app/curl.py /app/
|
||||||
COPY --from=build --chown=appuser:appgroup /app/tools.py /app/
|
COPY --from=build --chown=appuser:appgroup /app/tools.py /app/
|
||||||
COPY --from=build --chown=appuser:appgroup /app/mail.py /app/
|
COPY --from=build --chown=appuser:appgroup /app/mail.py /app/
|
||||||
|
COPY --from=build --chown=appuser:appgroup /app/cache_helper.py /app/
|
||||||
|
|
||||||
USER appuser
|
USER appuser
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|||||||
35
addCoin.py
35
addCoin.py
@@ -1,35 +1,38 @@
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
|
||||||
if not os.path.exists('.well-known/wallets'):
|
if not os.path.exists(".well-known/wallets"):
|
||||||
os.makedirs('.well-known/wallets')
|
os.makedirs(".well-known/wallets")
|
||||||
|
|
||||||
def addCoin(token:str, name:str, address:str):
|
|
||||||
with open('.well-known/wallets/'+token.upper(),'w') as f:
|
def addCoin(token: str, name: str, address: str):
|
||||||
|
with open(".well-known/wallets/" + token.upper(), "w") as f:
|
||||||
f.write(address)
|
f.write(address)
|
||||||
|
|
||||||
with open('.well-known/wallets/.coins','r') as f:
|
with open(".well-known/wallets/.coins", "r") as f:
|
||||||
coins = json.load(f)
|
coins = json.load(f)
|
||||||
|
|
||||||
coins[token.upper()] = f'{name} ({token.upper()})'
|
coins[token.upper()] = f"{name} ({token.upper()})"
|
||||||
with open('.well-known/wallets/.coins','w') as f:
|
with open(".well-known/wallets/.coins", "w") as f:
|
||||||
f.write(json.dumps(coins, indent=4))
|
f.write(json.dumps(coins, indent=4))
|
||||||
|
|
||||||
def addDomain(token:str, domain:str):
|
|
||||||
with open('.well-known/wallets/.domains','r') as f:
|
def addDomain(token: str, domain: str):
|
||||||
|
with open(".well-known/wallets/.domains", "r") as f:
|
||||||
domains = json.load(f)
|
domains = json.load(f)
|
||||||
|
|
||||||
domains[token.upper()] = domain
|
domains[token.upper()] = domain
|
||||||
with open('.well-known/wallets/.domains','w') as f:
|
with open(".well-known/wallets/.domains", "w") as f:
|
||||||
f.write(json.dumps(domains, indent=4))
|
f.write(json.dumps(domains, indent=4))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
# Ask user for token
|
# Ask user for token
|
||||||
token = input('Enter token symbol: ')
|
token = input("Enter token symbol: ")
|
||||||
name = input('Enter token name: ')
|
name = input("Enter token name: ")
|
||||||
address = input('Enter wallet address: ')
|
address = input("Enter wallet address: ")
|
||||||
addCoin(token, name, address)
|
addCoin(token, name, address)
|
||||||
|
|
||||||
if input('Do you want to add a domain? (y/n): ').lower() == 'y':
|
if input("Do you want to add a domain? (y/n): ").lower() == "y":
|
||||||
domain = input('Enter domain: ')
|
domain = input("Enter domain: ")
|
||||||
addDomain(token, domain)
|
addDomain(token, domain)
|
||||||
@@ -3,7 +3,7 @@ import os
|
|||||||
from cloudflare import Cloudflare
|
from cloudflare import Cloudflare
|
||||||
from tools import json_response
|
from tools import json_response
|
||||||
|
|
||||||
app = Blueprint('acme', __name__)
|
app = Blueprint("acme", __name__)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/hnsdoh-acme", methods=["POST"])
|
@app.route("/hnsdoh-acme", methods=["POST"])
|
||||||
@@ -23,7 +23,9 @@ def post():
|
|||||||
zone = cf.zones.list(name="hnsdoh.com").to_dict()
|
zone = cf.zones.list(name="hnsdoh.com").to_dict()
|
||||||
zone_id = zone["result"][0]["id"] # type: ignore
|
zone_id = zone["result"][0]["id"] # type: ignore
|
||||||
existing_records = cf.dns.records.list(
|
existing_records = cf.dns.records.list(
|
||||||
zone_id=zone_id, type="TXT", name="_acme-challenge.hnsdoh.com" # type: ignore
|
zone_id=zone_id,
|
||||||
|
type="TXT",
|
||||||
|
name="_acme-challenge.hnsdoh.com", # type: ignore
|
||||||
).to_dict()
|
).to_dict()
|
||||||
record_id = existing_records["result"][0]["id"] # type: ignore
|
record_id = existing_records["result"][0]["id"] # type: ignore
|
||||||
cf.dns.records.delete(dns_record_id=record_id, zone_id=zone_id)
|
cf.dns.records.delete(dns_record_id=record_id, zone_id=zone_id)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ HTTP_NOT_FOUND = 404
|
|||||||
HTTP_UNSUPPORTED_MEDIA = 415
|
HTTP_UNSUPPORTED_MEDIA = 415
|
||||||
HTTP_SERVER_ERROR = 500
|
HTTP_SERVER_ERROR = 500
|
||||||
|
|
||||||
app = Blueprint('api', __name__, url_prefix='/api/v1')
|
app = Blueprint("api", __name__, url_prefix="/api/v1")
|
||||||
# Register solana blueprint
|
# Register solana blueprint
|
||||||
app.register_blueprint(sol.app)
|
app.register_blueprint(sol.app)
|
||||||
|
|
||||||
@@ -27,7 +27,8 @@ app.register_blueprint(sol.app)
|
|||||||
@app.route("/help")
|
@app.route("/help")
|
||||||
def help():
|
def help():
|
||||||
"""Provide API documentation and help."""
|
"""Provide API documentation and help."""
|
||||||
return jsonify({
|
return jsonify(
|
||||||
|
{
|
||||||
"message": "Welcome to Nathan.Woodburn/ API! This is a personal website. For more information, visit https://nathan.woodburn.au",
|
"message": "Welcome to Nathan.Woodburn/ API! This is a personal website. For more information, visit https://nathan.woodburn.au",
|
||||||
"endpoints": {
|
"endpoints": {
|
||||||
"/time": "Get the current time",
|
"/time": "Get the current time",
|
||||||
@@ -42,19 +43,22 @@ def help():
|
|||||||
"/ping": "Just check if the site is up",
|
"/ping": "Just check if the site is up",
|
||||||
"/ip": "Get your IP address",
|
"/ip": "Get your IP address",
|
||||||
"/headers": "Get your request headers",
|
"/headers": "Get your request headers",
|
||||||
"/help": "Get this help message"
|
"/help": "Get this help message",
|
||||||
},
|
},
|
||||||
"base_url": "/api/v1",
|
"base_url": "/api/v1",
|
||||||
"version": getGitCommit(),
|
"version": getGitCommit(),
|
||||||
"ip": getClientIP(request),
|
"ip": getClientIP(request),
|
||||||
"status": HTTP_OK
|
"status": HTTP_OK,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/status")
|
@app.route("/status")
|
||||||
@app.route("/ping")
|
@app.route("/ping")
|
||||||
def status():
|
def status():
|
||||||
return json_response(request, "200 OK", HTTP_OK)
|
return json_response(request, "200 OK", HTTP_OK)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/version")
|
@app.route("/version")
|
||||||
def version():
|
def version():
|
||||||
"""Get the current version of the website."""
|
"""Get the current version of the website."""
|
||||||
@@ -68,45 +72,44 @@ def time():
|
|||||||
timezone_offset = datetime.timedelta(hours=nc_config["time-zone"])
|
timezone_offset = datetime.timedelta(hours=nc_config["time-zone"])
|
||||||
timezone = datetime.timezone(offset=timezone_offset)
|
timezone = datetime.timezone(offset=timezone_offset)
|
||||||
current_time = datetime.datetime.now(tz=timezone)
|
current_time = datetime.datetime.now(tz=timezone)
|
||||||
return jsonify({
|
return jsonify(
|
||||||
|
{
|
||||||
"timestring": current_time.strftime("%A, %B %d, %Y %I:%M %p"),
|
"timestring": current_time.strftime("%A, %B %d, %Y %I:%M %p"),
|
||||||
"timestamp": current_time.timestamp(),
|
"timestamp": current_time.timestamp(),
|
||||||
"timezone": nc_config["time-zone"],
|
"timezone": nc_config["time-zone"],
|
||||||
"timeISO": current_time.isoformat(),
|
"timeISO": current_time.isoformat(),
|
||||||
"ip": getClientIP(request),
|
"ip": getClientIP(request),
|
||||||
"status": HTTP_OK
|
"status": HTTP_OK,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/timezone")
|
@app.route("/timezone")
|
||||||
def timezone():
|
def timezone():
|
||||||
"""Get the current timezone setting."""
|
"""Get the current timezone setting."""
|
||||||
nc_config = get_nc_config()
|
nc_config = get_nc_config()
|
||||||
return jsonify({
|
return jsonify(
|
||||||
|
{
|
||||||
"timezone": nc_config["time-zone"],
|
"timezone": nc_config["time-zone"],
|
||||||
"ip": getClientIP(request),
|
"ip": getClientIP(request),
|
||||||
"status": HTTP_OK
|
"status": HTTP_OK,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/message")
|
@app.route("/message")
|
||||||
def message():
|
def message():
|
||||||
"""Get the message from the configuration."""
|
"""Get the message from the configuration."""
|
||||||
nc_config = get_nc_config()
|
nc_config = get_nc_config()
|
||||||
return jsonify({
|
return jsonify(
|
||||||
"message": nc_config["message"],
|
{"message": nc_config["message"], "ip": getClientIP(request), "status": HTTP_OK}
|
||||||
"ip": getClientIP(request),
|
)
|
||||||
"status": HTTP_OK
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/ip")
|
@app.route("/ip")
|
||||||
def ip():
|
def ip():
|
||||||
"""Get the client's IP address."""
|
"""Get the client's IP address."""
|
||||||
return jsonify({
|
return jsonify({"ip": getClientIP(request), "status": HTTP_OK})
|
||||||
"ip": getClientIP(request),
|
|
||||||
"status": HTTP_OK
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/email", methods=["POST"])
|
@app.route("/email", methods=["POST"])
|
||||||
@@ -114,7 +117,9 @@ def email_post():
|
|||||||
"""Send an email via the API (requires API key)."""
|
"""Send an email via the API (requires API key)."""
|
||||||
# Verify json
|
# Verify json
|
||||||
if not request.is_json:
|
if not request.is_json:
|
||||||
return json_response(request, "415 Unsupported Media Type", HTTP_UNSUPPORTED_MEDIA)
|
return json_response(
|
||||||
|
request, "415 Unsupported Media Type", HTTP_UNSUPPORTED_MEDIA
|
||||||
|
)
|
||||||
|
|
||||||
# Check if api key sent
|
# Check if api key sent
|
||||||
data = request.json
|
data = request.json
|
||||||
@@ -145,13 +150,16 @@ def project():
|
|||||||
"website": git["repo"].get("website"),
|
"website": git["repo"].get("website"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonify({
|
return jsonify(
|
||||||
|
{
|
||||||
"repo_name": repo_name,
|
"repo_name": repo_name,
|
||||||
"repo_description": repo_description,
|
"repo_description": repo_description,
|
||||||
"repo": gitinfo,
|
"repo": gitinfo,
|
||||||
"ip": getClientIP(request),
|
"ip": getClientIP(request),
|
||||||
"status": HTTP_OK
|
"status": HTTP_OK,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/tools")
|
@app.route("/tools")
|
||||||
def tools():
|
def tools():
|
||||||
@@ -164,6 +172,7 @@ def tools():
|
|||||||
|
|
||||||
return json_response(request, {"tools": tools}, HTTP_OK)
|
return json_response(request, {"tools": tools}, HTTP_OK)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/playing")
|
@app.route("/playing")
|
||||||
def playing():
|
def playing():
|
||||||
"""Get the currently playing Spotify track."""
|
"""Get the currently playing Spotify track."""
|
||||||
@@ -186,15 +195,11 @@ def headers():
|
|||||||
# Remove from headers
|
# Remove from headers
|
||||||
toremove.append(key)
|
toremove.append(key)
|
||||||
|
|
||||||
|
|
||||||
for key in toremove:
|
for key in toremove:
|
||||||
headers.pop(key)
|
headers.pop(key)
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({"headers": headers, "ip": getClientIP(request), "status": HTTP_OK})
|
||||||
"headers": headers,
|
|
||||||
"ip": getClientIP(request),
|
|
||||||
"status": HTTP_OK
|
|
||||||
})
|
|
||||||
|
|
||||||
@app.route("/page_date")
|
@app.route("/page_date")
|
||||||
def page_date():
|
def page_date():
|
||||||
@@ -211,33 +216,33 @@ def page_date():
|
|||||||
r = requests.get(url, timeout=5)
|
r = requests.get(url, timeout=5)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
return json_response(request, f"400 Bad Request 'url' unreachable: {e}", HTTP_BAD_REQUEST)
|
return json_response(
|
||||||
|
request, f"400 Bad Request 'url' unreachable: {e}", HTTP_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
page_text = r.text
|
page_text = r.text
|
||||||
|
|
||||||
# Remove ordinal suffixes globally
|
# Remove ordinal suffixes globally
|
||||||
page_text = re.sub(r'(\d+)(st|nd|rd|th)', r'\1', page_text, flags=re.IGNORECASE)
|
page_text = re.sub(r"(\d+)(st|nd|rd|th)", r"\1", page_text, flags=re.IGNORECASE)
|
||||||
# Remove HTML comments
|
# Remove HTML comments
|
||||||
page_text = re.sub(r'<!--.*?-->', '', page_text, flags=re.DOTALL)
|
page_text = re.sub(r"<!--.*?-->", "", page_text, flags=re.DOTALL)
|
||||||
|
|
||||||
date_patterns = [
|
date_patterns = [
|
||||||
r'(\d{4})[/-](\d{1,2})[/-](\d{1,2})', # YYYY-MM-DD
|
r"(\d{4})[/-](\d{1,2})[/-](\d{1,2})", # YYYY-MM-DD
|
||||||
r'(\d{1,2})[/-](\d{1,2})[/-](\d{4})', # DD-MM-YYYY
|
r"(\d{1,2})[/-](\d{1,2})[/-](\d{4})", # DD-MM-YYYY
|
||||||
r'(?:Last updated:|Updated:|Updated last:)?\s*(\d{1,2})\s+([A-Za-z]{3,9})[, ]?\s*(\d{4})', # DD Month YYYY
|
r"(?:Last updated:|Updated:|Updated last:)?\s*(\d{1,2})\s+([A-Za-z]{3,9})[, ]?\s*(\d{4})", # DD Month YYYY
|
||||||
r'(?:\b\w+\b\s+){0,3}([A-Za-z]{3,9})\s+(\d{1,2}),?\s*(\d{4})', # Month DD, YYYY with optional words
|
r"(?:\b\w+\b\s+){0,3}([A-Za-z]{3,9})\s+(\d{1,2}),?\s*(\d{4})", # Month DD, YYYY with optional words
|
||||||
r'\b(\d{4})(\d{2})(\d{2})\b', # YYYYMMDD
|
r"\b(\d{4})(\d{2})(\d{2})\b", # YYYYMMDD
|
||||||
r'(?:Last updated:|Updated:|Last update)?\s*([A-Za-z]{3,9})\s+(\d{4})', # Month YYYY only
|
r"(?:Last updated:|Updated:|Last update)?\s*([A-Za-z]{3,9})\s+(\d{4})", # Month YYYY only
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Structured data patterns
|
# Structured data patterns
|
||||||
json_date_patterns = {
|
json_date_patterns = {
|
||||||
r'"datePublished"\s*:\s*"([^"]+)"': "published",
|
r'"datePublished"\s*:\s*"([^"]+)"': "published",
|
||||||
r'"dateModified"\s*:\s*"([^"]+)"': "modified",
|
r'"dateModified"\s*:\s*"([^"]+)"': "modified",
|
||||||
r'<meta\s+(?:[^>]*?)property\s*=\s*"article:published_time"\s+content\s*=\s*"([^"]+)"': "published",
|
r'<meta\s+(?:[^>]*?)property\s*=\s*"article:published_time"\s+content\s*=\s*"([^"]+)"': "published",
|
||||||
r'<meta\s+(?:[^>]*?)property\s*=\s*"article:modified_time"\s+content\s*=\s*"([^"]+)"': "modified",
|
r'<meta\s+(?:[^>]*?)property\s*=\s*"article:modified_time"\s+content\s*=\s*"([^"]+)"': "modified",
|
||||||
r'<time\s+datetime\s*=\s*"([^"]+)"': "published"
|
r'<time\s+datetime\s*=\s*"([^"]+)"': "published",
|
||||||
}
|
}
|
||||||
|
|
||||||
found_dates = []
|
found_dates = []
|
||||||
@@ -255,7 +260,7 @@ def page_date():
|
|||||||
for match in re.findall(pattern, page_text):
|
for match in re.findall(pattern, page_text):
|
||||||
try:
|
try:
|
||||||
dt = date_parser.isoparse(match)
|
dt = date_parser.isoparse(match)
|
||||||
formatted_date = dt.strftime('%Y-%m-%d')
|
formatted_date = dt.strftime("%Y-%m-%d")
|
||||||
found_dates.append([[formatted_date], -1, date_type])
|
found_dates.append([[formatted_date], -1, date_type])
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
continue
|
continue
|
||||||
@@ -264,7 +269,9 @@ def page_date():
|
|||||||
return json_response(request, "Date not found on page", HTTP_BAD_REQUEST)
|
return json_response(request, "Date not found on page", HTTP_BAD_REQUEST)
|
||||||
|
|
||||||
today = datetime.date.today()
|
today = datetime.date.today()
|
||||||
tolerance_date = today + datetime.timedelta(days=1) # Allow for slight future dates (e.g., time zones)
|
tolerance_date = today + datetime.timedelta(
|
||||||
|
days=1
|
||||||
|
) # Allow for slight future dates (e.g., time zones)
|
||||||
# When processing dates
|
# When processing dates
|
||||||
processed_dates = []
|
processed_dates = []
|
||||||
for date_groups, pattern_format, date_type in found_dates:
|
for date_groups, pattern_format, date_type in found_dates:
|
||||||
@@ -285,18 +292,32 @@ def page_date():
|
|||||||
date_obj = {"date": dt.strftime("%Y-%m-%d"), "type": date_type}
|
date_obj = {"date": dt.strftime("%Y-%m-%d"), "type": date_type}
|
||||||
if verbose:
|
if verbose:
|
||||||
if pattern_format == -1:
|
if pattern_format == -1:
|
||||||
date_obj.update({"source": "metadata", "pattern_used": pattern_format, "raw": date_groups[0]})
|
date_obj.update(
|
||||||
|
{
|
||||||
|
"source": "metadata",
|
||||||
|
"pattern_used": pattern_format,
|
||||||
|
"raw": date_groups[0],
|
||||||
|
}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
date_obj.update({"source": "content", "pattern_used": pattern_format, "raw": " ".join(date_groups)})
|
date_obj.update(
|
||||||
|
{
|
||||||
|
"source": "content",
|
||||||
|
"pattern_used": pattern_format,
|
||||||
|
"raw": " ".join(date_groups),
|
||||||
|
}
|
||||||
|
)
|
||||||
processed_dates.append(date_obj)
|
processed_dates.append(date_obj)
|
||||||
|
|
||||||
if not processed_dates:
|
if not processed_dates:
|
||||||
if verbose:
|
if verbose:
|
||||||
return jsonify({
|
return jsonify(
|
||||||
|
{
|
||||||
"message": "No valid dates found on page",
|
"message": "No valid dates found on page",
|
||||||
"found_dates": found_dates,
|
"found_dates": found_dates,
|
||||||
"processed_dates": processed_dates
|
"processed_dates": processed_dates,
|
||||||
}), HTTP_BAD_REQUEST
|
}
|
||||||
|
), HTTP_BAD_REQUEST
|
||||||
return json_response(request, "No valid dates found on page", HTTP_BAD_REQUEST)
|
return json_response(request, "No valid dates found on page", HTTP_BAD_REQUEST)
|
||||||
# Sort dates and return latest
|
# Sort dates and return latest
|
||||||
processed_dates.sort(key=lambda x: x["date"])
|
processed_dates.sort(key=lambda x: x["date"])
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import re
|
|||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from tools import isCLI, getClientIP, getHandshakeScript
|
from tools import isCLI, getClientIP, getHandshakeScript
|
||||||
|
|
||||||
app = Blueprint('blog', __name__, url_prefix='/blog')
|
app = Blueprint("blog", __name__, url_prefix="/blog")
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=32)
|
@lru_cache(maxsize=32)
|
||||||
@@ -14,11 +14,13 @@ def list_page_files():
|
|||||||
blog_pages = os.listdir("data/blog")
|
blog_pages = os.listdir("data/blog")
|
||||||
# Sort pages by modified time, newest first
|
# Sort pages by modified time, newest first
|
||||||
blog_pages.sort(
|
blog_pages.sort(
|
||||||
key=lambda x: os.path.getmtime(os.path.join("data/blog", x)), reverse=True)
|
key=lambda x: os.path.getmtime(os.path.join("data/blog", x)), reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
# Remove .md extension
|
# Remove .md extension
|
||||||
blog_pages = [page.removesuffix(".md")
|
blog_pages = [
|
||||||
for page in blog_pages if page.endswith(".md")]
|
page.removesuffix(".md") for page in blog_pages if page.endswith(".md")
|
||||||
|
]
|
||||||
|
|
||||||
return blog_pages
|
return blog_pages
|
||||||
|
|
||||||
@@ -37,7 +39,8 @@ def get_blog_content(date):
|
|||||||
def render_markdown_to_html(content):
|
def render_markdown_to_html(content):
|
||||||
"""Convert markdown to HTML with caching."""
|
"""Convert markdown to HTML with caching."""
|
||||||
html = markdown.markdown(
|
html = markdown.markdown(
|
||||||
content, extensions=['sane_lists', 'codehilite', 'fenced_code'])
|
content, extensions=["sane_lists", "codehilite", "fenced_code"]
|
||||||
|
)
|
||||||
# Add target="_blank" to all links
|
# Add target="_blank" to all links
|
||||||
html = html.replace('<a href="', '<a target="_blank" href="')
|
html = html.replace('<a href="', '<a target="_blank" href="')
|
||||||
html = html.replace("<h4", "<h4 style='margin-bottom:0px;'")
|
html = html.replace("<h4", "<h4 style='margin-bottom:0px;'")
|
||||||
@@ -65,18 +68,18 @@ def render_page(date, handshake_scripts=None):
|
|||||||
|
|
||||||
|
|
||||||
def fix_numbered_lists(html):
|
def fix_numbered_lists(html):
|
||||||
soup = BeautifulSoup(html, 'html.parser')
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
|
||||||
# Find the <p> tag containing numbered steps
|
# Find the <p> tag containing numbered steps
|
||||||
paragraphs = soup.find_all('p')
|
paragraphs = soup.find_all("p")
|
||||||
for p in paragraphs:
|
for p in paragraphs:
|
||||||
content = p.decode_contents() # type: ignore
|
content = p.decode_contents() # type: ignore
|
||||||
|
|
||||||
# Check for likely numbered step structure
|
# Check for likely numbered step structure
|
||||||
if re.search(r'1\.\s', content):
|
if re.search(r"1\.\s", content):
|
||||||
# Split into pre-list and numbered steps
|
# Split into pre-list and numbered steps
|
||||||
# Match: <br>, optional whitespace, then a number and dot
|
# Match: <br>, optional whitespace, then a number and dot
|
||||||
parts = re.split(r'(?:<br\s*/?>)?\s*(\d+)\.\s', content)
|
parts = re.split(r"(?:<br\s*/?>)?\s*(\d+)\.\s", content)
|
||||||
|
|
||||||
# Result: [pre-text, '1', step1, '2', step2, ..., '10', step10]
|
# Result: [pre-text, '1', step1, '2', step2, ..., '10', step10]
|
||||||
pre_text = parts[0].strip()
|
pre_text = parts[0].strip()
|
||||||
@@ -85,10 +88,9 @@ def fix_numbered_lists(html):
|
|||||||
# Assemble the ordered list
|
# Assemble the ordered list
|
||||||
ol_items = []
|
ol_items = []
|
||||||
for i in range(0, len(steps), 2):
|
for i in range(0, len(steps), 2):
|
||||||
if i+1 < len(steps):
|
if i + 1 < len(steps):
|
||||||
step_html = steps[i+1].strip()
|
step_html = steps[i + 1].strip()
|
||||||
ol_items.append(
|
ol_items.append(f"<li style='list-style: auto;'>{step_html}</li>")
|
||||||
f"<li style='list-style: auto;'>{step_html}</li>")
|
|
||||||
|
|
||||||
# Build the final list HTML
|
# Build the final list HTML
|
||||||
ol_html = "<ol>\n" + "\n".join(ol_items) + "\n</ol>"
|
ol_html = "<ol>\n" + "\n".join(ol_items) + "\n</ol>"
|
||||||
@@ -97,7 +99,7 @@ def fix_numbered_lists(html):
|
|||||||
new_html = f"{pre_text}<br />\n{ol_html}" if pre_text else ol_html
|
new_html = f"{pre_text}<br />\n{ol_html}" if pre_text else ol_html
|
||||||
|
|
||||||
# Replace old <p> with parsed version
|
# Replace old <p> with parsed version
|
||||||
new_fragment = BeautifulSoup(new_html, 'html.parser')
|
new_fragment = BeautifulSoup(new_html, "html.parser")
|
||||||
p.replace_with(new_fragment)
|
p.replace_with(new_fragment)
|
||||||
break # Only process the first matching <p>
|
break # Only process the first matching <p>
|
||||||
|
|
||||||
@@ -134,16 +136,23 @@ def index():
|
|||||||
blog_pages = list_page_files()
|
blog_pages = list_page_files()
|
||||||
# Create a html list of pages
|
# Create a html list of pages
|
||||||
blog_pages = [
|
blog_pages = [
|
||||||
{"name": page.replace("_", " "), "url": f"/blog/{page}", "download": f"/blog/{page}.md"} for page in blog_pages
|
{
|
||||||
|
"name": page.replace("_", " "),
|
||||||
|
"url": f"/blog/{page}",
|
||||||
|
"download": f"/blog/{page}.md",
|
||||||
|
}
|
||||||
|
for page in blog_pages
|
||||||
]
|
]
|
||||||
|
|
||||||
# Render the template
|
# Render the template
|
||||||
return jsonify({
|
return jsonify(
|
||||||
|
{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"message": "Check out my various blog postsa",
|
"message": "Check out my various blog postsa",
|
||||||
"ip": getClientIP(request),
|
"ip": getClientIP(request),
|
||||||
"blogs": blog_pages
|
"blogs": blog_pages,
|
||||||
}), 200
|
}
|
||||||
|
), 200
|
||||||
|
|
||||||
|
|
||||||
@app.route("/<path:path>")
|
@app.route("/<path:path>")
|
||||||
@@ -158,14 +167,16 @@ def path(path):
|
|||||||
|
|
||||||
# Get the title from the file name
|
# Get the title from the file name
|
||||||
title = path.replace("_", " ")
|
title = path.replace("_", " ")
|
||||||
return jsonify({
|
return jsonify(
|
||||||
|
{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"message": f"Blog post: {title}",
|
"message": f"Blog post: {title}",
|
||||||
"ip": getClientIP(request),
|
"ip": getClientIP(request),
|
||||||
"title": title,
|
"title": title,
|
||||||
"content": content,
|
"content": content,
|
||||||
"download": f"/blog/{path}.md"
|
"download": f"/blog/{path}.md",
|
||||||
}), 200
|
}
|
||||||
|
), 200
|
||||||
|
|
||||||
|
|
||||||
@app.route("/<path:path>.md")
|
@app.route("/<path:path>.md")
|
||||||
@@ -175,4 +186,4 @@ def path_md(path):
|
|||||||
return render_template("404.html"), 404
|
return render_template("404.html"), 404
|
||||||
|
|
||||||
# Return the raw markdown file
|
# Return the raw markdown file
|
||||||
return content, 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
return content, 200, {"Content-Type": "text/plain; charset=utf-8"}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from bs4 import BeautifulSoup
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
# Create blueprint
|
# Create blueprint
|
||||||
app = Blueprint('now', __name__, url_prefix='/now')
|
app = Blueprint("now", __name__, url_prefix="/now")
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=16)
|
@lru_cache(maxsize=16)
|
||||||
@@ -55,7 +55,10 @@ def render(date, handshake_scripts=None):
|
|||||||
|
|
||||||
date_formatted = datetime.datetime.strptime(date, "%y_%m_%d")
|
date_formatted = datetime.datetime.strptime(date, "%y_%m_%d")
|
||||||
date_formatted = date_formatted.strftime("%A, %B %d, %Y")
|
date_formatted = date_formatted.strftime("%A, %B %d, %Y")
|
||||||
return render_template(f"now/{date}.html", DATE=date_formatted, handshake_scripts=handshake_scripts)
|
return render_template(
|
||||||
|
f"now/{date}.html", DATE=date_formatted, handshake_scripts=handshake_scripts
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def render_curl(date=None):
|
def render_curl(date=None):
|
||||||
# If the date is not available, render the latest page
|
# If the date is not available, render the latest page
|
||||||
@@ -75,7 +78,7 @@ def render_curl(date=None):
|
|||||||
# Load HTML
|
# Load HTML
|
||||||
with open(f"templates/now/{date}.html", "r", encoding="utf-8") as f:
|
with open(f"templates/now/{date}.html", "r", encoding="utf-8") as f:
|
||||||
raw_html = f.read().replace("{{ date }}", date_formatted)
|
raw_html = f.read().replace("{{ date }}", date_formatted)
|
||||||
soup = BeautifulSoup(raw_html, 'html.parser')
|
soup = BeautifulSoup(raw_html, "html.parser")
|
||||||
|
|
||||||
posts = []
|
posts = []
|
||||||
|
|
||||||
@@ -107,7 +110,7 @@ def render_curl(date=None):
|
|||||||
for line in text.splitlines():
|
for line in text.splitlines():
|
||||||
while len(line) > MAX_WIDTH:
|
while len(line) > MAX_WIDTH:
|
||||||
# Find last space within max_width
|
# Find last space within max_width
|
||||||
split_at = line.rfind(' ', 0, MAX_WIDTH)
|
split_at = line.rfind(" ", 0, MAX_WIDTH)
|
||||||
if split_at == -1:
|
if split_at == -1:
|
||||||
split_at = MAX_WIDTH
|
split_at = MAX_WIDTH
|
||||||
wrapped_lines.append(line[:split_at].rstrip())
|
wrapped_lines.append(line[:split_at].rstrip())
|
||||||
@@ -128,8 +131,9 @@ def render_curl(date=None):
|
|||||||
for post in posts:
|
for post in posts:
|
||||||
response += f"[1m{post['header']}[0m\n\n{post['content']}\n\n"
|
response += f"[1m{post['header']}[0m\n\n{post['content']}\n\n"
|
||||||
|
|
||||||
return render_template("now.ascii", date=date_formatted, content=response, header=get_header())
|
return render_template(
|
||||||
|
"now.ascii", date=date_formatted, content=response, header=get_header()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/", strict_slashes=False)
|
@app.route("/", strict_slashes=False)
|
||||||
@@ -157,8 +161,9 @@ def old():
|
|||||||
date_fmt = datetime.datetime.strptime(date, "%y_%m_%d")
|
date_fmt = datetime.datetime.strptime(date, "%y_%m_%d")
|
||||||
date_fmt = date_fmt.strftime("%A, %B %d, %Y")
|
date_fmt = date_fmt.strftime("%A, %B %d, %Y")
|
||||||
response += f"{date_fmt} - /now/{link}\n"
|
response += f"{date_fmt} - /now/{link}\n"
|
||||||
return render_template("now.ascii", date="Old Now Pages", content=response, header=get_header())
|
return render_template(
|
||||||
|
"now.ascii", date="Old Now Pages", content=response, header=get_header()
|
||||||
|
)
|
||||||
|
|
||||||
html = '<ul class="list-group">'
|
html = '<ul class="list-group">'
|
||||||
html += f'<a style="text-decoration:none;" href="/now"><li class="list-group-item" style="background-color:#000000;color:#ffffff;">{get_latest_date(True)}</li></a>'
|
html += f'<a style="text-decoration:none;" href="/now"><li class="list-group-item" style="background-color:#000000;color:#ffffff;">{get_latest_date(True)}</li></a>'
|
||||||
@@ -171,7 +176,9 @@ def old():
|
|||||||
|
|
||||||
html += "</ul>"
|
html += "</ul>"
|
||||||
return render_template(
|
return render_template(
|
||||||
"now/old.html", handshake_scripts=getHandshakeScript(request.host), now_pages=html
|
"now/old.html",
|
||||||
|
handshake_scripts=getHandshakeScript(request.host),
|
||||||
|
now_pages=html,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -189,7 +196,7 @@ def rss():
|
|||||||
link = page.strip(".html")
|
link = page.strip(".html")
|
||||||
date = datetime.datetime.strptime(link, "%y_%m_%d")
|
date = datetime.datetime.strptime(link, "%y_%m_%d")
|
||||||
date = date.strftime("%A, %B %d, %Y")
|
date = date.strftime("%A, %B %d, %Y")
|
||||||
rss += f'<item><title>What\'s Happening {date}</title><link>{host}/now/{link}</link><description>Latest updates for {date}</description><guid>{host}/now/{link}</guid></item>'
|
rss += f"<item><title>What's Happening {date}</title><link>{host}/now/{link}</link><description>Latest updates for {date}</description><guid>{host}/now/{link}</guid></item>"
|
||||||
rss += "</channel></rss>"
|
rss += "</channel></rss>"
|
||||||
return make_response(rss, 200, {"Content-Type": "application/rss+xml"})
|
return make_response(rss, 200, {"Content-Type": "application/rss+xml"})
|
||||||
|
|
||||||
@@ -200,6 +207,17 @@ def json():
|
|||||||
host = "https://" + request.host
|
host = "https://" + request.host
|
||||||
if ":" in request.host:
|
if ":" in request.host:
|
||||||
host = "http://" + request.host
|
host = "http://" + request.host
|
||||||
now_pages = [{"url": host+"/now/"+page.strip(".html"), "date": datetime.datetime.strptime(page.strip(".html"), "%y_%m_%d").strftime(
|
now_pages = [
|
||||||
"%A, %B %d, %Y"), "title": "What's Happening "+datetime.datetime.strptime(page.strip(".html"), "%y_%m_%d").strftime("%A, %B %d, %Y")} for page in now_pages]
|
{
|
||||||
|
"url": host + "/now/" + page.strip(".html"),
|
||||||
|
"date": datetime.datetime.strptime(
|
||||||
|
page.strip(".html"), "%y_%m_%d"
|
||||||
|
).strftime("%A, %B %d, %Y"),
|
||||||
|
"title": "What's Happening "
|
||||||
|
+ datetime.datetime.strptime(page.strip(".html"), "%y_%m_%d").strftime(
|
||||||
|
"%A, %B %d, %Y"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
for page in now_pages
|
||||||
|
]
|
||||||
return jsonify(now_pages)
|
return jsonify(now_pages)
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ from flask import Blueprint, make_response, request
|
|||||||
from tools import error_response
|
from tools import error_response
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
app = Blueprint('podcast', __name__)
|
app = Blueprint("podcast", __name__)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/ID1")
|
@app.route("/ID1")
|
||||||
def index():
|
def index():
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import binascii
|
|||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
|
|
||||||
app = Blueprint('sol', __name__)
|
app = Blueprint("sol", __name__)
|
||||||
|
|
||||||
SOLANA_HEADERS = {
|
SOLANA_HEADERS = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-Action-Version": "2.4.2",
|
"X-Action-Version": "2.4.2",
|
||||||
"X-Blockchain-Ids": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
|
"X-Blockchain-Ids": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
||||||
}
|
}
|
||||||
|
|
||||||
SOLANA_ADDRESS = None
|
SOLANA_ADDRESS = None
|
||||||
@@ -23,15 +23,19 @@ if os.path.isfile(".well-known/wallets/SOL"):
|
|||||||
address = file.read()
|
address = file.read()
|
||||||
SOLANA_ADDRESS = Pubkey.from_string(address.strip())
|
SOLANA_ADDRESS = Pubkey.from_string(address.strip())
|
||||||
|
|
||||||
|
|
||||||
def create_transaction(sender_address: str, amount: float) -> str:
|
def create_transaction(sender_address: str, amount: float) -> str:
|
||||||
if SOLANA_ADDRESS is None:
|
if SOLANA_ADDRESS is None:
|
||||||
raise ValueError("SOLANA_ADDRESS is not set. Please ensure the .well-known/wallets/SOL file exists and contains a valid address.")
|
raise ValueError(
|
||||||
|
"SOLANA_ADDRESS is not set. Please ensure the .well-known/wallets/SOL file exists and contains a valid address."
|
||||||
|
)
|
||||||
# Create transaction
|
# Create transaction
|
||||||
sender = Pubkey.from_string(sender_address)
|
sender = Pubkey.from_string(sender_address)
|
||||||
transfer_ix = transfer(
|
transfer_ix = transfer(
|
||||||
TransferParams(
|
TransferParams(
|
||||||
from_pubkey=sender, to_pubkey=SOLANA_ADDRESS, lamports=int(
|
from_pubkey=sender,
|
||||||
amount * 1000000000)
|
to_pubkey=SOLANA_ADDRESS,
|
||||||
|
lamports=int(amount * 1000000000),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
solana_client = Client("https://api.mainnet-beta.solana.com")
|
solana_client = Client("https://api.mainnet-beta.solana.com")
|
||||||
@@ -50,11 +54,15 @@ def create_transaction(sender_address: str, amount: float) -> str:
|
|||||||
base64_string = base64.b64encode(raw_bytes).decode("utf-8")
|
base64_string = base64.b64encode(raw_bytes).decode("utf-8")
|
||||||
return base64_string
|
return base64_string
|
||||||
|
|
||||||
|
|
||||||
def get_solana_address() -> str:
|
def get_solana_address() -> str:
|
||||||
if SOLANA_ADDRESS is None:
|
if SOLANA_ADDRESS is None:
|
||||||
raise ValueError("SOLANA_ADDRESS is not set. Please ensure the .well-known/wallets/SOL file exists and contains a valid address.")
|
raise ValueError(
|
||||||
|
"SOLANA_ADDRESS is not set. Please ensure the .well-known/wallets/SOL file exists and contains a valid address."
|
||||||
|
)
|
||||||
return str(SOLANA_ADDRESS)
|
return str(SOLANA_ADDRESS)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/donate", methods=["GET", "OPTIONS"])
|
@app.route("/donate", methods=["GET", "OPTIONS"])
|
||||||
def sol_donate():
|
def sol_donate():
|
||||||
data = {
|
data = {
|
||||||
@@ -103,7 +111,6 @@ def sol_donate_amount(amount):
|
|||||||
|
|
||||||
@app.route("/donate/<amount>", methods=["POST"])
|
@app.route("/donate/<amount>", methods=["POST"])
|
||||||
def sol_donate_post(amount):
|
def sol_donate_post(amount):
|
||||||
|
|
||||||
if not request.json:
|
if not request.json:
|
||||||
return jsonify({"message": "Error: No JSON data provided"}), 400, SOLANA_HEADERS
|
return jsonify({"message": "Error: No JSON data provided"}), 400, SOLANA_HEADERS
|
||||||
|
|
||||||
@@ -122,4 +129,8 @@ def sol_donate_post(amount):
|
|||||||
return jsonify({"message": "Error: Amount too small"}), 400, SOLANA_HEADERS
|
return jsonify({"message": "Error: Amount too small"}), 400, SOLANA_HEADERS
|
||||||
|
|
||||||
transaction = create_transaction(sender, amount)
|
transaction = create_transaction(sender, amount)
|
||||||
return jsonify({"message": "Success", "transaction": transaction}), 200, SOLANA_HEADERS
|
return (
|
||||||
|
jsonify({"message": "Success", "transaction": transaction}),
|
||||||
|
200,
|
||||||
|
SOLANA_HEADERS,
|
||||||
|
)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import requests
|
|||||||
import time
|
import time
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
app = Blueprint('spotify', __name__, url_prefix='/spotify')
|
app = Blueprint("spotify", __name__, url_prefix="/spotify")
|
||||||
|
|
||||||
CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
|
CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
|
||||||
CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
|
CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
|
||||||
@@ -21,6 +21,7 @@ ACCESS_TOKEN = None
|
|||||||
REFRESH_TOKEN = os.getenv("SPOTIFY_REFRESH_TOKEN")
|
REFRESH_TOKEN = os.getenv("SPOTIFY_REFRESH_TOKEN")
|
||||||
TOKEN_EXPIRES = 0
|
TOKEN_EXPIRES = 0
|
||||||
|
|
||||||
|
|
||||||
def refresh_access_token():
|
def refresh_access_token():
|
||||||
"""Refresh Spotify access token when expired."""
|
"""Refresh Spotify access token when expired."""
|
||||||
global ACCESS_TOKEN, TOKEN_EXPIRES
|
global ACCESS_TOKEN, TOKEN_EXPIRES
|
||||||
@@ -52,6 +53,7 @@ def refresh_access_token():
|
|||||||
TOKEN_EXPIRES = time.time() + token_info.get("expires_in", 3600)
|
TOKEN_EXPIRES = time.time() + token_info.get("expires_in", 3600)
|
||||||
return ACCESS_TOKEN
|
return ACCESS_TOKEN
|
||||||
|
|
||||||
|
|
||||||
@app.route("/login")
|
@app.route("/login")
|
||||||
def login():
|
def login():
|
||||||
auth_query = (
|
auth_query = (
|
||||||
@@ -60,6 +62,7 @@ def login():
|
|||||||
)
|
)
|
||||||
return redirect(auth_query)
|
return redirect(auth_query)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/callback")
|
@app.route("/callback")
|
||||||
def callback():
|
def callback():
|
||||||
code = request.args.get("code")
|
code = request.args.get("code")
|
||||||
@@ -76,12 +79,14 @@ def callback():
|
|||||||
response = requests.post(SPOTIFY_TOKEN_URL, data=data)
|
response = requests.post(SPOTIFY_TOKEN_URL, data=data)
|
||||||
token_info = response.json()
|
token_info = response.json()
|
||||||
if "access_token" not in token_info:
|
if "access_token" not in token_info:
|
||||||
return json_response(request, {"error": "Failed to obtain token", "details": token_info}, 400)
|
return json_response(
|
||||||
|
request, {"error": "Failed to obtain token", "details": token_info}, 400
|
||||||
|
)
|
||||||
|
|
||||||
access_token = token_info["access_token"]
|
access_token = token_info["access_token"]
|
||||||
me = requests.get(
|
me = requests.get(
|
||||||
"https://api.spotify.com/v1/me",
|
"https://api.spotify.com/v1/me",
|
||||||
headers={"Authorization": f"Bearer {access_token}"}
|
headers={"Authorization": f"Bearer {access_token}"},
|
||||||
).json()
|
).json()
|
||||||
|
|
||||||
if me.get("id") != ALLOWED_SPOTIFY_USER_ID:
|
if me.get("id") != ALLOWED_SPOTIFY_USER_ID:
|
||||||
@@ -93,12 +98,14 @@ def callback():
|
|||||||
print("Refresh Token:", REFRESH_TOKEN)
|
print("Refresh Token:", REFRESH_TOKEN)
|
||||||
return redirect(url_for("spotify.currently_playing"))
|
return redirect(url_for("spotify.currently_playing"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/", strict_slashes=False)
|
@app.route("/", strict_slashes=False)
|
||||||
@app.route("/playing")
|
@app.route("/playing")
|
||||||
def currently_playing():
|
def currently_playing():
|
||||||
"""Public endpoint showing your current track."""
|
"""Public endpoint showing your current track."""
|
||||||
track = get_spotify_track()
|
track = get_spotify_track()
|
||||||
return json_response(request, {"spotify":track}, 200)
|
return json_response(request, {"spotify": track}, 200)
|
||||||
|
|
||||||
|
|
||||||
def get_spotify_track():
|
def get_spotify_track():
|
||||||
"""Internal function to get current playing track without HTTP context."""
|
"""Internal function to get current playing track without HTTP context."""
|
||||||
@@ -124,7 +131,7 @@ def get_spotify_track():
|
|||||||
"album_name": data["item"]["album"]["name"],
|
"album_name": data["item"]["album"]["name"],
|
||||||
"album_art": data["item"]["album"]["images"][0]["url"],
|
"album_art": data["item"]["album"]["images"][0]["url"],
|
||||||
"is_playing": data["is_playing"],
|
"is_playing": data["is_playing"],
|
||||||
"progress_ms": data.get("progress_ms",0),
|
"progress_ms": data.get("progress_ms", 0),
|
||||||
"duration_ms": data["item"].get("duration_ms",1)
|
"duration_ms": data["item"].get("duration_ms", 1),
|
||||||
}
|
}
|
||||||
return track
|
return track
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from flask import Blueprint, request
|
from flask import Blueprint, request
|
||||||
from tools import json_response
|
from tools import json_response
|
||||||
|
|
||||||
app = Blueprint('template', __name__)
|
app = Blueprint("template", __name__)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/", strict_slashes=False)
|
@app.route("/", strict_slashes=False)
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
from flask import Blueprint, make_response, request, jsonify, send_from_directory, redirect
|
from flask import (
|
||||||
|
Blueprint,
|
||||||
|
make_response,
|
||||||
|
request,
|
||||||
|
jsonify,
|
||||||
|
send_from_directory,
|
||||||
|
redirect,
|
||||||
|
)
|
||||||
from tools import error_response
|
from tools import error_response
|
||||||
import os
|
import os
|
||||||
|
|
||||||
app = Blueprint('well-known', __name__, url_prefix='/.well-known')
|
app = Blueprint("well-known", __name__, url_prefix="/.well-known")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/<path:path>")
|
@app.route("/<path:path>")
|
||||||
@@ -12,7 +19,7 @@ def index(path):
|
|||||||
|
|
||||||
@app.route("/wallets/<path:path>")
|
@app.route("/wallets/<path:path>")
|
||||||
def wallets(path):
|
def wallets(path):
|
||||||
if path[0] == "." and 'proof' not in path:
|
if path[0] == "." and "proof" not in path:
|
||||||
return send_from_directory(
|
return send_from_directory(
|
||||||
".well-known/wallets", path, mimetype="application/json"
|
".well-known/wallets", path, mimetype="application/json"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Cache helper module for expensive API calls and configuration.
|
Cache helper module for expensive API calls and configuration.
|
||||||
Provides centralized caching with TTL for external API calls.
|
Provides centralized caching with TTL for external API calls.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
@@ -26,14 +27,17 @@ def get_nc_config():
|
|||||||
current_time = datetime.datetime.now().timestamp()
|
current_time = datetime.datetime.now().timestamp()
|
||||||
|
|
||||||
# Check if cache is valid
|
# Check if cache is valid
|
||||||
if _nc_config_cache["data"] and (current_time - _nc_config_cache["timestamp"]) < _nc_config_ttl:
|
if (
|
||||||
|
_nc_config_cache["data"]
|
||||||
|
and (current_time - _nc_config_cache["timestamp"]) < _nc_config_ttl
|
||||||
|
):
|
||||||
return _nc_config_cache["data"]
|
return _nc_config_cache["data"]
|
||||||
|
|
||||||
# Fetch new config
|
# Fetch new config
|
||||||
try:
|
try:
|
||||||
config = requests.get(
|
config = requests.get(
|
||||||
"https://cloud.woodburn.au/s/4ToXgFe3TnnFcN7/download/website-conf.json",
|
"https://cloud.woodburn.au/s/4ToXgFe3TnnFcN7/download/website-conf.json",
|
||||||
timeout=5
|
timeout=5,
|
||||||
).json()
|
).json()
|
||||||
_nc_config_cache = {"data": config, "timestamp": current_time}
|
_nc_config_cache = {"data": config, "timestamp": current_time}
|
||||||
return config
|
return config
|
||||||
@@ -61,15 +65,20 @@ def get_git_latest_activity():
|
|||||||
current_time = datetime.datetime.now().timestamp()
|
current_time = datetime.datetime.now().timestamp()
|
||||||
|
|
||||||
# Check if cache is valid
|
# Check if cache is valid
|
||||||
if _git_data_cache["data"] and (current_time - _git_data_cache["timestamp"]) < _git_data_ttl:
|
if (
|
||||||
|
_git_data_cache["data"]
|
||||||
|
and (current_time - _git_data_cache["timestamp"]) < _git_data_ttl
|
||||||
|
):
|
||||||
return _git_data_cache["data"]
|
return _git_data_cache["data"]
|
||||||
|
|
||||||
# Fetch new data
|
# Fetch new data
|
||||||
try:
|
try:
|
||||||
git = requests.get(
|
git = requests.get(
|
||||||
"https://git.woodburn.au/api/v1/users/nathanwoodburn/activities/feeds?only-performed-by=true&limit=1",
|
"https://git.woodburn.au/api/v1/users/nathanwoodburn/activities/feeds?only-performed-by=true&limit=1",
|
||||||
headers={"Authorization": os.getenv("GIT_AUTH") or os.getenv("git_token") or ""},
|
headers={
|
||||||
timeout=5
|
"Authorization": os.getenv("GIT_AUTH") or os.getenv("git_token") or ""
|
||||||
|
},
|
||||||
|
timeout=5,
|
||||||
)
|
)
|
||||||
git_data = git.json()
|
git_data = git.json()
|
||||||
if git_data and len(git_data) > 0:
|
if git_data and len(git_data) > 0:
|
||||||
@@ -111,15 +120,17 @@ def get_projects(limit=3):
|
|||||||
current_time = datetime.datetime.now().timestamp()
|
current_time = datetime.datetime.now().timestamp()
|
||||||
|
|
||||||
# Check if cache is valid
|
# Check if cache is valid
|
||||||
if _projects_cache["data"] and (current_time - _projects_cache["timestamp"]) < _projects_ttl:
|
if (
|
||||||
|
_projects_cache["data"]
|
||||||
|
and (current_time - _projects_cache["timestamp"]) < _projects_ttl
|
||||||
|
):
|
||||||
return _projects_cache["data"][:limit]
|
return _projects_cache["data"][:limit]
|
||||||
|
|
||||||
# Fetch new data
|
# Fetch new data
|
||||||
try:
|
try:
|
||||||
projects = []
|
projects = []
|
||||||
projectsreq = requests.get(
|
projectsreq = requests.get(
|
||||||
"https://git.woodburn.au/api/v1/users/nathanwoodburn/repos",
|
"https://git.woodburn.au/api/v1/users/nathanwoodburn/repos", timeout=5
|
||||||
timeout=5
|
|
||||||
)
|
)
|
||||||
projects = projectsreq.json()
|
projects = projectsreq.json()
|
||||||
|
|
||||||
@@ -128,7 +139,7 @@ def get_projects(limit=3):
|
|||||||
while 'rel="next"' in projectsreq.headers.get("link", ""):
|
while 'rel="next"' in projectsreq.headers.get("link", ""):
|
||||||
projectsreq = requests.get(
|
projectsreq = requests.get(
|
||||||
f"https://git.woodburn.au/api/v1/users/nathanwoodburn/repos?page={pageNum}",
|
f"https://git.woodburn.au/api/v1/users/nathanwoodburn/repos?page={pageNum}",
|
||||||
timeout=5
|
timeout=5,
|
||||||
)
|
)
|
||||||
projects += projectsreq.json()
|
projects += projectsreq.json()
|
||||||
pageNum += 1
|
pageNum += 1
|
||||||
@@ -143,7 +154,9 @@ def get_projects(limit=3):
|
|||||||
project["name"] = project["name"].replace("_", " ").replace("-", " ")
|
project["name"] = project["name"].replace("_", " ").replace("-", " ")
|
||||||
|
|
||||||
# Sort by last updated
|
# Sort by last updated
|
||||||
projects_sorted = sorted(projects, key=lambda x: x.get("updated_at", ""), reverse=True)
|
projects_sorted = sorted(
|
||||||
|
projects, key=lambda x: x.get("updated_at", ""), reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
# Remove duplicates by name
|
# Remove duplicates by name
|
||||||
seen_names = set()
|
seen_names = set()
|
||||||
@@ -178,14 +191,16 @@ def get_uptime_status():
|
|||||||
current_time = datetime.datetime.now().timestamp()
|
current_time = datetime.datetime.now().timestamp()
|
||||||
|
|
||||||
# Check if cache is valid
|
# Check if cache is valid
|
||||||
if _uptime_cache["data"] is not None and (current_time - _uptime_cache["timestamp"]) < _uptime_ttl:
|
if (
|
||||||
|
_uptime_cache["data"] is not None
|
||||||
|
and (current_time - _uptime_cache["timestamp"]) < _uptime_ttl
|
||||||
|
):
|
||||||
return _uptime_cache["data"]
|
return _uptime_cache["data"]
|
||||||
|
|
||||||
# Fetch new data
|
# Fetch new data
|
||||||
try:
|
try:
|
||||||
uptime = requests.get(
|
uptime = requests.get(
|
||||||
"https://uptime.woodburn.au/api/status-page/main/badge",
|
"https://uptime.woodburn.au/api/status-page/main/badge", timeout=5
|
||||||
timeout=5
|
|
||||||
)
|
)
|
||||||
content = uptime.content.decode("utf-8").lower()
|
content = uptime.content.decode("utf-8").lower()
|
||||||
status = "maintenance" in content or uptime.content.count(b"Up") > 1
|
status = "maintenance" in content or uptime.content.count(b"Up") > 1
|
||||||
@@ -247,4 +262,3 @@ def get_wallet_domains():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading domains: {e}")
|
print(f"Error loading domains: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|||||||
23
cleanSite.py
23
cleanSite.py
@@ -1,36 +1,37 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
def cleanSite(path:str):
|
|
||||||
|
def cleanSite(path: str):
|
||||||
# Check if the file is sitemap.xml
|
# Check if the file is sitemap.xml
|
||||||
if path.endswith('sitemap.xml'):
|
if path.endswith("sitemap.xml"):
|
||||||
# Open the file
|
# Open the file
|
||||||
with open(path, 'r') as f:
|
with open(path, "r") as f:
|
||||||
# Read the content
|
# Read the content
|
||||||
content = f.read()
|
content = f.read()
|
||||||
# Replace all .html with empty string
|
# Replace all .html with empty string
|
||||||
content = content.replace('.html', '')
|
content = content.replace(".html", "")
|
||||||
# Write the content back to the file
|
# Write the content back to the file
|
||||||
with open(path, 'w') as f:
|
with open(path, "w") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
# Skip the file
|
# Skip the file
|
||||||
return
|
return
|
||||||
|
|
||||||
# If the file is not an html file, skip it
|
# If the file is not an html file, skip it
|
||||||
if not path.endswith('.html'):
|
if not path.endswith(".html"):
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
for file in os.listdir(path):
|
for file in os.listdir(path):
|
||||||
cleanSite(path + '/' + file)
|
cleanSite(path + "/" + file)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Open the file
|
# Open the file
|
||||||
with open(path, 'r') as f:
|
with open(path, "r") as f:
|
||||||
# Read and remove all .html
|
# Read and remove all .html
|
||||||
content = f.read().replace('.html"', '"')
|
content = f.read().replace('.html"', '"')
|
||||||
# Write the cleaned content back to the file
|
# Write the cleaned content back to the file
|
||||||
with open(path, 'w') as f:
|
with open(path, "w") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
for file in os.listdir('templates'):
|
for file in os.listdir("templates"):
|
||||||
cleanSite('templates/' + file)
|
cleanSite("templates/" + file)
|
||||||
|
|||||||
89
curl.py
89
curl.py
@@ -8,7 +8,8 @@ from cache_helper import get_git_latest_activity, get_projects as get_projects_c
|
|||||||
|
|
||||||
MAX_WIDTH = 80
|
MAX_WIDTH = 80
|
||||||
|
|
||||||
def clean_path(path:str):
|
|
||||||
|
def clean_path(path: str):
|
||||||
path = path.strip("/ ").lower()
|
path = path.strip("/ ").lower()
|
||||||
# Strip any .html extension
|
# Strip any .html extension
|
||||||
if path.endswith(".html"):
|
if path.endswith(".html"):
|
||||||
@@ -19,19 +20,21 @@ def clean_path(path:str):
|
|||||||
path = "index"
|
path = "index"
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1)
|
@lru_cache(maxsize=1)
|
||||||
def get_header():
|
def get_header():
|
||||||
with open("templates/header.ascii", "r") as f:
|
with open("templates/header.ascii", "r") as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=16)
|
@lru_cache(maxsize=16)
|
||||||
def get_current_project():
|
def get_current_project():
|
||||||
git = get_git_latest_activity()
|
git = get_git_latest_activity()
|
||||||
repo_name = git["repo"]["name"].lower()
|
repo_name = git["repo"]["name"].lower()
|
||||||
repo_description = git["repo"]["description"]
|
repo_description = git["repo"]["description"]
|
||||||
if not repo_description:
|
if not repo_description:
|
||||||
return f"[1;36m{repo_name}[0m"
|
return f"[1;36m{repo_name}[0m"
|
||||||
return f"[1;36m{repo_name}[0m - [1m{repo_description}[0m"
|
return f"[1;36m{repo_name}[0m - [1m{repo_description}[0m"
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=16)
|
@lru_cache(maxsize=16)
|
||||||
@@ -39,11 +42,13 @@ def get_projects():
|
|||||||
projects_data = get_projects_cached(limit=5)
|
projects_data = get_projects_cached(limit=5)
|
||||||
projects = ""
|
projects = ""
|
||||||
for project in projects_data:
|
for project in projects_data:
|
||||||
projects += f"""[1m{project['name']}[0m - {project['description'] if project['description'] else 'No description'}
|
projects += f"""[1m{project["name"]}[0m - {project["description"] if project["description"] else "No description"}
|
||||||
{project['html_url']}
|
{project["html_url"]}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return projects
|
return projects
|
||||||
|
|
||||||
|
|
||||||
def curl_response(request):
|
def curl_response(request):
|
||||||
# Check if <path>.ascii exists
|
# Check if <path>.ascii exists
|
||||||
path = clean_path(request.path)
|
path = clean_path(request.path)
|
||||||
@@ -51,39 +56,81 @@ def curl_response(request):
|
|||||||
# Handle special cases
|
# Handle special cases
|
||||||
if path == "index":
|
if path == "index":
|
||||||
# Get current project
|
# Get current project
|
||||||
return render_template("index.ascii",repo=get_current_project(), ip=getClientIP(request), spotify=get_spotify_track()), 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
return (
|
||||||
|
render_template(
|
||||||
|
"index.ascii",
|
||||||
|
repo=get_current_project(),
|
||||||
|
ip=getClientIP(request),
|
||||||
|
spotify=get_spotify_track(),
|
||||||
|
),
|
||||||
|
200,
|
||||||
|
{"Content-Type": "text/plain; charset=utf-8"},
|
||||||
|
)
|
||||||
if path == "projects":
|
if path == "projects":
|
||||||
# Get projects
|
# Get projects
|
||||||
return render_template("projects.ascii",header=get_header(),projects=get_projects()), 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
return (
|
||||||
|
render_template(
|
||||||
|
"projects.ascii", header=get_header(), projects=get_projects()
|
||||||
|
),
|
||||||
|
200,
|
||||||
|
{"Content-Type": "text/plain; charset=utf-8"},
|
||||||
|
)
|
||||||
|
|
||||||
if path == "donate":
|
if path == "donate":
|
||||||
# Get donation info
|
# Get donation info
|
||||||
return render_template("donate.ascii",header=get_header(),
|
return (
|
||||||
HNS=getAddress("HNS"), BTC=getAddress("BTC"),
|
render_template(
|
||||||
SOL=getAddress("SOL"), ETH=getAddress("ETH")
|
"donate.ascii",
|
||||||
), 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
header=get_header(),
|
||||||
|
HNS=getAddress("HNS"),
|
||||||
|
BTC=getAddress("BTC"),
|
||||||
|
SOL=getAddress("SOL"),
|
||||||
|
ETH=getAddress("ETH"),
|
||||||
|
),
|
||||||
|
200,
|
||||||
|
{"Content-Type": "text/plain; charset=utf-8"},
|
||||||
|
)
|
||||||
|
|
||||||
if path == "donate/more":
|
if path == "donate/more":
|
||||||
coinList = os.listdir(".well-known/wallets")
|
coinList = os.listdir(".well-known/wallets")
|
||||||
coinList = [file for file in coinList if file[0] != "."]
|
coinList = [file for file in coinList if file[0] != "."]
|
||||||
coinList.sort()
|
coinList.sort()
|
||||||
return render_template("donate_more.ascii",header=get_header(),
|
return (
|
||||||
coins=coinList
|
render_template("donate_more.ascii", header=get_header(), coins=coinList),
|
||||||
), 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
200,
|
||||||
|
{"Content-Type": "text/plain; charset=utf-8"},
|
||||||
|
)
|
||||||
|
|
||||||
# For other donation pages, fall back to ascii if it exists
|
# For other donation pages, fall back to ascii if it exists
|
||||||
if path.startswith("donate/"):
|
if path.startswith("donate/"):
|
||||||
coin = path.split("/")[1]
|
coin = path.split("/")[1]
|
||||||
address = getAddress(coin)
|
address = getAddress(coin)
|
||||||
if address != "":
|
if address != "":
|
||||||
return render_template("donate_coin.ascii",header=get_header(),coin=coin.upper(),address=address), 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
return (
|
||||||
|
render_template(
|
||||||
|
"donate_coin.ascii",
|
||||||
|
header=get_header(),
|
||||||
|
coin=coin.upper(),
|
||||||
|
address=address,
|
||||||
|
),
|
||||||
|
200,
|
||||||
|
{"Content-Type": "text/plain; charset=utf-8"},
|
||||||
|
)
|
||||||
|
|
||||||
if path == "tools":
|
if path == "tools":
|
||||||
tools = get_tools_data()
|
tools = get_tools_data()
|
||||||
return render_template("tools.ascii",header=get_header(),tools=tools), 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
return (
|
||||||
|
render_template("tools.ascii", header=get_header(), tools=tools),
|
||||||
|
200,
|
||||||
|
{"Content-Type": "text/plain; charset=utf-8"},
|
||||||
|
)
|
||||||
|
|
||||||
if os.path.exists(f"templates/{path}.ascii"):
|
if os.path.exists(f"templates/{path}.ascii"):
|
||||||
return render_template(f"{path}.ascii",header=get_header()), 200, {'Content-Type': 'text/plain; charset=utf-8'}
|
return (
|
||||||
|
render_template(f"{path}.ascii", header=get_header()),
|
||||||
|
200,
|
||||||
|
{"Content-Type": "text/plain; charset=utf-8"},
|
||||||
|
)
|
||||||
|
|
||||||
# Fallback to html if it exists
|
# Fallback to html if it exists
|
||||||
if os.path.exists(f"templates/{path}.html"):
|
if os.path.exists(f"templates/{path}.html"):
|
||||||
@@ -92,6 +139,10 @@ def curl_response(request):
|
|||||||
# Return curl error page
|
# Return curl error page
|
||||||
error = {
|
error = {
|
||||||
"code": 404,
|
"code": 404,
|
||||||
"message": "The requested resource was not found on this server."
|
"message": "The requested resource was not found on this server.",
|
||||||
}
|
}
|
||||||
return render_template("error.ascii",header=get_header(),error=error), 404, {'Content-Type': 'text/plain; charset=utf-8'}
|
return (
|
||||||
|
render_template("error.ascii", header=get_header(), error=error),
|
||||||
|
404,
|
||||||
|
{"Content-Type": "text/plain; charset=utf-8"},
|
||||||
|
)
|
||||||
|
|||||||
57
mail.py
57
mail.py
@@ -21,6 +21,7 @@ import os
|
|||||||
# "body":"G'\''day\nThis is a test email from my website api\n\nRegards,\nNathan.Woodburn/"
|
# "body":"G'\''day\nThis is a test email from my website api\n\nRegards,\nNathan.Woodburn/"
|
||||||
# }'
|
# }'
|
||||||
|
|
||||||
|
|
||||||
def validateSender(email):
|
def validateSender(email):
|
||||||
domains = os.getenv("EMAIL_DOMAINS")
|
domains = os.getenv("EMAIL_DOMAINS")
|
||||||
if not domains:
|
if not domains:
|
||||||
@@ -33,37 +34,29 @@ def validateSender(email):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def sendEmail(data):
|
def sendEmail(data):
|
||||||
fromEmail = "noreply@woodburn.au"
|
fromEmail = "noreply@woodburn.au"
|
||||||
if "from" in data:
|
if "from" in data:
|
||||||
fromEmail = data["from"]
|
fromEmail = data["from"]
|
||||||
|
|
||||||
if not validateSender(fromEmail):
|
if not validateSender(fromEmail):
|
||||||
return jsonify({
|
return jsonify({"status": 400, "message": "Bad request 'from' email invalid"})
|
||||||
"status": 400,
|
|
||||||
"message": "Bad request 'from' email invalid"
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
if "to" not in data:
|
if "to" not in data:
|
||||||
return jsonify({
|
return jsonify({"status": 400, "message": "Bad request 'to' json data missing"})
|
||||||
"status": 400,
|
|
||||||
"message": "Bad request 'to' json data missing"
|
|
||||||
})
|
|
||||||
to = data["to"]
|
to = data["to"]
|
||||||
|
|
||||||
if "subject" not in data:
|
if "subject" not in data:
|
||||||
return jsonify({
|
return jsonify(
|
||||||
"status": 400,
|
{"status": 400, "message": "Bad request 'subject' json data missing"}
|
||||||
"message": "Bad request 'subject' json data missing"
|
)
|
||||||
})
|
|
||||||
subject = data["subject"]
|
subject = data["subject"]
|
||||||
|
|
||||||
if "body" not in data:
|
if "body" not in data:
|
||||||
return jsonify({
|
return jsonify(
|
||||||
"status": 400,
|
{"status": 400, "message": "Bad request 'body' json data missing"}
|
||||||
"message": "Bad request 'body' json data missing"
|
)
|
||||||
})
|
|
||||||
body = data["body"]
|
body = data["body"]
|
||||||
|
|
||||||
if not re.match(r"[^@]+@[^@]+\.[^@]+", to):
|
if not re.match(r"[^@]+@[^@]+\.[^@]+", to):
|
||||||
@@ -76,15 +69,15 @@ def sendEmail(data):
|
|||||||
raise ValueError("Body cannot be empty.")
|
raise ValueError("Body cannot be empty.")
|
||||||
|
|
||||||
fromName = "Nathan Woodburn"
|
fromName = "Nathan Woodburn"
|
||||||
if 'sender' in data:
|
if "sender" in data:
|
||||||
fromName = data['sender']
|
fromName = data["sender"]
|
||||||
|
|
||||||
# Create the email message
|
# Create the email message
|
||||||
msg = MIMEMultipart()
|
msg = MIMEMultipart()
|
||||||
msg['From'] = formataddr((fromName, fromEmail))
|
msg["From"] = formataddr((fromName, fromEmail))
|
||||||
msg['To'] = to
|
msg["To"] = to
|
||||||
msg['Subject'] = subject
|
msg["Subject"] = subject
|
||||||
msg.attach(MIMEText(body, 'plain'))
|
msg.attach(MIMEText(body, "plain"))
|
||||||
|
|
||||||
# Sending the email
|
# Sending the email
|
||||||
try:
|
try:
|
||||||
@@ -92,24 +85,12 @@ def sendEmail(data):
|
|||||||
user = os.getenv("EMAIL_USER")
|
user = os.getenv("EMAIL_USER")
|
||||||
password = os.getenv("EMAIL_PASS")
|
password = os.getenv("EMAIL_PASS")
|
||||||
if host is None or user is None or password is None:
|
if host is None or user is None or password is None:
|
||||||
return jsonify({
|
return jsonify({"status": 500, "error": "Email server not configured"})
|
||||||
"status": 500,
|
|
||||||
"error": "Email server not configured"
|
|
||||||
})
|
|
||||||
|
|
||||||
with smtplib.SMTP_SSL(host, 465) as server:
|
with smtplib.SMTP_SSL(host, 465) as server:
|
||||||
server.login(user, password)
|
server.login(user, password)
|
||||||
server.sendmail(fromEmail, to, msg.as_string())
|
server.sendmail(fromEmail, to, msg.as_string())
|
||||||
print("Email sent successfully.")
|
print("Email sent successfully.")
|
||||||
return jsonify({
|
return jsonify({"status": 200, "message": "Send email successfully"})
|
||||||
"status": 200,
|
|
||||||
"message": "Send email successfully"
|
|
||||||
})
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({
|
return jsonify({"status": 500, "error": "Sending email failed", "exception": e})
|
||||||
"status": 500,
|
|
||||||
"error": "Sending email failed",
|
|
||||||
"exception":e
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
22
main.py
22
main.py
@@ -17,9 +17,10 @@ class GunicornApp(BaseApplication):
|
|||||||
def load(self):
|
def load(self):
|
||||||
return self.application
|
return self.application
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
workers = os.getenv('WORKERS')
|
if __name__ == "__main__":
|
||||||
threads = os.getenv('THREADS')
|
workers = os.getenv("WORKERS")
|
||||||
|
threads = os.getenv("THREADS")
|
||||||
if workers is None:
|
if workers is None:
|
||||||
workers = 1
|
workers = 1
|
||||||
if threads is None:
|
if threads is None:
|
||||||
@@ -27,10 +28,17 @@ if __name__ == '__main__':
|
|||||||
workers = int(workers)
|
workers = int(workers)
|
||||||
threads = int(threads)
|
threads = int(threads)
|
||||||
options = {
|
options = {
|
||||||
'bind': '0.0.0.0:5000',
|
"bind": "0.0.0.0:5000",
|
||||||
'workers': workers,
|
"workers": workers,
|
||||||
'threads': threads,
|
"threads": threads,
|
||||||
}
|
}
|
||||||
gunicorn_app = GunicornApp(app, options)
|
gunicorn_app = GunicornApp(app, options)
|
||||||
print('Starting server with ' + str(workers) + ' workers and ' + str(threads) + ' threads', flush=True)
|
print(
|
||||||
|
"Starting server with "
|
||||||
|
+ str(workers)
|
||||||
|
+ " workers and "
|
||||||
|
+ str(threads)
|
||||||
|
+ " threads",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
gunicorn_app.run()
|
gunicorn_app.run()
|
||||||
|
|||||||
24
server.py
24
server.py
@@ -363,7 +363,11 @@ def donate():
|
|||||||
|
|
||||||
for token in tokenList:
|
for token in tokenList:
|
||||||
chain_display = f" on {token['chain']}" if token["chain"] != "null" else ""
|
chain_display = f" on {token['chain']}" if token["chain"] != "null" else ""
|
||||||
symbol_display = f" ({token['symbol']}{chain_display})" if token["symbol"] != token["name"] else chain_display
|
symbol_display = (
|
||||||
|
f" ({token['symbol']}{chain_display})"
|
||||||
|
if token["symbol"] != token["name"]
|
||||||
|
else chain_display
|
||||||
|
)
|
||||||
coins += f'<a class="dropdown-item" style="display:none;" href="?t={token["symbol"].lower()}&c={token["chain"].lower()}">{token["name"]}{symbol_display}</a>'
|
coins += f'<a class="dropdown-item" style="display:none;" href="?t={token["symbol"].lower()}&c={token["chain"].lower()}">{token["name"]}{symbol_display}</a>'
|
||||||
|
|
||||||
crypto = request.args.get("c")
|
crypto = request.args.get("c")
|
||||||
@@ -404,8 +408,12 @@ def donate():
|
|||||||
if not token:
|
if not token:
|
||||||
cryptoHTML += f"<br>Donate with {coin_display}:"
|
cryptoHTML += f"<br>Donate with {coin_display}:"
|
||||||
else:
|
else:
|
||||||
token_symbol = f" ({token['symbol']})" if token['symbol'] != token['name'] else ""
|
token_symbol = (
|
||||||
cryptoHTML += f"<br>Donate with {token['name']}{token_symbol} on {crypto}:"
|
f" ({token['symbol']})" if token["symbol"] != token["name"] else ""
|
||||||
|
)
|
||||||
|
cryptoHTML += (
|
||||||
|
f"<br>Donate with {token['name']}{token_symbol} on {crypto}:"
|
||||||
|
)
|
||||||
cryptoHTML += f'<br><code data-bs-toggle="tooltip" data-bss-tooltip="" id="crypto-address" class="address" style="color: rgb(242,90,5);display: inline-block;" data-bs-original-title="Click to copy">{address}</code>'
|
cryptoHTML += f'<br><code data-bs-toggle="tooltip" data-bss-tooltip="" id="crypto-address" class="address" style="color: rgb(242,90,5);display: inline-block;" data-bs-original-title="Click to copy">{address}</code>'
|
||||||
|
|
||||||
if proof:
|
if proof:
|
||||||
@@ -413,9 +421,13 @@ def donate():
|
|||||||
elif token:
|
elif token:
|
||||||
if "address" in token:
|
if "address" in token:
|
||||||
address = token["address"]
|
address = token["address"]
|
||||||
token_symbol = f" ({token['symbol']})" if token['symbol'] != token['name'] else ""
|
token_symbol = (
|
||||||
chain_display = f" on {crypto}" if crypto != 'NULL' else ""
|
f" ({token['symbol']})" if token["symbol"] != token["name"] else ""
|
||||||
cryptoHTML += f"<br>Donate with {token['name']}{token_symbol}{chain_display}:"
|
)
|
||||||
|
chain_display = f" on {crypto}" if crypto != "NULL" else ""
|
||||||
|
cryptoHTML += (
|
||||||
|
f"<br>Donate with {token['name']}{token_symbol}{chain_display}:"
|
||||||
|
)
|
||||||
cryptoHTML += f'<br><code data-bs-toggle="tooltip" data-bss-tooltip="" id="crypto-address" class="address" style="color: rgb(242,90,5);display: inline-block;" data-bs-original-title="Click to copy">{address}</code>'
|
cryptoHTML += f'<br><code data-bs-toggle="tooltip" data-bss-tooltip="" id="crypto-address" class="address" style="color: rgb(242,90,5);display: inline-block;" data-bs-original-title="Click to copy">{address}</code>'
|
||||||
if proof:
|
if proof:
|
||||||
cryptoHTML += proof
|
cryptoHTML += proof
|
||||||
|
|||||||
42
tools.py
42
tools.py
@@ -24,17 +24,10 @@ CRAWLERS = [
|
|||||||
"Exabot",
|
"Exabot",
|
||||||
"facebot",
|
"facebot",
|
||||||
"ia_archiver",
|
"ia_archiver",
|
||||||
"Twitterbot"
|
"Twitterbot",
|
||||||
]
|
]
|
||||||
|
|
||||||
CLI_AGENTS = [
|
CLI_AGENTS = ["curl", "hurl", "xh", "Posting", "HTTPie", "nushell"]
|
||||||
"curl",
|
|
||||||
"hurl",
|
|
||||||
"xh",
|
|
||||||
"Posting",
|
|
||||||
"HTTPie",
|
|
||||||
"nushell"
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def getClientIP(request: Request) -> str:
|
def getClientIP(request: Request) -> str:
|
||||||
@@ -56,6 +49,7 @@ def getClientIP(request: Request) -> str:
|
|||||||
ip = "unknown"
|
ip = "unknown"
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1)
|
@lru_cache(maxsize=1)
|
||||||
def getGitCommit() -> str:
|
def getGitCommit() -> str:
|
||||||
"""
|
"""
|
||||||
@@ -115,6 +109,7 @@ def isCrawler(request: Request) -> bool:
|
|||||||
return any(crawler in user_agent for crawler in CRAWLERS)
|
return any(crawler in user_agent for crawler in CRAWLERS)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=128)
|
@lru_cache(maxsize=128)
|
||||||
def isDev(host: str) -> bool:
|
def isDev(host: str) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -135,6 +130,7 @@ def isDev(host: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=128)
|
@lru_cache(maxsize=128)
|
||||||
def getHandshakeScript(host: str) -> str:
|
def getHandshakeScript(host: str) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -150,6 +146,7 @@ def getHandshakeScript(host: str) -> str:
|
|||||||
return ""
|
return ""
|
||||||
return '<script src="https://nathan.woodburn/handshake.js" domain="nathan.woodburn" async></script><script src="https://nathan.woodburn/https.js" async></script>'
|
return '<script src="https://nathan.woodburn/handshake.js" domain="nathan.woodburn" async></script><script src="https://nathan.woodburn/https.js" async></script>'
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=64)
|
@lru_cache(maxsize=64)
|
||||||
def getAddress(coin: str) -> str:
|
def getAddress(coin: str) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -187,7 +184,9 @@ def getFilePath(name: str, path: str) -> Optional[str]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def json_response(request: Request, message: Union[str, Dict] = "404 Not Found", code: int = 404):
|
def json_response(
|
||||||
|
request: Request, message: Union[str, Dict] = "404 Not Found", code: int = 404
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create a JSON response with standard formatting.
|
Create a JSON response with standard formatting.
|
||||||
|
|
||||||
@@ -205,17 +204,20 @@ def json_response(request: Request, message: Union[str, Dict] = "404 Not Found",
|
|||||||
message["ip"] = getClientIP(request)
|
message["ip"] = getClientIP(request)
|
||||||
return jsonify(message), code
|
return jsonify(message), code
|
||||||
|
|
||||||
return jsonify({
|
return jsonify(
|
||||||
|
{
|
||||||
"status": code,
|
"status": code,
|
||||||
"message": message,
|
"message": message,
|
||||||
"ip": getClientIP(request),
|
"ip": getClientIP(request),
|
||||||
}), code
|
}
|
||||||
|
), code
|
||||||
|
|
||||||
|
|
||||||
def error_response(
|
def error_response(
|
||||||
request: Request,
|
request: Request,
|
||||||
message: str = "404 Not Found",
|
message: str = "404 Not Found",
|
||||||
code: int = 404,
|
code: int = 404,
|
||||||
force_json: bool = False
|
force_json: bool = False,
|
||||||
) -> Union[Tuple[Dict, int], object]:
|
) -> Union[Tuple[Dict, int], object]:
|
||||||
"""
|
"""
|
||||||
Create an error response in JSON or HTML format.
|
Create an error response in JSON or HTML format.
|
||||||
@@ -233,10 +235,12 @@ def error_response(
|
|||||||
return json_response(request, message, code)
|
return json_response(request, message, code)
|
||||||
|
|
||||||
# Check if <error code>.html exists in templates
|
# Check if <error code>.html exists in templates
|
||||||
template_name = f"{code}.html" if os.path.isfile(
|
template_name = (
|
||||||
f"templates/{code}.html") else "404.html"
|
f"{code}.html" if os.path.isfile(f"templates/{code}.html") else "404.html"
|
||||||
response = make_response(render_template(
|
)
|
||||||
template_name, code=code, message=message), code)
|
response = make_response(
|
||||||
|
render_template(template_name, code=code, message=message), code
|
||||||
|
)
|
||||||
|
|
||||||
# Add message to response headers
|
# Add message to response headers
|
||||||
response.headers["X-Error-Message"] = message
|
response.headers["X-Error-Message"] = message
|
||||||
@@ -260,8 +264,7 @@ def parse_date(date_groups: list[str]) -> str | None:
|
|||||||
date_str = " ".join(date_groups).strip()
|
date_str = " ".join(date_groups).strip()
|
||||||
|
|
||||||
# Remove ordinal suffixes
|
# Remove ordinal suffixes
|
||||||
date_str = re.sub(r'(\d+)(st|nd|rd|th)', r'\1',
|
date_str = re.sub(r"(\d+)(st|nd|rd|th)", r"\1", date_str, flags=re.IGNORECASE)
|
||||||
date_str, flags=re.IGNORECASE)
|
|
||||||
|
|
||||||
# Parse with dateutil, default day=1 if missing
|
# Parse with dateutil, default day=1 if missing
|
||||||
dt = parse(date_str, default=datetime.datetime(1900, 1, 1))
|
dt = parse(date_str, default=datetime.datetime(1900, 1, 1))
|
||||||
@@ -275,6 +278,7 @@ def parse_date(date_groups: list[str]) -> str | None:
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_tools_data():
|
def get_tools_data():
|
||||||
with open("data/tools.json", "r") as f:
|
with open("data/tools.json", "r") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|||||||
Reference in New Issue
Block a user