Compare commits
No commits in common. "master" and "develop" have entirely different histories.
6 changed files with 19 additions and 117 deletions
21
LICENSE.txt
21
LICENSE.txt
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Kelly
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
56
README.md
56
README.md
|
@ -1,56 +0,0 @@
|
||||||
Chainlink
|
|
||||||
=========
|
|
||||||
|
|
||||||
Chainlink is a utility that will run on a webserver to handle crypto-backed
|
|
||||||
domain names Specifically this works with
|
|
||||||
[Unstoppable Domains](https://unstoppabledomains.com)
|
|
||||||
This utilizes public IPFS gateways to display IPFS content, or will redirect to
|
|
||||||
whatever the URL is set to in the Unstoppable Domain management.
|
|
||||||
|
|
||||||
Installation
|
|
||||||
============
|
|
||||||
|
|
||||||
To install this on your own server:
|
|
||||||
|
|
||||||
1. Clone this repository
|
|
||||||
2. Install the python dependancies via `pip install -r requirements.txt`
|
|
||||||
3. Run the server with `./chainlink.py`
|
|
||||||
4. You can now hit the app at http://localhost:5000
|
|
||||||
|
|
||||||
Testing on a running instance
|
|
||||||
=============================
|
|
||||||
|
|
||||||
Assuming that it is running (I could be messing with it, or it might just be
|
|
||||||
broken), you may try this without installing by going to
|
|
||||||
https://chainlink.arachnitech.com/
|
|
||||||
|
|
||||||
URL Patterns
|
|
||||||
============
|
|
||||||
|
|
||||||
The general format is http://localhost:5000/{domain}/{action}
|
|
||||||
where {domain} is a .crypto or .zil name registered with Unstoppable
|
|
||||||
Domains and {action} can be
|
|
||||||
* html - uses the IPFS hash set in Unstoppable Domains management
|
|
||||||
* redir - uses the redirect_url set in Unstoppable Domains management
|
|
||||||
* raw - displays an HTML table view of the full JSON returned from Unstoppable
|
|
||||||
Domains API
|
|
||||||
|
|
||||||
The default action is "html", so if you don't specify anything it will attempt
|
|
||||||
to use that field.
|
|
||||||
|
|
||||||
Browser Search Engine
|
|
||||||
=====================
|
|
||||||
|
|
||||||
I added a browser search engine for each of the UD domains (.zil and .crypto) so
|
|
||||||
that, when I type `.crypto domain` The browser will automatically redirect based
|
|
||||||
on the output from my chainlink script.
|
|
||||||
|
|
||||||
The settings to make that work for .crypto is: <p>
|
|
||||||
![.crypto search registration][crypto]
|
|
||||||
|
|
||||||
It's basically the same for .zil: <p>
|
|
||||||
![.crypto search registration][zil]
|
|
||||||
|
|
||||||
|
|
||||||
[crypto]: images/cryptosearch.png
|
|
||||||
[zil]: images/zilsearch.png
|
|
59
chainlink.py
59
chainlink.py
|
@ -1,34 +1,25 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
"""Utilize crypto domains DNS and either redirect, or display information."""
|
"""Utilize crypto domains DNS and either redirect, or display information."""
|
||||||
from bottle import Bottle, run, response, request, static_file, redirect
|
from bottle import Bottle, run, response, request
|
||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
import json2html
|
import json2html
|
||||||
|
|
||||||
VERSION = '0.1.0'
|
VERSION = '0.0.1'
|
||||||
|
|
||||||
app = Bottle()
|
app = Bottle()
|
||||||
|
|
||||||
|
|
||||||
def domainLookup(domain):
|
def domainLookup(domain):
|
||||||
"""Return a dictionary from JSON results of API call
|
apibase = 'https://unstoppabledomains.com/api/v1/'
|
||||||
or return False if there is an error
|
dnslookup = requests.get(apibase + domain)
|
||||||
"""
|
domainJSON = json.loads(dnslookup.content)
|
||||||
try:
|
print(domainJSON)
|
||||||
apibase = 'https://unstoppabledomains.com/api/v1/'
|
return domainJSON
|
||||||
dnslookup = requests.get(apibase + domain)
|
|
||||||
if dnslookup.status_code == 200:
|
|
||||||
domainInfo = json.loads(dnslookup.content)
|
|
||||||
return domainInfo
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
except requests.exceptions.ConnectionError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def root():
|
def root():
|
||||||
"""Index page, just displays information about URL formatting"""
|
|
||||||
host = request.get_header('host')
|
host = request.get_header('host')
|
||||||
helptext = f"""
|
helptext = f"""
|
||||||
<p>General format is {host}/<domain>/<action>
|
<p>General format is {host}/<domain>/<action>
|
||||||
|
@ -42,7 +33,7 @@ def root():
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>html</td>
|
<td>html</td>
|
||||||
<td>Hit the IPFS hash via gateway.ipfs.io</td>
|
<td>Hit the IPFS hash via cloudflare-ipfs.com</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>redir</td>
|
<td>redir</td>
|
||||||
|
@ -58,24 +49,10 @@ def root():
|
||||||
@app.route("/<domain>/")
|
@app.route("/<domain>/")
|
||||||
@app.route("/<domain>/<action>")
|
@app.route("/<domain>/<action>")
|
||||||
def redirectDomain(domain, action=None):
|
def redirectDomain(domain, action=None):
|
||||||
"""Handle the actual redirect for the domain queried"""
|
|
||||||
lookupResult = domainLookup(domain)
|
lookupResult = domainLookup(domain)
|
||||||
if domain == 'yourdomain.crypto' or domain == 'yourdomain.zil':
|
redirect_url = lookupResult['ipfs']['redirect_domain']
|
||||||
redirect('/')
|
html = lookupResult['ipfs']['html']
|
||||||
if lookupResult:
|
if action == 'redir':
|
||||||
redirect_url = lookupResult['ipfs']['redirect_domain']
|
|
||||||
ipfs_hash = lookupResult['ipfs']['html']
|
|
||||||
else:
|
|
||||||
return f"Unable to get info for {domain}"
|
|
||||||
if action is None or action == 'html':
|
|
||||||
response.status = 302
|
|
||||||
if not ipfs_hash.startswith('/ip'):
|
|
||||||
ipfshash = "ipfs/" + ipfs_hash
|
|
||||||
else:
|
|
||||||
ipfshash = ipfs_hash
|
|
||||||
response.set_header('Location',
|
|
||||||
f"https://gateway.ipfs.io/{ipfshash}")
|
|
||||||
elif action == 'redir':
|
|
||||||
try:
|
try:
|
||||||
if not redirect_url.startswith('http'):
|
if not redirect_url.startswith('http'):
|
||||||
redirect_url = "http://" + redirect_url
|
redirect_url = "http://" + redirect_url
|
||||||
|
@ -83,15 +60,17 @@ def redirectDomain(domain, action=None):
|
||||||
response.set_header('Location', redirect_url)
|
response.set_header('Location', redirect_url)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return f'Did not find a redirect for {domain}'
|
return f'Did not find a redirect for {domain}'
|
||||||
|
elif action is None or action == 'html':
|
||||||
|
response.status = 302
|
||||||
|
if not html.startswith('/ip'):
|
||||||
|
ipfshash = "ipfs/" + html
|
||||||
|
else:
|
||||||
|
ipfshash = html
|
||||||
|
response.set_header('Location',
|
||||||
|
f"https://cloudflare-ipfs.com/{ipfshash}")
|
||||||
elif action == 'raw':
|
elif action == 'raw':
|
||||||
return json2html.json2html.convert(json=lookupResult)
|
return json2html.json2html.convert(json=lookupResult)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/favicon.ico")
|
|
||||||
def favicon():
|
|
||||||
"""Display a favicon to make the errors go away :)"""
|
|
||||||
return static_file('favicon.ico', root='images')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
run(app, host='0.0.0.0', port='5000', reloader=True)
|
run(app, host='0.0.0.0', port='5000', reloader=True)
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
Loading…
Reference in a new issue