diff --git a/FireWalletBrowser.bsdesign b/FireWalletBrowser.bsdesign index e0e019f..fca0fa5 100644 Binary files a/FireWalletBrowser.bsdesign and b/FireWalletBrowser.bsdesign differ diff --git a/main.py b/main.py index 931ee51..4d6c8f0 100644 --- a/main.py +++ b/main.py @@ -11,6 +11,7 @@ from flask_qrcode import QRcode import domainLookup import urllib.parse import importlib +import plugin as plugins_module dotenv.load_dotenv() @@ -91,10 +92,17 @@ def index(): domain_count = len(domains) domains = render.domains(domains) + plugins = "" + dashFunctions = plugins_module.getDashboardFunctions() + for function in dashFunctions: + functionOutput = plugins_module.runPluginFunction(function["plugin"],function["function"],{},account_module.check_account(request.cookies.get("account"))) + plugins += render.plugin_output_dash(functionOutput,plugins_module.getPluginFunctionReturns(function["plugin"],function["function"])) + + return render_template("index.html", account=account, available=available, - total=total, pending=pending, domains=domains, + total=total, pending=pending, domains=domains, plugins=plugins, domain_count=domain_count, sync=account_module.getNodeSync(), sort_price=sort_price,sort_expiry=sort_expiry, sort_domain=sort_domain,sort_price_next=sort_price_next, @@ -320,10 +328,20 @@ def search(): dns = render.dns(dns) txs = render.txs(txs) + + plugins = "
" + # Execute domain plugins + searchFunctions = plugins_module.getSearchFunctions() + for function in searchFunctions: + functionOutput = plugins_module.runPluginFunction(function["plugin"],function["function"],{"domain":search_term},account_module.check_account(request.cookies.get("account"))) + plugins += render.plugin_output(functionOutput,plugins_module.getPluginFunctionReturns(function["plugin"],function["function"])) + + plugins += "
" + return render_template("search.html", account=account, sync=account_module.getNodeSync(), search_term=search_term,domain=domain['info']['name'], raw=domain,state=state, next=next, owner=owner, - dns=dns, txs=txs) + dns=dns, txs=txs,plugins=plugins) @app.route('/manage/') def manage(domain: str): @@ -373,20 +391,11 @@ def manage(domain: str): plugins = "
" # Execute domain plugins - plugin_links = os.listdir("plugins") - for plugin in plugin_links: - if os.path.isdir("plugins/" + plugin): - module = importlib.import_module("plugins." + plugin + ".main") - moduleFunctions = module.listFunctions() - for moduleFunction in moduleFunctions: - data = moduleFunctions[moduleFunction] - if "type" in data: - if data["type"] == "domain": - # Run function - print(data) - functionOutput = module.runFunction(moduleFunction,{"domain":domain},account_module.check_account(request.cookies.get("account"))) - print(functionOutput) - plugins += render.plugin_output(functionOutput,data['returns']) + domainFunctions = plugins_module.getDomainFunctions() + for function in domainFunctions: + functionOutput = plugins_module.runPluginFunction(function["plugin"],function["function"],{"domain":domain},account_module.check_account(request.cookies.get("account"))) + plugins += render.plugin_output(functionOutput,plugins_module.getPluginFunctionReturns(function["plugin"],function["function"])) + plugins += "
" @@ -1053,21 +1062,7 @@ def plugins_index(): if not account: return redirect("/logout") - plugin_links = os.listdir("plugins") - plugins = [] - for plugin in plugin_links: - if os.path.isdir("plugins/" + plugin): - if os.path.isfile("plugins/" + plugin + "/"+plugin+".json"): - with open("plugins/" + plugin + "/"+plugin+".json") as f: - data = json.load(f) - data['link'] = plugin - if 'name' not in data: - data['name'] = plugin - if 'description' not in data: - data['description'] = "No description provided" - plugins.append(data) - - plugins = render.plugins(plugins) + plugins = render.plugins(plugins_module.listPlugins()) return render_template("plugins.html", account=account, sync=account_module.getNodeSync(), plugins=plugins) @@ -1082,37 +1077,22 @@ def plugin(plugin): if not account: return redirect("/logout") - if not os.path.isdir("plugins/" + plugin): + if not plugins_module.pluginExists(plugin): return redirect("/plugins") - if not os.path.isfile("plugins/" + plugin + "/"+plugin+".json"): - return redirect("/plugins") + data = plugins_module.getPluginData(plugin) - with open("plugins/" + plugin + "/"+plugin+".json") as f: - data = json.load(f) - data['link'] = plugin - if 'name' not in data: - data['name'] = plugin - if 'description' not in data: - data['description'] = "No description provided" - - - functions = [] - - if os.path.isfile("plugins/" + plugin + "/main.py"): - # Get plugin/main.py listfunctions() - print("Loading plugin: " + plugin) - module = importlib.import_module("plugins." + plugin + ".main") - functions = module.listFunctions() - functions = render.plugin_functions(functions,plugin) + functions = plugins_module.getPluginFunctions(plugin) + functions = render.plugin_functions(functions,plugin) error = request.args.get("error") if error == None: error = "" return render_template("plugin.html", account=account, sync=account_module.getNodeSync(), - name=data['name'],description=data['description'],functions=functions, - error=error) + name=data['name'],description=data['description'], + author=data['author'],version=data['version'], + functions=functions,error=error) @app.route('/plugin//', methods=["POST"]) def plugin_function(plugin,function): @@ -1124,59 +1104,45 @@ def plugin_function(plugin,function): if not account: return redirect("/logout") - if not os.path.isdir("plugins/" + plugin): + if not plugins_module.pluginExists(plugin): return redirect("/plugins") - if not os.path.isfile("plugins/" + plugin + "/"+plugin+".json"): - return redirect("/plugins") + data = plugins_module.getPluginData(plugin) - with open("plugins/" + plugin + "/"+plugin+".json") as f: - data = json.load(f) - data['link'] = plugin - if 'name' not in data: - data['name'] = plugin - if 'description' not in data: - data['description'] = "No description provided" - - if os.path.isfile("plugins/" + plugin + "/main.py"): - # Get plugin/main.py listfunctions() - print("Loading plugin: " + plugin) - module = importlib.import_module("plugins." + plugin + ".main") - if function in module.listFunctions(): - inputs = module.listFunctions()[function]["params"] - request_data = {} - for input in inputs: - request_data[input] = request.form.get(input) - - if inputs[input]['type'] == "address": - # Handle hip2 - address_check = account_module.check_address(request_data[input],True,True) - if not address_check: - return redirect("/plugin/" + plugin + "?error=Invalid address") - request_data[input] = address_check - elif inputs[input]['type'] == "dns": - # Handle URL encoding of DNS - request_data[input] = urllib.parse.unquote(request_data[input]) - - - - - response = module.runFunction(function,request_data,request.cookies.get("account")) - if not response: - return redirect("/plugin/" + plugin + "?error=An error occurred") - if 'error' in response: - return redirect("/plugin/" + plugin + "?error=" + response['error']) + # Get plugin/main.py listfunctions() + if function in plugins_module.getPluginFunctions(plugin): + inputs = plugins_module.getPluginFunctionInputs(plugin,function) + request_data = {} + for input in inputs: + request_data[input] = request.form.get(input) - response = render.plugin_output(response,module.listFunctions()[function]["returns"]) - - return render_template("plugin-output.html", account=account, sync=account_module.getNodeSync(), - name=data['name'],description=data['description'],output=response) + if inputs[input]['type'] == "address": + # Handle hip2 + address_check = account_module.check_address(request_data[input],True,True) + if not address_check: + return redirect("/plugin/" + plugin + "?error=Invalid address") + request_data[input] = address_check + elif inputs[input]['type'] == "dns": + # Handle URL encoding of DNS + request_data[input] = urllib.parse.unquote(request_data[input]) - else: - return jsonify({"error": "Function not found"}) - return jsonify({"error": "Plugin not found"}) + + response = plugins_module.runPluginFunction(plugin,function,request_data,request.cookies.get("account")) + if not response: + return redirect("/plugin/" + plugin + "?error=An error occurred") + if 'error' in response: + return redirect("/plugin/" + plugin + "?error=" + response['error']) + + response = render.plugin_output(response,plugins_module.getPluginFunctionReturns(plugin,function)) + + return render_template("plugin-output.html", account=account, sync=account_module.getNodeSync(), + name=data['name'],description=data['description'],output=response) + + + else: + return jsonify({"error": "Function not found"}) #endregion diff --git a/plugin.py b/plugin.py new file mode 100644 index 0000000..10d784d --- /dev/null +++ b/plugin.py @@ -0,0 +1,101 @@ +import os +import json +import importlib + + + +def listPlugins(): + plugins = [] + for file in os.listdir("plugins"): + if file.endswith(".py"): + if file != "main.py": + plugin = importlib.import_module("plugins."+file[:-3]) + details = plugin.info + details["link"] = file[:-3] + plugins.append(details) + return plugins + + +def pluginExists(plugin: str): + for file in os.listdir("plugins"): + if file == plugin+".py": + return True + return False + +def getPluginData(plugin: str): + plugin = importlib.import_module("plugins."+plugin) + return plugin.info + +def getPluginFunctions(plugin: str): + plugin = importlib.import_module("plugins."+plugin) + return plugin.functions + +def runPluginFunction(plugin: str, function: str, params: dict, authentication: str): + plugin_module = importlib.import_module("plugins."+plugin) + if function not in plugin_module.functions: + return {"error": "Function not found"} + + if not hasattr(plugin_module, function): + return {"error": "Function not found"} + + # Get the function object from the plugin module + plugin_function = getattr(plugin_module, function) + + # Call the function with provided parameters + try: + result = plugin_function(params, authentication) + return result + except Exception as e: + print(f"Error running plugin: {e}") + return {"error": str(e)} + # return plugin.runFunction(function, params, authentication) + +def getPluginFunctionInputs(plugin: str, function: str): + plugin = importlib.import_module("plugins."+plugin) + return plugin.functions[function]["params"] + +def getPluginFunctionReturns(plugin: str, function: str): + plugin = importlib.import_module("plugins."+plugin) + return plugin.functions[function]["returns"] + +def getDomainFunctions(): + plugins = listPlugins() + domainFunctions = [] + for plugin in plugins: + functions = getPluginFunctions(plugin["link"]) + for function in functions: + if functions[function]["type"] == "domain": + domainFunctions.append({ + "plugin": plugin["link"], + "function": function, + "description": functions[function]["description"] + }) + return domainFunctions + +def getSearchFunctions(): + plugins = listPlugins() + searchFunctions = [] + for plugin in plugins: + functions = getPluginFunctions(plugin["link"]) + for function in functions: + if functions[function]["type"] == "search": + searchFunctions.append({ + "plugin": plugin["link"], + "function": function, + "description": functions[function]["description"] + }) + return searchFunctions + +def getDashboardFunctions(): + plugins = listPlugins() + dashboardFunctions = [] + for plugin in plugins: + functions = getPluginFunctions(plugin["link"]) + for function in functions: + if functions[function]["type"] == "dashboard": + dashboardFunctions.append({ + "plugin": plugin["link"], + "function": function, + "description": functions[function]["description"] + }) + return dashboardFunctions \ No newline at end of file diff --git a/plugins.md b/plugins.md index 6c05ffe..2142ee4 100644 --- a/plugins.md +++ b/plugins.md @@ -1,5 +1,24 @@ # Plugins +## Types +### Default +Type: `default` +This is the default type and is used when no type is specified. +This type is displayed in the plugin page only. +This is the onlu type of plugin that takes user input + +### Manage & Search +For manage page use type: `domain` +For search page use type: `search` + +This type is used for domain plugins. It shows in the manage domain page or the search page. +It gets the `domain` paramater as the only input (in addition to authentication) + +### Dashboard +This type is used for dashboard plugins. +It shows in the dashboard page. It doesn't get any inputs other than the authentication + + ## Inputs ### Plain Text diff --git a/plugins/domain/domain.json b/plugins/domain/domain.json deleted file mode 100644 index f999f8b..0000000 --- a/plugins/domain/domain.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name":"Domains Plugin", - "description":"This is plugin for the domain page" -} \ No newline at end of file diff --git a/plugins/domain/main.py b/plugins/domain/main.py deleted file mode 100644 index d6a296e..0000000 --- a/plugins/domain/main.py +++ /dev/null @@ -1,48 +0,0 @@ -import json -import account - - -functions = { - "bids": { - "name": "Bid info", - "type": "domain", - "description": "Check when the domain was last updated", - "params": { - "domain": { - "name":"domain to check", - "type":"domain" - } - }, - "returns": { - "highest": - { - "name": "Highest bid", - "type": "text" - }, - "paid": - { - "name": "Amount paid in auction", - "type": "text" - } - } - } -} - -def listFunctions(): - return functions - -def runFunction(function, params, authentication): - if function == "bids": - return bids(params['domain'], authentication) - else: - return "Function not found" - - -def bids(domain, authentication): - wallet = authentication.split(":")[0] - data = account.getDomain(domain) - value = str(account.convertHNS(data['info']['value'])) + " HNS" - highest = str(account.convertHNS(data['info']['highest'])) + " HNS" - - - return {"highest": highest,"paid":value} \ No newline at end of file diff --git a/plugins/example/main.py b/plugins/example.py similarity index 56% rename from plugins/example/main.py rename to plugins/example.py index d9bda51..ccfdc1b 100644 --- a/plugins/example/main.py +++ b/plugins/example.py @@ -1,27 +1,22 @@ import json import account +import requests +# Plugin Data +info = { + "name": "Example Plugin", + "description": "This is a plugin to be used as an example", + "version": "1.0", + "author": "Nathan.Woodburn/" +} + + +# Functions functions = { - "check": { - "name": "Domain Check", - "description": "Check if domains in file are owned by the wallet", - "params": { - "domains": { - "name":"List of domains to check", - "type":"longText" - } - }, - "returns": { - "domains": - { - "name": "List of owned domains", - "type": "list" - } - } - }, "search":{ "name": "Search Owned", + "type": "default", "description": "Search for owned domains containing a string", "params": { "search": { @@ -39,6 +34,7 @@ functions = { }, "transfer":{ "name": "Bulk Transfer Domains", + "type": "default", "description": "Transfer domains to another wallet", "params": { "address": { @@ -63,6 +59,7 @@ functions = { }, "dns":{ "name": "Set DNS for Domains", + "type": "default", "description": "Set DNS for domains", "params": { "domains": { @@ -84,26 +81,50 @@ functions = { "type": "dns" } } + }, + "niami": { + "name": "Niami info", + "type": "domain", + "description": "Check the domains niami rating", + "params": {}, + "returns": { + "rating": + { + "name": "Niami Rating", + "type": "text" + } + } + }, + "niamiSearch": { + "name": "Niami info", + "type": "search", + "description": "Check the domains niami rating", + "params": {}, + "returns": { + "rating": + { + "name": "Niami Rating", + "type": "text" + } + } + }, + "connections":{ + "name": "HSD Connections", + "type": "dashboard", + "description": "Show the number of connections the HSD node is connected to", + "params": {}, + "returns": { + "connections": + { + "name": "HSD Connections", + "type": "text" + } + } } } -def listFunctions(): - return functions - -def runFunction(function, params, authentication): - if function == "check": - return check(params['domains'], authentication) - elif function == "search": - return search(params['search'], authentication) - elif function == "transfer": - return transfer(params['address'], params['domains'], authentication) - elif function == "dns": - return dns(params['domains'],params['dns'],authentication) - else: - return "Function not found" - - -def check(domains, authentication): +def check(params, authentication): + domains = params["domains"] domains = domains.splitlines() wallet = authentication.split(":")[0] @@ -116,7 +137,8 @@ def check(domains, authentication): return {"domains": domains} -def search(search, authentication): +def search(params, authentication): + search = params["search"] wallet = authentication.split(":")[0] owned = account.getDomains(wallet) # Only keep owned domains ["name"] @@ -127,8 +149,27 @@ def search(search, authentication): return {"domains": domains} -def transfer(address, domains, authentication): +def transfer(params, authentication): + address = params["address"] return {"hash":"f921ffe1bb01884bf515a8079073ee9381cb93a56b486694eda2cce0719f27c0","address":address} -def dns(domains,dns,authentication): - return {"hash":"f921ffe1bb01884bf515a8079073ee9381cb93a56b486694eda2cce0719f27c0","dns":dns} \ No newline at end of file +def dns(params,authentication): + dns = params["dns"] + return {"hash":"f921ffe1bb01884bf515a8079073ee9381cb93a56b486694eda2cce0719f27c0","dns":dns} + +def niami(params, authentication): + domain = params["domain"] + print(domain) + response = requests.get(f"https://api.handshake.niami.io/domain/{domain}") + print(response.text) + data = response.json()["data"] + rating = str(data["rating"]["score"]) + " (" + data["rating"]["rarity"] + ")" + return {"rating":rating} + +def niamiSearch(params, authentication): + return niami(params, authentication) + + +def connections(params,authentication): + outbound = account.hsd.getInfo()['pool']['outbound'] + return {"connections": outbound} \ No newline at end of file diff --git a/plugins/example/example.json b/plugins/example/example.json deleted file mode 100644 index 6773397..0000000 --- a/plugins/example/example.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name":"Example Plugin", - "description":"This is an example plugin" -} \ No newline at end of file diff --git a/render.py b/render.py index 75a3ae2..d430c76 100644 --- a/render.py +++ b/render.py @@ -204,7 +204,7 @@ def plugin_functions(functions, pluginName): returns = returns.removesuffix(', ') - functionType = "manual" + functionType = "default" if "type" in functions[function]: functionType = functions[function]["type"] @@ -215,7 +215,7 @@ def plugin_functions(functions, pluginName): html += f'
{description}
' html += f'
Function type: {functionType.capitalize()}
' - if functionType != "manual": + if functionType != "default": html += f'

Returns: {returns}

' html += f'' html += f'' @@ -286,8 +286,17 @@ def plugin_output(outputs, returns): html += f'' + html += f'' + return html + +def plugin_output_dash(outputs, returns): + + html = '' + + for returnOutput in returns: + html += render_template('components/dashboard-plugin.html', name=returns[returnOutput]["name"], output=outputs[returnOutput]) + + html += f'' - - - + html += f'' return html \ No newline at end of file diff --git a/templates/components/dashboard-plugin.html b/templates/components/dashboard-plugin.html new file mode 100644 index 0000000..c6fdb75 --- /dev/null +++ b/templates/components/dashboard-plugin.html @@ -0,0 +1,8 @@ +
+
+
+
{{name}}
+
{{output}}
+
+
+
\ No newline at end of file diff --git a/templates/index.html b/templates/index.html index eb396d8..1f79865 100644 --- a/templates/index.html +++ b/templates/index.html @@ -126,7 +126,7 @@ - + {{plugins|safe}}
diff --git a/templates/plugin.html b/templates/plugin.html index af6d813..38db15c 100644 --- a/templates/plugin.html +++ b/templates/plugin.html @@ -63,7 +63,8 @@

{{error}}

{{name}}

-

{{description}}

{{functions|safe}} +

{{description}}

+
Author: {{author}}
Version: {{version}}
{{functions|safe}}
diff --git a/templates/search.html b/templates/search.html index f494772..373715d 100644 --- a/templates/search.html +++ b/templates/search.html @@ -68,7 +68,7 @@
Owner: {{owner}}
ManageAuction
- + {{plugins|safe}}