diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..bc3e420 --- /dev/null +++ b/LICENSE.txt @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a032e78 --- /dev/null +++ b/README.md @@ -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:

+![.crypto search registration][crypto] + +It's basically the same for .zil:

+![.crypto search registration][zil] + + +[crypto]: images/cryptosearch.png +[zil]: images/zilsearch.png diff --git a/chainlink.py b/chainlink.py index ebd5f33..6157fd1 100755 --- a/chainlink.py +++ b/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"""

General format is {host}/<domain>/<action> @@ -33,7 +42,7 @@ def root(): html - Hit the IPFS hash via cloudflare-ipfs.com + Hit the IPFS hash via gateway.ipfs.io redir @@ -49,10 +58,24 @@ def root(): @app.route("//") @app.route("//") 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) diff --git a/images/cryptosearch.png b/images/cryptosearch.png new file mode 100644 index 0000000..64e8e3b Binary files /dev/null and b/images/cryptosearch.png differ diff --git a/images/favicon.ico b/images/favicon.ico new file mode 100644 index 0000000..f62f4e6 Binary files /dev/null and b/images/favicon.ico differ diff --git a/images/zilsearch.png b/images/zilsearch.png new file mode 100644 index 0000000..b42881e Binary files /dev/null and b/images/zilsearch.png differ