Source code for pygodaddy.client

############################################################################
#   Copyright 2013 observerss
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
############################################################################
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from collections import defaultdict, namedtuple

import requests
import logging
import re

__all__  = ['LoginError', 'GoDaddyClient', 'GoDaddyAccount']

logger = logging.getLogger(__name__)

DNSRecord = namedtuple('DNSRecord', 'index, hostname, value, ttl, host_td, points_to, rec_modified')

class LoginError(Exception):
    pass

[docs]class GoDaddyAccount(object): """ This is a context manager for GoDaddyClient Usage: >>> from pygodaddy import GoDaddyAccount >>> with GoDaddyAccount(username, password) as client: ... client.update_dns_record('sub1.exmaple.com', '1.2.3.4') ... client.update_dns_record('sub2.exmaple.com', '1.2.3.5') """ def __init__(self, username, password): self.username = username self.password = password self.client = None def __enter__(self): self.client = GoDaddyClient() if not self.client.login(self.username, self.password): # set client to `None`, force context manager to fail quickly self.client = None return self.client def __exit__(self, exec_type, exc_value, traceback): # suppress exceptions return True
[docs]class GoDaddyClient(object): """ GoDaddy Client Library Typical Usage: >>> from pygodaddy import GoDaddyClient >>> client = GoDaddyClient() >>> if client.login(username, password): ... client.update_dns_record('sub.example.com', '1.2.3.4') """ def __init__(self): self.logged_in = False self.default_url = 'https://dns.godaddy.com/default.aspx' self.zonefile_url = 'https://dns.godaddy.com/ZoneFile.aspx?zoneType=0&sa=&zone={domain}' self.zonefile_ws_url = 'https://dns.godaddy.com/ZoneFile_WS.asmx' self.session = requests.Session()
[docs] def is_loggedin(self, html=None): """ Test login according to returned html, then set value to self.logged_in :param html: the html content returned by self.session.get/post :returns: `True` if there's welcome message, else `False` """ if html is None: html = self.session.get(self.default_url).text self.logged_in = bool(re.compile(r'Welcome:&nbsp;<span id="ctl00_lblUser" .*?\>(.*)</span>').search(html)) return self.logged_in
[docs] def login(self, username, password): """ Login to a godaddy account :param username: godaddy username :param password: godaddy password :returns: `True` if login is successful, else `False` """ r = self.session.get(self.default_url) try: viewstate = re.compile(r'id="__VIEWSTATE" value="([^"]+)"').search(r.text).group(1) except: logger.exception('Login routine broken, godaddy may have updated their login mechanism') return False data = { 'Login$userEntryPanel2$LoginImageButton.x' : 0, 'Login$userEntryPanel2$LoginImageButton.y' : 0, 'Login$userEntryPanel2$UsernameTextBox' : username, 'Login$userEntryPanel2$PasswordTextBox' : password, '__VIEWSTATE': viewstate, } r = self.session.post(r.url, data=data) return self.is_loggedin(r.text)
[docs] def find_domains(self): """ return all domains of user """ html = self.session.get(self.default_url).text return re.compile(r'''GoToZoneEdit\('([^']+)''').findall(html)
[docs] def find_dns_records(self, domain, record_type='A'): """ find all dns reocrds of a given domain :param domain: a typical domain name, e.g. "example.com" :returns: a dict of (hostname -> DNSRecord) """ html = self.session.get(self.zonefile_url.format(domain=domain)).text pattern = r'''Undo{rt}Edit\('tbl{rt}Records_([0-9]+)', '([^']+)', '([^']+)', '([^']+)', '([^']+)', '([^']+)', '([^']+)'\)'''.format(rt=record_type) try: results = map(DNSRecord._make, re.compile(pattern).findall(html)) except: logger.exception('find domains broken, maybe godaddy has changed its web structure') return [] return results
[docs] def update_dns_record(self, hostname, value, record_type='A', new=True): """ Update a dns record :param hostname: hostname to update :param value: value for hostname to point to, for A record, it's like 'XX.XX.XX.XX' :param record_type: only 'A' is implemented for now :param new: if `True`, will create a new record if necessary, default to `True` :returns: `True` if update is successful, else `False` """ if record_type != 'A': raise NotImplementedError('Only A Record Update is supported for now') prefix, domain = self._split_hostname(hostname) records = self.find_dns_records(domain, record_type) for record in records: if record.hostname == prefix: if record.value != value: if not self._edit_record(value=value, index=record.index, record_type=record_type): return False if not self._save_records(domain=domain, index=record.index, record_type=record_type): return False return True break else: # record.hostname == prefix not found if new: # let's create a new record index = int(records[-1].index)+1 if records else 0 if not self._add_record(prefix=prefix, value=value, index=index, record_type=record_type): return False if not self._save_records(domain=domain, index=index, record_type=record_type): return False return True return False
[docs] def delete_dns_record(self, hostname, record_type='A'): """ delete hostname in accounts :param hostname: hostname to be deleted :param record_type: only 'A' is implemented for now :returns: `True` if successful, else `False` """ if record_type != 'A': raise NotImplementedError('Only A Record Update is supported for now') prefix, domain = self._split_hostname(hostname) for record in self.find_dns_records(domain, record_type): if record.hostname == prefix: if not self._delete_record(record.index, record_type=record_type): return False if not self._save_records(domain=domain, index=record.index, record_type=record_type): return False return True return False
def _split_hostname(self, hostname): """ split hostname into prefix + domain """ try: prefix, name, postfix = hostname.rsplit('.', 2) domain = name + '.' + postfix except: domain = hostname prefix = '@' return prefix, domain def _delete_record(self, index, record_type='A'): """ delete old record, return `True` if successful """ data = {'sInput':"{index}|true".format(index=index)} r = self.session.post(self.zonefile_ws_url + '/Flag{rt}RecForDeletion'.format(rt=record_type), data=data) return 'SUCCESS' in r.text def _add_record(self, prefix, value, index, record_type='A'): """ add new record, return `True` if successful """ data = {'sInput':'<PARAMS><PARAM name="lstIndex" value="{index}" /><PARAM name="host" value="{prefix}" /><PARAM name="pointsTo" value="{value}" /><PARAM name="ttl" value="600" /></PARAMS>'.format(index=index, prefix=prefix, value=value)} r = self.session.post(self.zonefile_ws_url + '/AddNew{rt}Record'.format(rt=record_type), data=data) return 'SUCCESS' in r.text def _edit_record(self, index, value, record_type='A'): """ set value of record on `index` to `value`, return `True` if successful """ data = {'sInput' : '<PARAMS><PARAM name="type" value="{rt}record" /><PARAM name="fieldName" value="data" /><PARAM name="fieldValue" value="{value}" /><PARAM name="lstIndex" value="{index}" /></PARAMS>'.format(value=value, index=index, rt=record_type.lower())} r = self.session.post(self.zonefile_ws_url + '/EditRecordField', data=data) return 'SUCCESS' in r.text def _save_records(self, domain, index, record_type='A'): """ save edit of `index` of `domain` """ data = {'sInput' : '<PARAMS><PARAM name="domainName" value="{domain}" /><PARAM name="zoneType" value="0" /><PARAM name="{rt}RecEditCount" value="1" /><PARAM name="{rt}RecEdit0Index" value="{index}" /></PARAMS>'.format(domain=domain, index=index, rt=record_type.lower())} r = self.session.post(self.zonefile_ws_url + '/SaveRecords', data=data) return 'SUCCESS' in r.text

This Page