ddns.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import asyncio
  4. import os
  5. import json
  6. import signal
  7. import functools
  8. import logging
  9. import socket
  10. import time
  11. from urllib import request, error, parse
  12. from config import read_config, save_config, check_config, cfg
  13. from get_ip import get_ip
  14. def header():
  15. h = {
  16. 'User-Agent': 'Client/0.0.1 ({})'.format(cfg['email'])
  17. }
  18. return h
  19. def get_record_id(domain, sub_domain):
  20. url = 'https://dnsapi.cn/Record.List'
  21. params = parse.urlencode({
  22. 'login_token': cfg['login_token'],
  23. 'format': 'json',
  24. 'domain': domain
  25. })
  26. req = request.Request(url=url, data=params.encode('utf-8'), method='POST', headers=header())
  27. try:
  28. resp = request.urlopen(req).read().decode()
  29. except (error.HTTPError, error.URLError, socket.timeout):
  30. return None
  31. records = json.loads(resp).get('records', {})
  32. for item in records:
  33. if item.get('name') == sub_domain:
  34. return item.get('id')
  35. return None
  36. def update_record():
  37. url = 'https://dnsapi.cn/Record.Ddns'
  38. params = parse.urlencode({
  39. 'login_token': cfg['login_token'],
  40. 'format': 'json',
  41. 'domain': cfg['domain'],
  42. 'sub_domain': cfg['sub_domain'],
  43. 'record_id': cfg['record_id'],
  44. 'record_line': '默认'
  45. })
  46. req = request.Request(url=url, data=params.encode('utf-8'), method='POST', headers=header())
  47. resp = request.urlopen(req).read().decode()
  48. records = json.loads(resp)
  49. cfg['last_update_time'] = str(time.gmtime())
  50. logging.info("record updated: %s" % records)
  51. # async def main():
  52. def main():
  53. while 1:
  54. current_ip = get_ip()
  55. if current_ip:
  56. # 对于拥有多个出口 IP 负载均衡的服务器,上面的 get_ip() 函数会在几个 ip 之间不停切换
  57. # 然后频繁进入这个判断,进行 update_record(),然后很快就会触发 API Limited 了
  58. # 于是建立一个IP池记载这个服务器的几个出口IP,以免频繁切换
  59. ip_count = int(cfg['ip_count'])
  60. ip_pool = cfg['ip_pool'].split(',')[:ip_count]
  61. cfg['current_ip'] = current_ip
  62. if current_ip not in ip_pool:
  63. # new ip found
  64. logging.info("new ip found: %s", current_ip)
  65. ip_pool.insert(0, current_ip)
  66. cfg['ip_pool'] = ','.join([str(x) for x in ip_pool[:ip_count]])
  67. update_record()
  68. save_config()
  69. else:
  70. logging.error('get current ip FAILED.')
  71. try:
  72. interval = int(cfg['interval'])
  73. except ValueError:
  74. interval = 5
  75. # await asyncio.sleep(interval)
  76. time.sleep(interval)
  77. def ask_exit(_sig_name):
  78. logging.warning('got signal {}: exit'.format(_sig_name))
  79. loop.stop()
  80. if __name__ == '__main__':
  81. logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)-8s : %(message)s')
  82. logging.info('start...')
  83. read_config()
  84. check_config()
  85. cfg['record_id'] = get_record_id(cfg['domain'], cfg['sub_domain'])
  86. logging.info("get record_id: %s" % str(cfg['record_id']))
  87. logging.info("watching ip for ddns: %s.%s" % (cfg['sub_domain'], cfg['domain']))
  88. loop = asyncio.get_event_loop()
  89. for sig_name in ('SIGINT', 'SIGTERM'):
  90. try:
  91. loop.add_signal_handler(getattr(signal, sig_name), functools.partial(ask_exit, sig_name))
  92. except NotImplementedError:
  93. pass # 使兼容 WINDOWS
  94. try:
  95. loop.run_until_complete(main())
  96. except (KeyboardInterrupt, RuntimeError):
  97. logging.info('stop...')
  98. finally:
  99. loop.close()