123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- import asyncio
- import os
- import json
- import signal
- import functools
- import logging
- import socket
- import time
- from urllib import request, error, parse
- from config import read_config, save_config, check_config, cfg
- from get_ip import get_ip
- def header():
- h = {
- 'User-Agent': 'Client/0.0.1 ({})'.format(cfg['email'])
- }
- return h
- def get_record_id(domain, sub_domain):
- url = 'https://dnsapi.cn/Record.List'
- params = parse.urlencode({
- 'login_token': cfg['login_token'],
- 'format': 'json',
- 'domain': domain
- })
- req = request.Request(url=url, data=params.encode('utf-8'), method='POST', headers=header())
- try:
- resp = request.urlopen(req).read().decode()
- except (error.HTTPError, error.URLError, socket.timeout):
- return None
- records = json.loads(resp).get('records', {})
- for item in records:
- if item.get('name') == sub_domain:
- return item.get('id')
- return None
- def update_record():
- url = 'https://dnsapi.cn/Record.Ddns'
- params = parse.urlencode({
- 'login_token': cfg['login_token'],
- 'format': 'json',
- 'domain': cfg['domain'],
- 'sub_domain': cfg['sub_domain'],
- 'record_id': cfg['record_id'],
- 'record_line': '默认'
- })
- req = request.Request(url=url, data=params.encode('utf-8'), method='POST', headers=header())
- resp = request.urlopen(req).read().decode()
- records = json.loads(resp)
- cfg['last_update_time'] = str(time.gmtime())
- logging.info("record updated: %s" % records)
- # async def main():
- def main():
- while 1:
- current_ip = get_ip()
- if current_ip:
- # 对于拥有多个出口 IP 负载均衡的服务器,上面的 get_ip() 函数会在几个 ip 之间不停切换
- # 然后频繁进入这个判断,进行 update_record(),然后很快就会触发 API Limited 了
- # 于是建立一个IP池记载这个服务器的几个出口IP,以免频繁切换
-
- ip_count = int(cfg['ip_count'])
- ip_pool = cfg['ip_pool'].split(',')[:ip_count]
- cfg['current_ip'] = current_ip
- if current_ip not in ip_pool:
- # new ip found
- logging.info("new ip found: %s", current_ip)
-
- ip_pool.insert(0, current_ip)
- cfg['ip_pool'] = ','.join([str(x) for x in ip_pool[:ip_count]])
- update_record()
- save_config()
- else:
- logging.error('get current ip FAILED.')
- try:
- interval = int(cfg['interval'])
- except ValueError:
- interval = 5
- # await asyncio.sleep(interval)
- time.sleep(interval)
- def ask_exit(_sig_name):
- logging.warning('got signal {}: exit'.format(_sig_name))
- loop.stop()
- if __name__ == '__main__':
- logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)-8s : %(message)s')
- logging.info('start...')
- read_config()
- check_config()
- cfg['record_id'] = get_record_id(cfg['domain'], cfg['sub_domain'])
- logging.info("get record_id: %s" % str(cfg['record_id']))
- logging.info("watching ip for ddns: %s.%s" % (cfg['sub_domain'], cfg['domain']))
- loop = asyncio.get_event_loop()
- for sig_name in ('SIGINT', 'SIGTERM'):
- try:
- loop.add_signal_handler(getattr(signal, sig_name), functools.partial(ask_exit, sig_name))
- except NotImplementedError:
- pass # 使兼容 WINDOWS
- try:
- loop.run_until_complete(main())
- except (KeyboardInterrupt, RuntimeError):
- logging.info('stop...')
- finally:
- loop.close()
|