dnspod.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. # coding=utf-8
  2. """
  3. DNSPOD API
  4. DNSPOD 接口解析操作库
  5. http://www.dnspod.cn/docs/domains.html
  6. @author: New Future
  7. """
  8. from json import loads as jsondecode
  9. from logging import debug, info, warning
  10. from os import environ
  11. try: # python 3
  12. from http.client import HTTPSConnection
  13. from urllib.parse import urlencode
  14. except ImportError: # python 2
  15. from httplib import HTTPSConnection
  16. from urllib import urlencode
  17. __author__ = 'New Future'
  18. class Config:
  19. ID = "token id"
  20. TOKEN = "token key"
  21. PROXY = None # 代理设置
  22. TTL = None
  23. class API:
  24. # API 配置
  25. SITE = "dnsapi.cn" # API endpoint
  26. METHOD = "POST" # 请求方法
  27. TOKEN_PARAM = "login_token" # token参数
  28. DEFAULT = "默认" # 默认线路名
  29. LENGTH = "length" # 添加参数
  30. def request(action, param=None, **params):
  31. """
  32. 发送请求数据
  33. """
  34. if param:
  35. params.update(param)
  36. params = dict((k, params[k]) for k in params if params[k] is not None)
  37. params.update({API.TOKEN_PARAM: '***', 'format': 'json'})
  38. info("%s/%s : %s", API.SITE, action, params)
  39. params[API.TOKEN_PARAM] = "%s,%s" % (Config.ID, Config.TOKEN)
  40. params[API.LENGTH] = "3000" # 添加参数
  41. if Config.PROXY:
  42. conn = HTTPSConnection(Config.PROXY)
  43. conn.set_tunnel(API.SITE, 443)
  44. else:
  45. conn = HTTPSConnection(API.SITE)
  46. conn.request(API.METHOD, '/' + action, urlencode(params), {
  47. "Content-type": "application/x-www-form-urlencoded",
  48. "User-Agent": "DDNS/%s ([email protected])" % environ.get("DDNS_VERSION", "1.0.0")
  49. })
  50. response = conn.getresponse()
  51. res = response.read().decode('utf8')
  52. conn.close()
  53. if response.status < 200 or response.status >= 300:
  54. warning('%s : error[%d]:%s', action, response.status, res)
  55. raise Exception(res)
  56. else:
  57. data = jsondecode(res)
  58. debug('%s : result:%s', action, data)
  59. if not data:
  60. raise Exception("empty response")
  61. elif data.get("status", {}).get("code") == "1":
  62. return data
  63. else:
  64. raise Exception(data.get('status', {}))
  65. def get_domain_info(domain):
  66. """
  67. 切割域名获取主域名和对应ID
  68. """
  69. domain_split = domain.split('.')
  70. sub, did = None, None
  71. main = domain_split.pop()
  72. while domain_split: # 通过API判断,最后两个,三个递增
  73. main = domain_split.pop() + '.' + main
  74. did = get_domain_id(main)
  75. if did:
  76. sub = ".".join(domain_split) or '@'
  77. # root domain根域名https://github.com/NewFuture/DDNS/issues/9
  78. break
  79. info('domain_id: %s, sub: %s', did, sub)
  80. return did, sub
  81. def get_domain_id(domain):
  82. """
  83. 获取域名ID
  84. http://www.dnspod.cn/docs/domains.html#domain-info
  85. """
  86. if not hasattr(get_domain_id, "domain_list"):
  87. get_domain_id.domain_list = {} # "静态变量"存储已查询过的id
  88. if domain in get_domain_id.domain_list:
  89. # 如果已经存在直接返回防止再次请求
  90. return get_domain_id.domain_list[domain]
  91. else:
  92. try:
  93. info = request('Domain.Info', domain=domain)
  94. except Exception:
  95. return
  96. did = info.get("domain", {}).get("id")
  97. if did:
  98. get_domain_id.domain_list[domain] = did
  99. return did
  100. def get_records(did, **conditions):
  101. """
  102. 获取记录ID
  103. 返回满足条件的所有记录[]
  104. TODO 大于3000翻页
  105. http://www.dnspod.cn/docs/records.html#record-list
  106. """
  107. if not hasattr(get_records, "records"):
  108. get_records.records = {} # "静态变量"存储已查询过的id
  109. get_records.keys = ("id", "name", "type", "line",
  110. "line_id", "enabled", "mx", "value")
  111. if did not in get_records.records:
  112. get_records.records[did] = {}
  113. data = request('Record.List', domain_id=did)
  114. if data:
  115. for record in data.get('records'):
  116. get_records.records[did][record["id"]] = {
  117. k: v for (k, v) in record.items() if k in get_records.keys}
  118. records = {}
  119. for (did, record) in get_records.records[did].items():
  120. for (k, value) in conditions.items():
  121. if record.get(k) != value:
  122. break
  123. else: # for else push
  124. records[did] = record
  125. return records
  126. def update_record(domain, value, record_type="A"):
  127. """
  128. 更新记录
  129. """
  130. info(">>>>>%s(%s)", domain, record_type)
  131. domainid, sub = get_domain_info(domain)
  132. if not domainid:
  133. raise Exception("invalid domain: [ %s ] " % domain)
  134. records = get_records(domainid, name=sub, type=record_type)
  135. result = {}
  136. if records: # update
  137. # http://www.dnspod.cn/docs/records.html#record-modify
  138. for (did, record) in records.items():
  139. if record["value"] != value:
  140. debug(sub, record)
  141. res = request('Record.Modify', record_id=did, record_line=record["line"].replace("Default", "default").encode(
  142. "utf-8"), value=value, sub_domain=sub, domain_id=domainid, record_type=record_type, ttl=Config.TTL)
  143. if res:
  144. get_records.records[domainid][did]["value"] = value
  145. result[did] = res.get("record")
  146. else:
  147. result[did] = "update fail!\n" + str(res)
  148. else:
  149. result[did] = domain
  150. else: # create
  151. # http://www.dnspod.cn/docs/records.html#record-create
  152. res = request("Record.Create", domain_id=domainid, value=value,
  153. sub_domain=sub, record_type=record_type, record_line=API.DEFAULT, ttl=Config.TTL)
  154. if res:
  155. did = res.get("record")["id"]
  156. get_records.records[domainid][did] = res.get("record")
  157. get_records.records[domainid][did].update(
  158. value=value, sub_domain=sub, record_type=record_type)
  159. result = res.get("record")
  160. else:
  161. result = domain + " created fail!"
  162. return result