Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 35 additions & 16 deletions namecheap-api-cli
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python

import argparse
import json

from namecheap import Api, ApiError

Expand All @@ -11,22 +12,35 @@ except:


def get_args():
parser = argparse.ArgumentParser(description="CLI tool to manage NameCheap.com domain records")
parser = argparse.ArgumentParser(
description="CLI tool to manage NameCheap.com domain records")

parser.add_argument("--debug", action="store_true", help="If set, enables debug output")
parser.add_argument("--sandbox", action="store_true", help="If set, forcing usage of Sandbox API, see README.md for details")
parser.add_argument("--debug", action="store_true",
help="If set, enables debug output")
parser.add_argument("--sandbox", action="store_true",
help="If set, forcing usage of Sandbox API, see README.md for details")

group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--add", action="store_true", help="Use to add a record")
group.add_argument("--delete", action="store_true", help="Use to remove a record")
group.add_argument("--list", action="store_true", help="List existing records")

parser.add_argument("--domain", type=str, default="example.org", help="Domain to manage, default is \"example.org\", don't forget to override")

parser.add_argument("--type", type=str, default="A", help="Record type, default is \"A\"")
parser.add_argument("--name", type=str, default="test", help="Record name, default is \"test\"")
parser.add_argument("--address", type=str, default="127.0.0.1", help="Address for record to point to, default is \"127.0.0.1\"")
parser.add_argument("--ttl", type=int, default=300, help="Time-To-Live, in seconds, default is 300")
group.add_argument("--add", action="store_true",
help="Use to add a record")
group.add_argument("--delete", action="store_true",
help="Use to remove a record")
group.add_argument("--list", action="store_true",
help="List existing records")
group.add_argument("--json", action="store_true",
help="List existing records and return JSON list")

parser.add_argument("--domain", type=str, default="example.org",
help="Domain to manage, default is \"example.org\", don't forget to override")

parser.add_argument("--type", type=str, default="A",
help="Record type, default is \"A\"")
parser.add_argument("--name", type=str, default="test",
help="Record name, default is \"test\"")
parser.add_argument("--address", type=str, default="127.0.0.1",
help="Address for record to point to, default is \"127.0.0.1\"")
parser.add_argument("--ttl", type=int, default=300,
help="Time-To-Live, in seconds, default is 300")

args = parser.parse_args()

Expand Down Expand Up @@ -56,12 +70,13 @@ def record_add(record_type, hostname, address, ttl=300):
}
api.domains_dns_addHost(domain, record)


args = get_args()

domain = args.domain
print("domain: %s" % domain)

api = Api(username, api_key, username, ip_address, sandbox=args.sandbox, debug=args.debug)
api = Api(username, api_key, username, ip_address,
sandbox=args.sandbox, debug=args.debug)

if args.add:
record_add(
Expand All @@ -78,4 +93,8 @@ elif args.delete:
)
elif args.list:
for line in list_records():
print("\t%s \t%s\t%s -> %s" % (line["Type"], line["TTL"], line["Name"], line["Address"]))
print("\t%s \t%s\t%s -> %s" %
(line["Type"], line["TTL"], line["Name"], line["Address"]))
elif args.json:
records = json.dumps([l for l in list_records()])
print(records)
46 changes: 31 additions & 15 deletions namecheap.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ def __init__(self, ApiUser, ApiKey, UserName, ClientIP,
self.ClientIP = ClientIP
self.endpoint = ENDPOINTS['sandbox' if sandbox else 'production']
self.debug = debug
self.payload_limit = 10 # After hitting this lenght limit script will move payload from POST params to POST data
# After hitting this lenght limit script will move payload from POST params to POST data
self.payload_limit = 10
self.attempts_count = attempts_count
self.attempts_delay = attempts_delay

Expand Down Expand Up @@ -104,12 +105,13 @@ def _payload(self, Command, extra_payload={}):
extra_payload = {}
return payload, extra_payload

def _fetch_xml(self, payload, extra_payload = None):
def _fetch_xml(self, payload, extra_payload=None):
"""Make network call and return parsed XML element"""
attempts_left = self.attempts_count
while attempts_left > 0:
if extra_payload:
r = requests.post(self.endpoint, params=payload, data=extra_payload)
r = requests.post(
self.endpoint, params=payload, data=extra_payload)
else:
r = requests.post(self.endpoint, params=payload)
if 200 <= r.status_code <= 299:
Expand Down Expand Up @@ -148,9 +150,11 @@ class LazyGetListIterator(object):
"""When listing domain names, only one page is returned
initially. The list needs to be paged through to see all.
This iterator gets the next page when necessary."""

def _get_more_results(self):
xml = self.api._fetch_xml(self.payload)
xpath = './/{%(ns)s}CommandResponse/{%(ns)s}DomainGetListResult/{%(ns)s}Domain' % {'ns': NAMESPACE}
xpath = './/{%(ns)s}CommandResponse/{%(ns)s}DomainGetListResult/{%(ns)s}Domain' % {
'ns': NAMESPACE}
domains = xml.findall(xpath)
for domain in domains:
self.results.append(domain.attrib)
Expand Down Expand Up @@ -211,7 +215,8 @@ def domains_check(self, domains):
xpath = './/{%(ns)s}CommandResponse/{%(ns)s}DomainCheckResult' % {'ns': NAMESPACE}
results = {}
for check_result in xml.findall(xpath):
results[check_result.attrib['Domain']] = check_result.attrib['Available'] == 'true'
results[check_result.attrib['Domain']
] = check_result.attrib['Available'] == 'true'
return results

@classmethod
Expand All @@ -236,8 +241,10 @@ def _list_of_dictionaries_to_numbered_payload(cls, l):
'cat3' : 'meow'
}
"""

disallow_change = ['EmailType']
return dict(sum([
[(k + str(i + 1), v) for k, v in d.items()] for i, d in enumerate(l)
[(k + str(i + 1), v) if k not in disallow_change else (k, v) for k, v in d.items()] for i, d in enumerate(l)
], []))

@classmethod
Expand Down Expand Up @@ -291,14 +298,17 @@ def domains_getContacts(self, DomainName):
...
}
"""
xml = self._call('namecheap.domains.getContacts', {'DomainName': DomainName})
xml = self._call('namecheap.domains.getContacts',
{'DomainName': DomainName})
xpath = './/{%(ns)s}CommandResponse/{%(ns)s}DomainContactsResult/*' % {'ns': NAMESPACE}
results = {}
for contact_type in xml.findall(xpath):
fields_for_one_contact_type = {}
for contact_detail in contact_type.findall('*'):
fields_for_one_contact_type[self._tag_without_namespace(contact_detail)] = contact_detail.text
results[self._tag_without_namespace(contact_type)] = fields_for_one_contact_type
fields_for_one_contact_type[self._tag_without_namespace(
contact_detail)] = contact_detail.text
results[self._tag_without_namespace(
contact_type)] = fields_for_one_contact_type
return results

# https://www.namecheap.com/support/api/methods/domains-dns/set-hosts.aspx
Expand All @@ -317,7 +327,8 @@ def domains_dns_setHosts(self, domain, host_records):
}
])"""

extra_payload = self._list_of_dictionaries_to_numbered_payload(host_records)
extra_payload = self._list_of_dictionaries_to_numbered_payload(
host_records)
sld, tld = domain.split(".")
extra_payload.update({
'SLD': sld,
Expand Down Expand Up @@ -375,11 +386,13 @@ def domains_dns_addHost(self, domain, host_record):
print("Remote: %i" % len(host_records_remote))

host_records_remote.append(host_record)
host_records_remote = [self._elements_names_fix(x) for x in host_records_remote]
host_records_remote = [self._elements_names_fix(
x) for x in host_records_remote]

print("To set: %i" % len(host_records_remote))

extra_payload = self._list_of_dictionaries_to_numbered_payload(host_records_remote)
extra_payload = self._list_of_dictionaries_to_numbered_payload(
host_records_remote)
sld, tld = domain.split(".")
extra_payload.update({
'SLD': sld,
Expand Down Expand Up @@ -416,7 +429,8 @@ def domains_dns_delHost(self, domain, host_record):
else:
host_records_new.append(r)

host_records_new = [self._elements_names_fix(x) for x in host_records_new]
host_records_new = [self._elements_names_fix(
x) for x in host_records_new]

print("To set: %i" % len(host_records_new))

Expand All @@ -430,7 +444,8 @@ def domains_dns_delHost(self, domain, host_record):
)
return False

extra_payload = self._list_of_dictionaries_to_numbered_payload(host_records_new)
extra_payload = self._list_of_dictionaries_to_numbered_payload(
host_records_new)
sld, tld = domain.split(".")
extra_payload.update({
'SLD': sld,
Expand Down Expand Up @@ -467,5 +482,6 @@ def domains_getList(self, ListType=None, SearchTerm=None, PageSize=None, SortBy=
extra_payload['PageSize'] = PageSize
if SortBy:
extra_payload['SortBy'] = SortBy
payload, extra_payload = self._payload('namecheap.domains.getList', extra_payload)
payload, extra_payload = self._payload(
'namecheap.domains.getList', extra_payload)
return self.LazyGetListIterator(self, payload)