├── dns ├── __init__.py ├── nameSilo.py ├── aliyun.py ├── huawei.py ├── qCloud.py └── core.py ├── requirements.txt ├── .gitignore ├── runNameSilo.py ├── runDnsPod.py ├── runHwDns.py ├── runAliDns.py ├── README.md └── log.py /dns/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aliyun-python-sdk-alidns==2.6.19 2 | aliyun-python-sdk-core==2.15.0 3 | huaweicloudsdkcore==3.1.5 4 | huaweicloudsdkdns==3.1.5 5 | requests==2.28.1 6 | tencentcloud-sdk-python==3.0.806 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | __pycache__ 3 | 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | cf2dns.log 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | 24 | config.json 25 | -------------------------------------------------------------------------------- /runNameSilo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import random 4 | import time 5 | import requests 6 | from dns.nameSilo import NameSiloClient 7 | from dns.core import Core 8 | from log import Logger 9 | import traceback 10 | 11 | 12 | #CM:移动 CU:联通 CT:电信 AB:境外 DEF:默认 13 | #需要动态解析到Cf优选ip的域名和子域名#NameSilo不支持多线解析,只能选DEF默认线路 14 | DOMAINS = { 15 | "vps789.com": {"@": ["DEF"],"www":["DEF"]}, 16 | "vpsxxx777.com": {"@": ["DEF"]}, 17 | } 18 | 19 | #解析生效条数 20 | AFFECT_NUM = 2 21 | 22 | #解析生效时间,NameSilo最少为3600 23 | TTL = 3600 24 | 25 | #NameSilo后台获取 https://www.namesilo.com/account/api-manager NameSilo只用填SECRETKEY即可 26 | SECRETKEY = '72xxxxxxxxxxxxa332' 27 | 28 | if __name__ == '__main__': 29 | config = {'DOMAINS':DOMAINS,"AFFECT_NUM":AFFECT_NUM,"TTL":TTL,"DNS_SERVER":4} 30 | cloud = NameSiloClient(SECRETKEY) 31 | core = Core(cloud,config) 32 | core.main() -------------------------------------------------------------------------------- /runDnsPod.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import random 4 | import time 5 | import requests 6 | from dns.qCloud import QcloudApiv3 # QcloudApiv3 DNSPod 的 API 更新了 By github@z0z0r4 7 | from dns.core import Core 8 | from log import Logger 9 | import traceback 10 | 11 | 12 | #CM:移动 CU:联通 CT:电信 AB:境外 DEF:默认 13 | #需要动态解析到Cf优选ip的域名和子域名 14 | DOMAINS = { 15 | "vps789.com": {"@": ["CM","CU","CT"],"www": ["CM","CU","CT"]}, 16 | "vpsxxx777.com": {"@": ["CM","CU","CT"]} 17 | } 18 | 19 | #解析生效条数 20 | #免费的DNSPod相同线路最多支持2条解析 21 | AFFECT_NUM = 2 22 | 23 | #解析生效时间,默认为600秒 如果不是DNS付费版用户 不要修改!!! 24 | TTL = 600 25 | 26 | #腾讯云后台获取 https://console.cloud.tencent.com/cam/capi 27 | SECRETID = 'LTAI5txxxxxxxxxxxxxxxxxcRDH' 28 | SECRETKEY = 'LOixxxxxxxxxxxxxxxxx4mzcGR' 29 | 30 | if __name__ == '__main__': 31 | config = {'DOMAINS':DOMAINS,"AFFECT_NUM":AFFECT_NUM,"TTL":TTL,"DNS_SERVER":1} 32 | cloud = QcloudApiv3(SECRETID, SECRETKEY) 33 | core = Core(cloud,config) 34 | core.main() -------------------------------------------------------------------------------- /runHwDns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import random 4 | import time 5 | import requests 6 | from dns.huawei import HuaWeiApi 7 | from dns.core import Core 8 | from log import Logger 9 | import traceback 10 | 11 | 12 | #CM:移动 CU:联通 CT:电信 AB:境外 DEF:默认 13 | #需要动态解析到Cf优选ip的域名和子域名 14 | DOMAINS = { 15 | "vps789.com": {"@": ["CM","CU","CT"],"www": ["CM","CU","CT"]}, 16 | "vpsxxx777.com": {"@": ["CM","CU","CT"]} 17 | } 18 | 19 | #解析生效条数 20 | AFFECT_NUM = 2 21 | 22 | #如果使用华为云解析 需要从API凭证-项目列表中获取 23 | REGION_HW = 'cn-east-3' 24 | 25 | #解析生效时间,默认为600秒 26 | TTL = 600 27 | 28 | #华为云后台获取 https://support.huaweicloud.com/devg-apisign/api-sign-provide-aksk.html 29 | SECRETID = 'LTxxxxxxxxxxxxxxWcRDH' 30 | SECRETKEY = 'LOitxxxxxxxxxxxxxxxxxxmzcGR' 31 | 32 | if __name__ == '__main__': 33 | config = {'DOMAINS':DOMAINS,"AFFECT_NUM":AFFECT_NUM,"TTL":TTL,"DNS_SERVER":3} 34 | cloud = HuaWeiApi(SECRETID, SECRETKEY, REGION_HW) 35 | core = Core(cloud,config) 36 | core.main() 37 | -------------------------------------------------------------------------------- /runAliDns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import random 4 | import time 5 | import requests 6 | from dns.aliyun import AliApi 7 | from dns.core import Core 8 | from log import Logger 9 | import traceback 10 | 11 | 12 | #CM:移动 CU:联通 CT:电信 AB:境外 DEF:默认 13 | #需要动态解析到Cf优选ip的域名和子域名 14 | DOMAINS = { 15 | "vps789.com": {"@": ["CM","CU","CT"],"www": ["CM","CU","CT"]}, 16 | "vpsxxx777.com": {"@": ["CM","CU","CT"]} 17 | } 18 | 19 | #解析生效条数 20 | AFFECT_NUM = 2 21 | 22 | #如果使用阿里云解析 REGION出现错误再修改 默认不需要修改 https://help.aliyun.com/document_detail/198326.html 23 | REGION_ALI = 'cn-hongkong' 24 | 25 | #解析生效时间,默认为600秒 26 | TTL = 600 27 | 28 | #阿里云后台获取 https://help.aliyun.com/document_detail/53045.html 注意需要添加DNS控制权限 AliyunDNSFullAccess 29 | SECRETID = 'LTAIxxxxxxxxxxxxxxxxxxxWcRDH' 30 | SECRETKEY = 'LOitxxxxxxxxxxxxxxxxxxxzcGR' 31 | 32 | if __name__ == '__main__': 33 | config = {'DOMAINS':DOMAINS,"AFFECT_NUM":AFFECT_NUM,"TTL":TTL,"DNS_SERVER":2} 34 | cloud = AliApi(SECRETID, SECRETKEY,REGION_ALI) 35 | core = Core(cloud,config) 36 | core.main() 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 功能介绍 2 | 3 | 本项目的主要功能是动态获取cf优选ip,并将域名实时解析到这些优选IP上,以达到网站使用cloudflare CDN并使用国内访问速度更快的CDN节点。 4 | 5 | ★ 动态获取cf优选ip的接口由vps789.com提供 6 | 7 | ★ 网站使用CF优选IP的教程参考:https://www.vpsjxw.com/vps_use/cloudflare_select_ip/ 8 | 9 | ★ 本工具主要是将网站域名动态解析到获取到的优选ip上,目前支持阿里云DNS、DNSPod、华为云DNS、NameSilo 10 | 11 |
12 | 13 | ### CF优选IP接口(VPS789.com提供) 14 | 15 | **CF优选IP机制说明:** 16 | 17 | 基于CloudFlareST工具生成一个[初选IP池],vps789固定24小时监测200-500个IP。每天根据网络情况综合评估,淘汰1/3网络较差的IP,然后从[初选IP池]补充到vps789中继续监测。**通过持续补充优选IP和IP优胜略汰机制,保证vps789上的IP都是优中选优。** 18 | 19 | ★ 支持对cloudFlare IP进行延迟、丢包率、下载速度、晚高峰网络状态等多个维度的筛选 20 | 21 | ★ vps789优选IP监控页面地址:https://vps789.com/cfip 22 | 23 | ★ VPS789优选IP动态获取接口:https://vps789.com/public/sum/cfIpApi 24 | 25 |
26 | 27 | ### 使用方法 28 | 29 | 0. 需要python3、pip环境 30 | 31 | 1. 安装运行脚本所需依赖 32 | 33 | pip install -r requirements.txt 34 | 35 | 36 | 2. 登录DNS解析平台,获取 SecretId、SecretKey。如果使用NameSilo只用填SecretKey即可。 37 | 38 | 3. 将脚本下载到本地,修改启动文件runXXX.py中的SecretId、SecretKey 39 | 40 | 4. 修改脚本中域名配置信息,可配置多个域名和多个子域名,注意选择DNS服务商 41 | 42 | 43 | 5. 运行程序,如果能够正常运行可以选择cron定时执行(建议1个小时执行一次) 44 | 45 | ```python 46 | #域名解析在阿里云DNS 47 | python runAliDns.py 48 | #域名解析在DnsPod 49 | python runDnsPod.py 50 | #域名解析在华为云DNS 51 | python runHwDns.py 52 | #域名解析在NameSilo 53 | python runNameSilo.py 54 | ``` 55 |
56 | 57 | ### 鸣谢 58 | 感谢以下项目提供技术支撑与实现思路 59 | 60 | [cf2dns](https://github.com/ddgth/cf2dns)@ddgth 61 | 62 | [CloudflareSpeedTest](https://github.com/XIU2/CloudflareSpeedTest)@XIU2 63 | -------------------------------------------------------------------------------- /log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from logging import handlers 3 | 4 | class Logger(object): 5 | level_relations = { 6 | 'debug':logging.DEBUG, 7 | 'info':logging.INFO, 8 | 'warning':logging.WARNING, 9 | 'error':logging.ERROR, 10 | 'crit':logging.CRITICAL 11 | }#日志级别关系映射 12 | 13 | def __init__(self,filename,level='info',when='D',backCount=3,fmt='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'): 14 | self.logger = logging.getLogger(filename) 15 | format_str = logging.Formatter(fmt)#设置日志格式 16 | self.logger.setLevel(self.level_relations.get(level))#设置日志级别 17 | sh = logging.StreamHandler()#往屏幕上输出 18 | sh.setFormatter(format_str) #设置屏幕上显示的格式 19 | th = handlers.TimedRotatingFileHandler(filename=filename,when=when,backupCount=backCount,encoding='utf-8')#往文件里写入#指定间隔时间自动生成文件的处理器 20 | #实例化TimedRotatingFileHandler 21 | #interval是时间间隔,backupCount是备份文件的个数,如果超过这个个数,就会自动删除,when是间隔的时间单位,单位有以下几种: 22 | # S 秒 23 | # M 分 24 | # H 小时、 25 | # D 天、 26 | # W 每星期(interval==0时代表星期一) 27 | # midnight 每天凌晨 28 | th.setFormatter(format_str)#设置文件里写入的格式 29 | self.logger.addHandler(sh) #把对象加到logger里 30 | self.logger.addHandler(th) 31 | if __name__ == '__main__': 32 | log = Logger('monitor.log',level='debug') 33 | log.logger.debug('debug') 34 | log.logger.info('info') 35 | log.logger.warning('警告') 36 | log.logger.error('报错') 37 | log.logger.critical('严重') 38 | Logger('error.log', level='error').logger.error('error') -------------------------------------------------------------------------------- /dns/nameSilo.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import copy 3 | import sys 4 | import json 5 | 6 | import requests 7 | 8 | 9 | class NameSiloClient: 10 | 11 | 12 | def __init__(self, key) -> None: 13 | self._api_key = key 14 | 15 | 16 | def get_record(self, domain, length, sub_domain, record_type): 17 | 18 | try: 19 | url = f"https://www.namesilo.com/api/dnsListRecords?version=1&type=json&key={self._api_key}&domain={domain}" 20 | response = requests.get(url) 21 | response_body = response.json() 22 | if response.status_code == 200: 23 | if response_body["reply"]["code"] !=300: 24 | raise Exception('namesilo接口错误:'+response_body["reply"]["detail"]) 25 | res={} 26 | res["code"]=0 27 | res["data"]={} 28 | res["data"]["records"]=[] 29 | for record in response_body["reply"]["resource_record"]: 30 | if record["type"] == record_type: 31 | if sub_domain == "@": 32 | if record["host"] == domain : 33 | res["data"]["records"].append({'id':record["record_id"],'value':record['value'],'line':'默认'}) 34 | else: 35 | if sub_domain+"."+domain == record["host"]: 36 | res["data"]["records"].append({'id':record["record_id"],'value':record['value'],'line':'默认'}) 37 | return res 38 | else: 39 | raise Exception('namesilo接口错误') 40 | 41 | except Exception as e: 42 | raise Exception('namesilo接口错误') 43 | 44 | def delete_record(self, domain,recordId): 45 | 46 | try: 47 | url = f"https://www.namesilo.com/api/dnsDeleteRecord?version=1&type=json&key={self._api_key}&domain={domain}&rrid={recordId}" 48 | response = requests.get(url) 49 | if response.status_code == 200: 50 | return response.json() 51 | else: 52 | raise Exception('namesilo接口错误') 53 | except Exception as e: 54 | raise Exception('namesilo接口错误') 55 | 56 | def create_record(self, domain, sub_domain, value, record_type,line, ttl): 57 | try: 58 | url = f"https://www.namesilo.com/api/dnsAddRecord?version=1&type=json&key={self._api_key}&domain={domain}&rrtype={record_type}&rrhost={sub_domain}&rrvalue={value}&rrttl={ttl}" 59 | response = requests.get(url) 60 | if response.status_code == 200: 61 | return response.json() 62 | else: 63 | raise Exception('namesilo接口错误') 64 | except Exception as e: 65 | raise Exception('namesilo接口错误') 66 | 67 | def change_record(self, domain, record_id, sub_domain,value, record_type, line,ttl): 68 | if sub_domain == "@": 69 | sub_domain = "" 70 | try: 71 | url = f"https://www.namesilo.com/api/dnsUpdateRecord?version=1&type=json&key={self._api_key}&domain={domain}&rrid={record_id}&rrhost={sub_domain}&rrvalue={value}&rrttl={ttl}" 72 | response = requests.get(url) 73 | if response.status_code == 200: 74 | return response.json() 75 | else: 76 | raise Exception('namesilo接口错误') 77 | except Exception as e: 78 | raise Exception('namesilo接口错误') -------------------------------------------------------------------------------- /dns/aliyun.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Mail: tongdongdong@outlook.com 4 | # Reference: https://help.aliyun.com/document_detail/29776.html?spm=a2c4g.11186623.2.38.3fc33efexrOFkT 5 | # REGION: https://help.aliyun.com/document_detail/198326.html 6 | import json 7 | from aliyunsdkcore import client 8 | from aliyunsdkalidns.request.v20150109 import DescribeDomainRecordsRequest 9 | from aliyunsdkalidns.request.v20150109 import DeleteDomainRecordRequest 10 | from aliyunsdkalidns.request.v20150109 import UpdateDomainRecordRequest 11 | from aliyunsdkalidns.request.v20150109 import AddDomainRecordRequest 12 | 13 | 14 | rc_format = 'json' 15 | class AliApi(): 16 | def __init__(self, ACCESSID, SECRETKEY, REGION='cn-hongkong'): 17 | self.access_key_id = ACCESSID 18 | self.access_key_secret = SECRETKEY 19 | self.region = REGION 20 | 21 | def del_record(self, domain, record): 22 | clt = client.AcsClient(self.access_key_id, self.access_key_secret, self.region) 23 | request = DeleteDomainRecordRequest.DeleteDomainRecordRequest() 24 | request.set_RecordId(record) 25 | request.set_accept_format(rc_format) 26 | result = clt.do_action(request).decode('utf-8') 27 | result = json.JSONDecoder().decode(result) 28 | return result 29 | 30 | def get_record(self, domain, length, sub_domain, record_type): 31 | clt = client.AcsClient(self.access_key_id, self.access_key_secret, self.region) 32 | request = DescribeDomainRecordsRequest.DescribeDomainRecordsRequest() 33 | request.set_DomainName(domain) 34 | request.set_PageSize(length) 35 | request.set_RRKeyWord(sub_domain) 36 | request.set_Type(record_type) 37 | request.set_accept_format(rc_format) 38 | result = clt.do_action(request).decode('utf-8').replace('DomainRecords', 'data', 1).replace('Record', 'records', 1).replace('RecordId', 'id').replace('Value', 'value').replace('Line', 'line').replace('telecom', '电信').replace('unicom', '联通').replace('mobile', '移动').replace('oversea', '境外').replace('default', '默认') 39 | result = json.JSONDecoder().decode(result) 40 | return result 41 | 42 | def create_record(self, domain, sub_domain, value, record_type, line, ttl): 43 | clt = client.AcsClient(self.access_key_id, self.access_key_secret, self.region) 44 | request = AddDomainRecordRequest.AddDomainRecordRequest() 45 | request.set_DomainName(domain) 46 | request.set_RR(sub_domain) 47 | if line == "电信": 48 | line = "telecom" 49 | elif line == "联通": 50 | line = "unicom" 51 | elif line == "移动": 52 | line = "mobile" 53 | elif line == "境外": 54 | line = "oversea" 55 | elif line == "默认": 56 | line = "default" 57 | request.set_Line(line) 58 | request.set_Type(record_type) 59 | request.set_Value(value) 60 | request.set_TTL(ttl) 61 | request.set_accept_format(rc_format) 62 | result = clt.do_action(request).decode('utf-8') 63 | result = json.JSONDecoder().decode(result) 64 | return result 65 | 66 | def change_record(self, domain, record_id, sub_domain, value, record_type, line, ttl): 67 | clt = client.AcsClient(self.access_key_id, self.access_key_secret, self.region) 68 | request = UpdateDomainRecordRequest.UpdateDomainRecordRequest() 69 | request.set_RR(sub_domain) 70 | request.set_RecordId(record_id) 71 | if line == "电信": 72 | line = "telecom" 73 | elif line == "联通": 74 | line = "unicom" 75 | elif line == "移动": 76 | line = "mobile" 77 | elif line == "境外": 78 | line = "oversea" 79 | elif line == "默认": 80 | line = "default" 81 | request.set_Line(line) 82 | request.set_Type(record_type) 83 | request.set_Value(value) 84 | request.set_TTL(ttl) 85 | request.set_accept_format(rc_format) 86 | result = clt.do_action(request).decode('utf-8') 87 | result = json.JSONDecoder().decode(result) 88 | return result 89 | 90 | -------------------------------------------------------------------------------- /dns/huawei.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author/Mail: tongdongdong@outlook.com 4 | # Reference1: https://github.com/huaweicloud/huaweicloud-sdk-python-v3/tree/ff7df92d2a496871c7c2d84dfd2a7f4e2467fff5/huaweicloud-sdk-dns/huaweicloudsdkdns/v2/model 5 | # Reference2: https://support.huaweicloud.com/api-dns/dns_api_65003.html 6 | # REGION: https://developer.huaweicloud.com/endpoint 7 | 8 | from re import sub 9 | from huaweicloudsdkcore.auth.credentials import BasicCredentials 10 | from huaweicloudsdkdns.v2 import * 11 | from huaweicloudsdkdns.v2.region.dns_region import DnsRegion 12 | import json 13 | 14 | 15 | class HuaWeiApi(): 16 | def __init__(self, ACCESSID, SECRETKEY, REGION = 'cn-east-3'): 17 | self.AK = ACCESSID 18 | self.SK = SECRETKEY 19 | self.region = REGION 20 | self.client = DnsClient.new_builder().with_credentials(BasicCredentials(self.AK, self.SK)).with_region(DnsRegion.value_of(self.region)).build() 21 | self.zone_id = self.get_zones() 22 | 23 | def del_record(self, domain, record): 24 | request = DeleteRecordSetsRequest() 25 | request.zone_id = self.zone_id[domain + '.'] 26 | request.recordset_id = record 27 | response = self.client.delete_record_sets(request) 28 | result = json.loads(str(response)) 29 | print(result) 30 | return result 31 | 32 | def get_record(self, domain, length, sub_domain, record_type): 33 | request = ListRecordSetsWithLineRequest() 34 | request.limit = length 35 | request.type = record_type 36 | if sub_domain == '@': 37 | request.name = domain + "." 38 | else: 39 | request.name = sub_domain + '.' + domain + "." 40 | response = self.client.list_record_sets_with_line(request) 41 | data = json.loads(str(response)) 42 | result = {} 43 | records_temp = [] 44 | for record in data['recordsets']: 45 | if (sub_domain == '@' and domain + "." == record['name']) or (sub_domain + '.' + domain + "." == record['name']): 46 | record['line'] = self.line_format(record['line']) 47 | record['value'] = '1.1.1.1' 48 | records_temp.append(record) 49 | result['data'] = {'records': records_temp} 50 | return result 51 | 52 | def create_record(self, domain, sub_domain, value, record_type, line, ttl): 53 | request = CreateRecordSetWithLineRequest() 54 | request.zone_id = self.zone_id[domain + '.'] 55 | if sub_domain == '@': 56 | name = domain + "." 57 | else: 58 | name = sub_domain + '.' + domain + "." 59 | request.body = CreateRecordSetWithLineReq( 60 | type = record_type, 61 | name = name, 62 | ttl = ttl, 63 | weight = 1, 64 | records = [value], 65 | line = self.line_format(line) 66 | ) 67 | response = self.client.create_record_set_with_line(request) 68 | result = json.loads(str(response)) 69 | return result 70 | 71 | def change_record(self, domain, record_id, sub_domain, value, record_type, line, ttl): 72 | request = UpdateRecordSetRequest() 73 | request.zone_id = self.zone_id[domain + '.'] 74 | request.recordset_id = record_id 75 | if sub_domain == '@': 76 | name = domain + "." 77 | else: 78 | name = sub_domain + '.' + domain + "." 79 | request.body = UpdateRecordSetReq( 80 | name = name, 81 | type = record_type, 82 | ttl = ttl, 83 | records=[value] 84 | ) 85 | response = self.client.update_record_set(request) 86 | result = json.loads(str(response)) 87 | return result 88 | 89 | def get_zones(self): 90 | request = ListPublicZonesRequest() 91 | response = self.client.list_public_zones(request) 92 | result = json.loads(str(response)) 93 | zone_id = {} 94 | for zone in result['zones']: 95 | zone_id[zone['name']] = zone['id'] 96 | return zone_id 97 | 98 | def line_format(self, line): 99 | lines = { 100 | '默认' : 'default_view', 101 | '电信' : 'Dianxin', 102 | '联通' : 'Liantong', 103 | '移动' : 'Yidong', 104 | '境外' : 'Abroad', 105 | 'default_view' : '默认', 106 | 'Dianxin' : '电信', 107 | 'Liantong' : '联通', 108 | 'Yidong' : '移动', 109 | 'Abroad' : '境外', 110 | } 111 | return lines.get(line, None) 112 | 113 | if __name__ == '__main__': 114 | hw_api = HuaWeiApi('WTTCWxxxxxxxxx84O0V', 'GXkG6D4X1Nxxxxxxxxxxxxxxxxxxxxx4lRg6lT') 115 | print(hw_api.get_record('xxxx.com', 100, '@', 'A')) 116 | -------------------------------------------------------------------------------- /dns/qCloud.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # Mail: tongdongdong@outlook.com 4 | # Reference: https://cloud.tencent.com/document/product/302/8517 5 | # QcloudApiv3 DNSPod 的 API 更新了 By github@z0z0r4 6 | 7 | import json 8 | from tencentcloud.common import credential 9 | from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException 10 | from tencentcloud.dnspod.v20210323 import dnspod_client, models 11 | 12 | class QcloudApiv3(): 13 | def __init__(self, SECRETID, SECRETKEY): 14 | self.SecretId = SECRETID 15 | self.secretKey = SECRETKEY 16 | self.cred = credential.Credential(SECRETID, SECRETKEY) 17 | 18 | def del_record(self, domain: str, record_id: int): 19 | client = dnspod_client.DnspodClient(self.cred, "") 20 | req_model = models.DeleteRecordRequest() 21 | params = { 22 | "Domain": domain, 23 | "RecordId": record_id 24 | } 25 | req_model.from_json_string(json.dumps(params)) 26 | 27 | 28 | resp = client.DeleteRecord(req_model) 29 | resp = json.loads(resp.to_json_string()) 30 | resp["code"] = 0 31 | resp["message"] = "None" 32 | return resp 33 | 34 | def get_record(self, domain: str, length: int, sub_domain: str, record_type: str): 35 | def format_record(record: dict): 36 | new_record = {} 37 | record["id"] = record['RecordId'] 38 | for key in record: 39 | new_record[key.lower()] = record[key] 40 | return new_record 41 | try: 42 | client = dnspod_client.DnspodClient(self.cred, "") 43 | 44 | req_model = models.DescribeRecordListRequest() 45 | params = { 46 | "Domain": domain, 47 | "Subdomain": sub_domain, 48 | "RecordType": record_type, 49 | "Limit": length 50 | } 51 | req_model.from_json_string(json.dumps(params)) 52 | 53 | 54 | resp = client.DescribeRecordList(req_model) 55 | resp = json.loads(resp.to_json_string()) 56 | temp_resp = {} 57 | temp_resp["code"] = 0 58 | temp_resp["data"] = {} 59 | temp_resp["data"]["records"] = [] 60 | for record in resp['RecordList']: 61 | temp_resp["data"]["records"].append(format_record(record)) 62 | temp_resp["data"]["domain"] = {} 63 | temp_resp["data"]["domain"]["grade"] = self.get_domain(domain)["DomainInfo"]["Grade"] # DP_Free 64 | return temp_resp 65 | except TencentCloudSDKException: 66 | # 构造空响应... 67 | temp_resp = {} 68 | temp_resp["code"] = 0 69 | temp_resp["data"] = {} 70 | temp_resp["data"]["records"] = [] 71 | temp_resp["data"]["domain"] = {} 72 | temp_resp["data"]["domain"]["grade"] = self.get_domain(domain)["DomainInfo"]["Grade"] # DP_Free 73 | return temp_resp 74 | 75 | def create_record(self, domain: str, sub_domain: str, value: int, record_type: str = "A", line: str = "默认", ttl: int = 600): 76 | client = dnspod_client.DnspodClient(self.cred, "") 77 | req = models.CreateRecordRequest() 78 | params = { 79 | "Domain": domain, 80 | "SubDomain": sub_domain, 81 | "RecordType": record_type, 82 | "RecordLine": line, 83 | "Value": value, 84 | "ttl": ttl 85 | } 86 | req.from_json_string(json.dumps(params)) 87 | 88 | # 返回的resp是一个CreateRecordResponse的实例,与请求对象对应 89 | resp = client.CreateRecord(req) 90 | resp = json.loads(resp.to_json_string()) 91 | resp["code"] = 0 92 | resp["message"] = "None" 93 | return resp 94 | 95 | def change_record(self, domain: str, record_id: int, sub_domain: str, value: str, record_type: str = "A", line: str = "默认", ttl: int = 600): 96 | client = dnspod_client.DnspodClient(self.cred, "") 97 | req = models.ModifyRecordRequest() 98 | params = { 99 | "Domain": domain, 100 | "SubDomain": sub_domain, 101 | "RecordType": record_type, 102 | "RecordLine": line, 103 | "Value": value, 104 | "TTL": ttl, 105 | "RecordId": record_id 106 | } 107 | req.from_json_string(json.dumps(params)) 108 | 109 | # 返回的resp是一个ChangeRecordResponse的实例,与请求对象对应 110 | resp = client.ModifyRecord(req) 111 | resp = json.loads(resp.to_json_string()) 112 | resp["code"] = 0 113 | resp["message"] = "None" 114 | return resp 115 | 116 | def get_domain(self, domain: str): 117 | client = dnspod_client.DnspodClient(self.cred, "") 118 | 119 | # 实例化一个请求对象,每个接口都会对应一个request对象 120 | req = models.DescribeDomainRequest() 121 | params = { 122 | "Domain": domain 123 | } 124 | req.from_json_string(json.dumps(params)) 125 | 126 | # 返回的resp是一个DescribeDomainResponse的实例,与请求对象对应 127 | resp = client.DescribeDomain(req) 128 | resp = json.loads(resp.to_json_string()) 129 | return resp -------------------------------------------------------------------------------- /dns/core.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import random 4 | import time 5 | import requests 6 | from dns.qCloud import QcloudApiv3 # QcloudApiv3 DNSPod 的 API 更新了 By github@z0z0r4 7 | from dns.aliyun import AliApi 8 | from dns.huawei import HuaWeiApi 9 | from dns.nameSilo import NameSiloClient 10 | from log import Logger 11 | import traceback 12 | 13 | class Core(): 14 | 15 | log_cf2dns = Logger('cf2dns.log', level='debug') 16 | 17 | def __init__(self, cloud,config): 18 | self.cloud = cloud 19 | self.config = config 20 | 21 | def get_optimization_ip(self): 22 | try: 23 | headers = headers = {'Content-Type': 'application/json'} 24 | data = {} 25 | response = requests.post('https://vps789.com/public/sum/cfIpApi', json=data, headers=headers) 26 | if response.status_code == 200: 27 | self.log_cf2dns.logger.info("GET OPTIMIZATION IP SUCCESS" ) 28 | return response.json() 29 | else: 30 | self.log_cf2dns.logger.error("GET OPTIMIZATION IP ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----MESSAGE: REQUEST STATUS CODE IS NOT 200") 31 | return None 32 | except Exception as e: 33 | self.log_cf2dns.logger.error("GETs OPTIMIZATION IP ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----MESSAGE: " + str(e)) 34 | return None 35 | 36 | #s_info已存在的ip,c_info 最新的优选cfip 37 | def changeDNS(self,line, s_info, c_info, domain, sub_domain): 38 | recordType = "A" 39 | 40 | lines = {"CM": "移动", "CU": "联通", "CT": "电信", "AB": "境外", "DEF": "默认"} 41 | line = lines[line] 42 | print(s_info) 43 | print(c_info) 44 | try: 45 | create_num = self.config['AFFECT_NUM'] - len(s_info) 46 | # 如果线路能解析的ip已满,就用cfip一个一个去替换原来的ip解析 47 | if create_num == 0: 48 | for info in s_info: 49 | if len(c_info) == 0: 50 | break 51 | cf_ip = c_info.pop(random.randint(0,len(c_info)-1))["ip"] 52 | if cf_ip in str(s_info): 53 | continue 54 | ret = self.cloud.change_record(domain, info["recordId"], sub_domain, cf_ip, recordType, line, self.config['TTL']) 55 | if(self.config['DNS_SERVER'] != 1 or ret["code"] == 0): 56 | self.log_cf2dns.logger.info("CHANGE DNS SUCCESS: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+line+"----RECORDID: " + str(info["recordId"]) + "----VALUE: " + cf_ip ) 57 | else: 58 | self.log_cf2dns.logger.error("CHANGE DNS ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+line+"----RECORDID: " + str(info["recordId"]) + "----VALUE: " + cf_ip + "----MESSAGE: " + ret["message"] ) 59 | # 如果线路还可以解析更多的ip,就添加cfip解析,直到用完 60 | elif create_num > 0: 61 | for i in range(create_num): 62 | if len(c_info) == 0: 63 | break 64 | cf_ip = c_info.pop(random.randint(0,len(c_info)-1))["ip"] 65 | if cf_ip in str(s_info): 66 | continue 67 | ret = self.cloud.create_record(domain, sub_domain, cf_ip, recordType, line, self.config['TTL']) 68 | if(self.config['DNS_SERVER'] != 1 or ret["code"] == 0): 69 | self.log_cf2dns.logger.info("CREATE DNS SUCCESS: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+line+"----VALUE: " + cf_ip ) 70 | else: 71 | self.log_cf2dns.logger.error("CREATE DNS ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+line+"----RECORDID: " + str(info["recordId"]) + "----VALUE: " + cf_ip + "----MESSAGE: " + ret["message"] ) 72 | # 如果线路能解析的ip已超出配额,就用cfip一个一个去替换原来的ip解析 73 | else: 74 | for info in s_info: 75 | if create_num == 0 or len(c_info) == 0: 76 | break 77 | cf_ip = c_info.pop(random.randint(0,len(c_info)-1))["ip"] 78 | if cf_ip in str(s_info): 79 | create_num += 1 80 | continue 81 | ret = self.cloud.change_record(domain, info["recordId"], sub_domain, cf_ip, recordType, line, self.config['TTL']) 82 | if(self.config['DNS_SERVER'] != 1 or ret["code"] == 0): 83 | self.log_cf2dns.logger.info("CHANGE DNS SUCCESS: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+line+"----RECORDID: " + str(info["recordId"]) + "----VALUE: " + cf_ip ) 84 | else: 85 | self.log_cf2dns.logger.error("CHANGE DNS ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+line+"----RECORDID: " + str(info["recordId"]) + "----VALUE: " + cf_ip + "----MESSAGE: " + ret["message"] ) 86 | create_num += 1 87 | except Exception as e: 88 | self.log_cf2dns.logger.error("CHANGE DNS ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----MESSAGE: " + str(e)) 89 | 90 | def main(self): 91 | 92 | recordType = "A" 93 | if len(self.config['DOMAINS']) > 0: 94 | try: 95 | cfips = self.get_optimization_ip() 96 | if cfips == None or cfips["code"] != 0: 97 | self.log_cf2dns.logger.error("GET CLOUDFLARE IP ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----MESSAGE: " + str(cfips["data"])) 98 | return 99 | cf_cmips = cfips["data"]["CM"] 100 | cf_cuips = cfips["data"]["CU"] 101 | cf_ctips = cfips["data"]["CT"] 102 | cf_defips = cfips["data"]["AllAvg"] 103 | for domain, sub_domains in self.config['DOMAINS'].items(): 104 | for sub_domain, lines in sub_domains.items(): 105 | if self.config['DNS_SERVER'] == 4 and len(lines)!=1: 106 | self.log_cf2dns.logger.info("域名解析为NameSilo时,线路只能填DEF") 107 | return 108 | 109 | #下面5个数组存的是不同线路最新获取的优选IP列表 110 | temp_cf_cmips = cf_cmips.copy() 111 | temp_cf_cuips = cf_cuips.copy() 112 | temp_cf_ctips = cf_ctips.copy() 113 | temp_cf_abips = cf_ctips.copy() 114 | temp_cf_defips = cf_defips.copy() 115 | if self.config['DNS_SERVER'] == 1: 116 | ret = self.cloud.get_record(domain, 20, sub_domain, "CNAME") 117 | if ret["code"] == 0: 118 | for record in ret["data"]["records"]: 119 | if record["line"] == "移动" or record["line"] == "联通" or record["line"] == "电信": 120 | retMsg = self.cloud.del_record(domain, record["id"]) 121 | if(retMsg["code"] == 0): 122 | self.log_cf2dns.logger.info("DELETE DNS SUCCESS: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+record["line"] ) 123 | else: 124 | self.log_cf2dns.logger.error("DELETE DNS ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----DOMAIN: " + domain + "----SUBDOMAIN: " + sub_domain + "----RECORDLINE: "+record["line"] + "----MESSAGE: " + retMsg["message"] ) 125 | ret = self.cloud.get_record(domain, 100, sub_domain, recordType) 126 | # ret["code"] == 0 就是self.config['DNS_SERVER'] == 1 127 | if self.config['DNS_SERVER'] != 1 or ret["code"] == 0 : 128 | if self.config['DNS_SERVER'] == 1 and "Free" in ret["data"]["domain"]["grade"] and self.config['AFFECT_NUM'] > 2: 129 | self.config['AFFECT_NUM'] = 2 130 | #下面5个数组存的是已存在的DNS解析列表 131 | cm_info = [] 132 | cu_info = [] 133 | ct_info = [] 134 | ab_info = [] 135 | def_info = [] 136 | for record in ret["data"]["records"]: 137 | info = {} 138 | info["recordId"] = record["id"] 139 | info["value"] = record["value"] 140 | if record["line"] == "移动": 141 | cm_info.append(info) 142 | elif record["line"] == "联通": 143 | cu_info.append(info) 144 | elif record["line"] == "电信": 145 | ct_info.append(info) 146 | elif record["line"] == "境外": 147 | ab_info.append(info) 148 | elif record["line"] == "默认": 149 | def_info.append(info) 150 | for line in lines: 151 | if line == "CM": 152 | self.changeDNS("CM", cm_info, temp_cf_cmips, domain, sub_domain) 153 | elif line == "CU": 154 | self.changeDNS("CU", cu_info, temp_cf_cuips, domain, sub_domain) 155 | elif line == "CT": 156 | self.changeDNS("CT", ct_info, temp_cf_ctips, domain, sub_domain) 157 | elif line == "AB": 158 | self.changeDNS("AB", ab_info, temp_cf_abips, domain, sub_domain) 159 | elif line == "DEF": 160 | self.changeDNS("DEF", def_info, temp_cf_defips, domain, sub_domain) 161 | except Exception as e: 162 | traceback.print_exc() 163 | self.log_cf2dns.logger.error("CHANGE DNS ERROR: ----Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + "----MESSAGE: " + str(e)) --------------------------------------------------------------------------------