Compare commits

..

14 commits

6 changed files with 117 additions and 19 deletions

21
LICENSE.txt Normal file
View file

@ -0,0 +1,21 @@
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 Normal file
View file

@ -0,0 +1,56 @@
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

View file

@ -1,25 +1,34 @@
#!/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 from bottle import Bottle, run, response, request, static_file, redirect
import json import json
import requests import requests
import json2html import json2html
VERSION = '0.0.1' VERSION = '0.1.0'
app = Bottle() app = Bottle()
def domainLookup(domain): def domainLookup(domain):
"""Return a dictionary from JSON results of API call
or return False if there is an error
"""
try:
apibase = 'https://unstoppabledomains.com/api/v1/' apibase = 'https://unstoppabledomains.com/api/v1/'
dnslookup = requests.get(apibase + domain) dnslookup = requests.get(apibase + domain)
domainJSON = json.loads(dnslookup.content) if dnslookup.status_code == 200:
print(domainJSON) domainInfo = json.loads(dnslookup.content)
return domainJSON 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}/&lt;domain&gt;/&lt;action&gt; <p>General format is {host}/&lt;domain&gt;/&lt;action&gt;
@ -33,7 +42,7 @@ def root():
</tr> </tr>
<tr> <tr>
<td>html</td> <td>html</td>
<td>Hit the IPFS hash via cloudflare-ipfs.com</td> <td>Hit the IPFS hash via gateway.ipfs.io</td>
</tr> </tr>
<tr> <tr>
<td>redir</td> <td>redir</td>
@ -49,10 +58,24 @@ 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('/')
if lookupResult:
redirect_url = lookupResult['ipfs']['redirect_domain'] redirect_url = lookupResult['ipfs']['redirect_domain']
html = lookupResult['ipfs']['html'] ipfs_hash = lookupResult['ipfs']['html']
if action == 'redir': 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
@ -60,17 +83,15 @@ 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)

BIN
images/cryptosearch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
images/zilsearch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB