-
-
Save dankrause/5585907 to your computer and use it in GitHub Desktop.
import requests | |
class HoverException(Exception): | |
pass | |
class HoverAPI(object): | |
def __init__(self, username, password): | |
params = {"username": username, "password": password} | |
r = requests.post("https://www.hover.com/api/login", params=params) | |
if not r.ok or "hoverauth" not in r.cookies: | |
raise HoverException(r) | |
self.cookies = {"hoverauth": r.cookies["hoverauth"]} | |
def call(self, method, resource, data=None): | |
url = "https://www.hover.com/api/{0}".format(resource) | |
r = requests.request(method, url, data=data, cookies=self.cookies) | |
if not r.ok: | |
raise HoverException(r) | |
if r.content: | |
body = r.json() | |
if "succeeded" not in body or body["succeeded"] is not True: | |
raise HoverException(body) | |
return body | |
# connect to the API using your account | |
client = HoverAPI("myusername", "mypassword") | |
# get details of a domains without DNS records | |
client.call("get", "domains") | |
# get all domains and DNS records | |
client.call("get", "dns") | |
# notice the "id" field of domains in response to the above calls - that's needed | |
# to address the domains individually, like so: | |
# get details of a specific domain without DNS records | |
client.call("get", "domains/dom123456") | |
# get DNS records of a specific domain: | |
client.call("get", "domains/dom123456/dns") | |
# create a new A record: | |
record = {"name": "mysubdomain", "type": "A", "content": "127.0.0.1"} | |
client.call("post", "domains/dom123456/dns", record) | |
# create a new SRV record | |
# note that content is "{priority} {weight} {port} {target}" | |
record = {"name": "mysubdomain", "type": "SRV", "content": "10 10 123 __service"} | |
client.call("post", "domains/dom123456/dns", record) | |
# create a new MX record | |
# note that content is "{priority} {host}" | |
record = {"name": "mysubdomain", "type": "MX", "content": "10 mail"} | |
client.call("post", "domains/dom123456/dns", record) | |
# notice the "id" field of DNS records in the above calls - that's | |
# needed to address the DNS records individually, like so: | |
# update an existing DNS record | |
client.call("put", "dns/dns1234567", {"content": "127.0.0.1"}) | |
# delete a DNS record: | |
client.call("delete", "dns/dns1234567") |
#!/usr/bin/python | |
""" | |
bulkhover.py 1.1 | |
This is a command-line script to import and export DNS records for a single | |
domain into or out of a hover account. | |
Usage: | |
bulkhover.py [options] (import|export) <domain> <dnsfile> | |
bulkhover.py (-h | --help) | |
bulkhover.py --version | |
Options: | |
-h --help Show this screen | |
--version Show version | |
-c --conf=<conf> Path to conf | |
-u --username=<user> Your hover username | |
-p --password=<pass> Your hover password | |
-f --flush Delete all existing records before importing | |
Examples: | |
The DNS file should have one record per line, in the format: | |
{name} {type} {content} | |
For example: | |
www A 127.0.0.1 | |
@ MX 10 example.com | |
Since the script output is in the same format as its input, you can use shell | |
pipelines to do complex operations. | |
Copy all DNS records from one domain to another: | |
bulkhover.py -c my.conf export example.com - | ./bulkhover.py -c my.conf -f import other.com - | |
Copy only MX records from one domain to another: | |
./bulkhover.py -c my.conf export foo.com - | awk '$2 == "MX" {print $0}' | ./bulkhover.py -c my.conf import bar.com - | |
To avoid passing your username and password in the command-line, you can use | |
a conf file that contains them instead: | |
[hover] | |
username=YOUR_USERNAME | |
password=YOUR_PASSWORD | |
""" | |
import ConfigParser | |
import docopt | |
import requests | |
import sys | |
class HoverException(Exception): | |
pass | |
class HoverAPI(object): | |
def __init__(self, username, password): | |
params = {"username": username, "password": password} | |
r = requests.post("https://www.hover.com/api/login", params=params) | |
if not r.ok or "hoverauth" not in r.cookies: | |
raise HoverException(r) | |
self.cookies = {"hoverauth": r.cookies["hoverauth"]} | |
def call(self, method, resource, data=None): | |
url = "https://www.hover.com/api/{0}".format(resource) | |
r = requests.request(method, url, data=data, cookies=self.cookies) | |
if not r.ok: | |
raise HoverException(r) | |
if r.content: | |
body = r.json() | |
if "succeeded" not in body or body["succeeded"] is not True: | |
raise HoverException(body) | |
return body | |
def import_dns(username, password, domain, filename, flush=False): | |
try: | |
client = HoverAPI(username, password) | |
except HoverException as e: | |
raise HoverException("Authentication failed") | |
if flush: | |
records = client.call("get", "domains/{0}/dns".format(domain))["domains"][0]["entries"] | |
for record in records: | |
client.call("delete", "dns/{0}".format(record["id"])) | |
print "Deleted {name} {type} {content}".format(**record) | |
domain_id = client.call("get", "domains/{0}".format(domain))["domain"]["id"] | |
if filename == "-": filename = "/dev/stdin" | |
with open(filename, "r") as f: | |
for line in f: | |
parts = line.strip().split(" ", 2) | |
record = {"name": parts[0], "type": parts[1], "content": parts[2]} | |
client.call("post", "domains/{0}/dns".format(domain), record) | |
print "Created {name} {type} {content}".format(**record) | |
def export_dns(username, password, domain, filename): | |
try: | |
client = HoverAPI(username, password) | |
except HoverException as e: | |
raise HoverException("Authentication failed") | |
records = client.call("get", "domains/{0}/dns".format(domain))["domains"][0]["entries"] | |
if filename == "-": filename = "/dev/stdout" | |
with open(filename, "w") as f: | |
for record in records: | |
f.write("{name} {type} {content}\n".format(**record)) | |
def main(args): | |
def get_conf(filename): | |
config = ConfigParser.ConfigParser() | |
config.read(filename) | |
items = dict(config.items("hover")) | |
return items["username"], items["password"] | |
if args["--conf"] is None: | |
if not all((args["--username"], args["--password"])): | |
print("You must specifiy either a conf file, or a username and password") | |
return 1 | |
else: | |
username, password = args["--username"], args["--password"] | |
else: | |
username, password = get_conf(args["--conf"]) | |
try: | |
if args["import"]: | |
import_dns(username, password, args["<domain>"], args["<dnsfile>"], args["--flush"]) | |
elif args["export"]: | |
export_dns(username, password, args["<domain>"], args["<dnsfile>"]) | |
except HoverException as e: | |
print "Unable to update DNS: {0}".format(e) | |
return 1 | |
if __name__ == "__main__": | |
version = __doc__.strip().split("\n")[0] | |
args = docopt.docopt(__doc__, version=version) | |
status = main(args) | |
sys.exit(status) |
#!/usr/bin/env python | |
""" | |
dynhover.py 1.2 | |
This tool will update an A record for given (sub)domain in your hover.com | |
with your IP, or an IP that you specify | |
Usage: | |
dynhover.py (-c <conf> | -u <user> -p <password>) <domain> | |
dynhover.py (-h | --help) | |
dynhover.py --version | |
Options: | |
-h --help Show this screen | |
--version Show version | |
-c --conf=<conf> Path to conf | |
-u --username=<user> Your hover username | |
-p --password=<pass> Your hover password | |
-i --ip=<ip> An IP to set (auto-detected by default) | |
""" | |
import ConfigParser | |
import docopt | |
import requests | |
import sys | |
class HoverException(Exception): | |
pass | |
class HoverAPI(object): | |
def __init__(self, username, password): | |
params = {"username": username, "password": password} | |
r = requests.post("https://www.hover.com/api/login", params=params) | |
if not r.ok or "hoverauth" not in r.cookies: | |
raise HoverException(r) | |
self.cookies = {"hoverauth": r.cookies["hoverauth"]} | |
def call(self, method, resource, data=None): | |
url = "https://www.hover.com/api/{0}".format(resource) | |
r = requests.request(method, url, data=data, cookies=self.cookies) | |
if not r.ok: | |
raise HoverException(r) | |
if r.content: | |
body = r.json() | |
if "succeeded" not in body or body["succeeded"] is not True: | |
raise HoverException(body) | |
return body | |
def get_public_ip(): | |
return requests.get("http://ifconfig.me/ip").content | |
def update_dns(username, password, fqdn, ip): | |
try: | |
client = HoverAPI(username, password) | |
except HoverException as e: | |
raise HoverException("Authentication failed") | |
dns = client.call("get", "dns") | |
dns_id = None | |
for domain in dns["domains"]: | |
if fqdn == domain["domain_name"]: | |
fqdn = "@.{domain_name}".format(**domain) | |
for entry in domain["entries"]: | |
if entry["type"] != "A": continue | |
if "{0}.{1}".format(entry["name"], domain["domain_name"]) == fqdn: | |
dns_id = entry["id"] | |
break | |
if dns_id is None: | |
raise HoverException("No DNS record found for {0}".format(fqdn)) | |
response = client.call("put", "dns/{0}".format(dns_id), {"content": my_ip}) | |
if "succeeded" not in response or response["succeeded"] is not True: | |
raise HoverException(response) | |
def main(args): | |
if args["--username"]: | |
username, password = args["--username"], args["--password"] | |
else: | |
config = ConfigParser.ConfigParser() | |
config.read(args["--conf"]) | |
items = dict(config.items("hover")) | |
username, password = items["username"], items["password"] | |
domain = args["<domain>"] | |
ip = args["--ip"] or get_public_ip() | |
try: | |
update_dns(username, password, domain, ip) | |
except HoverException as e: | |
print "Unable to update DNS: {0}".format(e) | |
return 1 | |
return 0 | |
if __name__ == "__main__": | |
version = __doc__.strip().split("\n")[0] | |
args = docopt.docopt(__doc__, version=version) | |
status = main(args) | |
sys.exit(status) |
#!/bin/bash | |
[[ $# -lt 3 ]] && echo "Usage: $0 USERNAME PASSWORD DNS_ID" | |
USERNAME=${1} | |
PASSWORD=${2} | |
DNS_ID=${3} | |
# find your DNS ID here: https://www.hover.com/api/domains/yourdomain.com/dns/ | |
# (replace "yourdomain.com" with your actual domain, and look for the record | |
# you want to change. The ID looks like: dns1234567) | |
IP=$(curl "http://ifconfig.me/ip" -s) | |
curl "https://www.hover.com/api/dns/${DNS_ID}" \ | |
-X PUT \ | |
-d "content=${IP}" \ | |
-s \ | |
-b <(curl "https://www.hover.com/api/login" \ | |
-X POST \ | |
-G \ | |
-d "username=${USERNAME}" \ | |
-d "password=${PASSWORD}" \ | |
-s \ | |
-o /dev/null \ | |
-c -) | |
echo | |
Sorry for the delay in getting back to you... slipped through the cracks!
a 404 error is normal to that URL when doing a GET request... you have use the POST method. Make sure you are doing that by using the "-X POST" command line parameter with curl.
Sorry for the delay in getting back to you... slipped through the cracks!
a 404 error is normal to that URL when doing a GET request... you have use the POST method. Make sure you are doing that by using the "-X POST" command line parameter with curl.
Thank you - curl "https://www.hover.com/api/login" -H "Content-type: application/json" -X POST -d "{"password": "$PASSWORD", "username": "$USERNAME"}" -s -S -c $COOKIEJAR -o /dev/null
I was using -X POST
.
I contacted hover.com and it's definately been disabled even with 2FA
Lei (Hover Help Center)
Jul 6, 2023, 12:05 EDT
Hello!
Thanks for your reply - API is no longer usable to log in to Hover.
If using an API is critical to your setup, I may suggest our sister company, [OpenSRS.com](http://opensrs.com/).
Best,
Just FYI - there's an example that does use 2FA. I've copied code from it. dns-lexicon seems to have support for 2FA now as well. https://github.com/pjslauta/hover-dyn-dns
Thank you very much. I'll try this later today. I do note that the login is different so that's a positive thing.
https://www.hover.com/signin/auth.json
. I emailed hover yesterday asking how to do it also. No idea if they will reply in a helpful manner.UPDATE: I just tried with username password as you described and it gets a 404. A direct curl to that endpoint shows a 404 also. Could you please confirm on your end?