2025-01-29 15:23:52 +11:00
from functools import cache
import json
from flask import (
Flask ,
make_response ,
redirect ,
request ,
jsonify ,
render_template ,
send_from_directory ,
send_file ,
)
import os
import json
import requests
from datetime import datetime
import dotenv
2025-01-30 16:22:58 +11:00
import sales
2025-01-29 15:23:52 +11:00
dotenv . load_dotenv ( )
app = Flask ( __name__ )
def find ( name , path ) :
for root , dirs , files in os . walk ( path ) :
if name in files :
return os . path . join ( root , name )
# Assets routes
@app.route ( " /assets/<path:path> " )
def send_assets ( path ) :
if path . endswith ( " .json " ) :
return send_from_directory (
" templates/assets " , path , mimetype = " application/json "
)
if os . path . isfile ( " templates/assets/ " + path ) :
return send_from_directory ( " templates/assets " , path )
# Try looking in one of the directories
filename : str = path . split ( " / " ) [ - 1 ]
if (
filename . endswith ( " .png " )
or filename . endswith ( " .jpg " )
or filename . endswith ( " .jpeg " )
or filename . endswith ( " .svg " )
) :
if os . path . isfile ( " templates/assets/img/ " + filename ) :
return send_from_directory ( " templates/assets/img " , filename )
if os . path . isfile ( " templates/assets/img/favicon/ " + filename ) :
return send_from_directory ( " templates/assets/img/favicon " , filename )
return render_template ( " 404.html " ) , 404
# region Special routes
@app.route ( " /favicon.png " )
def faviconPNG ( ) :
return send_from_directory ( " templates/assets/img " , " favicon.png " )
@app.route ( " /.well-known/<path:path> " )
def wellknown ( path ) :
# Try to proxy to https://nathan.woodburn.au/.well-known/
req = requests . get ( f " https://nathan.woodburn.au/.well-known/ { path } " )
return make_response (
req . content , 200 , { " Content-Type " : req . headers [ " Content-Type " ] }
)
# endregion
# region Main routes
@app.route ( " / " )
def index ( ) :
2025-01-30 16:22:58 +11:00
listings = sales . get_listings_rendered ( )
return render_template ( " index.html " , listings = listings )
@app.route ( " /view/<path:domain> " )
def view ( domain : str ) :
listing = sales . search_listings ( domain )
if not listing :
return render_template ( " 404.html " ) , 404
return render_template ( " view.html " , listing = listing )
2025-01-30 17:47:12 +11:00
@app.route ( " /list " , methods = [ " POST " ] )
def list_form ( ) :
# Get form data
data = request . form
domain = data [ ' domain ' ]
price = data [ ' price ' ]
description = data [ ' description ' ]
contact = data [ ' contact ' ]
signature = data [ ' signature ' ]
listing = sales . Listing ( domain = domain , price = price , description = description , contact = contact , signature = signature )
if not listing . signed ( ) :
return render_template ( " list.html " , domain = domain , price = price , description = description , contact = contact , signature = signature , error = " Signature is not valid " )
status = sales . add_listing ( listing )
if status != True :
return render_template ( " list.html " , domain = domain , price = price , description = description , contact = contact , signature = signature , error = " Failed to add listing " )
return render_template ( " list.html " , success = " Successfully added listing " )
@app.route ( " /delete/<path:domain> " )
def delete_web ( domain : str ) :
message = f " FS: { domain } "
return render_template ( " delete.html " , domain = domain , message = message )
@app.route ( " /delete/<path:domain> " , methods = [ " POST " ] )
def delete_form ( domain : str ) :
data = request . form
signature = data [ ' signature ' ]
if not sales . validate_cancel_signature ( domain , signature ) :
return render_template ( " delete.html " , domain = domain , error = " Invalid signature " )
sales . remove_listing ( domain )
return redirect ( " / " )
2025-01-30 16:22:58 +11:00
@app.route ( " /view/<path:domain> " , methods = [ " POST " ] )
def post_offer ( domain : str ) :
data = request . form
# Convert to JSON
data = json . loads ( json . dumps ( data ) )
offer = sales . send_offer ( domain , data )
listing = sales . search_listings ( domain )
return render_template ( " view.html " , listing = listing , message = offer [ ' message ' ] )
@app.route ( " /report/<path:domain> " )
def report ( domain : str ) :
listing = sales . search_listings ( domain )
if not listing :
return render_template ( " 404.html " ) , 404
return redirect ( " https://l.woodburn.au/contact " )
2025-01-30 17:47:12 +11:00
@app.route ( " /verify/<path:domain> " )
def verify ( domain : str ) :
listing = sales . search_listings ( domain )
if not listing :
return render_template ( " 404.html " ) , 404
2025-01-30 16:22:58 +11:00
2025-01-30 17:47:12 +11:00
return render_template ( " verify.html " , domain = domain , string = str ( listing ) . replace ( ' \n ' , ' <br> ' ) , message = listing . getMessage ( ) , signature = listing . signature )
2025-01-30 16:22:58 +11:00
#region API Routes
def validate_data ( data , required ) :
for key in required :
if key not in data :
2025-01-30 17:47:12 +11:00
return jsonify ( { ' error ' : f ' Missing { key } ' , ' success ' : False } ) , 400
2025-01-30 16:22:58 +11:00
return True
2025-01-30 17:47:12 +11:00
@app.route ( " /api/v1/listing-message " , methods = [ " GET " ] )
def listingMessage ( ) :
# Create listing
valid = validate_data ( request . args , [ ' domain ' , ' description ' , ' price ' , ' contact ' ] )
if valid != True :
return valid
listing = sales . Listing ( * * request . args )
return {
' message ' : listing . getMessage ( ) ,
' success ' : True
}
2025-01-30 16:22:58 +11:00
@app.route ( " /api/v1/list " , methods = [ " POST " ] )
def list ( ) :
data = request . get_json ( )
# Validate data has domain,description,price,contact
2025-01-30 17:47:12 +11:00
valid = validate_data ( data , [ ' domain ' , ' description ' , ' price ' , ' contact ' ] )
2025-01-30 16:22:58 +11:00
if valid != True :
return valid
listing = sales . Listing ( * * data )
2025-01-30 17:47:12 +11:00
if not listing . signed ( ) :
return jsonify ( { ' error ' : ' Not signed ' , ' success ' : False , ' message ' : f ' Sign this message to post listing: { listing . getMessage ( ) } ' } )
2025-01-30 16:22:58 +11:00
status = sales . add_listing ( listing )
if status != True :
return jsonify ( { ' error ' : ' Failed to add listing ' , ' success ' : False , ' message ' : status } )
return jsonify ( { ' success ' : True , ' error ' : None } )
@app.route ( " /api/v1/delete " , methods = [ " POST " ] )
def delete ( ) :
data = request . get_json ( )
# Validate data has domain
if not ' domain ' in data :
return jsonify ( { ' error ' : ' Domain is required ' , ' success ' : False } )
if ' tx ' in data :
if not sales . validate_buy_tx ( data [ ' domain ' ] , data [ ' tx ' ] ) :
return jsonify ( { ' error ' : ' Invalid tx ' , ' success ' : False } )
sales . remove_listing ( data [ ' domain ' ] )
return jsonify ( { ' success ' : True , ' error ' : None } )
if ' signature ' in data :
if not sales . validate_cancel_signature ( data [ ' domain ' ] , data [ ' signature ' ] ) :
return jsonify ( { ' error ' : ' Invalid signature ' , ' success ' : False , ' message ' : f " FS: { data [ ' domain ' ] } " } )
sales . remove_listing ( data [ ' domain ' ] )
return jsonify ( { ' success ' : True , ' error ' : None } )
return jsonify ( { ' error ' : ' Signature or tx is required ' , ' success ' : False } )
#endregion
2025-01-29 15:23:52 +11:00
2025-01-30 18:20:31 +11:00
@app.route ( " /plugin " )
def plugin ( ) :
return redirect ( ' https://git.woodburn.au/nathanwoodburn/firesales-plugin ' )
@app.route ( " /docs " )
def docs ( ) :
return redirect ( ' https://git.woodburn.au/nathanwoodburn/FireSales/wiki/Home ' )
2025-01-29 15:23:52 +11:00
@app.route ( " /<path:path> " )
def catch_all ( path : str ) :
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 " )
if os . path . isfile ( " templates/ " + path . strip ( " / " ) + " .html " ) :
return render_template ( path . strip ( " / " ) + " .html " )
# Try to find a file matching
if path . count ( " / " ) < 1 :
# Try to find a file matching
filename = find ( path , " templates " )
if filename :
return send_file ( filename )
return render_template ( " 404.html " ) , 404
# endregion
# region Error Catching
# 404 catch all
@app.errorhandler ( 404 )
def not_found ( e ) :
return render_template ( " 404.html " ) , 404
# endregion
if __name__ == " __main__ " :
app . run ( debug = True , port = 5000 , host = " 0.0.0.0 " )