├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── dom ├── domainr ├── __init__.py └── core.py ├── requirements.txt ├── setup.py └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | dom.egg-info/ 4 | *.pyc 5 | 6 | # IDE 7 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.7 4 | script: make travis 5 | install: 6 | - pip install -r requirements.txt --use-mirrors 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | travis: 2 | python test.py 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dom 2 | === 3 | 4 | An easy-to-use command line utility for checking domain name 5 | availability using [Domainr's JSON API](https://rapidapi.com/domainr/api/domainr/). 6 | 7 | 8 | ![Example of dom in action](http://i.imgur.com/oijaG.png) 9 | 10 | Setup requires two things: 11 | 12 | 1. Installing dom 13 | 2. Obtaining an API key 14 | 15 | 16 | Installation 17 | ------------ 18 | 19 | The simplest method is to install the package through `pip` by running: 20 | 21 | pip install dom 22 | 23 | Alternatively, you can install from source: 24 | 25 | 1. Clone the repo: 26 | 27 | `$ git clone git@github.com:zachwill/dom.git` 28 | `$ cd dom` 29 | 30 | 2. Run the following command to install: 31 | `python setup.py install` 32 | 33 | 34 | Get an API key for Domainr 35 | -------------------------- 36 | 37 | Due to abuse of their API, Domainr now requires an API key for each user. This key can be obtained through one 38 | of the following ways: 39 | 40 | 1. Get a Rapidapi Domainr API key from [Rapidapi](https://rapidapi.com/domainr/api/domainr/) 41 | **Note:** While it is free up to 10,000 calls/mo., you are required to submit a valid credit card to cover 42 | any requests over the free limit. 43 | * You can use something like [Privacy.com](https://privacy.com/) to create a credit temporary card. I've been told that some banks offer a similar feature as well. 44 | 2. Contact Domainr at `partners@domainr.com` to get a personal use client ID, as detailed 45 | [here](https://github.com/UltrosBot/Ultros-contrib/issues/29#issuecomment-135285713) 46 | 47 | Once you have obtained one of the two types of keys, insert either the Rapidapi API key or the Client ID into your local environment: 48 | ``` 49 | $ export DOMAINR_RAPIDAPI_KEY={your-rapidapi-key} 50 | ``` 51 | or 52 | ``` 53 | $ export DOMAINR_CLIENT_ID={your-client-id} 54 | ``` 55 | 56 | You can do this manually everytime before running dom, or you can search for how to do this on login. Digital Ocean 57 | has an excellent guide here: [How To Read and Set Environmental and Shell Variables on a Linux VPS](https://www.digitalocean.com/community/tutorials/how-to-read-and-set-environmental-and-shell-variables-on-a-linux-vps). 58 | 59 | Note that in the event that both keys are present, dom will default to using the Rapidapi Key. 60 | 61 | 62 | Optional Flags 63 | -------------- 64 | 65 | The optional `--ascii` flag can be used to look up domain availability without 66 | the use of the Unicode characters. 67 | 68 | ``` 69 | dom --ascii zachwill 70 | 71 | X zachwill.com 72 | A zachwill.net 73 | A zachwill.org 74 | A zachwill.co 75 | X za.ch 76 | X z.ac 77 | ``` 78 | 79 | The `--available` flag only shows domain names that are currently available: 80 | 81 | ``` 82 | dom --available zachwill 83 | 84 | ✓ zachwill.net 85 | ✓ zachwill.org 86 | ✓ zachwill.co 87 | ✓ zachwill.io 88 | ✓ zachwill.me 89 | ``` 90 | 91 | The `--no-suggest` flag only check the exact domain names that are in query: 92 | 93 | ``` 94 | dom --no-suggest zachwill.com 95 | 96 | ✗ zachwill.com 97 | ``` 98 | 99 | And, the `--tld` flag only shows top-level domains: 100 | 101 | ``` 102 | dom --tld zachwill 103 | 104 | ✗ zachwill.com 105 | ✓ zachwill.net 106 | ✓ zachwill.org 107 | ``` 108 | 109 | 110 | Deploying 111 | --------- 112 | 113 | You won't need to worry about this, but since the Python `upload` 114 | command is so obtuse, I'm going to keep it here: 115 | 116 | python setup.py sdist bdist_egg upload 117 | -------------------------------------------------------------------------------- /dom: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from domainr import Domain 4 | 5 | 6 | if __name__ == '__main__': 7 | Domain().main() 8 | -------------------------------------------------------------------------------- /domainr/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import Domain 2 | -------------------------------------------------------------------------------- /domainr/core.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core functionality for Domainr. 3 | """ 4 | 5 | from argparse import ArgumentParser 6 | import os 7 | import requests 8 | import simplejson as json 9 | import sys 10 | from termcolor import colored 11 | 12 | 13 | class Domain(object): 14 | """Main class for interacting with the domains API.""" 15 | 16 | def __init__(self): 17 | """Instantiate class, grab credentials from env if available""" 18 | if os.environ.get('DOMAINR_RAPIDAPI_KEY'): 19 | self.api_key = os.environ.get('DOMAINR_RAPIDAPI_KEY') 20 | self.api_key_name = 'rapidapi-key' 21 | self.api_endpoint = "https://domainr.p.rapidapi.com" 22 | elif os.environ.get('DOMAINR_CLIENT_ID'): 23 | self.api_key = os.environ.get('DOMAINR_CLIENT_ID') 24 | self.api_key_name = 'client_id' 25 | self.api_endpoint = "https://api.domainr.com" 26 | else: 27 | sys.exit("Error: No API key found in environment\n" + 28 | "For more information, see README") 29 | 30 | @staticmethod 31 | def environment(): 32 | """Parse any command line arguments.""" 33 | parser = Domain._get_argparser() 34 | args = parser.parse_args() 35 | return args 36 | 37 | def search(self, env): 38 | """Use domainr to get information about domain names.""" 39 | query = " ".join(env.query) 40 | params = {'query': query, self.api_key_name: self.api_key} 41 | 42 | url = self.api_endpoint + "/v2/search" 43 | 44 | json_data = requests.get(url, params=params) 45 | 46 | if not json_data.status_code == 200: 47 | return "Error: Status {0}; Response: {1}".format(json_data.status_code, json_data.content) 48 | data = self.parse_search(json_data.content, env) 49 | if not data: 50 | return "No results found\n" 51 | else: 52 | return data 53 | 54 | def status(self, env): 55 | """Use domainr to get information about domain names.""" 56 | url = self.api_endpoint + "/v2/status" 57 | query = ",".join(env.query) 58 | json_data = requests.get(url, params={'domain': query, self.api_key_name: self.api_key}) 59 | data = Domain.parse_status(json_data.content, env) 60 | return data 61 | 62 | @staticmethod 63 | def parse_search(content, env): 64 | """Parse the relevant data from JSON.""" 65 | data = json.loads(content) 66 | results = data['results'] 67 | if env.tld: 68 | output = [result['domain'] for result in results if Domain._tld_check(result['domain'])] 69 | else: 70 | output = [result['domain'] for result in results] 71 | output.sort() 72 | return output 73 | 74 | @staticmethod 75 | def parse_status(content, env): 76 | """Parse the relevant data from JSON.""" 77 | data = json.loads(content) 78 | output = [] 79 | status = data['status'] 80 | for s in status: 81 | name = s['domain'] 82 | status = s['status'] 83 | if status.endswith('inactive'): 84 | name = colored(name, 'blue', attrs=['bold']) 85 | symbol = colored(u"\u2713", 'green') 86 | if env.ascii: 87 | symbol = colored('A', 'green') 88 | else: 89 | # The available flag should skip these. 90 | if env.available: 91 | continue 92 | symbol = colored(u"\u2717", 'red') 93 | if env.ascii: 94 | symbol = colored('X', 'red') 95 | string = "%s %s" % (symbol, name) 96 | output.append(string) 97 | output.sort() 98 | return output 99 | 100 | @staticmethod 101 | def _tld_check(name): 102 | """Make sure we're dealing with a top-level domain.""" 103 | if name.endswith(".com") or name.endswith(".net") or name.endswith(".org"): 104 | return True 105 | return False 106 | 107 | @staticmethod 108 | def _get_argparser(): 109 | """Helper function to get argument parser""" 110 | parser = ArgumentParser() 111 | parser.add_argument('query', type=str, nargs='+', 112 | help="Your domain name query. With --no-suggest, must give full domain in query.") 113 | parser.add_argument('--ascii', action='store_true', 114 | help="Use ASCII characters for domain availability.") 115 | parser.add_argument('--available', action='store_true', 116 | help="Only show domain names that are currently available.") 117 | parser.add_argument('--tld', action='store_true', 118 | help="Only check for top-level domains.") 119 | parser.add_argument('--no-suggest', action='store_true', 120 | help="No suggested domains.") 121 | return parser 122 | 123 | def main(self): 124 | args = self.environment() 125 | if not args.no_suggest: 126 | args.query = self.search(args) 127 | status = self.status(args) 128 | print('\n'.join(status)) 129 | 130 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | simplejson 3 | termcolor 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | setup( 6 | name="dom", 7 | version="0.9", 8 | description="An easy-to-use command line utility for domain name lookups.", 9 | author="Zach Williams", 10 | author_email="hey@zachwill.com", 11 | url="http://github.com/zachwill/dom", 12 | license="MIT", 13 | classifiers=[ 14 | "Environment :: Console", 15 | "Intended Audience :: Developers", 16 | "Intended Audience :: End Users/Desktop", 17 | "License :: OSI Approved :: MIT License", 18 | "Natural Language :: English", 19 | "Operating System :: OS Independent", 20 | "Programming Language :: Python", 21 | "Programming Language :: Python :: 2.6", 22 | "Programming Language :: Python :: 2.7", 23 | "Programming Language :: Python :: 3.6", 24 | "Topic :: Scientific/Engineering :: Information Analysis", 25 | "Topic :: Software Development :: Libraries :: Python Modules", 26 | "Topic :: Utilities", 27 | ], 28 | packages=[ 29 | "domainr" 30 | ], 31 | scripts=[ 32 | "dom" 33 | ], 34 | install_requires=[ 35 | "requests", 36 | "simplejson", 37 | "termcolor", 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import unittest 5 | from domainr import Domain 6 | 7 | 8 | search_result = ['goo', 'goo.gl', 'goo.gle', 'goog', 'google', 'google.com', 'google.net', 'google.org', 'google.us'] 9 | status_result = ['\x1b[31m✗\x1b[0m goo', '\x1b[31m✗\x1b[0m goo.gl', '\x1b[31m✗\x1b[0m goo.gle', 10 | '\x1b[31m✗\x1b[0m goog', '\x1b[31m✗\x1b[0m google', '\x1b[31m✗\x1b[0m google.com', 11 | '\x1b[31m✗\x1b[0m google.net', '\x1b[31m✗\x1b[0m google.org', '\x1b[31m✗\x1b[0m google.us'] 12 | 13 | 14 | class TestDomain(unittest.TestCase): 15 | 16 | def setUp(self): 17 | sys.argv = ['core.py', 'google'] 18 | 19 | def tearDown(self): 20 | sys.argv = [] 21 | 22 | def test_search(self): 23 | env = Domain._get_argparser().parse_args() 24 | domain = Domain() 25 | result = domain.search(env) 26 | self.assertEquals(result, search_result) 27 | 28 | def test_status(self): 29 | env = Domain._get_argparser().parse_args() 30 | env.query = search_result 31 | domain = Domain() 32 | result = domain.status(env) 33 | self.assertEquals(result, status_result) 34 | 35 | if __name__ == '__main__': 36 | unittest.main() 37 | --------------------------------------------------------------------------------