feat: Finish v1 of plugins
All checks were successful
Build Docker / Build Image (push) Successful in 32s

This commit is contained in:
Nathan Woodburn 2024-02-07 13:58:03 +11:00
parent 78eae529e5
commit 798bac1d2f
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
13 changed files with 290 additions and 201 deletions

Binary file not shown.

154
main.py
View File

@ -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 = "<div class='container-fluid'>"
# 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 += "</div>"
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/<domain>')
def manage(domain: str):
@ -373,20 +391,11 @@ def manage(domain: str):
plugins = "<div class='container-fluid'>"
# 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 += "</div>"
@ -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/<plugin>/<function>', 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"
# 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)
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])
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'])
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,module.listFunctions()[function]["returns"])
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)
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"})
return jsonify({"error": "Plugin not found"})
else:
return jsonify({"error": "Function not found"})
#endregion

101
plugin.py Normal file
View File

@ -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

View File

@ -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

View File

@ -1,4 +0,0 @@
{
"name":"Domains Plugin",
"description":"This is plugin for the domain page"
}

View File

@ -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}

View File

@ -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):
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}

View File

@ -1,4 +0,0 @@
{
"name":"Example Plugin",
"description":"This is an example plugin"
}

View File

@ -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'<h6 class="text-muted card-subtitle mb-2">{description}</h6>'
html += f'<h6 class="text-muted card-subtitle mb-2">Function type: {functionType.capitalize()}</h6>'
if functionType != "manual":
if functionType != "default":
html += f'<p class="card-text">Returns: {returns}</p>'
html += f'</div>'
html += f'</div>'
@ -287,7 +287,16 @@ def plugin_output(outputs, returns):
html += f'</div>'
html += f'</div>'
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'</div>'
html += f'</div>'
return html

View File

@ -0,0 +1,8 @@
<div class="col-md-6 col-xl-3 mb-4">
<div class="card shadow border-start-warning py-2">
<div class="card-body">
<div class="text-uppercase fw-bold text-xs mb-1"><span style="color: var(--bs-dark);">{{name}}</span></div>
<div class="text-dark fw-bold h5 mb-0"><span>{{output}}</span></div>
</div>
</div>
</div>

View File

@ -126,7 +126,7 @@
</div>
</div>
</div>
</div>
</div>{{plugins|safe}}
</div>
<div class="row">
<div class="col">

View File

@ -63,7 +63,8 @@
<h1 class="text-center" style="color: rgb(255,0,0);">{{error}}</h1>
<div class="container-fluid" style="margin-bottom: 20px;">
<h3 class="text-dark mb-1">{{name}}</h3>
<h4 class="text-dark mb-1">{{description}}</h4>{{functions|safe}}
<h4 class="text-dark mb-1">{{description}}</h4>
<h6 class="text-dark mb-1">Author: {{author}}<br>Version: {{version}}</h6>{{functions|safe}}
</div>
</div>
<footer class="sticky-footer" style="background: var(--bs-primary-text-emphasis);">

View File

@ -68,7 +68,7 @@
<h6 class="text-muted card-subtitle mb-2">Owner: {{owner}}</h6><a class="btn btn-primary" role="button" style="margin-right: 25px;" href="/manage/{{domain}}">Manage</a><a class="btn btn-primary" role="button" href="/auction/{{domain}}">Auction</a>
</div>
</div>
</div>
</div>{{plugins|safe}}
<div class="container-fluid" style="margin-top: 50px;">
<div class="card">
<div class="card-body">