diff --git a/Dockerfile b/Dockerfile index 2a8fa82..0a627cf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,9 +10,9 @@ COPY . /app # Add mount point for data volume # VOLUME /data -RUN apk add git openssl +RUN apk add git openssl curl ENTRYPOINT ["python3"] CMD ["server.py"] -FROM builder as dev-envs \ No newline at end of file +FROM builder as dev-envs diff --git a/FireWalletBrowser.bsdesign b/FireWalletBrowser.bsdesign index e4f696a..299e27a 100644 Binary files a/FireWalletBrowser.bsdesign and b/FireWalletBrowser.bsdesign differ diff --git a/account.py b/account.py index c481add..d92d15d 100644 --- a/account.py +++ b/account.py @@ -529,10 +529,24 @@ def setDNS(account, domain, records): for txt in record['txt']: TXTRecords.append(txt) elif record['type'] == 'NS': - newRecords.append({ - 'type': 'NS', - 'ns': record['value'] - }) + if 'value' in record: + newRecords.append({ + 'type': 'NS', + 'ns': record['value'] + }) + elif 'ns' in record: + newRecords.append({ + 'type': 'NS', + 'ns': record['ns'] + }) + else: + return { + 'error': { + 'message': 'Invalid NS record' + } + } + + elif record['type'] in ['GLUE4', 'GLUE6', "SYNTH4", "SYNTH6"]: newRecords.append({ 'type': record['type'], diff --git a/main.py b/main.py index 810b295..d5a1c50 100644 --- a/main.py +++ b/main.py @@ -429,12 +429,12 @@ def search(): if 'error' in domain: return render_template("search.html", account=account, - + rendered=renderDomain(search_term), search_term=search_term, domain=domain['error'],plugins=plugins) if domain['info'] is None: return render_template("search.html", account=account, - + rendered=renderDomain(search_term), search_term=search_term,domain=search_term, state="AVAILABLE", next="Available Now",plugins=plugins) @@ -478,7 +478,7 @@ def search(): txs = render.txs(txs) return render_template("search.html", account=account, - + rendered=renderDomain(search_term), search_term=search_term,domain=domain['info']['name'], raw=domain,state=state, next=next, owner=owner, dns=dns, txs=txs,plugins=plugins) @@ -504,7 +504,7 @@ def manage(domain: str): domain_info = account_module.getDomain(domain) if 'error' in domain_info: return render_template("manage.html", account=account, - + rendered=renderDomain(domain), domain=domain, error=domain_info['error']) expiry = domain_info['info']['stats']['daysUntilExpire'] @@ -541,7 +541,7 @@ def manage(domain: str): return render_template("manage.html", account=account, - + rendered=renderDomain(domain), error=errorMessage, address=address, domain=domain,expiry=expiry, dns=dns, raw_dns=urllib.parse.quote(raw_dns), @@ -716,7 +716,7 @@ def editPage(domain: str): return render_template("edit.html", account=account, - + rendered=renderDomain(domain), domain=domain, error=errorMessage, dns=dns,raw_dns=urllib.parse.quote(raw_dns)) @@ -862,7 +862,7 @@ def auction(domain): if 'error' in domainInfo: return render_template("auction.html", account=account, - + rendered=renderDomain(search_term), search_term=search_term, domain=domainInfo['error'], error=error) @@ -873,7 +873,7 @@ def auction(domain): else: next_action = f'Open Auction' return render_template("auction.html", account=account, - + rendered=renderDomain(search_term), search_term=search_term,domain=search_term,next_action=next_action, state="AVAILABLE", next="Open Auction", error=error) @@ -934,7 +934,7 @@ def auction(domain): return render_template("auction.html", account=account, - + rendered=renderDomain(search_term), search_term=search_term,domain=domainInfo['info']['name'], raw=domainInfo,state=state, next=next, next_action=next_action, bids=bids,error=message) @@ -1544,7 +1544,12 @@ def api_wallet(function): if function == "domains": domains = account_module.getDomains(account) if 'error' in domains: - return jsonify({"result": [], "error": domains['error']}) + return jsonify({"result": [], "error": domains['error']}) + + # Add nameRender to each domain + for domain in domains: + domain['nameRender'] = renderDomain(domain['name']) + return jsonify({"result": domains}) if function == "icon": @@ -1570,6 +1575,35 @@ def api_icon(account): return send_file(f'user_data/images/{file}') return send_file('templates/assets/img/HNS.png') + +@app.route('/api/v1/status') +def api_status(): + # This doesn't require a login + # Check if the node is connected + if not account_module.hsdConnected(): + return jsonify({"status":503,"error": "Node not connected"}), 503 + return jsonify({"status": 200,"result": "FireWallet is running"}) + + +#endregion + +#region Helper functions + +def renderDomain(name: str) -> str: + """ + Render a domain name with emojis and other special characters. + """ + # Convert emoji to punycode + try: + rendered = name.encode("ascii").decode("idna") + if rendered == name: + return f"{name}/" + return f"{rendered}/ ({name})" + + + except Exception as e: + return f"{name}/" + #endregion @@ -1609,10 +1643,9 @@ def page_not_found(e): #endregion if __name__ == '__main__': + #TODO add parsing to allow for custom port and host # Check to see if --debug is in the command line arguments - debug = "--debug" in sys.argv - port = 5000 - if "--port" in sys.argv: - port = int(sys.argv[sys.argv.index("--port")+1]) - app.run(debug=True,host='0.0.0.0',port=port) - \ No newline at end of file + if "--debug" in sys.argv: + app.run(debug=True) + else: + app.run() diff --git a/render.py b/render.py index af80fcd..6861228 100644 --- a/render.py +++ b/render.py @@ -24,10 +24,7 @@ def domains(domains, mobile=False): paid = paid / 1000000 # Handle punycodes - name = domain['name'] - emoji = punycode_to_emoji(name) - if emoji != name: - name = f'{emoji} ({name})' + name = renderDomain(domain['name']) link = f'/manage/{domain["name"]}' @@ -199,7 +196,7 @@ def bidDomains(bids,domains, sortbyDomains=False): html += "" - html += f"{domain['name']}" + html += f"{renderDomain(domain['name'])}" html += f"{domain['state']}" html += f"{bidDisplay}" html += f"{domain['height']:,}" @@ -215,7 +212,7 @@ def bidDomains(bids,domains, sortbyDomains=False): bidDisplay = f'{bidValue:,.2f} HNS + {blind:,.2f} HNS blind' html += "" - html += f"{domain['name']}" + html += f"{renderDomain(domain['name'])}" html += f"{domain['state']}" html += f"{bidDisplay}" html += f"{domain['height']:,}" @@ -351,4 +348,21 @@ def plugin_output_dash(outputs, returns): if outputs[returnOutput] == None: continue html += render_template('components/dashboard-plugin.html', name=returns[returnOutput]["name"], output=outputs[returnOutput]) - return html \ No newline at end of file + return html + + + +def renderDomain(name: str) -> str: + """ + Render a domain name with emojis and other special characters. + """ + # Convert emoji to punycode + try: + rendered = name.encode("ascii").decode("idna") + if rendered == name: + return f"{name}/" + return f"{rendered}/ ({name})" + + + except Exception as e: + return f"{name}/" diff --git a/templates/assets/js/script.min.js b/templates/assets/js/script.min.js index 2294f33..f6ea83b 100644 --- a/templates/assets/js/script.min.js +++ b/templates/assets/js/script.min.js @@ -1 +1 @@ -async function request(e){try{const t=await fetch(`/api/v1/${e}`);if(!t.ok)throw new Error(`HTTP error! Status: ${t.status}`);const n=await t.json();return void 0!==n.error?`Error: ${n.error}`:n.result}catch(e){return console.error("Request failed:",e),"Error"}}function sortTable(e,t=!1){const n=document.getElementById("data-table"),a=n.querySelector("tbody"),l=Array.from(a.querySelectorAll("tr")),r=n.querySelectorAll("th");let o=n.getAttribute("data-sort-order")||"asc",i=n.getAttribute("data-sort-column")||"-1";o=t||i!=e?"asc":"asc"===o?"desc":"asc",n.setAttribute("data-sort-order",o),n.setAttribute("data-sort-column",e);const c=determineColumnDataType(l,e);l.sort(((t,n)=>{let a=t.cells[e].innerText.trim(),l=n.cells[e].innerText.trim();if("number"===c){let e=parseFloat(a.replace(/[^0-9.,]/g,"").replace(/,/g,"")),t=parseFloat(l.replace(/[^0-9.,]/g,"").replace(/,/g,""));return"asc"===o?e-t:t-e}if("date"===c){let e=new Date(a),t=new Date(l);return"asc"===o?e-t:t-e}return"asc"===o?a.localeCompare(l,void 0,{sensitivity:"base"}):l.localeCompare(a,void 0,{sensitivity:"base"})})),a.innerHTML="",l.forEach((e=>a.appendChild(e))),updateSortIndicators(r,e,o)}function determineColumnDataType(e,t){const n=Math.min(5,e.length);let a=0,l=0;for(let r=0;r=e.length);r++){const n=e[r].cells[t].innerText.trim(),o=parseFloat(n.replace(/[^0-9.,]/g,"").replace(/,/g,""));if(!isNaN(o)&&n.replace(/[^0-9.,\s$%]/g,"").length===n.length){a++;continue}const i=new Date(n);isNaN(i)||"Invalid Date"===i.toString()||l++}return a>=n/2?"number":l>=n/2?"date":"text"}function updateSortIndicators(e,t,n){e.forEach(((e,a)=>{let l=e.querySelector(".sort-indicator");l.innerHTML=a===t?"asc"===n?" ▲":" ▼":""}))}window.addEventListener("load",(async()=>{const e=["hsd-sync","hsd-version","hsd-height","wallet-sync","wallet-available","wallet-total","wallet-locked","wallet-pending","wallet-domainCount","wallet-bidCount","wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"],t=["wallet-available","wallet-total","wallet-locked"],n=["wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"];for(const a of e){const e=document.getElementById(a);if(e){const l=a.replace(/-/g,"/");let r=await request(l);n.includes(a)&&"Error"!=r&&(r=r.length),t.includes(a)&&(r=Number(r).toFixed(2)),r=r.toString().replace(/\B(?=(\d{3})+(?!\d))/g,","),e.innerHTML=r}}})),document.addEventListener("DOMContentLoaded",(function(){fetch("/api/v1/wallet/domains").then((e=>e.json())).then((e=>{const t=document.querySelector("#data-table tbody");t&&(t.innerHTML="",e.result.forEach((e=>{const n=document.createElement("tr"),a=document.createElement("td");a.textContent=e.name,n.appendChild(a);var l="Unknown";"stats"in e&&"daysUntilExpire"in e.stats&&(l=e.stats.daysUntilExpire);const r=document.createElement("td");r.textContent=`${l} days`,n.appendChild(r);const o=document.createElement("td");o.textContent=`${(e.value/1e6).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,",")} HNS`,n.appendChild(o);const i=document.createElement("td");i.innerHTML=e.registered?"Manage":"Register",n.appendChild(i),t.appendChild(n)})),sortTable(0,!0))})).catch((e=>console.error("Error fetching data:",e)))})),setInterval((async function(){const e=["hsd-sync","hsd-height","wallet-sync","wallet-pending","wallet-available","wallet-total"];for(const t of e){const e=document.getElementById(t);if(e){const n=t.replace(/-/g,"/");let a=await request(n);["wallet-available","wallet-total"].includes(t)&&(a=Number(a).toFixed(2)),a=a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,","),e.innerHTML=a}}}),2e4),function(){"use strict";var e=document.querySelector(".sidebar"),t=document.querySelectorAll("#sidebarToggle, #sidebarToggleTop");if(e){e.querySelector(".collapse");var n=[].slice.call(document.querySelectorAll(".sidebar .collapse")).map((function(e){return new bootstrap.Collapse(e,{toggle:!1})}));for(var a of t)a.addEventListener("click",(function(t){if(document.body.classList.toggle("sidebar-toggled"),e.classList.toggle("toggled"),e.classList.contains("toggled"))for(var a of n)a.hide()}));window.addEventListener("resize",(function(){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)<768)for(var e of n)e.hide()}))}var l=document.querySelector("body.fixed-nav .sidebar");l&&l.on("mousewheel DOMMouseScroll wheel",(function(e){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)>768){var t=e.originalEvent,n=t.wheelDelta||-t.detail;this.scrollTop+=30*(n<0?1:-1),e.preventDefault()}}));var r=document.querySelector(".scroll-to-top");r&&window.addEventListener("scroll",(function(){var e=window.pageYOffset;r.style.display=e>100?"block":"none"}))}(); \ No newline at end of file +async function request(e){try{const t=await fetch(`/api/v1/${e}`);if(!t.ok)throw new Error(`HTTP error! Status: ${t.status}`);const n=await t.json();return void 0!==n.error?`Error: ${n.error}`:n.result}catch(e){return console.error("Request failed:",e),"Error"}}function sortTable(e,t=!1){const n=document.getElementById("data-table"),a=n.querySelector("tbody"),l=Array.from(a.querySelectorAll("tr")),r=n.querySelectorAll("th");let o=n.getAttribute("data-sort-order")||"asc",i=n.getAttribute("data-sort-column")||"-1";o=t||i!=e?"asc":"asc"===o?"desc":"asc",n.setAttribute("data-sort-order",o),n.setAttribute("data-sort-column",e);const c=determineColumnDataType(l,e);l.sort(((t,n)=>{let a=t.cells[e].innerText.trim(),l=n.cells[e].innerText.trim();if("number"===c){let e=parseFloat(a.replace(/[^0-9.,]/g,"").replace(/,/g,"")),t=parseFloat(l.replace(/[^0-9.,]/g,"").replace(/,/g,""));return"asc"===o?e-t:t-e}if("date"===c){let e=new Date(a),t=new Date(l);return"asc"===o?e-t:t-e}return"asc"===o?a.localeCompare(l,void 0,{sensitivity:"base"}):l.localeCompare(a,void 0,{sensitivity:"base"})})),a.innerHTML="",l.forEach((e=>a.appendChild(e))),updateSortIndicators(r,e,o)}function determineColumnDataType(e,t){const n=Math.min(5,e.length);let a=0,l=0;for(let r=0;r=e.length);r++){const n=e[r].cells[t].innerText.trim(),o=parseFloat(n.replace(/[^0-9.,]/g,"").replace(/,/g,""));if(!isNaN(o)&&n.replace(/[^0-9.,\s$%]/g,"").length===n.length){a++;continue}const i=new Date(n);isNaN(i)||"Invalid Date"===i.toString()||l++}return a>=n/2?"number":l>=n/2?"date":"text"}function updateSortIndicators(e,t,n){e.forEach(((e,a)=>{let l=e.querySelector(".sort-indicator");l.innerHTML=a===t?"asc"===n?" ▲":" ▼":""}))}window.addEventListener("load",(async()=>{const e=["hsd-sync","hsd-version","hsd-height","wallet-sync","wallet-available","wallet-total","wallet-locked","wallet-pending","wallet-domainCount","wallet-bidCount","wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"],t=["wallet-available","wallet-total","wallet-locked"],n=["wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"];for(const a of e){const e=document.getElementById(a);if(e){const l=a.replace(/-/g,"/");let r=await request(l);n.includes(a)&&"Error"!=r&&(r=r.length),t.includes(a)&&(r=Number(r).toFixed(2)),r=r.toString().replace(/\B(?=(\d{3})+(?!\d))/g,","),e.innerHTML=r}}})),document.addEventListener("DOMContentLoaded",(function(){fetch("/api/v1/wallet/domains").then((e=>e.json())).then((e=>{const t=document.querySelector("#data-table tbody");t&&(t.innerHTML="",e.result.forEach((e=>{const n=document.createElement("tr"),a=document.createElement("td");a.textContent=e.nameRender,n.appendChild(a);var l="Unknown";"stats"in e&&"daysUntilExpire"in e.stats&&(l=e.stats.daysUntilExpire);const r=document.createElement("td");r.textContent=`${l} days`,n.appendChild(r);const o=document.createElement("td");o.textContent=`${(e.value/1e6).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,",")} HNS`,n.appendChild(o);const i=document.createElement("td");i.innerHTML=e.registered?"Manage":"Register",n.appendChild(i),t.appendChild(n)})),sortTable(0,!0))})).catch((e=>console.error("Error fetching data:",e)))})),setInterval((async function(){const e=["hsd-sync","hsd-height","wallet-sync","wallet-pending","wallet-available","wallet-total"];for(const t of e){const e=document.getElementById(t);if(e){const n=t.replace(/-/g,"/");let a=await request(n);["wallet-available","wallet-total"].includes(t)&&(a=Number(a).toFixed(2)),a=a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,","),e.innerHTML=a}}}),2e4),function(){"use strict";var e=document.querySelector(".sidebar"),t=document.querySelectorAll("#sidebarToggle, #sidebarToggleTop");if(e){e.querySelector(".collapse");var n=[].slice.call(document.querySelectorAll(".sidebar .collapse")).map((function(e){return new bootstrap.Collapse(e,{toggle:!1})}));for(var a of t)a.addEventListener("click",(function(t){if(document.body.classList.toggle("sidebar-toggled"),e.classList.toggle("toggled"),e.classList.contains("toggled"))for(var a of n)a.hide()}));window.addEventListener("resize",(function(){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)<768)for(var e of n)e.hide()}))}var l=document.querySelector("body.fixed-nav .sidebar");l&&l.on("mousewheel DOMMouseScroll wheel",(function(e){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)>768){var t=e.originalEvent,n=t.wheelDelta||-t.detail;this.scrollTop+=30*(n<0?1:-1),e.preventDefault()}}));var r=document.querySelector(".scroll-to-top");r&&window.addEventListener("scroll",(function(){var e=window.pageYOffset;r.style.display=e>100?"block":"none"}))}(); \ No newline at end of file diff --git a/templates/auction.html b/templates/auction.html index 04c4606..8ad5270 100644 --- a/templates/auction.html +++ b/templates/auction.html @@ -67,7 +67,7 @@
{{next_action|safe}}
-

{{domain}}/

+

{{rendered}}

{{next}}
diff --git a/templates/edit.html b/templates/edit.html index 81cd3fd..4b33fd5 100644 --- a/templates/edit.html +++ b/templates/edit.html @@ -66,7 +66,7 @@
-

{{domain}}/

+

{{rendered}}

diff --git a/templates/manage.html b/templates/manage.html index f00c25a..d717f89 100644 --- a/templates/manage.html +++ b/templates/manage.html @@ -66,7 +66,7 @@
-

{{domain}}/Renew

+

{{rendered}}Renew

Expires in {{expiry}} days
diff --git a/templates/search.html b/templates/search.html index ec50a3d..05de496 100644 --- a/templates/search.html +++ b/templates/search.html @@ -65,7 +65,7 @@
-

{{domain}}/{{next}}

+

{{rendered}}{{next}}

{{domain}}/

{{next}}


{{state}}
Owner: {{owner}}
ManageAuction