convert domainCheck to regular directory
This commit is contained in:
155
domainCheck/detect/aizhan.py
Normal file
155
domainCheck/detect/aizhan.py
Normal file
@@ -0,0 +1,155 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
@Project :domainScanDemo
|
||||
@File :aizhan.py
|
||||
@IDE :PyCharm
|
||||
@Author :梦伴
|
||||
@Date :2026/3/29 20:59
|
||||
@explain : 爱站网域名查询工具 - 获取域名的网站标题信息
|
||||
'''
|
||||
|
||||
import re # 正则表达式模块,用于从HTML中提取网站标题
|
||||
|
||||
import requests # HTTP请求库,用于发送网络请求
|
||||
from loguru import logger # 日志记录模块,用于记录程序运行日志
|
||||
|
||||
from typing import List, Optional # 类型提示
|
||||
|
||||
|
||||
def check_aizhan(domain: str, sensitive_words: Optional[List[str]] = None, proxies: dict = None):
|
||||
'''
|
||||
查询域名的网站标题信息是否存在敏感词
|
||||
|
||||
通过爱站网(aizhan.com)查询域名的网站标题
|
||||
|
||||
:param domain: 待查询的域名(如:www.baidu.com,不带协议头)
|
||||
:param proxies: 代理配置(如:{'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}
|
||||
:return: str - 网站标题字符串,如果查询失败则返回空字符串
|
||||
'''
|
||||
# 初始化敏感词列表
|
||||
if sensitive_words is None:
|
||||
sensitive_words = []
|
||||
|
||||
# 构建爱站网查询URL
|
||||
# 格式:https://www.aizhan.com/cha/{域名}/
|
||||
url = f'https://www.aizhan.com/cha/{domain}/'
|
||||
|
||||
# 构建HTTP请求头(模拟浏览器访问)
|
||||
headers = {
|
||||
'Accept': '*/*', # 接受所有类型的内容
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0',
|
||||
# 用户代理(浏览器标识)
|
||||
'Referer': url, # 来源页面(防盗链)
|
||||
'host': 'www.aizhan.com', # 目标主机地址
|
||||
}
|
||||
|
||||
# 添加重试机制
|
||||
max_retries = 3
|
||||
for retry in range(max_retries):
|
||||
try:
|
||||
# 发送GET请求获取页面内容
|
||||
# url: 请求URL
|
||||
# headers: HTTP请求头
|
||||
# timeout=10: 设置超时时间10秒
|
||||
# proxies: 代理配置(如需使用代理)
|
||||
response = requests.get(url, headers=headers, timeout=10, proxies=proxies)
|
||||
|
||||
# 判断请求是否成功(HTTP 200表示成功)
|
||||
if response.status_code == 200:
|
||||
# 使用正则表达式提取网站标题
|
||||
# 正则解释:匹配 id="webpage_title"> 开头,</div> 结尾,中间的内容
|
||||
# 匹配模式:id="webpage_title">标题内容</div>
|
||||
# ([^<]+) 表示匹配一个或多个非<字符,作为捕获组
|
||||
match = re.search(r'id="webpage_title">([^<]+)</div>', response.text)
|
||||
|
||||
# 如果找到匹配则返回标题文本,否则返回空字符串
|
||||
title = match.group(1) if match else ''
|
||||
if title:
|
||||
for sensitive_word in sensitive_words:
|
||||
# 使用正则表达式检查是否包含敏感词
|
||||
if re.search(sensitive_word, title, re.IGNORECASE):
|
||||
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {title}')
|
||||
return False, f'检测到敏感词: {sensitive_word} 在结果: {title}'
|
||||
return True, title
|
||||
|
||||
# 请求失败,继续重试
|
||||
logger.warning(f"爱站网检测第{retry+1}次失败: {domain}, 状态码: {response.status_code}")
|
||||
if retry < max_retries - 1:
|
||||
import time
|
||||
time.sleep(2) # 等待2秒后重试
|
||||
else:
|
||||
# 达到最大重试次数,尝试不使用代理
|
||||
if proxies:
|
||||
logger.info('爱站网检测失败,尝试不使用代理')
|
||||
try:
|
||||
response = requests.get(url, headers=headers, timeout=10, proxies=None)
|
||||
if response.status_code == 200:
|
||||
match = re.search(r'id="webpage_title">([^<]+)</div>', response.text)
|
||||
title = match.group(1) if match else ''
|
||||
if title:
|
||||
for sensitive_word in sensitive_words:
|
||||
if re.search(sensitive_word, title, re.IGNORECASE):
|
||||
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {title}')
|
||||
return False, f'检测到敏感词: {sensitive_word} 在结果: {title}'
|
||||
return True, title
|
||||
return True, ''
|
||||
except Exception as e2:
|
||||
logger.error(f"不使用代理的爱站网检测也失败: {domain}, 错误: {e2}")
|
||||
return False, str(e2)
|
||||
return True, ''
|
||||
|
||||
except Exception as e: # 捕获所有异常(网络错误、超时等)
|
||||
logger.warning(f"爱站网检测第{retry+1}次失败: {domain}, 错误: {e}")
|
||||
if retry < max_retries - 1:
|
||||
import time
|
||||
time.sleep(2) # 等待2秒后重试
|
||||
else:
|
||||
# 达到最大重试次数,尝试不使用代理
|
||||
if proxies:
|
||||
logger.info('爱站网检测失败,尝试不使用代理')
|
||||
try:
|
||||
response = requests.get(url, headers=headers, timeout=10, proxies=None)
|
||||
if response.status_code == 200:
|
||||
match = re.search(r'id="webpage_title">([^<]+)</div>', response.text)
|
||||
title = match.group(1) if match else ''
|
||||
if title:
|
||||
for sensitive_word in sensitive_words:
|
||||
if re.search(sensitive_word, title, re.IGNORECASE):
|
||||
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {title}')
|
||||
return False, f'检测到敏感词: {sensitive_word} 在结果: {title}'
|
||||
return True, title
|
||||
return True, ''
|
||||
except Exception as e2:
|
||||
logger.error(f"不使用代理的爱站网检测也失败: {domain}, 错误: {e2}")
|
||||
return False, str(e2)
|
||||
return False, str(e)
|
||||
|
||||
# # ========================= 【测试代码】 =========================
|
||||
# if __name__ == '__main__':
|
||||
# # 测试用例1:查询常见域名
|
||||
# logger.info("=== 测试爱站网查询 ===")
|
||||
# test_domain_1 = 'baidu.com'
|
||||
# logger.info(f"查询域名: {test_domain_1}")
|
||||
#
|
||||
# result_1 = check_aizhan(test_domain_1)
|
||||
# logger.info(f"网站标题: {result_1}")
|
||||
# logger.info("")
|
||||
#
|
||||
# # 测试用例2:查询另一个域名
|
||||
# logger.info("=== 测试查询QQ域名 ===")
|
||||
# test_domain_2 = 'qq.com'
|
||||
# logger.info(f"查询域名: {test_domain_2}")
|
||||
#
|
||||
# result_2 = check_aizhan(test_domain_2)
|
||||
# logger.info(f"网站标题: {result_2}")
|
||||
# logger.info("")
|
||||
#
|
||||
#
|
||||
#
|
||||
# # 测试用例4:查询可能不存在的域名
|
||||
# logger.info("=== 测试不存在域名 ===")
|
||||
# test_domain_4 = 'nonexistentdomain123456.com'
|
||||
# logger.info(f"查询域名: {test_domain_4}")
|
||||
#
|
||||
# result_4 = check_aizhan(test_domain_4)
|
||||
# logger.info(f"网站标题: {result_4 if result_4 else '(无结果)'}")
|
||||
263
domainCheck/detect/baidu.py
Normal file
263
domainCheck/detect/baidu.py
Normal file
@@ -0,0 +1,263 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
@Project :domainScanDemo
|
||||
@File :baidu.py
|
||||
@IDE :PyCharm
|
||||
@Author :梦伴
|
||||
@Date :2026/3/29 21:54
|
||||
@explain : 百度域名检测工具 - 包含百度搜索结果查询和百度安全API检测功能
|
||||
'''
|
||||
|
||||
import random # 随机数生成模块,用于随机选择浏览器类型
|
||||
import re # 正则表达式模块,用于从HTML中提取搜索结果
|
||||
import time
|
||||
|
||||
from loguru import logger # 日志记录模块,用于记录程序运行日志
|
||||
from curl_cffi import requests # curl_cffi库,支持浏览器模拟的HTTP请求
|
||||
from typing import List, Optional, Union, Any # 类型提示
|
||||
|
||||
def check_site(domain: str, sensitive_words: Optional[List[str]] = None, proxies: dict = None) -> Union[
|
||||
None, tuple[bool, str], list[Any]]:
|
||||
'''
|
||||
通过百度搜索查询域名的相关信息
|
||||
|
||||
使用curl_cffi模拟浏览器访问百度搜索,获取site:domain的搜索结果标题
|
||||
|
||||
:param domain: 待查询的域名(如:www.baidu.com)
|
||||
:param proxies: 代理配置(如:{'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'})
|
||||
:return: list - 搜索结果标题列表,如果查询失败则返回空列表
|
||||
'''
|
||||
# 初始化敏感词列表
|
||||
if sensitive_words is None:
|
||||
sensitive_words = []
|
||||
|
||||
# 添加重试机制
|
||||
max_retries = 3
|
||||
for retry in range(max_retries):
|
||||
try: # try-except块,用于捕获网络请求中的异常
|
||||
# 构建搜索查询参数
|
||||
# wd: 搜索关键词,格式为site:域名
|
||||
params = {
|
||||
'wd': f'site:{domain}' # 搜索site:domain,限制搜索结果为指定域名
|
||||
}
|
||||
|
||||
cookies = {
|
||||
'BIDUPSID': '053DBE4D820C6EFB729DC7B13B1F82B2',
|
||||
'PSTM': '1775657119',
|
||||
'H_PS_PSSID': '63148_67862_67986_68002_68142_68148_68152_68141_68165_68189_68226_68267_68296_68336_68369_68453_68438_68464_68541_68546_68558_68520_68589_68621_68615_68606_68601_68682_68671_68735_68544_68733_68766_68807_68901_68918_68836_68921_68955_68976_68997_69007_69010_69018_69024_69014',
|
||||
'BAIDUID': '053DBE4D820C6EFB729DC7B13B1F82B2:FG=1',
|
||||
'BDORZ': 'B490B5EBF6F3CD402E515D22BCDA1598',
|
||||
'BAIDUID_BFESS': '053DBE4D820C6EFB729DC7B13B1F82B2:FG=1',
|
||||
'BA_HECTOR': 'ah0l24210la4050l0g0k0g2g0h20071ktcocj27',
|
||||
'ZFY': 'Qxyr75Xm7o8zUxYzuFnYoW7cnS:AnVp:BpnrNVkr3usno:C',
|
||||
'delPer': '0',
|
||||
'BAIDUID_REF': '053DBE4D820C6EFB729DC7B13B1F82B2:FG=1',
|
||||
'H_WISE_SIDS': '110085_661771_667681_673676_683089_682564_685373_660925_687556_686285_690306_690303_690478_690576_690334_691545_685595_690883_692754_692777_692118_692910_693221_693364_693403_693656_693941_694124_693392_694171_694236_693996_694316_694324_693886_694409_694379_694696_694780_694783_692006_694884_694918_694996_694991_695032_695118_694840_695140_695193_695110_695262_688610_694933_695278_694865_695214_695374_690651_694577_692378_695452_695457_695480_695603_695642_695631_695668_695824_695843_695866_695888_695892_695897_695975_694177_695900_695939_696145_696150_696153_696069_696078_696066_694359_696288_696308_694985_696297_695715_696128_696313_696457_696431_696473_696492_696590_693385_696610_696682_696679_696662_696651_696655_696658_696673_696643_696110_696729_696733_696772_8000116_8000133_8000138_8000159_8000163_8000167_8000176_8000186_8000190_8000204',
|
||||
'H_WISE_SIDS_BFESS': '110085_661771_667681_673676_683089_682564_685373_660925_687556_686285_690306_690303_690478_690576_690334_691545_685595_690883_692754_692777_692118_692910_693221_693364_693403_693656_693941_694124_693392_694171_694236_693996_694316_694324_693886_694409_694379_694696_694780_694783_692006_694884_694918_694996_694991_695032_695118_694840_695140_695193_695110_695262_688610_694933_695278_694865_695214_695374_690651_694577_692378_695452_695457_695480_695603_695642_695631_695668_695824_695843_695866_695888_695892_695897_695975_694177_695900_695939_696145_696150_696153_696069_696078_696066_694359_696288_696308_694985_696297_695715_696128_696313_696457_696431_696473_696492_696590_693385_696610_696682_696679_696662_696651_696655_696658_696673_696643_696110_696729_696733_696772_8000116_8000133_8000138_8000159_8000163_8000167_8000176_8000186_8000190_8000204',
|
||||
'MSA_PBT': '147',
|
||||
'MSA_ZOOM': '1000',
|
||||
'wpr': '0',
|
||||
'COOKIE_SESSION': '0_0_0_0_0_0_0_0_0_0_0_0_0_1775657396%7C1%230_0_0_0_0_0_0_0_1775657396%7C1',
|
||||
'MSA_PHY_WH': '1440_3440',
|
||||
'POLYFILL': '0',
|
||||
'MSA_WH': '1254_940',
|
||||
'kleck': '7ce2abac229d2e8ba435a8bcc6256f57f53e9d08954443bf',
|
||||
'PSCBD': '16%3A1%3A3',
|
||||
'SE_LAUNCH': '5%3A1775657396_16%3A29594323%3A3',
|
||||
'BDSVRTM': '3',
|
||||
'PSINO': '6',
|
||||
'__bsi': '17976785662214065461_00_7_R_R_6_0303_c02f_Y',
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Connection': 'keep-alive',
|
||||
'Sec-Fetch-Dest': 'document',
|
||||
'Sec-Fetch-Mode': 'navigate',
|
||||
'Sec-Fetch-Site': 'none',
|
||||
'Sec-Fetch-User': '?1',
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0',
|
||||
'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
}
|
||||
|
||||
# 百度搜索URL
|
||||
url = 'https://m.baidu.com/s'
|
||||
|
||||
# 发送GET请求获取搜索结果
|
||||
# url: 请求URL
|
||||
# params: 查询参数
|
||||
# headers: HTTP请求头
|
||||
# timeout=15: 超时时间15秒
|
||||
# proxies: 代理配置
|
||||
# impersonate: 模拟的浏览器类型
|
||||
# .content: 获取响应内容(字节流)
|
||||
# .decode('utf-8'): 解码为UTF-8字符串
|
||||
response_text = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=15,
|
||||
proxies=proxies).content.decode('utf-8')
|
||||
# 使用正则表达式提取搜索结果标题
|
||||
# re.findall()返回所有匹配的结果列表
|
||||
# 正则解释:匹配 <!--s-text--> 和 <!--/s-text--> 之间的内容
|
||||
match = re.findall(r'<!--s-text-->([^<]+)<!--/s-text-->', response_text,re.S)
|
||||
|
||||
# 返回匹配结果列表,如果没有匹配则返回空列表
|
||||
search_results= match if match else []
|
||||
|
||||
# 如果没有搜索结果,直接返回通过
|
||||
if not search_results:
|
||||
return True, ''
|
||||
|
||||
# 检查每个搜索结果是否包含敏感词
|
||||
for result in search_results:
|
||||
for sensitive_word in sensitive_words:
|
||||
# 使用正则表达式检查是否包含敏感词
|
||||
if re.search(sensitive_word, result, re.IGNORECASE):
|
||||
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {result}')
|
||||
return False, f'检测到敏感词: {sensitive_word} 在结果: {result}'
|
||||
|
||||
# 所有搜索结果都不包含敏感词,返回通过
|
||||
return True, ''
|
||||
except Exception as e: # 捕获所有异常
|
||||
# logger.warning(f"百度site检测第{retry+1}次失败: {domain}, 错误: {e}")
|
||||
if retry < max_retries - 1:
|
||||
import time
|
||||
time.sleep(2) # 等待2秒后重试
|
||||
proxies=None
|
||||
else:
|
||||
# 达到最大重试次数,返回检测失败
|
||||
logger.error(f"百度site检测失败: {domain}, 已达到最大重试次数")
|
||||
return False, str(e) # 返回错误信息
|
||||
|
||||
|
||||
def baidu(domain: str, proxies: dict = None) -> str:
|
||||
'''
|
||||
通过百度安全API检测域名安全状态
|
||||
|
||||
调用百度移动安全API检测域名是否为风险网站
|
||||
|
||||
:param domain: 待检测的域名(如:www.baidu.com)
|
||||
:param proxies: 代理配置(如:{'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}
|
||||
:return: str - 安全状态描述("风险网站提示"、"高危网站提示"或"正常"),如果检测失败则返回错误信息
|
||||
'''
|
||||
# 构建HTTP请求头
|
||||
headers = {
|
||||
'Cache-Control': 'no-cache', # 不使用缓存
|
||||
'Content-Type': 'application/json; charset=utf-8', # 内容类型为JSON
|
||||
'Host': 'mobsec-sec.baidu.com', # 目标主机
|
||||
'User-Agent': 'okhttp/3.12.12', # 模拟OKHttp客户端
|
||||
}
|
||||
|
||||
# 构建请求参数
|
||||
params = {
|
||||
'auth_ver': '2', # 认证版本
|
||||
'appkey': '4665fd2c6b0922a551e8ae74', # API应用密钥
|
||||
'nonce': '1751309255340', # 随机数(注意:实际使用时应该动态生成)
|
||||
'lc': '77qHTv4VtmRiXYtd', # 本地配置参数
|
||||
'pkg': 'com.baidu.searchbox', # 应用包名
|
||||
'vc': '-1', # 版本码
|
||||
'cuid': 'CF0E6FCCD824D146605C16AC1C8BAC95%7CVFWAO56TC', # 设备ID(注意:实际使用时应该动态生成)
|
||||
'tk': '', # 令牌(空)
|
||||
'type': '3', # 检测类型
|
||||
'vn': '2.6.0', # 版本号
|
||||
's': 'fdfef2ce96c1c7193f9b80425020f63e', # 签名(注意:实际使用时应该动态生成)
|
||||
}
|
||||
|
||||
# 构建JSON请求数据
|
||||
json_data = {
|
||||
'url': f'https://{domain}/', # 待检测的URL(添加https协议头)
|
||||
}
|
||||
|
||||
# 提取URL作为后续获取结果的键
|
||||
key = json_data['url']
|
||||
|
||||
# 添加重试机制
|
||||
max_retries = 3
|
||||
for retry in range(max_retries):
|
||||
try: # try-except块,用于捕获API调用中的异常
|
||||
# 发送POST请求到百度安全API
|
||||
# url: API接口地址
|
||||
# params: 查询参数
|
||||
# headers: HTTP请求头
|
||||
# json: JSON请求数据
|
||||
# proxies: 代理配置
|
||||
# .json(): 解析JSON响应
|
||||
response = requests.post('https://mobsec-sec.baidu.com/3.1/scanurl', params=params, headers=headers,
|
||||
json=json_data, proxies=proxies, timeout=15).json()
|
||||
|
||||
# # 记录API响应(调试用)
|
||||
# logger.info(response)
|
||||
|
||||
# 安全状态码映射表
|
||||
msgs = {
|
||||
2193: '风险网站提示', # 风险网站
|
||||
2243: '高危网站提示', # 高危网站
|
||||
'_': '正常' # 默认正常
|
||||
}
|
||||
|
||||
# 从响应中获取安全等级并映射为状态描述
|
||||
# response['response']['datas'][key].get('grand_level', 'level'): 获取安全等级,默认值为'level'
|
||||
# msgs.get(): 根据安全等级获取对应的状态描述,默认值为'正常'
|
||||
return msgs.get(response['response']['datas'][key].get('grand_level', 'level'), '正常')
|
||||
|
||||
except Exception as e: # 捕获所有异常
|
||||
logger.warning(f"百度网页安全中心检测第{retry+1}次失败: {domain}, 错误: {e}")
|
||||
if retry < max_retries - 1:
|
||||
time.sleep(2) # 等待2秒后重试
|
||||
else:
|
||||
# 达到最大重试次数,尝试不使用代理
|
||||
if proxies:
|
||||
logger.info('百度网页安全中心检测失败,尝试不使用代理')
|
||||
try:
|
||||
response = requests.post('https://mobsec-sec.baidu.com/3.1/scanurl', params=params, headers=headers,
|
||||
json=json_data, proxies=None, timeout=15).json()
|
||||
msgs = {
|
||||
2193: '风险网站提示', # 风险网站
|
||||
2243: '高危网站提示', # 高危网站
|
||||
'_': '正常' # 默认正常
|
||||
}
|
||||
return msgs.get(response['response']['datas'][key].get('grand_level', 'level'), '正常')
|
||||
except Exception as e2:
|
||||
logger.error(f"不使用代理的百度网页安全中心检测也失败: {domain}, 错误: {e2}")
|
||||
return str(e2) # 返回错误信息
|
||||
return str(e) # 返回错误信息
|
||||
|
||||
|
||||
# # ========================= 【测试代码】 =========================
|
||||
# if __name__ == '__main__': # 程序入口,当直接运行此文件时执行以下代码
|
||||
# # proxies = get_proxies()
|
||||
# proxies = None
|
||||
# # 测试用例1:查询域名搜索结果
|
||||
# logger.info("=== 测试百度搜索结果查询 ===")
|
||||
# test_domain_1 = '920zl.com'
|
||||
# logger.info(f"查询域名: {test_domain_1}")
|
||||
#
|
||||
# result_1 = check_site(test_domain_1, proxies=proxies)
|
||||
# logger.info(result_1)
|
||||
|
||||
#
|
||||
# # 测试用例3:检测域名安全状态
|
||||
# logger.info("=== 测试域名安全检测 ===")
|
||||
# test_domain_3 = 'baidu.com'
|
||||
# logger.info(f"检测域名: {test_domain_3}")
|
||||
#
|
||||
# result_3 = baidu(test_domain_3, proxies=proxies)
|
||||
# logger.info(f"安全状态: {result_3}")
|
||||
# logger.debug(proxies)
|
||||
|
||||
# domains = ['Lqyingye.com', 'tjjinLikeji.com', 'guokangLxs.com', 'zhuoyijz.com', 'jiandanrongyi.com', 'fromhhc.com',
|
||||
# 'djxow.com', '725game.com', 'gcLpchd.cn', '6rdb.com.cn', 'sunmantech.com', 'hroxa.com', 'guLhu65.cn',
|
||||
# 'xxjdch.com', '7e7fm1.cn', 'kaoxueb.com', 'jabas.cn', 'shzhongyi.com', 'figuangze.com', 'eztw9k.cn',
|
||||
# 'foxok.com', 'oLLbmr.com', 'vwpwwkp.cn', 'cbmrs.com', 'zaozhuang56.com', 'hengxiangracing.com',
|
||||
# 'jdyfbpq.cn', 'czca.cn', 'shibingxiongdi.com', 'ubxbm.cn', 'ydbaoshi.com', '2046hd.cn', 'mffmcp.com',
|
||||
# 'hgLdhzz.com', 'okswmi.cn', 'caidaozg.com', 'dkdywx.cn', 'qkjps.com', 'Lwxgk.cn', 'drtechnoLogy.cn',
|
||||
# 'akcie.cn', '588uu.com', 'kuaiyijian.net', 'c5b.cn', 'faka866.cn', 'wiraf03.cn', 'bobjibar.com',
|
||||
# '166233.cn', 'adLfjcjq.cn', 'whrsom.com']
|
||||
# for domain in domains:
|
||||
# result_1 = check_site(domain)
|
||||
# logger.info(f"搜索结果标题数: {len(result_1)}")
|
||||
# for i, title in enumerate(result_1, 1):
|
||||
# logger.info(f" 结果{i}: {title}")
|
||||
# time.sleep(1)
|
||||
191
domainCheck/detect/c360.py
Normal file
191
domainCheck/detect/c360.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
@Project :domainScanDemo
|
||||
@File :c360.py
|
||||
@IDE :PyCharm
|
||||
@Author :梦伴
|
||||
@Date :2026/4/2 15:57
|
||||
@explain : 360搜索敏感词检测工具 - 检测域名是否包含敏感词
|
||||
'''
|
||||
|
||||
import re # 正则表达式模块
|
||||
import requests # HTTP请求库
|
||||
from loguru import logger # 日志记录
|
||||
from typing import List, Tuple, Optional, Dict # 类型提示
|
||||
|
||||
|
||||
# 常量定义
|
||||
SO_SEARCH_URL = 'https://www.so.com/s' # 360搜索URL
|
||||
SO_REFERER_TEMPLATE = 'https://www.so.com/s?ie=utf-8&q=site%3A{domain}' # Referer模板
|
||||
SEARCH_PATTERN = r'target="_blank">([^<]+)</a></h3>' # 搜索结果匹配模式
|
||||
BLOCKED_CODE = 3 # 拦截状态码
|
||||
|
||||
|
||||
def check_domain(
|
||||
domain: str,
|
||||
sensitive_words: Optional[List[str]] = None,
|
||||
proxies: Optional[Dict] = None
|
||||
) -> Tuple[bool, str]:
|
||||
"""
|
||||
检查域名是否包含敏感词
|
||||
|
||||
通过360搜索获取域名的搜索结果,然后检查搜索结果中是否包含敏感词
|
||||
|
||||
Args:
|
||||
domain: 要检测的域名
|
||||
sensitive_words: 敏感词列表,如果为None则初始化为空列表
|
||||
proxies: 代理配置
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (是否通过, 错误信息)
|
||||
- True, '': 通过检测,不包含敏感词
|
||||
- False, '敏感词:xxx': 包含敏感词,返回具体的敏感词
|
||||
"""
|
||||
# 初始化敏感词列表
|
||||
if sensitive_words is None:
|
||||
sensitive_words = []
|
||||
|
||||
# 构建请求头
|
||||
headers = {
|
||||
'Accept': '*/*', # 接受所有类型
|
||||
'Accept-Language': 'zh-cn', # 中文语言
|
||||
'Referer': SO_REFERER_TEMPLATE.format(domain=domain), # 来源页面
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36', # 用户代理
|
||||
'Host': 'www.so.com', # 主机名
|
||||
}
|
||||
|
||||
# 构建查询参数
|
||||
params = {
|
||||
'ie': 'utf-8', # 编码格式
|
||||
'q': f'site:{domain}', # 搜索查询
|
||||
}
|
||||
|
||||
# 添加重试机制
|
||||
max_retries = 3
|
||||
for retry in range(max_retries):
|
||||
try:
|
||||
# 发送GET请求获取搜索结果
|
||||
response = requests.get(
|
||||
SO_SEARCH_URL,
|
||||
params=params,
|
||||
headers=headers,
|
||||
proxies=proxies,
|
||||
timeout=15 # 增加超时时间到15秒
|
||||
)
|
||||
|
||||
# 解析响应内容
|
||||
response_html = response.content.decode('utf-8', errors='ignore')
|
||||
|
||||
# 使用正则表达式提取搜索结果
|
||||
search_results = re.findall(SEARCH_PATTERN, response_html)
|
||||
|
||||
# 记录搜索结果数量
|
||||
logger.info(f'360搜索结果数量: {len(search_results)}')
|
||||
|
||||
# 如果没有搜索结果,直接返回通过
|
||||
if not search_results:
|
||||
return True, ''
|
||||
|
||||
# 检查每个搜索结果是否包含敏感词
|
||||
for result in search_results:
|
||||
for sensitive_word in sensitive_words:
|
||||
# 使用正则表达式检查是否包含敏感词
|
||||
if re.search(sensitive_word, result, re.IGNORECASE):
|
||||
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {result}')
|
||||
return False, f'检测到敏感词: {sensitive_word} 在结果: {result}'
|
||||
|
||||
# 所有搜索结果都不包含敏感词,返回通过
|
||||
return True, ''
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
logger.warning(f'360搜索请求第{retry+1}次超时')
|
||||
if retry < max_retries - 1:
|
||||
import time
|
||||
time.sleep(2) # 等待2秒后重试
|
||||
else:
|
||||
# 达到最大重试次数,尝试不使用代理
|
||||
if proxies:
|
||||
logger.info('360搜索请求超时,尝试不使用代理')
|
||||
try:
|
||||
response = requests.get(
|
||||
SO_SEARCH_URL,
|
||||
params=params,
|
||||
headers=headers,
|
||||
proxies=None,
|
||||
timeout=15
|
||||
)
|
||||
response_html = response.content.decode('utf-8', errors='ignore')
|
||||
search_results = re.findall(SEARCH_PATTERN, response_html)
|
||||
logger.info(f'不使用代理的360搜索结果数量: {len(search_results)}')
|
||||
if not search_results:
|
||||
return True, ''
|
||||
for result in search_results:
|
||||
for sensitive_word in sensitive_words:
|
||||
if re.search(sensitive_word, result, re.IGNORECASE):
|
||||
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {result}')
|
||||
return False, f'检测到敏感词: {sensitive_word} 在结果: {result}'
|
||||
return True, ''
|
||||
except Exception as e:
|
||||
logger.error(f'不使用代理的360搜索请求也失败: {str(e)}')
|
||||
return False, f'360搜索请求超时'
|
||||
return False, '360搜索请求超时'
|
||||
except requests.exceptions.RequestException as e:
|
||||
error_str = str(e)
|
||||
logger.warning(f'360搜索请求第{retry+1}次失败: {error_str}')
|
||||
# 检查是否是"Too many open files"错误
|
||||
if 'Too many open files' in error_str:
|
||||
if retry < max_retries - 1:
|
||||
import time
|
||||
time.sleep(2) # 等待2秒后重试
|
||||
else:
|
||||
# 达到最大重试次数,返回failure标记,不拉黑域名
|
||||
logger.error(f'360搜索请求失败,已达到最大重试次数: {error_str}')
|
||||
return True, 'failure'
|
||||
else:
|
||||
if retry < max_retries - 1:
|
||||
import time
|
||||
time.sleep(2) # 等待2秒后重试
|
||||
else:
|
||||
# 达到最大重试次数,尝试不使用代理
|
||||
if proxies:
|
||||
logger.info('360搜索请求失败,尝试不使用代理')
|
||||
try:
|
||||
response = requests.get(
|
||||
SO_SEARCH_URL,
|
||||
params=params,
|
||||
headers=headers,
|
||||
proxies=None,
|
||||
timeout=15
|
||||
)
|
||||
response_html = response.content.decode('utf-8', errors='ignore')
|
||||
search_results = re.findall(SEARCH_PATTERN, response_html)
|
||||
logger.info(f'不使用代理的360搜索结果数量: {len(search_results)}')
|
||||
if not search_results:
|
||||
return True, ''
|
||||
for result in search_results:
|
||||
for sensitive_word in sensitive_words:
|
||||
if re.search(sensitive_word, result, re.IGNORECASE):
|
||||
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {result}')
|
||||
return False, f'检测到敏感词: {sensitive_word} 在结果: {result}'
|
||||
return True, ''
|
||||
except Exception as e2:
|
||||
logger.error(f'不使用代理的360搜索请求也失败: {str(e2)}')
|
||||
return False, f'360搜索请求失败: {str(e)}'
|
||||
return False, f'360搜索请求失败: {str(e)}'
|
||||
except Exception as e:
|
||||
logger.warning(f'360搜索第{retry+1}次未知错误: {str(e)}')
|
||||
if retry < max_retries - 1:
|
||||
import time
|
||||
time.sleep(2) # 等待2秒后重试
|
||||
else:
|
||||
return False, f'未知错误: {str(e)}'
|
||||
|
||||
|
||||
# if __name__ == '__main__':
|
||||
# # 测试用例
|
||||
# test_domain = 'niuniushouka.com'
|
||||
# test_sensitive_words = ['赌博', '色情', '暴力','收卡']
|
||||
#
|
||||
# logger.info(f'开始检测域名: {test_domain}')
|
||||
# result = check_domain(test_domain, test_sensitive_words)
|
||||
# logger.info(f'检测结果: {result}')
|
||||
1257
domainCheck/detect/chinaz.js
Normal file
1257
domainCheck/detect/chinaz.js
Normal file
File diff suppressed because one or more lines are too long
242
domainCheck/detect/chinaz.py
Normal file
242
domainCheck/detect/chinaz.py
Normal file
@@ -0,0 +1,242 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
@Project :domainScanDemo
|
||||
@File :chinaz.py
|
||||
@IDE :PyCharm
|
||||
@Author :梦伴
|
||||
@Date :2026/3/27 23:48
|
||||
@explain : 站长工具SEO查询工具 - 获取域名网站标题、SEO排名等信息
|
||||
'''
|
||||
|
||||
import json # JSON处理模块,用于解析API响应数据
|
||||
import os # 操作系统接口模块,用于获取脚本所在目录路径
|
||||
import re # 正则表达式模块,用于提取网页中的标题和加密密钥
|
||||
import time # 时间处理模块,用于生成时间戳
|
||||
from typing import List, Optional # 类型提示
|
||||
|
||||
import quickjs # JavaScript执行引擎,用于执行JS加密代码生成签名
|
||||
import requests # HTTP请求库,用于发送网络请求
|
||||
from loguru import logger # 日志记录模块,用于记录程序运行日志
|
||||
|
||||
|
||||
def check_title(domain: str,sensitive_words: Optional[List[str]] = None, proxies: dict = None):
|
||||
'''
|
||||
检测域名标题并获取SEO数据
|
||||
|
||||
:param domain: 待检测域名(如:www.baidu.com,不带协议头)
|
||||
:param proxies: 代理配置(如:{'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}
|
||||
:return: Tuple[bool, str, Tuple[bool, Any]] - (是否成功, 消息, (SEO查询是否成功, SEO数据或错误信息))
|
||||
'''
|
||||
# 初始化敏感词列表
|
||||
if sensitive_words is None:
|
||||
sensitive_words = []
|
||||
|
||||
# 添加重试机制
|
||||
max_retries = 3
|
||||
for retry in range(max_retries):
|
||||
try:
|
||||
# 构建SEO查询URL(站长工具网站)
|
||||
url = f'https://seo.chinaz.com/{domain}'
|
||||
|
||||
# 发送GET请求获取页面内容,设置超时10秒
|
||||
response = requests.get(url, timeout=10, proxies=proxies)
|
||||
|
||||
# 判断请求是否成功(HTTP 200表示成功)
|
||||
if response.status_code == 200:
|
||||
# 使用正则表达式提取网站标题(从id="site_title"的div标签中提取文本内容)
|
||||
# 正则解释:匹配 id="site_title"> 开头,</div> 结尾,中间的内容
|
||||
match = re.search(r'id="site_title">([^<]+)</div>', response.text)
|
||||
|
||||
# 使用正则表达式提取加密密钥enkey(用于后续API请求的身份验证)
|
||||
# 正则解释:匹配 var enkey = '...' 中的单引号内容
|
||||
pattern = r"var enkey = '([^']+)''"
|
||||
match_enkey = re.search(pattern, response.text)
|
||||
|
||||
# 如果上面的正则匹配失败,尝试匹配正确的单引号版本
|
||||
if not match_enkey:
|
||||
pattern = r"var enkey = '([^']+)" # 匹配 var enkey = '...' 格式
|
||||
match_enkey = re.search(pattern, response.text)
|
||||
|
||||
# 如果找到enkey则提取并去除首尾空格,否则为空字符串
|
||||
enkey = match_enkey.group(1).strip() if match_enkey else ''
|
||||
|
||||
# 提取网站标题,如果找到则返回,否则返回空字符串
|
||||
title = match.group(1).strip() if match else ''
|
||||
|
||||
if title:
|
||||
for sensitive_word in sensitive_words:
|
||||
# 使用正则表达式检查是否包含敏感词
|
||||
if re.search(sensitive_word, title, re.IGNORECASE):
|
||||
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {title}')
|
||||
return False, f'检测到敏感词: {sensitive_word} 在结果: {title}', ()
|
||||
|
||||
# 调用get_site_data获取详细SEO数据
|
||||
success, result = get_site_data(domain, enkey, proxies=proxies)
|
||||
|
||||
# 检查网站分类
|
||||
if success and result:
|
||||
# 定义需要拉黑的分类
|
||||
blacklist_categories = ['视频电影', '体育运动', '常用查询', '游戏网站', '游戏', '视频', '体育']
|
||||
|
||||
# 检查分类是否在黑名单中
|
||||
if isinstance(result, dict):
|
||||
# 检查可能的分类字段
|
||||
category_fields = ['Category', 'category', '分类', '网站分类']
|
||||
for field in category_fields:
|
||||
if field in result:
|
||||
category = result[field]
|
||||
if isinstance(category, str):
|
||||
for blacklist_category in blacklist_categories:
|
||||
if blacklist_category in category:
|
||||
logger.warning(f'检测到黑名单分类: {category} 在域名: {domain}')
|
||||
return False, f'网站分类: {category} 属于黑名单分类', ()
|
||||
|
||||
# 检查Result是否为列表
|
||||
elif isinstance(result, list):
|
||||
for item in result:
|
||||
if isinstance(item, dict):
|
||||
category_fields = ['Category', 'category', '分类', '网站分类']
|
||||
for field in category_fields:
|
||||
if field in item:
|
||||
category = item[field]
|
||||
if isinstance(category, str):
|
||||
for blacklist_category in blacklist_categories:
|
||||
if blacklist_category in category:
|
||||
logger.warning(f'检测到黑名单分类: {category} 在域名: {domain}')
|
||||
return False, f'网站分类: {category} 属于黑名单分类', ()
|
||||
|
||||
return True, 'success', (success, result)
|
||||
|
||||
# 请求失败返回空标题和空元组
|
||||
return True, '', ()
|
||||
|
||||
except Exception as e: # 捕获所有异常
|
||||
error_str = str(e)
|
||||
# 检查是否是需要重试的错误类型
|
||||
if any(error_type in error_str for error_type in ['Too many open files', 'EOF occurred in violation of protocol', 'Max retries exceeded', 'Read timed out']):
|
||||
logger.warning(f"站长工具检测第{retry+1}次失败: {domain}, 错误: {e}")
|
||||
if retry < max_retries - 1:
|
||||
time.sleep(2) # 等待2秒后重试
|
||||
continue
|
||||
else:
|
||||
# 达到最大重试次数,返回失败但不拉黑
|
||||
logger.error(f"站长工具检测失败,已达到最大重试次数: {domain}, 错误: {e}")
|
||||
return True, 'failure', (False, str(e))
|
||||
else:
|
||||
# 其他错误直接返回
|
||||
logger.error(f"站长工具检测失败: {domain}, 错误: {e}")
|
||||
return False, str(e), ()
|
||||
|
||||
|
||||
# ========================= 【最终请求】 =========================
|
||||
def get_site_data(domain, enkey: str, proxies: dict = None):
|
||||
'''
|
||||
获取站点的详细SEO数据
|
||||
|
||||
通过站长工具API获取域名的SEO排名、权重等信息
|
||||
|
||||
:param domain: 待检测域名(如:www.baidu.com)
|
||||
:param enkey: 加密密钥(从check_title函数获取)
|
||||
:param proxies: 代理配置(如:{'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'})
|
||||
:return: Tuple[bool, Any] - (是否成功, 结果数据或错误信息)
|
||||
- (True, Result数据): 查询成功
|
||||
- (False, 错误信息): 查询失败
|
||||
'''
|
||||
try:
|
||||
# 获取当前脚本所在目录(用于构建JS文件的完整路径)
|
||||
current_dir = os.path.dirname(__file__)
|
||||
|
||||
# 构建JS加密代码文件的完整路径
|
||||
js_path = os.path.join(current_dir, "chinaz.js")
|
||||
|
||||
# 读取JS加密代码文件(包含生成签名的函数)
|
||||
with open(js_path, "r", encoding="utf-8") as f:
|
||||
js_code = f.read()
|
||||
|
||||
# 创建JavaScript执行上下文(QuickJS引擎)
|
||||
ctx = quickjs.Context()
|
||||
|
||||
# 【关键步骤】加载JS代码到上下文,使其可以被调用
|
||||
# 这一步必须先执行,否则后续的函数调用会失败
|
||||
ctx.eval(js_code)
|
||||
|
||||
# 调用JS函数生成host_key(主机密钥,是生成签名的基础)
|
||||
# 格式:generateHostKey("domain")
|
||||
host_key = ctx.eval(f'generateHostKey("{domain}")')
|
||||
|
||||
# 获取当前时间戳(毫秒),用于防止缓存和重放攻击
|
||||
ts = str(int(time.time() * 1000))
|
||||
|
||||
# 构建请求参数字典(包含API所需的全部参数)
|
||||
params = {
|
||||
# jQuery回调函数名(格式:jQuery + 随机数字 + 时间戳)
|
||||
"callback": f"jQuery11130943805094955342_{int(time.time() * 1000) - 1000}",
|
||||
"action": "GetCategory", # 动作类型:获取分类/排名数据
|
||||
"host": domain, # 主机域名
|
||||
"secretkey": enkey, # 加密密钥(从页面获取)
|
||||
|
||||
# 随机数字(调用JS函数生成,与host_key相关)
|
||||
"rd": ctx.eval(f'getRandomNum("{host_key}")'),
|
||||
"ts": ts, # 时间戳(毫秒)
|
||||
|
||||
# MD5令牌(调用JS函数生成,用于请求签名验证)
|
||||
"token": ctx.eval(f'generateMD5Token("{host_key}","{ts}")'),
|
||||
|
||||
"_": int(time.time() * 1000) # 额外时间戳参数(用于缓存破坏)
|
||||
}
|
||||
|
||||
# 构建HTTP请求头(模拟浏览器访问)
|
||||
headers = {
|
||||
'Accept': '*/*', # 接受所有类型的内容
|
||||
'Accept-Language': 'zh-cn', # 接受的语言:简体中文
|
||||
'Referer': 'https://othertool.chinaz.com/GetTopRanked.ashx', # 来源页面(防盗链)
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36', # 用户代理(浏览器标识)
|
||||
'Host': 'othertool.chinaz.com', # 目标主机地址
|
||||
}
|
||||
|
||||
# API请求URL(站长工具SEO数据查询接口)
|
||||
url = 'https://othertool.chinaz.com/GetTopRanked.ashx'
|
||||
|
||||
# 发送GET请求获取SEO数据
|
||||
resp = requests.get(
|
||||
url, # 请求URL
|
||||
params=params, # 查询参数(自动拼接为URL参数)
|
||||
headers=headers, # 请求头
|
||||
timeout=10, # 超时时间10秒
|
||||
proxies=proxies # 代理配置
|
||||
)
|
||||
|
||||
# 判断请求是否成功
|
||||
if resp.status_code == 200:
|
||||
# 使用正则表达式提取JSON数据(API返回格式:callback(json_data))
|
||||
# 正则解释:匹配括号内的JSON内容
|
||||
match = re.search(r'\((.*?)\)', resp.text)
|
||||
|
||||
# 提取JSON字符串
|
||||
json_str = match.group(1)
|
||||
|
||||
# 解析JSON数据为Python字典
|
||||
data = json.loads(json_str)
|
||||
|
||||
# 返回状态码和结果(StateCode为1表示成功)
|
||||
return data['StateCode'] == 1, data['Result']
|
||||
|
||||
except Exception as e: # 捕获所有异常
|
||||
logger.error(e) # 记录错误日志
|
||||
return False, str(e) # 返回失败状态和错误信息
|
||||
|
||||
|
||||
# # ========================= 【测试代码】 =========================
|
||||
# if __name__ == '__main__':
|
||||
#
|
||||
#
|
||||
# # 测试用例3:使用代理查询
|
||||
# logger.info("=== 测试使用代理 ===")
|
||||
# test_domain_3 = 'tt.com'
|
||||
#
|
||||
# logger.info(f"查询域名: {test_domain_3}")
|
||||
#
|
||||
#
|
||||
# title_3, seo_data_3 = check_title(test_domain_3)
|
||||
# logger.info(f"域名标题: {title_3}")
|
||||
# logger.info(f"SEO数据: {seo_data_3}")
|
||||
253
domainCheck/detect/geetest2.py
Normal file
253
domainCheck/detect/geetest2.py
Normal file
@@ -0,0 +1,253 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
import requests, re, json, base64
|
||||
import io, os, random
|
||||
import time, cv2, json
|
||||
from PIL import Image
|
||||
from functools import partial
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from urllib3.util import proxy
|
||||
|
||||
from detect.module.use_ua import randomUA
|
||||
from detect.module.gap import quekou
|
||||
import subprocess
|
||||
|
||||
subprocess.Popen = partial(subprocess.Popen, encoding='utf-8')
|
||||
import execjs
|
||||
|
||||
|
||||
class slide():
|
||||
def __init__(self):
|
||||
self.headers = randomUA()
|
||||
self.t = round(time.time() * 1000)
|
||||
|
||||
def __ease_out_expo(self, sep):
|
||||
if sep == 1:
|
||||
return 1
|
||||
else:
|
||||
return 1 - pow(2, -10 * sep)
|
||||
|
||||
def get_slide_track(self, vdict):
|
||||
distance = self.huak(vdict)
|
||||
slide_track = [
|
||||
[random.randint(-50, -10), random.randint(-50, -10), 0],
|
||||
[0, 0, 0],
|
||||
]
|
||||
count = 30 + int(distance / 2)
|
||||
t = random.randint(50, 100)
|
||||
_x = 0
|
||||
_y = 0
|
||||
for i in range(count):
|
||||
x = round(self.__ease_out_expo(i / count) * distance)
|
||||
t += random.randint(10, 20)
|
||||
if x == _x:
|
||||
continue
|
||||
slide_track.append([x, _y, t])
|
||||
_x = x
|
||||
slide_track.append(slide_track[-1])
|
||||
return slide_track
|
||||
|
||||
def tp_huanyuan(self, content):
|
||||
_img = Image.open(BytesIO(content))
|
||||
_Ge = [{"x": -157, "y": -58}, {"x": -145, "y": -58}, {"x": -265, "y": -58}, {"x": -277, "y": -58},
|
||||
{"x": -181, "y": -58}, {"x": -169, "y": -58}, {"x": -241, "y": -58}, {"x": -253, "y": -58},
|
||||
{"x": -109, "y": -58}, {"x": -97, "y": -58}, {"x": -289, "y": -58}, {"x": -301, "y": -58},
|
||||
{"x": -85, "y": -58}, {"x": -73, "y": -58}, {"x": -25, "y": -58}, {"x": -37, "y": -58},
|
||||
{"x": -13, "y": -58}, {"x": -1, "y": -58}, {"x": -121, "y": -58}, {"x": -133, "y": -58},
|
||||
{"x": -61, "y": -58}, {"x": -49, "y": -58}, {"x": -217, "y": -58}, {"x": -229, "y": -58},
|
||||
{"x": -205, "y": -58}, {"x": -193, "y": -58}, {"x": -145, "y": 0}, {"x": -157, "y": 0},
|
||||
{"x": -277, "y": 0}, {"x": -265, "y": 0}, {"x": -169, "y": 0}, {"x": -181, "y": 0},
|
||||
{"x": -253, "y": 0}, {"x": -241, "y": 0}, {"x": -97, "y": 0}, {"x": -109, "y": 0},
|
||||
{"x": -301, "y": 0}, {"x": -289, "y": 0}, {"x": -73, "y": 0}, {"x": -85, "y": 0},
|
||||
{"x": -37, "y": 0}, {"x": -25, "y": 0}, {"x": -1, "y": 0}, {"x": -13, "y": 0},
|
||||
{"x": -133, "y": 0}, {"x": -121, "y": 0}, {"x": -49, "y": 0}, {"x": -61, "y": 0},
|
||||
{"x": -229, "y": 0}, {"x": -217, "y": 0}, {"x": -193, "y": 0}, {"x": -205, "y": 0}]
|
||||
w_sep, h_sep = 10, 58
|
||||
new_img = Image.new('RGB', (260, 116))
|
||||
|
||||
for idx in range(len(_Ge)):
|
||||
x = abs(_Ge[idx]['x'])
|
||||
y = 58 if _Ge[idx]['y'] == -58 else 0
|
||||
img_cut = _img.crop((x, y, x + w_sep, y + h_sep))
|
||||
new_x = idx % 26 * 10
|
||||
new_y = 0 if idx < 26 else 58
|
||||
new_img.paste(img_cut, (new_x, new_y))
|
||||
|
||||
img_byte = BytesIO()
|
||||
new_img.save(img_byte, 'png')
|
||||
return img_byte.getvalue()
|
||||
|
||||
def huak(self, vdict):
|
||||
count = 1
|
||||
bgbase64 = ""
|
||||
tpbase64 = ""
|
||||
for idv, p_url in vdict.items():
|
||||
p_url = 'http://static.geetest.com/' + p_url
|
||||
# print(p_url)
|
||||
vcode = requests.get(p_url, headers=self.headers)
|
||||
text = vcode.content
|
||||
if idv == 'bg':
|
||||
text = self.tp_huanyuan(text)
|
||||
bgbase64 = base64.encodebytes(text).decode()
|
||||
else:
|
||||
tpbase64 = base64.encodebytes(text).decode()
|
||||
|
||||
count += 1
|
||||
if bgbase64 and tpbase64:
|
||||
dis = quekou().get_distance(bgbase64, tpbase64)
|
||||
else:
|
||||
dis = 0
|
||||
return dis
|
||||
|
||||
|
||||
class Geetest2():
|
||||
def __init__(self):
|
||||
self.header = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36",
|
||||
}
|
||||
# 获取当前文件的目录
|
||||
current_dir = os.path.dirname(__file__)
|
||||
|
||||
with open(os.path.join(current_dir, 'module', 'crack_geetest2x.js'), "r", encoding="gb2312",
|
||||
errors='ignore') as f:
|
||||
js_encrypt = f.read()
|
||||
self.js = execjs.compile(js_encrypt)
|
||||
|
||||
self.t = round(time.time() * 1000)
|
||||
self.gct_vdi = {}
|
||||
self.gct_volue = ['', ''] # 初始值
|
||||
self.token_vdi = {}
|
||||
self.timeout = (7, 9.05)
|
||||
self.geetest_type = 'slide'
|
||||
self.geetest_path = "/static/js/geetest.6.0.9.js"
|
||||
|
||||
def new_requests(self):
|
||||
self.session = requests.session()
|
||||
self.session.headers = self.header
|
||||
|
||||
def get_vsion(self, http, gt, proxies, pparmas):
|
||||
url = "https://api.geetest.com/gettype.php"
|
||||
params = {
|
||||
"gt": gt,
|
||||
"callback": f"geetest_{self.t}"
|
||||
}
|
||||
try:
|
||||
respe = http.get(url=url, params=params, proxies=proxies).text
|
||||
except Exception as e:
|
||||
if proxy:
|
||||
if pparmas:
|
||||
vdict = {'result': '错误', 'message': f'代理IP超时!,请重新更换代理IP,{e}', 'proxy': proxies,
|
||||
'pparmas': pparmas}
|
||||
else:
|
||||
vdict = {'result': '错误', 'message': f'代理IP超时!,请重新更换代理IP,{e}', 'proxy': proxies}
|
||||
else:
|
||||
vdict = {'result': '错误', 'message': f'请求错误,{e}'}
|
||||
return vdict
|
||||
|
||||
try:
|
||||
data = json.loads(respe[22:-1])["data"]
|
||||
except:
|
||||
vdict = {'result': '错误', 'type': 'auto', 'message': respe}
|
||||
return vdict
|
||||
self.geetest_type = data["type"]
|
||||
self.geetest_path = data['path']
|
||||
self.token_vdi[gt] = 1
|
||||
|
||||
def get_tp(self, gt, challenge, type_, proxies=None, pparmas=None):
|
||||
self.new_requests()
|
||||
self.t = round(time.time() * 1000)
|
||||
if pparmas:
|
||||
h_list = re.findall('([\w-]+):(.+)', pparmas)
|
||||
for idx in h_list:
|
||||
print('新增协议头' + str(idx))
|
||||
self.session.headers[idx[0]] = idx[1].replace('\r', '').replace('\n', '').replace('\r\n', '')
|
||||
print(self.session.headers)
|
||||
|
||||
if not self.token_vdi.get('gt', ''):
|
||||
self.get_vsion(self.session, gt, proxies, pparmas)
|
||||
url = f"https://api.geetest.com/get.php"
|
||||
params = {
|
||||
"gt": gt,
|
||||
"challenge": challenge,
|
||||
"product": "popup",
|
||||
"offline": "false",
|
||||
"protocol": "https://",
|
||||
"type": self.geetest_type,
|
||||
"path": self.geetest_path,
|
||||
"callback": f"geetest_{self.t}"
|
||||
}
|
||||
response = self.session.get(url, params=params, timeout=self.timeout).text
|
||||
geetest_type = self.geetest_type
|
||||
if geetest_type == 'slide':
|
||||
data = json.loads(response[22:-1])
|
||||
else:
|
||||
try:
|
||||
data = json.loads(response[22:-1])["data"]
|
||||
except:
|
||||
vdict = {'result': '错误', 'type': type_, 'message': response}
|
||||
return vdict
|
||||
##print(data)
|
||||
nc, ns = data['c'], data['s']
|
||||
|
||||
if geetest_type == 'slide':
|
||||
data = json.loads(response[22:-1])
|
||||
else:
|
||||
try:
|
||||
data = json.loads(response[22:-1])["data"]
|
||||
except:
|
||||
vdict = {'result': '错误', 'type': type_, 'message': json.loads(response)}
|
||||
return vdict
|
||||
##print(data)
|
||||
nc, ns = data['c'], data['s']
|
||||
gct_url = 'http://static.geetest.com' + data['gct_path']
|
||||
##print('gct地址:', gct_url)
|
||||
if geetest_type == 'click':
|
||||
return {'result': '请求类型不是滑块', 'type': type_, 'gt': gt, 'challenge': challenge,
|
||||
'当前类型': '点选'}
|
||||
if geetest_type == 'slide': # slide
|
||||
|
||||
challenge = data['challenge'] # 这里challenge改变了
|
||||
bg = data['bg'] # 背景图片
|
||||
slice = data['slice'] # 缺口图片
|
||||
vdict = {
|
||||
"bg": bg,
|
||||
"slice": slice,
|
||||
}
|
||||
slide_track = slide().get_slide_track(vdict)
|
||||
imgload = 37
|
||||
w = self.js.call('get_slide_w', nc, ns, gt, challenge, slide_track, imgload, self.gct_volue)
|
||||
params = (
|
||||
('gt', gt),
|
||||
('challenge', challenge),
|
||||
('w', w),
|
||||
('callback', f"geetest_{self.t}"),
|
||||
)
|
||||
response = self.session.get('https://api.geetest.com/ajax.php', params=params, proxies=proxies,
|
||||
timeout=self.timeout).text
|
||||
data = json.loads(response[22:-1])
|
||||
validate = data.get('validate')
|
||||
vdict = {'result': data.get('message'), 'type': geetest_type, 'gt': gt, 'challenge': challenge,
|
||||
'validate': validate}
|
||||
|
||||
return vdict
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
a = Geetest2()
|
||||
headers = {
|
||||
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
|
||||
}
|
||||
url = "https://seo.juziseo.com/class/gtcode/msg/StartMsgCaptchaServlet.php"
|
||||
url = "https://gsxt.hlj.gov.cn/registerValidate.jspx"
|
||||
|
||||
for i in range(1):
|
||||
index = requests.get(url, headers=headers)
|
||||
# print(index.text)
|
||||
html = index.json()
|
||||
challenge = html["challenge"]
|
||||
gt = html["gt"]
|
||||
print('gt', gt, 'challenge', challenge)
|
||||
r = a.get_tp(gt, challenge, 'auto', '')
|
||||
print(r)
|
||||
557
domainCheck/detect/jucha.py
Normal file
557
domainCheck/detect/jucha.py
Normal file
@@ -0,0 +1,557 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
@Project :domainScanDemo
|
||||
@File :jucha.py
|
||||
@IDE :PyCharm
|
||||
@Author :梦伴
|
||||
@Date :2026/4/2 22:49
|
||||
@explain : 聚查网API封装类 - 优化版,减少冗余代码,添加详细注释
|
||||
'''
|
||||
|
||||
# 导入标准库
|
||||
import base64 # Base64编码解码
|
||||
import hashlib # 哈希加密
|
||||
import json # JSON数据处理
|
||||
import os # 操作系统接口
|
||||
import pickle # 序列化反序列化
|
||||
import random # 随机数生成
|
||||
import subprocess # 子进程管理
|
||||
import time # 时间处理
|
||||
from functools import partial
|
||||
|
||||
# 导入第三方库
|
||||
import requests # HTTP请求库
|
||||
from loguru import logger # 日志记录
|
||||
from requests.cookies import RequestsCookieJar # Cookie管理
|
||||
|
||||
|
||||
|
||||
# # 移除自定义子进程类的替换,避免影响其他模块
|
||||
# class MySubprocessPopen(subprocess.Popen): # 自定义子进程类
|
||||
# def __init__(self, *args, **kwargs): # 初始化方法
|
||||
# kwargs['encoding'] = "UTF-8" # 设置默认编码为UTF-8
|
||||
# super().__init__(*args, **kwargs) # 调用父类初始化方法
|
||||
#
|
||||
#
|
||||
# subprocess.Popen = MySubprocessPopen # 替换subprocess.Popen为自定义类
|
||||
os.environ["EXECJS_RUNTIME"] = "Node" # 设置JavaScript运行时环境为Node.js
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
subprocess.Popen = partial(subprocess.Popen, encoding='utf-8', startupinfo=startupinfo)
|
||||
|
||||
|
||||
def resolve_node_executable() -> str:
|
||||
current_dir = os.path.dirname(__file__)
|
||||
project_dir = os.path.dirname(current_dir)
|
||||
bundled_node = os.path.join(project_dir, "tools", "node-v20.19.4-win-x64", "node.exe")
|
||||
if os.path.exists(bundled_node):
|
||||
return bundled_node
|
||||
return "node"
|
||||
|
||||
def calculate_seed(t: int) -> str: # 计算验证码种子值
|
||||
now = int(time.time()) # 获取当前时间戳
|
||||
c = now - t # 计算时间差
|
||||
seed = base64.b64encode(str(c).encode()).decode() # Base64编码
|
||||
return seed # 返回种子字符串
|
||||
|
||||
|
||||
def random_fingerprint() -> str: # 生成随机设备指纹
|
||||
data = str(random.random()) + str(time.time()) # 组合随机数和时间戳
|
||||
return hashlib.sha256(data.encode()).hexdigest() # 返回SHA256哈希值
|
||||
|
||||
|
||||
class JC(object): # 聚查网API封装类
|
||||
token: str # 验证码token
|
||||
session_id: str # 会话ID
|
||||
fingerprint: str # 设备指纹
|
||||
captchaId: str # 验证码ID
|
||||
encryptionPublicKey: str # 加密公钥
|
||||
cookie: RequestsCookieJar = {} # 聚查网Cookie
|
||||
juming_cookie: RequestsCookieJar = {} # 聚名网Cookie
|
||||
|
||||
# 通用请求头配置
|
||||
headers = {
|
||||
'accept': 'application/json, text/javascript, */*; q=0.01', # 接受的内容类型
|
||||
'accept-language': 'zh-CN,zh;q=0.9', # 接受的语言
|
||||
'content-type': 'application/x-www-form-urlencoded', # 内容类型
|
||||
'origin': 'https://www.jucha.com', # 请求源
|
||||
'priority': 'u=1, i', # 请求优先级
|
||||
'referer': 'https://www.jucha.com/login', # 来源页面
|
||||
'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"', # 浏览器标识
|
||||
'sec-ch-ua-mobile': '?0', # 是否移动端
|
||||
'sec-ch-ua-platform': '"Windows"', # 操作系统平台
|
||||
'sec-fetch-dest': 'empty', # 请求目标
|
||||
'sec-fetch-mode': 'cors', # 请求模式
|
||||
'sec-fetch-site': 'same-origin', # 请求站点
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36',
|
||||
# 用户代理
|
||||
'x-requested-with': 'XMLHttpRequest', # AJAX请求标识
|
||||
}
|
||||
|
||||
def __init__(self, proxies: dict = None): # 初始化JC类
|
||||
self.session = requests.Session() # 创建会话对象
|
||||
self.session.timeout = 10 # 设置超时时间
|
||||
self.session.proxies = proxies # 设置代理
|
||||
self.base_url = "https://www.jucha.com" # 设置基础URL
|
||||
self.cookie = requests.cookies.RequestsCookieJar() # 初始化CookieJar
|
||||
self.juming_cookie = requests.cookies.RequestsCookieJar() # 初始化聚名网CookieJar
|
||||
|
||||
def _get_headers(self, referer: str = None) -> dict: # 获取请求头(可自定义referer)
|
||||
headers = self.headers.copy() # 复制默认请求头
|
||||
if referer: # 如果指定了referer
|
||||
headers['referer'] = referer # 更新referer
|
||||
return headers # 返回请求头
|
||||
|
||||
def _get_route_headers(self, route: str) -> dict: # 根据路由获取对应的请求头
|
||||
referer_map = { # 路由到referer的映射
|
||||
'whois': f'{self.base_url}/whois/', # WHOIS查询的referer
|
||||
'beian': f'{self.base_url}/baian/', # 备案查询的referer
|
||||
'safe': f'{self.base_url}/safe/', # 安全检测的referer
|
||||
}
|
||||
return self._get_headers(referer_map.get(route)) # 返回对应的请求头
|
||||
|
||||
def _handle_captcha(self, retry_times: int = 5) -> tuple: # 处理滑块验证码
|
||||
for _ in range(retry_times): # 循环重试
|
||||
init_result = self.captcha_init() # 初始化验证码
|
||||
if init_result[0]: # 初始化成功
|
||||
verify_result = self.captcha_verify() # 验证验证码
|
||||
if verify_result[0]: # 验证成功
|
||||
return True, '验证码验证成功' # 返回成功
|
||||
return False, f'滑块验证码失败,{retry_times}次内未成功' # 返回失败
|
||||
|
||||
def _check_request(self, url: str, data: dict, headers: dict, cookies=None) -> dict: # 检查请求并处理验证码
|
||||
res = self.session.post(url=url, data=data, headers=headers, cookies=cookies) # 发送POST请求
|
||||
try:
|
||||
response = res.json() # 尝试解析JSON
|
||||
except Exception as e: # JSON解析失败
|
||||
logger.error(f"JSON解析失败: {str(e)}") # 记录错误
|
||||
logger.error(f"原始响应内容(前500字符): {res.text[:500]}") # 记录原始响应
|
||||
return {'code': -1, 'msg': f'JSON解析失败: {str(e)}'} # 返回错误
|
||||
return response # 返回响应
|
||||
|
||||
def _check_and_handle_captcha(self, response: dict, domain: str, route: str, xm_codes,
|
||||
data: dict = None) -> tuple: # 检查并处理验证码
|
||||
# 检查response是否为字典
|
||||
if not isinstance(response, dict):
|
||||
return False, None, response # 返回失败
|
||||
|
||||
if response.get('code') == 1001: # 需要验证码
|
||||
captcha_result = self._handle_captcha() # 处理验证码
|
||||
if captcha_result[0]: # 验证码验证成功
|
||||
new_data = data.copy() if data else {} # 复制数据
|
||||
new_data.update({ # 更新验证码参数
|
||||
'_csrf': '', # CSRF令牌
|
||||
'ymlb': domain, # 域名
|
||||
'xm_codes[]': xm_codes, # 检测代码
|
||||
'type': '2' if route in ['beian', 'safe'] else '1', # 类型:备案和安全检测为2,WHOIS为1
|
||||
'route': route, # 路由
|
||||
'captcha_verify_param': self.token, # 验证码token
|
||||
'sessionId': self.session_id, # 会话ID
|
||||
})
|
||||
return True, new_data, None # 返回需要重试
|
||||
return False, None, captcha_result # 返回失败
|
||||
return False, None, response # 不需要验证码
|
||||
|
||||
def captcha_init(self): # 初始化滑块验证码
|
||||
self.fingerprint = random_fingerprint() # 生成随机指纹
|
||||
data = { # 构建请求数据
|
||||
"request_id": self.fingerprint, # 请求ID
|
||||
"scene": "default", # 场景
|
||||
"seed": calculate_seed(286) # 种子值
|
||||
}
|
||||
url = f"{self.base_url}/captcha/init" # 初始化URL
|
||||
response = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie).json() # 发送请求
|
||||
if response['code'] == 1: # 初始化成功
|
||||
self.captchaId = response['data']['captchaId'] # 保存验证码ID
|
||||
self.encryptionPublicKey = response['data']['encryptionPublicKey'] # 保存加密公钥
|
||||
return response['code'] == 1, response['msg'] # 返回结果
|
||||
|
||||
def captcha_verify(self): # 验证滑块验证码
|
||||
data = { # 构建验证数据
|
||||
"offset": 290, # 滑块偏移量
|
||||
"duration": 611, # 滑动持续时间
|
||||
"trail": [ # 滑动轨迹
|
||||
{"x": 0, "y": 0, "time": 0}, {"x": 0, "y": 0, "time": 17}, {"x": 1, "y": 0, "time": 89},
|
||||
{"x": 5, "y": 0, "time": 106}, {"x": 14, "y": 0, "time": 123}, {"x": 28, "y": 0, "time": 139},
|
||||
{"x": 47, "y": 0, "time": 156}, {"x": 74, "y": 0, "time": 173}, {"x": 102, "y": 0, "time": 189},
|
||||
{"x": 131, "y": 0, "time": 206}, {"x": 159, "y": 0, "time": 223}, {"x": 184, "y": 0, "time": 239},
|
||||
{"x": 205, "y": 0, "time": 256}, {"x": 227, "y": 1, "time": 273}, {"x": 248, "y": 0, "time": 289},
|
||||
{"x": 263, "y": 0, "time": 306}, {"x": 276, "y": 0, "time": 323}, {"x": 286, "y": 0, "time": 339},
|
||||
{"x": 290, "y": 0, "time": 356}, {"x": 290, "y": 0, "time": 373}, {"x": 290, "y": 0, "time": 389},
|
||||
{"x": 290, "y": 0, "time": 406}, {"x": 290, "y": 0, "time": 423},
|
||||
{"x": 290, "y": 0, "time": 478}
|
||||
],
|
||||
"fingerprint": self.fingerprint, # 设备指纹
|
||||
"captchaId": self.captchaId, # 验证码ID
|
||||
"serverPublicKey": self.encryptionPublicKey # 服务器公钥
|
||||
}
|
||||
data_str = json.dumps(data, separators=(",", ":")) # 转换为JSON字符串
|
||||
current_dir = os.path.dirname(__file__)
|
||||
|
||||
jsFile_path = current_dir + "/sdk_leg_env.js" # JavaScript脚本路径
|
||||
|
||||
if not os.path.exists(jsFile_path): # 检查脚本文件是否存在
|
||||
logger.error(f"Node.js脚本文件不存在: {jsFile_path}") # 记录错误
|
||||
return False, f"缺少签名脚本: {jsFile_path}" # 返回失败
|
||||
|
||||
try: # 尝试执行Node.js脚本
|
||||
with subprocess.Popen( # 创建子进程
|
||||
[resolve_node_executable(), jsFile_path], # 执行node命令
|
||||
stdin=subprocess.PIPE, # 标准输入管道
|
||||
stdout=subprocess.PIPE, # 标准输出管道
|
||||
stderr=subprocess.PIPE, # 标准错误管道
|
||||
text=True, # 文本模式
|
||||
encoding="utf-8", # UTF-8编码
|
||||
errors="ignore" # 忽略编码错误
|
||||
) as proc: # 进程上下文
|
||||
stdout, stderr = proc.communicate(data_str, timeout=10) # 传入参数并获取输出
|
||||
verify_data = stdout.strip() # 去除空白字符
|
||||
if stderr: # 如果有错误输出
|
||||
logger.error(f'genData_stderr->[{stderr}]') # 记录错误
|
||||
|
||||
if proc.returncode != 0: # 检查退出码
|
||||
return False, f"Node.js脚本执行失败,退出码: {proc.returncode}, 错误信息: {stderr}" # 返回失败
|
||||
|
||||
url = f"{self.base_url}/captcha/verify" # 验证URL
|
||||
verify_headers = self.headers.copy() # 复制请求头
|
||||
verify_headers['Content-Type'] = 'application/json' # 设置内容类型(仅用于验证请求)
|
||||
verify_response = self.session.post(url, headers=verify_headers, data=verify_data,
|
||||
cookies=self.cookie) # 发送验证请求
|
||||
if verify_response.cookies: # 如果响应中有新的Cookie
|
||||
self.cookie.update(verify_response.cookies) # 合并Cookie(而不是替换)
|
||||
response = verify_response.json() # 解析JSON响应
|
||||
if response['code'] == 1: # 验证成功
|
||||
self.token = response['data']['token'] # 保存token
|
||||
self.session_id = response['data']['session_id'] # 保存会话ID
|
||||
return response['code'] == 1, response['msg'] # 返回结果
|
||||
except subprocess.TimeoutExpired: # 超时异常
|
||||
logger.error("Node.js脚本执行超时(超过10秒)") # 记录错误
|
||||
return False, "Node.js脚本执行超时" # 返回失败
|
||||
except Exception as e: # 其他异常
|
||||
logger.error(f"执行Node.js脚本异常: {str(e)}") # 记录错误
|
||||
return False, f"执行Node.js脚本异常: {str(e)}" # 返回失败
|
||||
|
||||
if not verify_data: # 检查输出是否为空
|
||||
return False, "Node.js脚本未输出任何内容" # 返回失败
|
||||
|
||||
def save_cookies(self, filepath="jucha_cookies.pkl"): # 保存Cookie到文件
|
||||
with open(filepath, "wb") as f: # 以二进制写入模式打开文件
|
||||
pickle.dump(self.cookie, f) # 序列化保存Cookie
|
||||
logger.info(f"已保存Cookie到文件: {filepath}")
|
||||
# 保存到Redis
|
||||
try:
|
||||
import redis
|
||||
from app.config import config
|
||||
redis_client = redis.Redis(
|
||||
host=config.REDIS_HOST,
|
||||
port=config.REDIS_PORT,
|
||||
password=config.REDIS_PASSWORD,
|
||||
db=config.REDIS_DB,
|
||||
decode_responses=True
|
||||
)
|
||||
# 将cookie转换为字典
|
||||
cookie_dict = {}
|
||||
# 检查self.cookie的类型
|
||||
if isinstance(self.cookie, dict):
|
||||
# 如果是字典,直接使用
|
||||
cookie_dict = self.cookie
|
||||
logger.info(f"Cookie是字典类型,直接使用")
|
||||
elif hasattr(self.cookie, '__iter__'):
|
||||
# 如果是可迭代对象,遍历处理
|
||||
for cookie in self.cookie:
|
||||
# 检查cookie对象是否有name和value属性
|
||||
if hasattr(cookie, 'name') and hasattr(cookie, 'value'):
|
||||
cookie_dict[cookie.name] = cookie.value
|
||||
logger.info(f"Cookie是可迭代对象,处理后得到: {cookie_dict}")
|
||||
else:
|
||||
logger.warning(f"Cookie类型不支持: {type(self.cookie)}")
|
||||
|
||||
redis_client.set('domain_tool:jucha_cookies', str(cookie_dict))
|
||||
except Exception as e:
|
||||
logger.error(f"保存Cookie到Redis异常: {str(e)}")
|
||||
pass
|
||||
|
||||
def load_cookies(self, filepath="jucha_cookies.pkl"): # 从文件加载Cookie
|
||||
try: # 尝试加载
|
||||
with open(filepath, "rb") as f: # 以二进制读取模式打开文件
|
||||
loaded_cookie = pickle.load(f) # 反序列化加载Cookie
|
||||
# 检查加载的cookie类型
|
||||
if isinstance(loaded_cookie, dict):
|
||||
# 如果是字典,转换为RequestsCookieJar
|
||||
cookie_jar = requests.cookies.RequestsCookieJar()
|
||||
for name, value in loaded_cookie.items():
|
||||
cookie_jar.set(name, value)
|
||||
self.cookie = cookie_jar
|
||||
else:
|
||||
self.cookie = loaded_cookie
|
||||
except Exception as e: # 加载失败
|
||||
logger.error(f"加载Cookie失败: {e}")
|
||||
self.cookie = requests.cookies.RequestsCookieJar() # 创建空的CookieJar
|
||||
|
||||
def load_juming_cookies(self, filepath="juming_cookies.pkl"): # 从文件加载聚名网Cookie
|
||||
try: # 尝试加载
|
||||
with open(filepath, "rb") as f: # 以二进制读取模式打开文件
|
||||
self.juming_cookie = pickle.load(f) # 反序列化加载Cookie
|
||||
except: # 加载失败
|
||||
self.juming_cookie = requests.cookies.RequestsCookieJar() # 创建空的CookieJar
|
||||
|
||||
def auth_login(self): # 使用聚名网账号登录聚查网
|
||||
params = { # 请求参数
|
||||
'platform': 'juming', # 平台标识
|
||||
}
|
||||
res = self.session.get( # 发送GET请求
|
||||
url=f'{self.base_url}/home/login/get_auth_url', # 获取授权URL
|
||||
params=params, # 请求参数
|
||||
headers=self.headers, # 请求头
|
||||
cookies=self.juming_cookie # 聚名网Cookie
|
||||
)
|
||||
if res.cookies: # 如果响应中有Cookie
|
||||
self.cookie.update(res.cookies) # 更新Cookie
|
||||
response = res.json() # 解析JSON响应
|
||||
if response['code'] == 1: # 获取授权URL成功
|
||||
url = f'%s&tiao=1' % response['data']['auth_url'] # 构建跳转URL
|
||||
# 合并聚名网Cookie和聚查网Cookie
|
||||
combined_cookies = requests.cookies.RequestsCookieJar()
|
||||
combined_cookies.update(self.juming_cookie)
|
||||
combined_cookies.update(self.cookie)
|
||||
response = self.session.get( # 发送GET请求
|
||||
url=url, # 跳转URL
|
||||
headers=self.headers, # 请求头
|
||||
cookies=combined_cookies, # 合并后的Cookie
|
||||
allow_redirects=False # 不自动重定向
|
||||
)
|
||||
if response.cookies: # 如果响应中有Cookie
|
||||
# 安全更新Cookie,避免冲突
|
||||
for name, value in response.cookies.items():
|
||||
self.cookie[name] = value # 直接赋值,覆盖同名Cookie
|
||||
if response.status_code == 302: # 重定向状态码
|
||||
url = response.headers['location'] # 获取重定向URL
|
||||
response = self.session.get( # 发送GET请求
|
||||
url=url, # 重定向URL
|
||||
headers=self.headers, # 请求头
|
||||
allow_redirects=False # 不自动重定向
|
||||
)
|
||||
if response.cookies: # 如果响应中有Cookie
|
||||
# 安全更新Cookie,避免冲突
|
||||
for name, value in response.cookies.items():
|
||||
self.cookie[name] = value # 直接赋值,覆盖同名Cookie
|
||||
if response.status_code == 302: # 再次重定向
|
||||
return True, '登录成功' # 返回成功
|
||||
return False, '聚名登录过期' # 返回失败
|
||||
return False, response['msg'] # 返回失败
|
||||
|
||||
def _build_check_data(self, domain: str, route: str, xm_codes, data: dict = None) -> dict: # 构建检测请求数据
|
||||
if data is not None: # 如果data不为空
|
||||
return data # 直接返回data
|
||||
return { # 构建新的请求数据
|
||||
'_csrf': '', # CSRF令牌
|
||||
'ymlb': domain, # 域名
|
||||
'xm_codes[]': xm_codes, # 检测代码
|
||||
'type': '2' if route in ['beian', 'safe'] else '1', # 类型:备案和安全检测为2,WHOIS为1
|
||||
'route': route, # 路由
|
||||
}
|
||||
|
||||
def _make_check_request(self, domain: str, route: str, xm_codes, data: dict = None) -> tuple: # 发起检测请求
|
||||
headers = self._get_route_headers(route) # 获取对应的请求头
|
||||
data = self._build_check_data(domain, route, xm_codes, data) # 构建请求数据
|
||||
url = f'{self.base_url}/home_item/check' # 检测URL
|
||||
response = self._check_request(url, data, headers, self.cookie) # 发送请求
|
||||
return response, data # 返回响应和数据
|
||||
|
||||
def _handle_check_response(self, response: dict, domain: str, route: str, xm_codes, data: dict,
|
||||
callback) -> tuple: # 处理检测响应
|
||||
need_retry, retry_data, final_response = self._check_and_handle_captcha( # 检查并处理验证码
|
||||
response, domain, route, xm_codes, data
|
||||
)
|
||||
if need_retry: # 需要重试
|
||||
return callback(domain=domain, data=retry_data) # 递归调用回调
|
||||
|
||||
# 检查final_response是否为字典
|
||||
if not isinstance(final_response, dict):
|
||||
return False, f'响应格式错误: {final_response}', '' # 返回失败
|
||||
|
||||
if final_response['code'] == 1: # 检测成功
|
||||
return self._get_search_result(final_response, domain, route) # 获取查询结果(传入domain)
|
||||
return False, final_response.get('msg', '未知错误'), '' # 返回失败
|
||||
|
||||
def _get_search_result(self, response: dict, domain: str, route: str) -> tuple: # 获取查询结果
|
||||
data = { # 构建查询数据
|
||||
'_csrf': '', # CSRF令牌
|
||||
'domain': domain, # 域名(注意:使用domain而不是ymlb)
|
||||
'rwid': response['data']['rwid'], # 任务ID
|
||||
'type': '2' if route in ['beian', 'safe'] else '1', # 类型
|
||||
'route': 'baian' if route == 'beian' else route, # 路由
|
||||
}
|
||||
headers = self._get_route_headers(route) # 获取对应的请求头
|
||||
url = f'{self.base_url}/home/item/search' if route != 'whois' else f'{self.base_url}/home/item/search_one' # 查询URL
|
||||
search_response = self._check_request(url, data, headers, self.cookie) # 发送查询请求
|
||||
|
||||
# 检查search_response是否为字典
|
||||
if not isinstance(search_response, dict):
|
||||
return False, f'响应格式错误: {search_response}', '' # 返回失败
|
||||
|
||||
if search_response.get('code', -1) != 1: # 查询失败
|
||||
return False, search_response.get('msg', '查询失败'), '' # 返回失败
|
||||
|
||||
if route == 'whois': # WHOIS查询
|
||||
# 安全获取WHOIS状态
|
||||
whois_zt = ''
|
||||
try:
|
||||
data1 = search_response.get('data', {})
|
||||
if isinstance(data1, dict):
|
||||
data2 = data1.get('data', {})
|
||||
if isinstance(data2, dict):
|
||||
whois = data2.get('whois', {})
|
||||
if isinstance(whois, dict):
|
||||
data3 = whois.get('data', {})
|
||||
if isinstance(data3, dict):
|
||||
data4 = data3.get('data', {})
|
||||
if isinstance(data4, dict):
|
||||
whois_zt = data4.get('zt', '')
|
||||
except Exception as e:
|
||||
logger.error(f"获取WHOIS状态失败: {e}")
|
||||
|
||||
return ( # 返回WHOIS结果
|
||||
True, # 成功
|
||||
search_response['msg'], # 消息
|
||||
whois_zt # WHOIS状态
|
||||
)
|
||||
elif route == 'beian': # 备案查询
|
||||
# 安全获取备案数据
|
||||
beian_data = {}
|
||||
beian_msg = ''
|
||||
try:
|
||||
data1 = search_response.get('data', {})
|
||||
if isinstance(data1, dict):
|
||||
data2 = data1.get('data', {})
|
||||
if isinstance(data2, dict):
|
||||
beian = data2.get('beian', {})
|
||||
if isinstance(beian, dict):
|
||||
data3 = beian.get('data', {})
|
||||
if isinstance(data3, dict):
|
||||
beian_data = data3.get('data', {})
|
||||
beian_msg = data3.get('msg', '')
|
||||
except Exception as e:
|
||||
logger.error(f"获取备案数据失败: {e}")
|
||||
|
||||
is_dict = isinstance(beian_data, dict) # 是否为字典
|
||||
return ( # 返回备案结果
|
||||
True, # 成功
|
||||
search_response['msg'], # 消息
|
||||
( # 备案信息元组
|
||||
beian_data.get('sj', '') if is_dict else '', # 备案时间
|
||||
beian_data.get('lx', '') if is_dict else '', # 备案类型
|
||||
beian_data.get('sy', '') if is_dict else '', # 备案首页地址
|
||||
beian_msg, # 备案状态
|
||||
)
|
||||
)
|
||||
elif route == 'safe': # 安全检测
|
||||
# 安全获取安全检测数据
|
||||
safe_data = {}
|
||||
try:
|
||||
data1 = search_response.get('data', {})
|
||||
if isinstance(data1, dict):
|
||||
safe_data = data1.get('data', {})
|
||||
except Exception as e:
|
||||
logger.error(f"获取安全检测数据失败: {e}")
|
||||
|
||||
check_items = [ # 检测项配置列表
|
||||
('qqjc', 'QQ检测'),
|
||||
('weixin', '微信检测'),
|
||||
('qiang', '被墙检测'),
|
||||
('dyjc', '抖音检测'),
|
||||
('bdjc', '百度检测'),
|
||||
('llqjcgg', '谷歌检测'),
|
||||
('llqjchh', '火狐检测'),
|
||||
]
|
||||
|
||||
results = [] # 结果列表
|
||||
for item_key, _ in check_items: # 遍历检测项
|
||||
try:
|
||||
code = 1
|
||||
msg = ''
|
||||
item_data = safe_data.get(item_key, {})
|
||||
if isinstance(item_data, dict):
|
||||
data1 = item_data.get('data', {})
|
||||
if isinstance(data1, dict):
|
||||
code = int(data1.get('data', 1)) # 获取检测码
|
||||
msg = data1.get('msg', '') # 获取消息
|
||||
if code == 3: # 如果检测码为3
|
||||
msg = '拦截' # 设置为拦截
|
||||
results.append((code, msg)) # 添加到结果列表
|
||||
except Exception as e:
|
||||
logger.error(f"获取安全检测项 {item_key} 失败: {e}")
|
||||
results.append((1, '查询失败')) # 添加失败结果
|
||||
|
||||
return ( # 返回安全检测结果
|
||||
True, # 成功
|
||||
search_response['msg'], # 消息
|
||||
tuple(results) # 安全检测结果元组
|
||||
)
|
||||
return False, '未知路由', '' # 返回失败
|
||||
|
||||
def check_whois_domain(self, domain: str, data=None): # 查询域名WHOIS信息
|
||||
try: # 异常处理
|
||||
response, check_data = self._make_check_request(domain, 'whois', 'whois', data) # 发起检测请求
|
||||
return self._handle_check_response(response, domain, 'whois', 'whois', check_data,
|
||||
self.check_whois_domain) # 处理响应
|
||||
except Exception as e: # 异常处理
|
||||
logger.error(f"check_domain异常: {e}") # 记录错误
|
||||
return False, str(e), '' # 返回失败
|
||||
|
||||
def beian_check_domain(self, domain: str, data=None): # 查询域名备案信息
|
||||
try: # 异常处理
|
||||
response, check_data = self._make_check_request(domain, 'beian', 'beian', data) # 发起检测请求
|
||||
return self._handle_check_response(response, domain, 'beian', 'beian', check_data,
|
||||
self.beian_check_domain) # 处理响应
|
||||
except Exception as e: # 异常处理
|
||||
logger.error(f"beian_check_domain异常: {e}") # 记录错误
|
||||
return False, str(e), [] # 返回失败
|
||||
|
||||
def safe_check_domain(self, domain: str, data=None): # 查询域名安全信息
|
||||
try: # 异常处理
|
||||
xm_codes = [ # 检测代码列表
|
||||
'qqjc', # QQ检测
|
||||
'weixin', # 微信检测
|
||||
'dyjc', # 抖音检测
|
||||
'qiang', # 被墙检测
|
||||
'bdjc', # 百度检测
|
||||
'llqjcgg', # 谷歌检测
|
||||
'llqjchh', # 火狐检测
|
||||
]
|
||||
response, check_data = self._make_check_request(domain, 'safe', xm_codes, data) # 发起检测请求
|
||||
return self._handle_check_response(response, domain, 'safe', xm_codes, check_data,
|
||||
self.safe_check_domain) # 处理响应
|
||||
except Exception as e: # 异常处理
|
||||
logger.error(f"safe_check_domain异常: {e}") # 记录错误
|
||||
return False, str(e), [] # 返回失败
|
||||
|
||||
|
||||
# # 测试代码
|
||||
# if __name__ == '__main__':
|
||||
# j = JC() # 创建JC实例
|
||||
# j.load_cookies()
|
||||
# j.load_juming_cookies() # 加载聚名网Cookie
|
||||
# logger.info("开始登录...")
|
||||
# login_result = j.auth_login() # 使用聚名网账号登录聚查网
|
||||
# logger.info(f"登录结果: {login_result}")
|
||||
# if not login_result[0]:
|
||||
# logger.error("登录失败,无法继续测试")
|
||||
# exit(1)
|
||||
# # 测试WHOIS查询
|
||||
# logger.info("测试WHOIS查询:")
|
||||
# result = j.check_whois_domain('921229.com')
|
||||
# logger.info(f"WHOIS查询结果: {result}")
|
||||
# logger.info(j.cookie)
|
||||
|
||||
# # # 测试备案查询
|
||||
# # logger.info("测试备案查询:")
|
||||
# # result = j.beian_check_domain('baidu.com')
|
||||
# # logger.info(f"备案查询结果: {result}")
|
||||
# #
|
||||
# # 测试安全检测
|
||||
# logger.info("测试安全检测:")
|
||||
# result = j.safe_check_domain('576777.com')
|
||||
# logger.info(f"安全检测结果: {result}")
|
||||
#
|
||||
# # j.save_cookies() # 保存Cookie
|
||||
497
domainCheck/detect/juming.py
Normal file
497
domainCheck/detect/juming.py
Normal file
@@ -0,0 +1,497 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
@Project :domainScanDemo
|
||||
@File :juming.py
|
||||
@IDE :PyCharm
|
||||
@Author :梦伴
|
||||
@Date :2026/3/19 15:51
|
||||
@explain : 聚名网API封装类 - 提供登录、域名查询、删除域名列表等功能
|
||||
'''
|
||||
|
||||
import base64 # Base64编码模块
|
||||
import hashlib # 哈希算法模块
|
||||
import json # JSON处理模块
|
||||
import os # 操作系统接口模块
|
||||
import pickle # 序列化模块
|
||||
import random # 随机数生成模块
|
||||
import subprocess # 子进程管理模块
|
||||
import time # 时间处理模块
|
||||
from functools import partial
|
||||
from typing import Optional, Tuple, Dict, List, Any # 类型提示
|
||||
|
||||
import requests # HTTP请求库
|
||||
from loguru import logger # 日志记录库
|
||||
from requests.cookies import RequestsCookieJar # Cookie处理
|
||||
|
||||
|
||||
# 常量定义
|
||||
BASE_URL = "https://www.juming.com" # 聚名网基础URL
|
||||
CAPTCHA_MAX_RETRY = 5 # 验证码最大重试次数
|
||||
REQUEST_TIMEOUT = 10 # 请求超时时间(秒)
|
||||
DOWNLOAD_TIMEOUT = 60 # 下载超时时间(秒)
|
||||
SEED_TIME_OFFSET = 286 # 种子时间偏移量
|
||||
SLIDE_OFFSET = 290 # 滑块偏移量
|
||||
SLIDE_DURATION = 611 # 滑动持续时间
|
||||
|
||||
|
||||
class MySubprocessPopen(subprocess.Popen): # 自定义子进程类
|
||||
def __init__(self, *args, **kwargs): # 初始化方法
|
||||
kwargs['encoding'] = "UTF-8" # 设置编码为UTF-8
|
||||
super().__init__(*args, **kwargs) # 调用父类初始化
|
||||
|
||||
|
||||
subprocess.Popen = MySubprocessPopen # 替换默认的Popen类
|
||||
os.environ["EXECJS_RUNTIME"] = "Node" # 设置JavaScript运行时为Node.js
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
subprocess.Popen = partial(subprocess.Popen, encoding='utf-8', startupinfo=startupinfo)
|
||||
|
||||
|
||||
def resolve_node_executable() -> str:
|
||||
current_dir = os.path.dirname(__file__)
|
||||
project_dir = os.path.dirname(current_dir)
|
||||
bundled_node = os.path.join(project_dir, "tools", "node-v20.19.4-win-x64", "node.exe")
|
||||
if os.path.exists(bundled_node):
|
||||
return bundled_node
|
||||
return "node"
|
||||
|
||||
def calculate_seed(t: int) -> str: # 计算验证码种子值
|
||||
now = int(time.time()) # 获取当前时间戳
|
||||
c = now - t # 计算时间差
|
||||
seed = base64.b64encode(str(c).encode()).decode() # Base64编码
|
||||
return seed # 返回种子字符串
|
||||
|
||||
|
||||
def random_fingerprint() -> str: # 生成随机设备指纹
|
||||
data = str(random.random()) + str(time.time()) # 组合随机数和时间戳
|
||||
return hashlib.sha256(data.encode()).hexdigest() # 返回SHA256哈希值
|
||||
|
||||
|
||||
def glwb(s: Optional[str]) -> str: # JS文本过滤函数
|
||||
"""
|
||||
过滤特殊字符,防止XSS攻击
|
||||
Args:
|
||||
s: 待过滤的字符串
|
||||
Returns:
|
||||
过滤后的字符串
|
||||
"""
|
||||
if s is None or not isinstance(s, str): # 检查输入是否有效
|
||||
return '' # 返回空字符串
|
||||
|
||||
a_nr = s # 复制字符串
|
||||
a_nr = a_nr.replace('"', '"') # 替换双引号
|
||||
a_nr = a_nr.replace("'", ''') # 替换单引号
|
||||
a_nr = a_nr.replace('<', '<') # 替换小于号
|
||||
a_nr = a_nr.replace('>', '>') # 替换大于号
|
||||
a_nr = a_nr.replace('\\', '\') # 替换反斜杠
|
||||
|
||||
return a_nr # 返回过滤后的字符串
|
||||
|
||||
|
||||
def md5_19(text: str) -> str: # 计算MD5并返回前19位
|
||||
"""
|
||||
计算MD5哈希值并截取前19位
|
||||
Args:
|
||||
text: 待哈希的文本
|
||||
Returns:
|
||||
MD5哈希值的前19位
|
||||
"""
|
||||
if not text: # 检查输入是否为空
|
||||
return '' # 返回空字符串
|
||||
md5 = hashlib.md5(text.encode('utf-8')).hexdigest() # 生成32位MD5
|
||||
return md5[:19] # 返回前19位
|
||||
|
||||
|
||||
def encrypt_password(loginToken: str, password: str) -> str: # 加密密码
|
||||
"""
|
||||
使用双重MD5加密密码
|
||||
Args:
|
||||
loginToken: 登录令牌
|
||||
password: 明文密码
|
||||
Returns:
|
||||
加密后的密码
|
||||
"""
|
||||
filtered_pwd = glwb(password) # 过滤密码中的特殊字符
|
||||
step1 = f'[jiami{filtered_pwd}mima]' # 拼接固定盐值
|
||||
md5_step1 = md5_19(step1) # 第一次MD5加密
|
||||
step2 = loginToken + md5_step1 # 拼接登录令牌
|
||||
final_result = md5_19(step2) # 第二次MD5加密
|
||||
return final_result # 返回最终加密结果
|
||||
|
||||
|
||||
class JM(object): # 聚名网API封装类
|
||||
token: str # 验证码token
|
||||
loginToken: str = '' # 登录令牌
|
||||
session_id: str # 会话ID
|
||||
fingerprint: str # 设备指纹
|
||||
captchaId: str # 验证码ID
|
||||
encryptionPublicKey: str # 加密公钥
|
||||
cookie: RequestsCookieJar = {} # Cookie存储
|
||||
|
||||
# 默认请求头
|
||||
headers = {
|
||||
'Host': 'www.juming.com', # 主机名
|
||||
'sec-ch-ua-platform': '"Windows"', # 平台标识
|
||||
'x-requested-with': 'XMLHttpRequest', # AJAX请求标识
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0', # 用户代理
|
||||
'accept': '*/*', # 接受所有类型
|
||||
'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"', # 浏览器标识
|
||||
'sec-ch-ua-mobile': '?0', # 是否移动端
|
||||
'origin': 'https://www.juming.com', # 请求源
|
||||
'sec-fetch-site': 'same-origin', # 请求站点
|
||||
'sec-fetch-mode': 'cors', # 请求模式
|
||||
'sec-fetch-dest': 'empty', # 请求目标
|
||||
'referer': 'https://www.juming.com/', # 来源页面
|
||||
'accept-language': 'zh-CN,zh;q=0.9', # 接受的语言
|
||||
'priority': 'u=1, i', # 请求优先级
|
||||
'Content-Type': 'application/x-www-form-urlencoded', # 内容类型
|
||||
}
|
||||
|
||||
def __init__(self, proxies: Optional[Dict] = None): # 初始化方法
|
||||
self.session = requests.Session() # 创建会话对象
|
||||
self.session.timeout = REQUEST_TIMEOUT # 设置超时时间
|
||||
self.session.proxies = proxies # 设置代理
|
||||
self.base_url = BASE_URL # 设置基础URL
|
||||
logger.debug(proxies)
|
||||
|
||||
def captcha_init(self) -> Tuple[bool, str]: # 初始化滑块验证码
|
||||
"""
|
||||
初始化滑块验证码
|
||||
Returns:
|
||||
Tuple[bool, str]: (是否成功, 消息)
|
||||
"""
|
||||
self.fingerprint = random_fingerprint() # 生成随机指纹
|
||||
data = { # 构建请求数据
|
||||
"request_id": self.fingerprint, # 请求ID
|
||||
"scene": "default", # 场景
|
||||
"seed": calculate_seed(SEED_TIME_OFFSET) # 种子值
|
||||
}
|
||||
logger.info(data)
|
||||
self.headers['Content-Type'] = 'application/x-www-form-urlencoded' # 设置内容类型
|
||||
|
||||
url = f"{self.base_url}/captcha/init" # 初始化URL
|
||||
# self.cookie.clear()
|
||||
# self.cookie.update({'acw_sc__v2': '69d948ed49117d89433d4316a79ada6f00d68f26'})
|
||||
# logger.info(self.cookie)
|
||||
response = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie).json() # 发送请求
|
||||
logger.info(response)
|
||||
|
||||
if response['code'] == 1: # 初始化成功
|
||||
self.captchaId = response['data']['captchaId'] # 保存验证码ID
|
||||
self.encryptionPublicKey = response['data']['encryptionPublicKey'] # 保存加密公钥
|
||||
return response['code'] == 1, response['msg'] # 返回结果
|
||||
|
||||
def captcha_verify(self) -> Tuple[bool, str]: # 验证滑块验证码
|
||||
"""
|
||||
验证滑块验证码
|
||||
Returns:
|
||||
Tuple[bool, str]: (是否成功, 消息)
|
||||
"""
|
||||
data = { # 构建验证数据
|
||||
"offset": SLIDE_OFFSET, # 滑块偏移量
|
||||
"duration": SLIDE_DURATION, # 滑动持续时间
|
||||
"trail": [ # 滑动轨迹
|
||||
{"x": 0, "y": 0, "time": 0}, # 起点
|
||||
{"x": 0, "y": 0, "time": 17}, # 第1个点
|
||||
{"x": 1, "y": 0, "time": 89}, # 第2个点
|
||||
{"x": 5, "y": 0, "time": 106}, # 第3个点
|
||||
{"x": 14, "y": 0, "time": 123}, # 第4个点
|
||||
{"x": 28, "y": 0, "time": 139}, # 第5个点
|
||||
{"x": 47, "y": 0, "time": 156}, # 第6个点
|
||||
{"x": 74, "y": 0, "time": 173}, # 第7个点
|
||||
{"x": 102, "y": 0, "time": 189}, # 第8个点
|
||||
{"x": 131, "y": 0, "time": 206}, # 第9个点
|
||||
{"x": 159, "y": 0, "time": 223}, # 第10个点
|
||||
{"x": 184, "y": 0, "time": 239}, # 第11个点
|
||||
{"x": 205, "y": 0, "time": 256}, # 第12个点
|
||||
{"x": 227, "y": 1, "time": 273}, # 第13个点
|
||||
{"x": 248, "y": 0, "time": 289}, # 第14个点
|
||||
{"x": 263, "y": 0, "time": 306}, # 第15个点
|
||||
{"x": 276, "y": 0, "time": 323}, # 第16个点
|
||||
{"x": 286, "y": 0, "time": 339}, # 第17个点
|
||||
{"x": 290, "y": 0, "time": 356}, # 第18个点
|
||||
{"x": 290, "y": 0, "time": 373}, # 第19个点
|
||||
{"x": 290, "y": 0, "time": 389}, # 第20个点
|
||||
{"x": 290, "y": 0, "time": 406}, # 第21个点
|
||||
{"x": 290, "y": 0, "time": 423}, # 第22个点
|
||||
{"x": 290, "y": 0, "time": 478}, # 终点
|
||||
],
|
||||
"fingerprint": self.fingerprint, # 设备指纹
|
||||
"captchaId": self.captchaId, # 验证码ID
|
||||
"serverPublicKey": self.encryptionPublicKey # 服务器公钥
|
||||
}
|
||||
data_str = json.dumps(data, separators=(",", ":")) # 转换为JSON字符串(压缩格式)
|
||||
current_dir = os.path.dirname(__file__)
|
||||
|
||||
jsFile_path = current_dir+"/sdk_leg_env.js" # JavaScript脚本路径
|
||||
|
||||
if not os.path.exists(jsFile_path): # 检查脚本文件是否存在
|
||||
logger.error(f"Node.js脚本文件不存在: {jsFile_path}") # 记录错误
|
||||
return False, f"缺少签名脚本: {jsFile_path}" # 返回失败
|
||||
|
||||
try: # 异常处理
|
||||
with subprocess.Popen( # 执行Node.js脚本
|
||||
[resolve_node_executable(), jsFile_path], # 命令和参数
|
||||
stdin=subprocess.PIPE, # 标准输入
|
||||
stdout=subprocess.PIPE, # 标准输出
|
||||
stderr=subprocess.PIPE, # 标准错误
|
||||
text=True, # 文本模式
|
||||
encoding="utf-8", # UTF-8编码
|
||||
errors="ignore" # 忽略编码错误
|
||||
) as proc: # 进程上下文
|
||||
stdout, stderr = proc.communicate(data_str, timeout=REQUEST_TIMEOUT) # 传入参数并获取输出
|
||||
verify_data = stdout.strip() # 去除空白字符
|
||||
|
||||
if stderr: # 如果有错误输出
|
||||
logger.error(f'genData_stderr->[{stderr}]') # 记录错误
|
||||
|
||||
if proc.returncode != 0: # 检查退出码
|
||||
return False, f"Node.js脚本执行失败,退出码: {proc.returncode}, 错误信息: {stderr}" # 返回失败
|
||||
|
||||
url = f"{self.base_url}/captcha/verify" # 验证URL
|
||||
self.headers['Content-Type'] = 'application/json' # 设置内容类型
|
||||
response = self.session.post(url, headers=self.headers, data=verify_data, cookies=self.cookie).json() # 发送验证请求
|
||||
if response['code'] == 1: # 验证成功
|
||||
self.token = response['data']['token'] # 保存token
|
||||
self.session_id = response['data']['session_id'] # 保存会话ID
|
||||
return response['code'] == 1, response['msg'] # 返回结果
|
||||
except subprocess.TimeoutExpired: # 超时异常
|
||||
logger.error("Node.js脚本执行超时") # 记录错误
|
||||
return False, "Node.js脚本执行超时" # 返回失败
|
||||
except Exception as e: # 其他异常
|
||||
logger.error(f"执行Node.js脚本异常: {str(e)}") # 记录错误
|
||||
return False, f"执行Node.js脚本异常: {str(e)}" # 返回失败
|
||||
|
||||
if not verify_data: # 检查输出是否为空
|
||||
return False, "Node.js脚本未输出任何内容" # 返回失败
|
||||
|
||||
def user_zh_wxdl_ewm(self) -> Tuple[bool, str, Optional[str]]: # 获取微信登录二维码
|
||||
"""
|
||||
获取微信登录二维码
|
||||
Returns:
|
||||
Tuple[bool, str, Optional[str]]: (是否成功, 消息, 二维码URL)
|
||||
"""
|
||||
url = f"{self.base_url}/user_zh/wxdl_ewm" # 二维码URL
|
||||
try: # 异常处理
|
||||
response = self.session.post(url, headers=self.headers).json() # 发送请求
|
||||
self.token = response['data']["token"] # 保存token
|
||||
return response['code'] == 1, response['msg'], response['data']['url'] if response['code'] == 1 else None # 返回结果
|
||||
except Exception as e: # 异常处理
|
||||
return False, f"请求失败: {str(e)}", None # 返回失败
|
||||
|
||||
def user_zh_p_login(self, email: str, password: str, data: Optional[Dict] = None) -> Tuple[bool, str]: # 账号密码登录
|
||||
"""
|
||||
使用账号密码登录
|
||||
Args:
|
||||
email: 邮箱账号
|
||||
password: 明文密码
|
||||
data: 携带的请求数据(用于递归刷新token时传参)
|
||||
Returns:
|
||||
Tuple[bool, str]: (是否成功, 消息)
|
||||
"""
|
||||
try: # 异常处理
|
||||
captcha_success = False # 验证码成功标志
|
||||
captcha_msg = f"滑块验证码失败,{CAPTCHA_MAX_RETRY}次内未成功" # 默认失败消息
|
||||
|
||||
for _ in range(CAPTCHA_MAX_RETRY): # 循环尝试验证滑块
|
||||
if data is None: # 如果没有传入data
|
||||
captcha_success, captcha_msg = self.captcha_init() # 初始化验证码
|
||||
else: # 如果已有data
|
||||
captcha_success = True # 直接视为验证通过
|
||||
|
||||
if captcha_success: # 验证码初始化成功
|
||||
if data is None: # 只有第一次需要验证滑块
|
||||
captcha_success, captcha_msg = self.captcha_verify() # 验证滑块
|
||||
|
||||
if captcha_success: # 滑块验证成功
|
||||
if data is None: # 首次登录
|
||||
data = { # 构建登录数据
|
||||
"re_mm": encrypt_password(self.loginToken, password), # 加密密码
|
||||
"re_yx": email, # 邮箱
|
||||
"fs": "tl", # 登录方式
|
||||
"dltoken": self.loginToken, # 登录令牌
|
||||
"token": json.dumps( # token(压缩格式JSON)
|
||||
{"code": self.token, "sessionId": self.session_id},
|
||||
separators=(",", ":")
|
||||
)
|
||||
}
|
||||
|
||||
login_url = f"{self.base_url}/user_zh/p_login" # 登录URL
|
||||
response = self.session.post( # 发送登录请求
|
||||
url=login_url,
|
||||
headers=self.headers,
|
||||
json=data,
|
||||
cookies=self.cookie,
|
||||
)
|
||||
logger.info(response.text)
|
||||
result = response.json() # 解析响应
|
||||
code = result.get("code") # 获取状态码
|
||||
msg = result.get("msg", "登录接口未返回消息") # 获取消息
|
||||
|
||||
if code == 1: # 登录成功
|
||||
self.cookie = response.cookies # 保存Cookie
|
||||
return True, msg # 返回成功
|
||||
|
||||
elif code == -118: # token过期
|
||||
self.loginToken = result.get("token", "") # 更新登录令牌
|
||||
data["re_mm"] = encrypt_password(self.loginToken, password) # 重新加密密码
|
||||
data["dltoken"] = self.loginToken # 更新令牌
|
||||
return self.user_zh_p_login(email, password, data) # 递归重新登录
|
||||
|
||||
else: # 其他错误
|
||||
return False, msg # 返回失败
|
||||
|
||||
return captcha_success, captcha_msg # 返回验证码结果
|
||||
|
||||
except Exception as e: # 异常处理
|
||||
return False, f"请求失败: {str(e)}" # 返回失败
|
||||
|
||||
def ykj_get_list(self, page: int = 1, page_size: int = 50, data: Optional[Dict] = None) -> Tuple[bool, Any]: # 获取一口价域名列表
|
||||
"""
|
||||
获取一口价域名列表
|
||||
Args:
|
||||
page: 页码
|
||||
page_size: 每页数量
|
||||
data: 额外数据
|
||||
Returns:
|
||||
Tuple[bool, Any]: (是否成功, HTML内容或错误消息)
|
||||
"""
|
||||
data = { # 构建请求数据
|
||||
'psize': page_size, # 每页数量
|
||||
'page': page, # 页码
|
||||
} if data is None else data # 如果有data则使用data
|
||||
url = f"{self.base_url}/ykj/get_list" # 列表URL
|
||||
try: # 异常处理
|
||||
self.headers['Content-Type'] = 'application/x-www-form-urlencoded' # 设置内容类型
|
||||
res = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie) # 发送请求
|
||||
|
||||
response = res.json() # 解析响应
|
||||
if response['code'] == -401: # 需要验证码
|
||||
captcha_response = False, f'滑块验证码失败,{CAPTCHA_MAX_RETRY}次内未成功' # 默认失败
|
||||
for _ in range(CAPTCHA_MAX_RETRY): # 循环尝试验证
|
||||
captcha_response = self.captcha_init() # 初始化验证码
|
||||
if captcha_response[0]: # 初始化成功
|
||||
captcha_response = self.captcha_verify() # 验证验证码
|
||||
if captcha_response[0]: # 验证成功
|
||||
data = {
|
||||
'psize': page_size, # 每页数量
|
||||
'page': page, # 页码
|
||||
# 构建token数据
|
||||
'token': json.dumps({ # token(压缩格式JSON)
|
||||
'code': self.token,
|
||||
'sessionId': self.session_id,
|
||||
}, separators=(",", ":"))
|
||||
}
|
||||
return self.ykj_get_list(page, page_size,data=data) # 递归调用
|
||||
return captcha_response[0], captcha_response[1] # 返回验证码结果
|
||||
|
||||
elif response['code'] == 1 and response.get('data') == 'yzmhuaok': # 验证码通过
|
||||
self.cookie = res.cookies # 更新Cookie
|
||||
return self.ykj_get_list(page, page_size) # 递归调用
|
||||
|
||||
elif response['code'] == 1 and response.get('html'): # 成功获取HTML
|
||||
return response['code'] == 1, response['html'] # 返回HTML
|
||||
|
||||
return False, response['msg'] # 返回失败
|
||||
except Exception as e: # 异常处理
|
||||
return False, f"请求失败: {str(e)}" # 返回失败
|
||||
|
||||
def new_cha_del(self, date: str) -> List[str]: # 获取删除域名列表
|
||||
"""
|
||||
获取指定日期的删除域名列表
|
||||
Args:
|
||||
date: 日期(格式:YYYY-MM-DD)
|
||||
Returns:
|
||||
List[str]: 域名列表
|
||||
"""
|
||||
url = f"{self.base_url}/newcha/del_down?scsj={date}" # 下载URL
|
||||
try: # 异常处理
|
||||
response = self.session.get(url, headers=self.headers, cookies=self.cookie, allow_redirects=False) # 发送请求
|
||||
url = response.headers['Location'] # 获取重定向URL
|
||||
response = self.session.get(url, timeout=DOWNLOAD_TIMEOUT) # 下载文件
|
||||
response_text = response.content.decode('utf-8') # 解码内容
|
||||
lines = response_text.strip().splitlines() # 按行分割
|
||||
return lines # 返回域名列表
|
||||
except Exception as e: # 异常处理
|
||||
logger.error(f"<获取删除域名列表错误>: {str(e)}") # 记录错误
|
||||
return [] # 返回空列表
|
||||
|
||||
def save_cookies(self, filepath: str = "juming_cookies.pkl") -> None: # 保存Cookie到文件
|
||||
"""
|
||||
保存Cookie到文件和Redis
|
||||
Args:
|
||||
filepath: 文件路径
|
||||
"""
|
||||
with open(filepath, "wb") as f: # 以二进制写入模式打开文件
|
||||
pickle.dump(self.cookie, f) # 序列化保存
|
||||
|
||||
# 保存到Redis
|
||||
try:
|
||||
import redis
|
||||
from app.config import config
|
||||
redis_client = redis.Redis(
|
||||
host=config.REDIS_HOST,
|
||||
port=config.REDIS_PORT,
|
||||
password=config.REDIS_PASSWORD,
|
||||
db=config.REDIS_DB,
|
||||
decode_responses=True
|
||||
)
|
||||
# 将cookie转换为字典
|
||||
cookie_dict = {}
|
||||
for cookie in self.cookie:
|
||||
cookie_dict[cookie.name] = cookie.value
|
||||
redis_client.set('domain_tool:juming_cookies', str(cookie_dict))
|
||||
except Exception as e:
|
||||
logger.error(f"保存Cookie到Redis失败: {str(e)}")
|
||||
pass
|
||||
|
||||
def load_cookies(self, filepath: str = "juming_cookies.pkl") -> None: # 从文件加载Cookie
|
||||
"""
|
||||
从文件加载Cookie
|
||||
Args:
|
||||
filepath: 文件路径
|
||||
"""
|
||||
try: # 异常处理
|
||||
with open(filepath, "rb") as f: # 以二进制读取模式打开文件
|
||||
self.cookie = pickle.load(f) # 反序列化加载
|
||||
except: # 异常处理
|
||||
self.cookie = requests.cookies.RequestsCookieJar() # 创建空Cookie
|
||||
|
||||
|
||||
# if __name__ == '__main__': # 主程序入口
|
||||
# m = JM() # 创建JM实例
|
||||
#
|
||||
# # 账号密码登录(请替换为您的账号密码)
|
||||
# email = 'chaofanai1998@gmail.com' # 邮箱账号
|
||||
# password = 'llzz123,./' # 密码
|
||||
#
|
||||
# logger.info(f"开始登录: {email}") # 记录日志
|
||||
# login_result = m.user_zh_p_login(email, password) # 执行登录
|
||||
# logger.info(f"登录结果: {login_result}") # 记录结果
|
||||
#
|
||||
# if not login_result[0]: # 登录失败
|
||||
# logger.error("登录失败,程序退出") # 记录错误
|
||||
# exit(1) # 退出程序
|
||||
#
|
||||
# m.load_cookies() # 加载Cookie
|
||||
#
|
||||
# # 获取一口价域名
|
||||
# import re # 导入正则表达式模块
|
||||
#
|
||||
# for page in range(1, 2): # 循环获取页面
|
||||
# success, html = m.ykj_get_list(page) # 获取列表
|
||||
# if success: # 获取成功
|
||||
# pattern_ym = r"<a class='yda1 ydz' ym='([^']*)'" # 匹配域名
|
||||
# results = re.findall(pattern_ym, html) # 查找所有域名
|
||||
# logger.info(results)
|
||||
# logger.info(f"第{page}页,找到{len(results)}个域名") # 记录结果
|
||||
# if not results: # 如果没有域名
|
||||
# logger.info("没有更多域名,停止获取") # 记录日志
|
||||
# break # 退出循环
|
||||
# time.sleep(5) # 等待5秒
|
||||
#
|
||||
# # 获取删除域名
|
||||
# deleted_domains = m.new_cha_del("2026-03-11") # 获取删除域名
|
||||
# logger.info(f"找到{len(deleted_domains)}个删除域名") # 记录结果
|
||||
#
|
||||
# m.save_cookies() # 保存Cookie
|
||||
BIN
domainCheck/detect/juming_cookies.pkl
Normal file
BIN
domainCheck/detect/juming_cookies.pkl
Normal file
Binary file not shown.
236
domainCheck/detect/juziseo.py
Normal file
236
domainCheck/detect/juziseo.py
Normal file
@@ -0,0 +1,236 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
@Project :domainScanDemo
|
||||
@File :juziseo.py
|
||||
@IDE :PyCharm
|
||||
@Author :梦伴
|
||||
@Date :2026/4/10 15:00
|
||||
@explain : 桔子SEO API封装类 - 提供登录、域名查询等功能
|
||||
'''
|
||||
|
||||
# 导入标准库
|
||||
import os # 操作系统接口
|
||||
import pickle # 序列化反序列化
|
||||
import re
|
||||
import time # 时间处理
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad
|
||||
import base64
|
||||
# 导入第三方库
|
||||
import requests # HTTP请求库
|
||||
from loguru import logger # 日志记录
|
||||
from requests.cookies import RequestsCookieJar # Cookie管理
|
||||
|
||||
from detect.geetest2 import Geetest2
|
||||
|
||||
|
||||
|
||||
# AES-CBC 加密
|
||||
def aes_cbc_encrypt(data, key=b'pvjxzjmzwawfscft', iv=b'qvibva1wg0uxwjeu'):
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
padded_data = pad(data, AES.block_size) # PKCS7填充
|
||||
encrypted = cipher.encrypt(padded_data)
|
||||
return base64.b64encode(encrypted).decode()
|
||||
|
||||
|
||||
class Juziseo:
|
||||
"""
|
||||
桔子SEO API封装类
|
||||
"""
|
||||
cookie: RequestsCookieJar = {} # 桔子SEO Cookie
|
||||
|
||||
# 通用请求头配置
|
||||
headers = {
|
||||
'accept': 'application/json, text/javascript, */*; q=0.01', # 接受的内容类型
|
||||
'accept-language': 'zh-CN,zh;q=0.9', # 接受的语言
|
||||
'content-type': 'application/x-www-form-urlencoded', # 内容类型
|
||||
'origin': 'https://seo.juziseo.com', # 请求源
|
||||
'priority': 'u=1, i', # 请求优先级
|
||||
'referer': 'https://seo.juziseo.com/login', # 来源页面
|
||||
'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"', # 浏览器标识
|
||||
'sec-ch-ua-mobile': '?0', # 是否移动端
|
||||
'sec-ch-ua-platform': '"Windows"', # 操作系统平台
|
||||
'sec-fetch-dest': 'empty', # 请求目标
|
||||
'sec-fetch-mode': 'cors', # 请求模式
|
||||
'sec-fetch-site': 'same-origin', # 请求站点
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36',
|
||||
# 用户代理
|
||||
'x-requested-with': 'XMLHttpRequest', # AJAX请求标识
|
||||
}
|
||||
|
||||
def __init__(self, proxies: dict = None):
|
||||
"""
|
||||
初始化桔子SEO类
|
||||
|
||||
:param proxies: 代理配置
|
||||
"""
|
||||
self.validate = None
|
||||
self.challenge = None
|
||||
self.gt = None
|
||||
self.session = requests.Session() # 创建会话对象
|
||||
self.session.proxies = proxies # 设置代理
|
||||
self.session.timeout = 10 # 设置超时时间10秒
|
||||
self.base_url = "https://seo.juziseo.com" # 设置基础URL
|
||||
|
||||
def save_cookies(self, filepath="juziseo_cookies.pkl"):
|
||||
"""
|
||||
保存Cookie到文件和Redis
|
||||
|
||||
:param filepath: 文件路径
|
||||
"""
|
||||
with open(filepath, "wb") as f: # 以二进制写入模式打开文件
|
||||
pickle.dump(self.cookie, f) # 序列化保存Cookie
|
||||
logger.info(f"已保存桔子SEO Cookie到 {filepath}")
|
||||
|
||||
# 保存到Redis
|
||||
try:
|
||||
import redis
|
||||
from app.config import config
|
||||
redis_client = redis.Redis(
|
||||
host=config.REDIS_HOST,
|
||||
port=config.REDIS_PORT,
|
||||
password=config.REDIS_PASSWORD,
|
||||
db=config.REDIS_DB,
|
||||
decode_responses=True
|
||||
)
|
||||
# 将cookie转换为字典
|
||||
cookie_dict = {}
|
||||
for cookie in self.cookie:
|
||||
cookie_dict[cookie.name] = cookie.value
|
||||
redis_client.set('domain_tool:juziseo_cookies', str(cookie_dict))
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
def load_cookies(self, filepath="juziseo_cookies.pkl"):
|
||||
"""
|
||||
从文件加载Cookie
|
||||
|
||||
:param filepath: 文件路径
|
||||
"""
|
||||
try: # 尝试加载
|
||||
with open(filepath, "rb") as f: # 以二进制读取模式打开文件
|
||||
self.cookie = pickle.load(f) # 反序列化加载Cookie
|
||||
# logger.info(f"已从 {filepath} 加载桔子SEO Cookie")
|
||||
except: # 加载失败
|
||||
self.cookie = requests.cookies.RequestsCookieJar() # 创建空的CookieJar
|
||||
logger.warning(f"加载桔子SEO Cookie失败,创建空Cookie")
|
||||
|
||||
def start_msg_captcha_servlet(self):
|
||||
url = f"{self.base_url}/class/gtcode/msg/StartMsgCaptchaServlet.php"
|
||||
response = self.session.get(url, headers=self.headers, cookies=self.cookie).json()
|
||||
logger.info(response)
|
||||
self.gt = response['gt']
|
||||
self.challenge = response['challenge']
|
||||
return response['success'] == 1
|
||||
|
||||
def get_captcha(self):
|
||||
GETT2 = Geetest2()
|
||||
code = GETT2.get_tp(gt=self.gt, challenge=self.challenge, type_='auto')
|
||||
logger.info(code)
|
||||
if code['result'] == 'success':
|
||||
self.challenge = code['challenge']
|
||||
self.gt = code['gt']
|
||||
self.validate = code['validate']
|
||||
return True
|
||||
else:
|
||||
logger.error(f"获取桔子SEO验证码失败: {code.get('msg', '未知错误')}")
|
||||
return False
|
||||
|
||||
def login(self, username, password):
|
||||
"""
|
||||
登录桔子SEO
|
||||
|
||||
:param username: 账号
|
||||
:param password: 密码
|
||||
:return: tuple - (是否成功, 消息)
|
||||
"""
|
||||
try:
|
||||
for _ in range(3):
|
||||
if self.start_msg_captcha_servlet():
|
||||
if self.get_captcha():
|
||||
break
|
||||
data = {
|
||||
'return_url': '/',
|
||||
'user_name': aes_cbc_encrypt(bytes(username, 'utf-8')),
|
||||
'password': aes_cbc_encrypt(bytes(password, 'utf-8')),
|
||||
'geetest_challenge': self.challenge,
|
||||
'geetest_validate': self.validate,
|
||||
'geetest_seccode': self.validate + '|jordan',
|
||||
'_post_type': 'ajax',
|
||||
}
|
||||
logger.debug(data)
|
||||
# 发送登录请求
|
||||
url = f"{self.base_url}/account/ajax/login_process/"
|
||||
response = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie)
|
||||
logger.info(response.text)
|
||||
# 解析响应
|
||||
result = response.json()
|
||||
|
||||
if result.get('errno') == 1:
|
||||
# 登录成功,保存Cookie
|
||||
self.cookie = response.cookies
|
||||
self.save_cookies()
|
||||
logger.info("桔子SEO登录成功")
|
||||
return True, "登录成功"
|
||||
else:
|
||||
logger.error(f"桔子SEO登录失败: {result.get('msg', '未知错误')}")
|
||||
return False, result.get('msg', '登录失败')
|
||||
except Exception as e:
|
||||
logger.error(f"桔子SEO登录异常: {e}")
|
||||
return False, f"登录失败: {str(e)}"
|
||||
|
||||
def check_history(self, domain: str, sensitive_words: list = None):
|
||||
# 检测域名历史是否存在敏感词
|
||||
|
||||
if sensitive_words is None:
|
||||
sensitive_words = []
|
||||
data = {
|
||||
'qrtypeindex': '1',
|
||||
'domains': domain,
|
||||
'_post_type': 'ajax',
|
||||
}
|
||||
url = f"{self.base_url}/snapshot/save/"
|
||||
response = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie).json()
|
||||
# logger.info(response)
|
||||
if response['errno'] == 1:
|
||||
url = response['rsm']['url']
|
||||
response_html = self.session.get(url, headers=self.headers, cookies=self.cookie).content.decode('utf-8')
|
||||
title_sensitive_words_match = re.search(r'标题敏感词\D*(\d+)', response_html, re.S)
|
||||
title_suspected_sensitive_words_match = re.search(r'标题有疑似敏感词\D*(\d+)', response_html, re.S)
|
||||
content_sensitive_words_match = re.search(r'内容敏感词\D*(\d+)', response_html, re.S)
|
||||
baidu_sensitive_words_match = re.search(r'百度历史收录敏感\D*(\d+)', response_html, re.S)
|
||||
if bool(title_sensitive_words_match or title_suspected_sensitive_words_match or content_sensitive_words_match or baidu_sensitive_words_match):
|
||||
return False, "存在敏感词"
|
||||
subdomain_match = re.search(r'子域名:\D*(\d+)', response_html, re.S)
|
||||
if subdomain_match:
|
||||
return False, f"存在子域名: {subdomain_match.group(1)}"
|
||||
|
||||
for sensitive_word in sensitive_words:
|
||||
if sensitive_word in response_html:
|
||||
return False, f"存在敏感词: {sensitive_word}"
|
||||
return True, 'success'
|
||||
return False, str(response.get('err', '请求失败'))
|
||||
|
||||
def check_external_link(self, domain: str, sensitive_words: list = None):
|
||||
# 外链查询域名是否存在敏感词
|
||||
if sensitive_words is None:
|
||||
sensitive_words = []
|
||||
data = {
|
||||
'qrtypeindex': '1',
|
||||
'domains': domain,
|
||||
'_post_type': 'ajax',
|
||||
}
|
||||
url = f"{self.base_url}/domain_rank/save_domain/"
|
||||
response = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie).json()
|
||||
logger.info(response)
|
||||
if response['errno'] == 1:
|
||||
url = response['rsm']['url']
|
||||
response_html = self.session.get(url, headers=self.headers, cookies=self.cookie).content.decode('utf-8')
|
||||
subdomain_match = re.search(r'子域名:\D*(\d+)', response_html, re.S)
|
||||
if subdomain_match:
|
||||
return False, f"存在子域名: {subdomain_match.group(1)}"
|
||||
for sensitive_word in sensitive_words:
|
||||
if sensitive_word in response_html:
|
||||
return False, f"存在敏感词: {sensitive_word}"
|
||||
return True, 'success'
|
||||
return False, str(response.get('err', '请求失败'))
|
||||
3851
domainCheck/detect/module/crack_geetest2x.js
Normal file
3851
domainCheck/detect/module/crack_geetest2x.js
Normal file
File diff suppressed because one or more lines are too long
53
domainCheck/detect/module/gap.py
Normal file
53
domainCheck/detect/module/gap.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import base64,re
|
||||
import time, os,datetime, sched
|
||||
import cv2
|
||||
from io import BytesIO
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import requests,re
|
||||
import numpy as np
|
||||
|
||||
|
||||
class quekou():
|
||||
def __init__(self):
|
||||
self.t = int(time.time() * 1000)
|
||||
pass
|
||||
def save_png(self,path,con):
|
||||
with open(path, "wb") as fp:
|
||||
fp.write(con)
|
||||
|
||||
def get_distance2(self,bg,tp):
|
||||
|
||||
bg = base64.b64decode(bg) # base64转二进制
|
||||
|
||||
tp = base64.b64decode(tp) # base64转二进制
|
||||
|
||||
res = self.det.slide_match(tp, bg, simple_target=True)
|
||||
res=res['target'][0]
|
||||
|
||||
return res
|
||||
|
||||
def get_distance(self, bg, tp):
|
||||
|
||||
bg = base64.b64decode(bg) # base64转二进制
|
||||
|
||||
tp = base64.b64decode(tp) # base64转二进制
|
||||
''' bg: 背景图片 tp: 缺口图片 out:输出图片 '''
|
||||
# 读取背景图片和缺口图片
|
||||
bg_img = Image.open(BytesIO(bg)) # 背景图片
|
||||
tp_img = Image.open(BytesIO(tp)) # 缺口图片
|
||||
bg_edge = cv2.Canny(np.array(bg_img), 100, 200)
|
||||
tp_edge = cv2.Canny(np.array(tp_img), 100, 200)
|
||||
# 转换图片格式
|
||||
bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
|
||||
tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)
|
||||
# 缺口匹配
|
||||
res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
|
||||
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 寻找最优匹配
|
||||
# 绘制方框
|
||||
tl = max_loc # 左上角点的坐标
|
||||
return tl[0]
|
||||
|
||||
|
||||
|
||||
|
||||
85
domainCheck/detect/module/use_ua.py
Normal file
85
domainCheck/detect/module/use_ua.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# encoding: utf-8
|
||||
|
||||
import requests
|
||||
|
||||
import random
|
||||
from loguru import logger
|
||||
|
||||
msg="提示:返回出现'ip overtime','forbidden'等信息说明本机IP被目标网站限制,请使用/更换代理IP或更换电脑即可。"
|
||||
warning="仅用于学习交流,请勿用于非法用途,违者后果自负!"
|
||||
logger.warning(warning)
|
||||
logger.info(msg)
|
||||
UA = [
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/44.0.2403.155 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; pl-PL; rv:1.0.1) Gecko/20021111 Chimera/0.6",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418.8 (KHTML, like Gecko, Safari) Cheshire/1.0.UNOFFICIAL",
|
||||
"Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.8.1b2) Gecko/20060821 BonEcho/2.0b2 (Debian-1.99+2.0b2+dfsg-1)",
|
||||
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
|
||||
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0",
|
||||
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)",
|
||||
"Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
|
||||
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11",
|
||||
"Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36",
|
||||
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
|
||||
"Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
|
||||
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
|
||||
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
|
||||
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
|
||||
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
|
||||
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
|
||||
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
|
||||
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
|
||||
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
|
||||
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
|
||||
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)",
|
||||
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
|
||||
"Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre",
|
||||
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
|
||||
"Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10",
|
||||
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0.6",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36"
|
||||
]
|
||||
|
||||
def randomUA():
|
||||
return {"User-Agent": random.choice(UA)}
|
||||
|
||||
|
||||
# if __name__ == '__main__':
|
||||
#
|
||||
# print(randomUA())
|
||||
75
domainCheck/detect/register.py
Normal file
75
domainCheck/detect/register.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
@Project :domainScanDemo
|
||||
@File :register.py
|
||||
@IDE :PyCharm
|
||||
@Author :梦伴
|
||||
@Date :2026/3/27 22:55
|
||||
@explain : 域名注册状态检测工具
|
||||
'''
|
||||
|
||||
from datetime import datetime, timezone, timedelta # 日期时间处理,用于时区转换
|
||||
|
||||
import requests # HTTP请求库
|
||||
from loguru import logger # 日志记录
|
||||
|
||||
|
||||
def check_register(domain: str, postfix: str = 'com',proxies: dict = None):
|
||||
'''
|
||||
检测注册状态
|
||||
:param domain: 待检测域名(不包含后缀)
|
||||
:param postfix: 域名后缀com、net(默认com)
|
||||
:param proxies: 代理(默认None)
|
||||
:return: 注册状态(2:可注册,3:已注册,-1:检测失败,过期时间(格式:YYYY-MM-DD HH:MM:SS)为空)
|
||||
'''
|
||||
url = f'https://rdap.verisign.com/{postfix}/v1/domain/{domain}' # 构建RDAP查询URL
|
||||
# url = f'https://www.baidu.com' # 构建RDAP查询URL
|
||||
|
||||
# 添加重试机制
|
||||
max_retries = 3
|
||||
for retry in range(max_retries):
|
||||
try:
|
||||
response = requests.get(url, timeout=20, proxies=proxies) # 发送GET请求,设置超时5秒
|
||||
if response.status_code == 200: # HTTP 200表示域名已注册
|
||||
json_data = response.json() # 解析JSON响应
|
||||
for item in json_data['events']: # 遍历事件列表
|
||||
if item['eventAction'] == 'expiration': # 如果是过期时间事件
|
||||
utc_time = datetime.fromisoformat(item['eventDate'].replace('Z', '+00:00')) # 解析UTC时间
|
||||
beijing_tz = timezone(timedelta(hours=8)) # 创建北京时区(UTC+8)
|
||||
beijing_time = utc_time.astimezone(beijing_tz) # 将UTC时间转换为北京时间
|
||||
return 3, beijing_time.strftime("%Y-%m-%d %H:%M:%S") # 返回已注册状态和过期时间
|
||||
|
||||
elif response.status_code == 404: # HTTP 404表示域名不存在,可注册
|
||||
# 可注册状态
|
||||
return 2, '' # 返回可注册状态,过期时间为空
|
||||
logger.error(response.status_code)
|
||||
except requests.exceptions.RequestException as e:
|
||||
# logger.warning(f"注册状态检测第{retry+1}次失败: {domain}, 错误: {e}")
|
||||
if retry < max_retries - 1:
|
||||
import time
|
||||
time.sleep(2) # 等待2秒后重试
|
||||
proxies=None
|
||||
else:
|
||||
# 达到最大重试次数,返回检测失败
|
||||
logger.error(f"注册状态检测失败: {domain}, 已达到最大重试次数")
|
||||
return -1, '' # -1表示检测失败,过期时间为空
|
||||
|
||||
|
||||
|
||||
|
||||
# def get_proxy():
|
||||
#
|
||||
# url='http://api.ch12361.com/getProxy.php?group=A&count=1'
|
||||
# response = requests.get(url).json()
|
||||
# username=response['username']
|
||||
# password=response['password']
|
||||
# ip=response['ip']
|
||||
# port=response['port']
|
||||
#
|
||||
# proxy_url = f"http://{username}:{password}@{ip}:{port}"
|
||||
# logger.info(proxy_url)
|
||||
# return {'http':proxy_url,'https':proxy_url}
|
||||
#
|
||||
# t=datetime.now()
|
||||
# logger.info(check_register('75az.com',proxies=get_proxy())) # 测试查询nnsk.com域名的注册状态
|
||||
# logger.info(datetime.now()-t)
|
||||
15108
domainCheck/detect/sdk_leg.js
Normal file
15108
domainCheck/detect/sdk_leg.js
Normal file
File diff suppressed because one or more lines are too long
897
domainCheck/detect/sdk_leg_env.js
Normal file
897
domainCheck/detect/sdk_leg_env.js
Normal file
@@ -0,0 +1,897 @@
|
||||
process_ = process;
|
||||
require_ = require;
|
||||
delete Buffer;
|
||||
// delete process;
|
||||
delete require;
|
||||
delete global;
|
||||
delete module;
|
||||
delete exports;
|
||||
delete __filename;
|
||||
delete __dirname;
|
||||
delete SharedArrayBuffer;
|
||||
|
||||
AsObj = {
|
||||
// print: console.log,
|
||||
print: function () { },
|
||||
// print_:console.log,
|
||||
}
|
||||
|
||||
no_print = ['Boolean','String','parseFloat','Array','Object','prepareStackTrace_'];
|
||||
function watch(object, WatchName) {
|
||||
const handler = {
|
||||
get(target, property, receiver) {
|
||||
if (
|
||||
property !== 'isNaN' &&
|
||||
property !== 'encodeURI' &&
|
||||
property !== "Uint8Array" &&
|
||||
property !== 'undefined' &&
|
||||
property !== 'JSON' &&
|
||||
property !== 'Number' &&
|
||||
!no_print.includes(property) &&
|
||||
property !== Symbol.for('nodejs.util.inspect.custom') &&
|
||||
typeof property !== 'symbol'
|
||||
) {
|
||||
|
||||
if (property === 'global') {
|
||||
return undefined;
|
||||
}
|
||||
if (property === 'Buffer') {
|
||||
return undefined;
|
||||
}
|
||||
if (property === 'process') {
|
||||
return undefined;
|
||||
}
|
||||
if (WatchName === 'config_data') {
|
||||
debugger
|
||||
}
|
||||
if (WatchName.indexOf('.prototype') != -1 && target[property] != undefined) {
|
||||
return Reflect.get(target, property, receiver);
|
||||
}
|
||||
|
||||
AsObj.print(
|
||||
"方法:", "get",
|
||||
"对象:", WatchName,
|
||||
"属性:", property,
|
||||
"属性类型:", typeof property,
|
||||
"属性值:", typeof target[property] == 'object' ? "object" : target[property],
|
||||
"属性值类型:", typeof target[property]
|
||||
);
|
||||
}
|
||||
|
||||
if (WatchName === 'top') {
|
||||
return window;
|
||||
}
|
||||
|
||||
return Reflect.get(target, property, receiver);
|
||||
},
|
||||
|
||||
set(target, property, value, receiver) {
|
||||
if (WatchName.indexOf('.prototype') != -1 && value != undefined) {
|
||||
return Reflect.set(target, property, value, receiver);
|
||||
}
|
||||
AsObj.print(
|
||||
"方法:", "set",
|
||||
"对象:", WatchName,
|
||||
"属性:", property,
|
||||
"属性类型:", typeof property,
|
||||
"属性值:", typeof value == 'object' ? "object" : value,
|
||||
"属性值类型:", typeof target[property]
|
||||
);
|
||||
return Reflect.set(target, property, value, receiver);
|
||||
},
|
||||
// in操作 检测
|
||||
has(target, property) {
|
||||
AsObj.print(
|
||||
"代理对象:", WatchName,
|
||||
"方法:", "has",
|
||||
"检查属性:", property,
|
||||
"结果:", typeof target[property] == 'object' ? "object" : target[property],
|
||||
);
|
||||
return Reflect.has(target, property);
|
||||
},
|
||||
// Object.key 检测
|
||||
ownKeys(target) {
|
||||
AsObj.print(
|
||||
"方法:", "ownKeys",
|
||||
"对象:", target+''
|
||||
);
|
||||
return Reflect.ownKeys(target);
|
||||
}
|
||||
};
|
||||
|
||||
return new Proxy(object, handler);
|
||||
}
|
||||
// function watch(object, WatchName) {
|
||||
// return object
|
||||
// }
|
||||
|
||||
// 保护函数,toString检测
|
||||
const safeFunction = function safeFunction(func) {
|
||||
//处理安全函数
|
||||
Function.prototype.$call = Function.prototype.call;
|
||||
const $toString = Function.toString;
|
||||
const myFunction_toString_symbol = Symbol('('.concat('', ')'));
|
||||
|
||||
const myToString = function myToString() {
|
||||
return typeof this === 'function' && this[myFunction_toString_symbol] || $toString.$call(this);
|
||||
}
|
||||
|
||||
const set_native = function set_native(func, key, value) {
|
||||
Object.defineProperty(func, key, {
|
||||
"enumerable": false,
|
||||
"configurable": true,
|
||||
"writable": true,
|
||||
"value": value
|
||||
});
|
||||
}
|
||||
|
||||
delete Function.prototype['toString'];
|
||||
set_native(Function.prototype, "toString", myToString);
|
||||
set_native(Function.prototype.toString, myFunction_toString_symbol, "function toString() { [native code] }");
|
||||
|
||||
const safe_Function = function safe_Function(func) {
|
||||
set_native(func, myFunction_toString_symbol, "function" + (func.name ? " " + func.name : "") + "() { [native code] }");
|
||||
}
|
||||
|
||||
return safe_Function(func)
|
||||
}
|
||||
|
||||
//创建函数,并代理上
|
||||
const makeFunction = function makeFunction(name) {
|
||||
v_log = AsObj.print;
|
||||
// 使用 Function 保留函数名
|
||||
func = new Function("v_log", `
|
||||
return function ${name}() {
|
||||
v_log('函数${name}传参-->', arguments);
|
||||
};
|
||||
`)(v_log); // 传递 v_log 到动态函数
|
||||
|
||||
safeFunction(func);
|
||||
func = watch(func,`${name}`);
|
||||
func.prototype = watch(func.prototype, `${name}.prototype`);
|
||||
return func;
|
||||
}
|
||||
|
||||
!(function () {
|
||||
"use strict";
|
||||
const $toString = Function.toString;
|
||||
const myFunction_toString_symbol = Symbol('('.concat('', ')_', (Math.random() + '').toString(36)));
|
||||
const mytoString = function () {
|
||||
return typeof this == 'function' && this[myFunction_toString_symbol] || $toString.call(this);
|
||||
};
|
||||
|
||||
function set_native(func, key, value) {
|
||||
Object.defineProperty(func, key, {
|
||||
"enumerable": false,
|
||||
"configurable": true,
|
||||
"writable": true,
|
||||
"value": value
|
||||
})
|
||||
};
|
||||
delete Function.prototype['toString'];
|
||||
set_native(Function.prototype, "toString", mytoString);
|
||||
set_native(Function.prototype.toString, myFunction_toString_symbol, "function toString() { [native code] }");
|
||||
this.func_set_native = function (func) {
|
||||
set_native(func, myFunction_toString_symbol, `function ${myFunction_toString_symbol, func.name || ''}() { [native code] }`)
|
||||
}
|
||||
}).call(globalThis);
|
||||
|
||||
// 重写全局对象原型链
|
||||
function setTostringAndstringTag(obj) {
|
||||
Object.defineProperties(obj.prototype, {
|
||||
[Symbol.toStringTag]: {
|
||||
configurable: true,
|
||||
value: obj.name
|
||||
}
|
||||
});
|
||||
safeFunction(obj);
|
||||
};
|
||||
|
||||
// 创建标签原型
|
||||
function createTagProto(propObj,portotypeObj) {
|
||||
let res = propObj + ' = ' + 'function ' + propObj + '() { throw new TypeError("Illegal constructor"); };\n';
|
||||
res += 'setTostringAndstringTag(' + propObj + ',null);\n';
|
||||
if (portotypeObj) {
|
||||
for (let key in portotypeObj) {
|
||||
res += propObj + '.prototype.' + portotypeObj[key] + '= function ' + portotypeObj[key] + '() {AsObj.print("'+propObj+'.prototype.' + portotypeObj[key] + '原型方法(需在实例对象上补该方法)::",arguments)};\n';
|
||||
res += 'globalThis.func_set_native(' + propObj + '.prototype.' + portotypeObj[key] + ');\n';
|
||||
}
|
||||
}
|
||||
eval(res);
|
||||
}
|
||||
|
||||
Object.defineProperties(globalThis, {
|
||||
[Symbol.toStringTag]: {
|
||||
configurable: true,
|
||||
value: 'Window'
|
||||
}
|
||||
});
|
||||
|
||||
for (let key in globalThis) {
|
||||
if (typeof globalThis[key] === 'function') {
|
||||
safeFunction(globalThis[key])
|
||||
}
|
||||
}
|
||||
for (let key in console) {
|
||||
if (typeof console[key] === 'function') {
|
||||
safeFunction(console[key])
|
||||
}
|
||||
}
|
||||
|
||||
createTagProto('EventTarget',['addEventListener']);
|
||||
createTagProto('WindowProperties');
|
||||
createTagProto('Window');
|
||||
|
||||
window = globalThis;
|
||||
window.__proto__ = Window.prototype;
|
||||
window.__proto__.__proto__ = WindowProperties.prototype;
|
||||
window.__proto__.__proto__.__proto__ = EventTarget.prototype;
|
||||
Window.__proto__ = EventTarget;
|
||||
|
||||
Object.defineProperty(window, 'WindowProperties', {
|
||||
get: function () {
|
||||
return undefined;
|
||||
}
|
||||
})
|
||||
|
||||
function randoms(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1) + min)
|
||||
}
|
||||
|
||||
function getRandomValues(buf) {
|
||||
var min = 0,
|
||||
max = 255;
|
||||
if (buf instanceof Uint16Array) {
|
||||
max = 65535;
|
||||
} else if (buf instanceof Uint32Array) {
|
||||
max = 4294967295;
|
||||
}
|
||||
for (var element in buf) {
|
||||
buf[element] = randoms(min, max);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
self = window.self = window;
|
||||
frames = window.frames = window;
|
||||
top = window.top = window;
|
||||
parent = window.parent = window;
|
||||
global = window.global = window;
|
||||
|
||||
Object.defineProperty(window, "global", {
|
||||
configurable:false,
|
||||
enumerable: true,
|
||||
set: undefined,
|
||||
get: function global(){
|
||||
return window
|
||||
}
|
||||
})
|
||||
Object.defineProperty(window, "top", {
|
||||
configurable:false,
|
||||
enumerable: true,
|
||||
set: undefined,
|
||||
get: function top(){
|
||||
return window
|
||||
}
|
||||
})
|
||||
Object.defineProperty(window, "self", {
|
||||
configurable:false,
|
||||
enumerable: true,
|
||||
set: undefined,
|
||||
get: function self(){
|
||||
return window
|
||||
}
|
||||
})
|
||||
Object.defineProperty(window, "parent", {
|
||||
configurable:false,
|
||||
enumerable: true,
|
||||
set: undefined,
|
||||
get: function parent(){
|
||||
return window
|
||||
}
|
||||
})
|
||||
Object.defineProperty(window, "frames", {
|
||||
configurable:false,
|
||||
enumerable: true,
|
||||
set: undefined,
|
||||
get: function frames(){
|
||||
return window
|
||||
}
|
||||
})
|
||||
|
||||
innerWidth = 1536
|
||||
innerHeight = 715
|
||||
outerWidth = 1536
|
||||
outerHeight = 824
|
||||
devicePixelRatio = 1.25;
|
||||
screenLeft = 0;
|
||||
screenX = 0;
|
||||
screenTop = 0;
|
||||
screenY = 0;
|
||||
opener = null;
|
||||
isSecureContext = true;
|
||||
crypto = {
|
||||
getRandomValues:getRandomValues
|
||||
};
|
||||
|
||||
createTagProto('DOMStringMap')
|
||||
createTagProto('HTMLHeadElement',['insertBefore','removeChild'])
|
||||
createTagProto('HTMLBodyElement',['addEventListener','appendChild','removeChild'])
|
||||
createTagProto('HTMLHtmlElement',['getAttribute'])
|
||||
createTagProto('HTMLDocument')
|
||||
createTagProto('Document',['browsingTopics','appendChild','querySelector','evaluate','querySelectorAll','removeChild','requestStorageAccess','requestStorageAccessFor','hasStorageAccess','getElementsByTagName','hasPrivateToken','createElement','hasRedemptionRecord','hasFocus'])
|
||||
createTagProto('Node')
|
||||
document = {};
|
||||
document.__proto__ = HTMLDocument.prototype;
|
||||
document.__proto__.__proto__ = Document.prototype;
|
||||
document.__proto__.__proto__.__proto__ = Node.prototype;
|
||||
document.__proto__.__proto__.__proto__.__proto__ = EventTarget.prototype;
|
||||
HTMLDocument.__proto__ = Document;
|
||||
HTMLDocument.__proto__.__proto__ = Node;
|
||||
HTMLDocument.__proto__.__proto__.__proto__ = EventTarget;
|
||||
Document.__proto__ = Node;
|
||||
Document.__proto__.__proto__ = EventTarget;
|
||||
Node.__proto__ = EventTarget;
|
||||
|
||||
createTagProto('Plugin');
|
||||
createTagProto('PluginArray');
|
||||
plugins0 = {
|
||||
name: 'PDF Viewer',
|
||||
filename: 'internal-pdf-viewer',
|
||||
description:'Portable Document Format',
|
||||
length: 2,
|
||||
'0': {
|
||||
type: 'application/pdf',
|
||||
},
|
||||
'1':{
|
||||
type:'text/pdf'
|
||||
}
|
||||
}
|
||||
plugins0['0'].enabledPlugin = plugins0;
|
||||
plugins0['1'].enabledPlugin = plugins0;
|
||||
plugins1 = {
|
||||
name: 'Chrome PDF Viewer',
|
||||
filename: 'internal-pdf-viewer',
|
||||
description:'Portable Document Format',
|
||||
length: 2,
|
||||
'0': {
|
||||
type:'application/pdf'
|
||||
},
|
||||
'1': {
|
||||
type:'text/pdf'
|
||||
}
|
||||
}
|
||||
plugins1['0'].enabledPlugin = plugins1;
|
||||
plugins1['1'].enabledPlugin = plugins1;
|
||||
plugins2 = {
|
||||
name: 'Chromium PDF Viewer',
|
||||
filename: 'internal-pdf-viewer',
|
||||
description:'Portable Document Format',
|
||||
length: 2,
|
||||
'0': {
|
||||
type:'application/pdf'
|
||||
},
|
||||
'1': {
|
||||
type:'text/pdf'
|
||||
}
|
||||
}
|
||||
plugins2['0'].enabledPlugin = plugins2;
|
||||
plugins2['1'].enabledPlugin = plugins2;
|
||||
plugins3 = {
|
||||
name: 'Microsoft Edge PDF Viewer',
|
||||
filename: 'internal-pdf-viewer',
|
||||
description:'Portable Document Format',
|
||||
length: 2,
|
||||
'0':{
|
||||
type:'application/pdf'
|
||||
},
|
||||
'1': {
|
||||
type:'text/pdf'
|
||||
}
|
||||
}
|
||||
plugins3['0'].enabledPlugin = plugins3;
|
||||
plugins3['1'].enabledPlugin = plugins3;
|
||||
plugins4 = {
|
||||
name: 'WebKit built-in PDF',
|
||||
filename: 'internal-pdf-viewer',
|
||||
description:'Portable Document Format',
|
||||
length: 2,
|
||||
'0': {
|
||||
type:'application/pdf'
|
||||
},
|
||||
'1':{
|
||||
type:'text/pdf'
|
||||
}
|
||||
}
|
||||
plugins4['0'].enabledPlugin = plugins4;
|
||||
plugins4['1'].enabledPlugin = plugins4;
|
||||
plugins = {
|
||||
length: 5,
|
||||
'0': plugins0,
|
||||
'1': plugins1,
|
||||
'2': plugins2,
|
||||
'3': plugins3,
|
||||
'4': plugins4,
|
||||
namedItem : function (name) {
|
||||
AsObj.print('Plugin-namedItem:', name)
|
||||
},
|
||||
item: function (index) {
|
||||
AsObj.print('Plugin-item:', index)
|
||||
return watch(plugins0,'item-'+index);
|
||||
},
|
||||
refresh: function () {
|
||||
AsObj.print('Plugin-refresh:',arguments)
|
||||
},
|
||||
}
|
||||
plugins.__proto__ = PluginArray.prototype;
|
||||
|
||||
MimeTypeArray = function MimeTypeArray() {
|
||||
this.length = 2;
|
||||
this['0'] = {
|
||||
suffixes: 'pdf',
|
||||
type: 'application/pdf',
|
||||
description:"Portable Document Format",
|
||||
enabledPlugin: plugins0
|
||||
};
|
||||
this['1'] = {
|
||||
suffixes: 'pdf',
|
||||
type: 'text/pdf',
|
||||
description:"Portable Document Format",
|
||||
enabledPlugin: plugins0
|
||||
};
|
||||
};
|
||||
MimeTypeArray.prototype.toString = function () { return '[object MimeTypeArray]'; }
|
||||
MimeTypeArray.toString = function () { return 'function MimeTypeArray() { [native code] }'; }
|
||||
Object.defineProperties(MimeTypeArray.prototype, { [Symbol.toStringTag]: { value: 'MimeTypeArray' } })
|
||||
MimeTypeArrayc = new MimeTypeArray();
|
||||
MimeTypeArrayc[Symbol.iterator] = function* () {
|
||||
for (let key in this) {
|
||||
yield this[key];
|
||||
}
|
||||
}
|
||||
|
||||
// 创建电池管理器对象原型
|
||||
const BatteryManager = {
|
||||
level: 1,
|
||||
charging: true,
|
||||
chargingTime: 0,
|
||||
dischargingTime: null,
|
||||
onchargingchange: null,
|
||||
onlevelchange: null,
|
||||
toString: function toString() {
|
||||
return `BatteryManager {
|
||||
charging: ${this.charging},
|
||||
level: ${this.level},
|
||||
chargingTime: ${this.chargingTime},
|
||||
dischargingTime: ${this.dischargingTime}
|
||||
}`
|
||||
}
|
||||
}
|
||||
window.BatteryManager = BatteryManager;
|
||||
|
||||
Promise2 = {
|
||||
then: function () {
|
||||
return this;
|
||||
},
|
||||
catch: function (){},
|
||||
};
|
||||
|
||||
createTagProto('Bluetooth');
|
||||
createTagProto('Navigator');
|
||||
Navigator.prototype.hardwareConcurrency = 8;
|
||||
Navigator.prototype.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36';
|
||||
Navigator.prototype.appVersion = '5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
|
||||
Navigator.prototype.appName = 'Netscape';
|
||||
Navigator.prototype.appCodeName = 'Mozilla';
|
||||
Navigator.prototype.vendor = 'Google Inc.';
|
||||
Navigator.prototype.maxTouchPoints = 10;
|
||||
Navigator.prototype.platform = 'Win32';
|
||||
Navigator.prototype.adAuctionComponents = function adAuctionComponents() {
|
||||
AsObj.print('adAuctionComponents:::', arguments)
|
||||
}
|
||||
safeFunction(Navigator.prototype.adAuctionComponents)
|
||||
Navigator.prototype.runAdAuction = function runAdAuction() {
|
||||
AsObj.print('runAdAuction:::', arguments)
|
||||
}
|
||||
safeFunction(Navigator.prototype.runAdAuction)
|
||||
Navigator.prototype.canLoadAdAuctionFencedFrame = makeFunction('canLoadAdAuctionFencedFrame')
|
||||
Navigator.prototype.deprecatedReplaceInURN = makeFunction('deprecatedReplaceInURN')
|
||||
Navigator.prototype.deprecatedURNToURL = makeFunction('deprecatedURNToURL')
|
||||
Navigator.prototype.joinAdInterestGroup = makeFunction('joinAdInterestGroup')
|
||||
Navigator.prototype.leaveAdInterestGroup = makeFunction('leaveAdInterestGroup')
|
||||
Navigator.prototype.updateAdInterestGroups = makeFunction('updateAdInterestGroups')
|
||||
Navigator.prototype.connection = watch({
|
||||
downlink: 9.1,
|
||||
effectiveType: '4g',
|
||||
rtt: 0,
|
||||
saveData: false,
|
||||
},'connection')
|
||||
Navigator.prototype.language = 'zh-CN';
|
||||
Navigator.prototype.languages = ["zh-CN"];
|
||||
Navigator.prototype.plugins = plugins;
|
||||
Navigator.prototype.webdriver = false;
|
||||
Navigator.prototype.cookieEnabled = true;
|
||||
Navigator.prototype.onLine = true;
|
||||
Navigator.prototype.doNotTrack = null;
|
||||
Navigator.prototype.bluetooth = {};
|
||||
Navigator.prototype.product = 'Gecko'
|
||||
Navigator.prototype.deviceMemory = 8
|
||||
Navigator.prototype.mediaDevices = watch({
|
||||
enumerateDevices: function enumerateDevices() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const offer = [
|
||||
{deviceId: '', kind: 'audioinput', label: '', groupId: ''},
|
||||
{deviceId: '', kind: 'videoinput', label: '', groupId: ''},
|
||||
{deviceId: '', kind: 'audiooutput', label: '', groupId: ''},
|
||||
]
|
||||
resolve(offer);
|
||||
});
|
||||
},
|
||||
getUserMedia: function getUserMedia() {
|
||||
AsObj.print('getUserMedia:::', arguments)
|
||||
}
|
||||
},'mediaDevices')
|
||||
Navigator.prototype.storage = {
|
||||
estimate: function estimate() {
|
||||
AsObj.print('estimate:::', arguments)
|
||||
return new Promise((resolve, reject) => {
|
||||
const offer = {
|
||||
usage: 0, // 1GB
|
||||
quota: 2147483648, // 1GB,
|
||||
usageDetails: {caches: 512, indexedDB: 2855}
|
||||
};
|
||||
resolve(offer);
|
||||
});
|
||||
}
|
||||
}
|
||||
Navigator.prototype.webkitPersistentStorage = watch({},'webkitPersistentStorage')
|
||||
Navigator.prototype.webkitTemporaryStorage = watch({
|
||||
queryUsageAndQuota: function queryUsageAndQuota() {
|
||||
AsObj.print('queryUsageAndQuota:::', arguments)
|
||||
return new Promise((resolve, reject) => {
|
||||
const offer = {
|
||||
usage: 1024 * 1024 * 1024, // 1GB
|
||||
quota: 1024 * 1024 * 1024, // 1GB
|
||||
};
|
||||
resolve(offer);
|
||||
});
|
||||
}
|
||||
},'webkitTemporaryStorage')
|
||||
Navigator.prototype.bluetooth.__proto__ = Bluetooth.prototype;
|
||||
Navigator.prototype.javaEnabled = function javaEnabled() {
|
||||
return false
|
||||
};
|
||||
safeFunction(Navigator.prototype.javaEnabled)
|
||||
Navigator.prototype.getBattery = function getBattery() {
|
||||
AsObj.print('getBattery:::', arguments)
|
||||
return Promise.resolve({
|
||||
__proto__: BatteryManager,
|
||||
// 动态参数配置(示例值)
|
||||
level: 1,
|
||||
charging: true,
|
||||
dischargingTime: null // 2小时放电时间
|
||||
})
|
||||
}
|
||||
safeFunction(Navigator.prototype.getBattery)
|
||||
Navigator.prototype.registerProtocolHandler = function registerProtocolHandler() {
|
||||
AsObj.print('registerProtocolHandler:::',arguments)
|
||||
}
|
||||
safeFunction(Navigator.prototype.registerProtocolHandler)
|
||||
Navigator.prototype.mimeTypes = watch(MimeTypeArrayc,'mimeTypes');
|
||||
Navigator.prototype.geolocation = {
|
||||
getCurrentPosition: function getCurrentPosition() {
|
||||
return Promise2;
|
||||
}
|
||||
}
|
||||
Navigator.prototype.pdfViewerEnabled = true;
|
||||
Navigator.prototype.doNotTrack = null;
|
||||
Navigator.prototype.keyboard = watch({
|
||||
getLayoutMap: function getLayoutMap() {
|
||||
AsObj.print('Navigator.prototype.keyboard:', arguments)
|
||||
return {
|
||||
then: function () {
|
||||
// arguments[0](watch({
|
||||
// size: 48,
|
||||
// values: function () {
|
||||
// return ['k', 'g', '2', '0', 'v', 'a', '`', 'l', '\\', "'", 'w', '8', 'm', 'h', '.', '7', '1', 'p', 'd', 'f', 'o', 'q', 'c', 'n', '[', 'z', 'y', '3', '6', '5', 'x', '/', '\\', ',', '-', '4', 'b', 't', '9', 's', 'i', 'u', '=', 'j', ';', 'r', ']', 'e']
|
||||
// }
|
||||
// }, 'navigator.keyboard.getLayoutMap.then'))
|
||||
return {
|
||||
catch: function () {
|
||||
arguments[0]({
|
||||
message:'getLayoutMap() must be called from a top-level browsing context or allowed by the permission policy.'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},'navigator.keyboard')
|
||||
Navigator.prototype.permissions = watch({
|
||||
query: function query() {
|
||||
arg_obj = arguments[0];
|
||||
if (arg_obj.name === 'audio_capture') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const offer = {
|
||||
state: 'prompt',
|
||||
onchange: null,
|
||||
name:arg_obj.name
|
||||
};
|
||||
resolve(offer);
|
||||
});
|
||||
}
|
||||
if (arg_obj.name === 'microphone') {
|
||||
return {
|
||||
then: function () {
|
||||
arguments[0](watch({
|
||||
state: 'denied',
|
||||
onchange: null,
|
||||
name: 'audio_capture'
|
||||
},'permissions.query.microphone'));
|
||||
return {catch:function(){}}
|
||||
},
|
||||
catch:function(){}
|
||||
}
|
||||
}
|
||||
if (arg_obj.name === 'camera') {
|
||||
return {
|
||||
then: function () {
|
||||
arguments[0](watch({
|
||||
state: 'prompt',
|
||||
onchange: null,
|
||||
name: 'video_capture'
|
||||
},'permissions.query.camera'));
|
||||
return {catch:function(){}}
|
||||
},
|
||||
catch:function(){}
|
||||
}
|
||||
}
|
||||
AsObj.print('permissions.query:::', arguments)
|
||||
|
||||
}
|
||||
},'permissions')
|
||||
Navigator.prototype.productSub = '20030107'
|
||||
Navigator.prototype.getGamepads = function getGamepads() {
|
||||
AsObj.print('getGamepads:::', arguments)
|
||||
return [null,null,null,null]
|
||||
}
|
||||
safeFunction(Navigator.prototype.getGamepads)
|
||||
|
||||
Navigator.prototype.sendBeacon = makeFunction('sendBeacon')
|
||||
|
||||
Navigator.prototype.deprecatedRunAdAuctionEnforcesKAnonymity = false
|
||||
Navigator.prototype.gpu = watch({
|
||||
getPreferredCanvasFormat: function getPreferredCanvasFormat() {
|
||||
AsObj.print('gpu.getPreferredCanvasFormat:', arguments)
|
||||
return 'bgra8unorm'
|
||||
},
|
||||
wgslLanguageFeatures: watch({
|
||||
size: 7,
|
||||
values: function values() {
|
||||
debugger
|
||||
AsObj.print('wgslLanguageFeatures.values')
|
||||
return ['packed_4x8_integer_dot_product', 'unrestricted_pointer_parameters', 'subgroup_uniformity', 'subgroup_id', 'pointer_composite_access', 'readonly_and_readwrite_storage_textures', 'uniform_buffer_standard_layout']
|
||||
},
|
||||
}, 'gpu.wgslLanguageFeatures'),
|
||||
requestAdapter: function requestAdapter() {
|
||||
AsObj.print('gpu.requestAdapter:', arguments)
|
||||
return {
|
||||
then: function () {
|
||||
arguments[0](watch({
|
||||
features: watch({
|
||||
size: 19,
|
||||
values: function () {
|
||||
return ['depth32float-stencil8', 'rg11b10ufloat-renderable', 'bgra8unorm-storage', 'texture-formats-tier1', 'texture-compression-bc', 'dual-source-blending', 'core-features-and-limits', 'float32-filterable', 'indirect-first-instance', 'float32-blendable', 'depth-clip-control', 'texture-compression-bc-sliced-3d', 'timestamp-query', 'texture-formats-tier2', 'clip-distances', 'shader-f16', 'primitive-index', 'texture-component-swizzle', 'subgroups']
|
||||
}
|
||||
}, 'gpu.requestAdapter.features'),
|
||||
info: watch({ vendor: 'intel', architecture: 'gen-11', device: '', description: '', subgroupMinSize: 16 }, 'gpu.requestAdapter.info'),
|
||||
limits: watch({
|
||||
maxBufferSize: 2147483648,
|
||||
maxStorageBufferBindingSize:2147483644
|
||||
}, 'gpu.requestAdapter.limits'),
|
||||
catch:function(){}
|
||||
}, 'gpu.requestAdapter'));
|
||||
return {
|
||||
catch: function () {
|
||||
return {
|
||||
then: function () {
|
||||
arguments[0]()
|
||||
return {catch:function(){}}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
catch: function () {
|
||||
}
|
||||
}
|
||||
}
|
||||
},'navigator.gpu')
|
||||
Navigator.prototype.userAgentData = watch({
|
||||
brands:[
|
||||
{
|
||||
"brand": "Google Chrome",
|
||||
"version": "143"
|
||||
},
|
||||
{
|
||||
"brand": "Chromium",
|
||||
"version": "143"
|
||||
},
|
||||
{
|
||||
"brand": "Not A(Brand",
|
||||
"version": "24"
|
||||
}
|
||||
],
|
||||
mobile: false,
|
||||
platform: "Windows",
|
||||
getHighEntropyValues: function getHighEntropyValues() {
|
||||
if (arguments[0] + '' === 'architecture,bitness,model,platformVersion,uaFullVersion,wow64') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const offer = {
|
||||
"architecture": "x86",
|
||||
"bitness": "64",
|
||||
"brands": [
|
||||
{
|
||||
"brand": "Not:A-Brand",
|
||||
"version": "99"
|
||||
},
|
||||
{
|
||||
"brand": "Google Chrome",
|
||||
"version": "145"
|
||||
},
|
||||
{
|
||||
"brand": "Chromium",
|
||||
"version": "145"
|
||||
}
|
||||
],
|
||||
"mobile": false,
|
||||
"model": "",
|
||||
"platform": "Windows",
|
||||
"platformVersion": "10.0.0",
|
||||
"uaFullVersion": "145.0.7632.117",
|
||||
"wow64": false
|
||||
};
|
||||
resolve(offer);
|
||||
});
|
||||
}
|
||||
AsObj.print('getHighEntropyValues:::', arguments)
|
||||
}
|
||||
},'userAgentData')
|
||||
|
||||
navigator = {};
|
||||
navigator.__proto__ = Navigator.prototype;
|
||||
|
||||
createTagProto('Location');
|
||||
location = {
|
||||
"ancestorOrigins": {},
|
||||
"href": "https://www.neimanmarcus.com/",
|
||||
"origin": "https://www.neimanmarcus.com",
|
||||
"protocol": "https:",
|
||||
"host": "www.neimanmarcus.com",
|
||||
"hostname": "www.neimanmarcus.com",
|
||||
"port": "",
|
||||
"pathname": "/",
|
||||
"search": "",
|
||||
"hash": ""
|
||||
};
|
||||
|
||||
location.__proto__ = Location.prototype;
|
||||
location.toString = function toString() {
|
||||
return this.href;
|
||||
}
|
||||
|
||||
createTagProto('Screen');
|
||||
Screen.prototype = Object.assign(Screen.prototype, {
|
||||
availHeight: 824,
|
||||
availLeft: 0,
|
||||
availTop: 0,
|
||||
availWidth: 1536,
|
||||
colorDepth: 32,
|
||||
height: 864,
|
||||
isExtended: true,
|
||||
onchange: null,
|
||||
pixelDepth: 24,
|
||||
width: 1536,
|
||||
orientation: {
|
||||
angle: 0,
|
||||
type: "landscape-primary",
|
||||
onchange: null
|
||||
}
|
||||
})
|
||||
screen = {};
|
||||
screen.__proto__ = Screen.prototype;
|
||||
|
||||
createTagProto('History',['replaceState']);
|
||||
history = {};
|
||||
history.__proto__ = History.prototype;
|
||||
|
||||
chrome = {
|
||||
loadTimes: function loadTimes() { },
|
||||
csi: function csi() { },
|
||||
app: {
|
||||
InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' },
|
||||
RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' },
|
||||
getDetails:function getDetails(){},
|
||||
getIsInstalled:function getIsInstalled(){},
|
||||
installState:function installState(){},
|
||||
isInstalled: false,
|
||||
runningState: function runningState(){}
|
||||
},
|
||||
}
|
||||
|
||||
createTagProto('Storage');
|
||||
local = {
|
||||
};
|
||||
localStorage = {
|
||||
getItem: function getItem(key) {
|
||||
AsObj.print("localStorage.getItem::", arguments);
|
||||
if (!local[key]) {
|
||||
return null;
|
||||
}
|
||||
return local[key];
|
||||
},
|
||||
setItem: function setItem(key, value) {
|
||||
AsObj.print("localStorage.setItem::", arguments);
|
||||
local[key] = value;
|
||||
},
|
||||
clear: function clear() {
|
||||
local = {};
|
||||
},
|
||||
removeItem: function removeItem(key) {
|
||||
AsObj.print("localStorage.removeItem::", arguments);
|
||||
delete local[key];
|
||||
}
|
||||
}
|
||||
localStorage.__proto__ = Storage.prototype;
|
||||
sessionStorage = {
|
||||
getItem: function getItem(key) {
|
||||
AsObj.print("sessionStorage.getItem::", arguments);
|
||||
if (!local[key]) {
|
||||
return null;
|
||||
}
|
||||
return local[key];
|
||||
},
|
||||
setItem: function setItem(key, value) {
|
||||
AsObj.print("sessionStorage.setItem::", arguments);
|
||||
local[key] = value;
|
||||
},
|
||||
clear: function clear() {
|
||||
local = {};
|
||||
},
|
||||
removeItem: function removeItem(key) {
|
||||
AsObj.print("sessionStorage.removeItem::", arguments);
|
||||
delete local[key];
|
||||
}
|
||||
}
|
||||
sessionStorage.__proto__ = Storage.prototype;
|
||||
|
||||
// window = watch(window, 'window');
|
||||
// global = watch(global, 'global');
|
||||
// globalThis = watch(globalThis, 'globalThis');
|
||||
// self = watch(self, 'self');
|
||||
// crypto = watch(crypto, 'crypto');
|
||||
// performance = watch(performance, 'performance');
|
||||
// document = watch(document, 'document');
|
||||
// navigator = watch(navigator, 'navigator');
|
||||
// location = watch(location, 'location');
|
||||
// screen = watch(screen, 'screen');
|
||||
// history = watch(history, 'history');
|
||||
// localStorage = watch(localStorage, 'localStorage');
|
||||
// sessionStorage = watch(sessionStorage, 'sessionStorage');
|
||||
// chrome = watch(chrome, 'chrome');
|
||||
|
||||
require_('./sdk_leg.js');
|
||||
|
||||
let input = '';
|
||||
// 收集数据
|
||||
process.stdin.on('data', chunk => {
|
||||
input += chunk;
|
||||
});
|
||||
process.stdin.on('end', async () => {
|
||||
var config_data = JSON.parse(input);
|
||||
var cryptoManager = await CaptchaSDKCorecc();
|
||||
var encryptData = await buildEncryptedVerifyRequestcc(config_data, cryptoManager);
|
||||
console.log(JSON.stringify(encryptData));
|
||||
process.exit(0);
|
||||
})
|
||||
Reference in New Issue
Block a user