Compare commits
14 commits
Author | SHA1 | Date | |
---|---|---|---|
e1ddb50589 | |||
60b260c86f | |||
efe4533e72 | |||
59716d25b2 | |||
4107c86082 | |||
fcbac544d8 | |||
33059e1971 | |||
18bb8ee8c6 | |||
d8928a2037 | |||
06bdc39a75 | |||
84100ee981 | |||
369b7a94af | |||
6c0e596a09 | |||
a1d67cedb8 |
6 changed files with 117 additions and 19 deletions
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal 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
56
README.md
Normal 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
|
59
chainlink.py
59
chainlink.py
|
@ -1,25 +1,34 @@
|
|||
#!/usr/bin/env python
|
||||
"""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 requests
|
||||
import json2html
|
||||
|
||||
VERSION = '0.0.1'
|
||||
VERSION = '0.1.0'
|
||||
|
||||
app = Bottle()
|
||||
|
||||
|
||||
def domainLookup(domain):
|
||||
apibase = 'https://unstoppabledomains.com/api/v1/'
|
||||
dnslookup = requests.get(apibase + domain)
|
||||
domainJSON = json.loads(dnslookup.content)
|
||||
print(domainJSON)
|
||||
return domainJSON
|
||||
"""Return a dictionary from JSON results of API call
|
||||
or return False if there is an error
|
||||
"""
|
||||
try:
|
||||
apibase = 'https://unstoppabledomains.com/api/v1/'
|
||||
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("/")
|
||||
def root():
|
||||
"""Index page, just displays information about URL formatting"""
|
||||
host = request.get_header('host')
|
||||
helptext = f"""
|
||||
<p>General format is {host}/<domain>/<action>
|
||||
|
@ -33,7 +42,7 @@ def root():
|
|||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<td>redir</td>
|
||||
|
@ -49,10 +58,24 @@ def root():
|
|||
@app.route("/<domain>/")
|
||||
@app.route("/<domain>/<action>")
|
||||
def redirectDomain(domain, action=None):
|
||||
"""Handle the actual redirect for the domain queried"""
|
||||
lookupResult = domainLookup(domain)
|
||||
redirect_url = lookupResult['ipfs']['redirect_domain']
|
||||
html = lookupResult['ipfs']['html']
|
||||
if action == 'redir':
|
||||
if domain == 'yourdomain.crypto' or domain == 'yourdomain.zil':
|
||||
redirect('/')
|
||||
if lookupResult:
|
||||
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:
|
||||
if not redirect_url.startswith('http'):
|
||||
redirect_url = "http://" + redirect_url
|
||||
|
@ -60,17 +83,15 @@ def redirectDomain(domain, action=None):
|
|||
response.set_header('Location', redirect_url)
|
||||
except KeyError:
|
||||
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':
|
||||
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__":
|
||||
run(app, host='0.0.0.0', port='5000', reloader=True)
|
||||
|
|
BIN
images/cryptosearch.png
Normal file
BIN
images/cryptosearch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
images/favicon.ico
Normal file
BIN
images/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
BIN
images/zilsearch.png
Normal file
BIN
images/zilsearch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Loading…
Reference in a new issue