diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index 44ef91f..a0cee56 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -1,5 +1,4 @@ name: Build and Release - on: push: tags: @@ -7,20 +6,26 @@ on: jobs: build-and-release: - runs-on: ubuntu-latest strategy: matrix: - python-version: [3.12] - os: [windows, linux] - arch: [x86, x64] + include: + - os: ubuntu-latest + platform: Linux + - os: windows-latest + platform: Windows + runs-on: ${{ matrix.os }} + permissions: + contents: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # 确保检出完整的Git历史 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + - name: Set up Python 3.11 + uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} + python-version: '3.11' - name: Install dependencies run: | @@ -28,26 +33,72 @@ jobs: pip install -r requirements.txt pip install pyinstaller - - name: Install Wine for Windows builds - if: matrix.os == 'windows' - run: sudo apt-get install -y wine - - - name: Build executables + - name: Build executables (Linux) + if: matrix.os == 'ubuntu-latest' run: | for script in BestDnsUpdater.py BestHostsUpdater-IPv4.py BestHostsUpdater.py; do - if [[ "${{ matrix.os }}" == "windows" ]]; then - wine pyinstaller --onefile "$script" - mv "dist/${script%.py}.exe" "dist/${script%.py}-${{ matrix.os }}-${{ matrix.arch }}.exe" - else - pyinstaller --onefile "$script" - mv "dist/${script%.py}" "dist/${script%.py}-${{ matrix.os }}-${{ matrix.arch }}" - fi + pyinstaller --onefile "$script" + mv "dist/${script%.py}" "dist/${script%.py}-Linux-x64" done + zip -j "cnNetTool-Linux-x64.zip" dist/*-Linux-x64 + + - name: Build executables (Windows) + if: matrix.os == 'windows-latest' + run: | + $scripts = @("BestDnsUpdater.py", "BestHostsUpdater-IPv4.py", "BestHostsUpdater.py") + if (Test-Path -Path "dist") { + Remove-Item -Recurse -Force "dist" + } + foreach ($script in $scripts) { + pyinstaller --onefile $script --uac-admin + $exeName = $script -replace '\.py$', '' + Move-Item "dist\$exeName.exe" "dist\$exeName-Windows-x64.exe" + } + Compress-Archive -Path dist\*-Windows-x64.exe -DestinationPath "cnNetTool-Windows-x64.zip" + shell: pwsh + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.platform }}-executables + path: | + cnNetTool-Linux-x64.zip + cnNetTool-Windows-x64.zip + + create-release: + needs: build-and-release + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # 确保检出完整的Git历史 + + - name: Download all artifacts + uses: actions/download-artifact@v3 - - name: Zip executables + - name: Get tag description + id: tag_description run: | - zip -j "dist/${{ matrix.os }}-${{ matrix.arch }}-executables.zip" dist/* + TAG_DESCRIPTION=$(git tag -l --format='%(contents)' ${{ github.ref_name }}) + echo "tag_description=${TAG_DESCRIPTION}" >> $GITHUB_ENV + - name: Get latest commit message + id: commit_message + run: | + COMMIT_MESSAGE=$(git log -1 --pretty=%B) + echo "commit_message=${COMMIT_MESSAGE}" >> $GITHUB_ENV + + - name: Delete existing release + uses: dev-drprasad/delete-tag-and-release@v0.2.1 + with: + tag_name: ${{ github.ref_name }} + delete_release: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Create Release id: create_release uses: actions/create-release@v1 @@ -55,16 +106,15 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref_name }} - release_name: Release ${{ github.ref_name }} + release_name: "${{ github.ref_name }} ${{ env.tag_description }}" draft: false prerelease: false - name: Upload Release Assets - uses: actions/upload-release-asset@v1 + uses: softprops/action-gh-release@v1 + with: + files: | + **/cnNetTool-Linux-x64.zip + **/cnNetTool-Windows-x64.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./dist/*-executables.zip - asset_name: ${{ matrix.os }}-${{ matrix.arch }}-executables.zip - asset_content_type: application/zip diff --git a/BestDnsUpdater.py b/BestDnsUpdater.py index 4fa88e9..bee09cf 100644 --- a/BestDnsUpdater.py +++ b/BestDnsUpdater.py @@ -70,7 +70,9 @@ } -def test_dns_server(server: str, domain: str, record_type: str) -> tuple[bool, float, list[str]]: +def test_dns_server( + server: str, domain: str, record_type: str +) -> tuple[bool, float, list[str]]: """ 测试指定的DNS服务器 @@ -88,9 +90,7 @@ def test_dns_server(server: str, domain: str, record_type: str) -> tuple[bool, f end_time = time.time() response_time = (end_time - start_time) * 1000 # 转换为毫秒 ips = [str(rdata) for rdata in answers] - logger.debug( - f"成功解析 {domain} 使用 {server} ({record_type}): {ips}" - ) + logger.debug(f"成功解析 {domain} 使用 {server} ({record_type}): {ips}") return True, response_time, ips except Exception as e: end_time = time.time() @@ -129,7 +129,7 @@ def find_available_dns() -> tuple[dict, dict]: """ dns_performance = {} domain_resolutions = {domain: {} for domain in DOMAINS_TO_TEST} - + with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor: future_to_server = {} for region, providers in DNS_SERVERS.items(): @@ -161,7 +161,7 @@ def find_available_dns() -> tuple[dict, dict]: for domain, ips in resolutions.items(): domain_resolutions[domain][server] = ips - logger.info( + logger.debug( f"{ip_version.upper()} DNS {server} ({region} - {provider}) 成功率 {success_rate:.2%}, 平均延迟 {avg_response_time:.2f}ms" ) except Exception as exc: @@ -446,58 +446,30 @@ def print_available_dns(available_dns, best_dns_num): print(table) print() + def get_input_with_timeout(prompt, timeout=10): - print(prompt, end='', flush=True) + print(prompt, end="", flush=True) user_input = [] - + def input_thread(): user_input.append(input()) - + thread = threading.Thread(target=input_thread) thread.daemon = True thread.start() - + thread.join(timeout) if thread.is_alive(): print("\n已超时,自动执行...") - return 'y' + return "y" print() # 换行 - return user_input[0].strip() if user_input else 'y' + return user_input[0].strip() if user_input else "y" + def main(): """ 主函数 """ - parser = argparse.ArgumentParser(description="DNS解析器和设置工具") - parser.add_argument("--debug", action="store_true", help="启用调试日志") - parser.add_argument( - "--show-availbale-list", - "--list", - action="store_true", - help="显示可用dns列表,通过 --num 控制娴熟数量", - ) - parser.add_argument( - "--best-dns-num", - "--num", - default=BEST_DNS_NUM, - type=int, - action="store", - help="显示最佳DNS服务器的数量", - ) - parser.add_argument( - "--algorithm", - "--mode", - choices=["region", "overall"], - default="region", - help="推荐最佳DNS的算法 (按区域或整体)", - ) - parser.add_argument( - "--show-resolutions", - "--show", - action="store_true", - help="显示域名解析结果", - ) - args = parser.parse_args() # 创建自定义的日志格式化器 log_formatter = logging.Formatter( @@ -525,9 +497,13 @@ def main(): if available_dns["ipv4"] or available_dns["ipv6"]: if args.show_resolutions: logger.info("显示域名解析结果...") - dns_performance = {server: info for dns_list in available_dns.values() for server, info in dns_list} + dns_performance = { + server: info + for dns_list in available_dns.values() + for server, info in dns_list + } print_domain_resolutions(domain_resolutions, dns_performance) - + # 防止 best_dns_num 数值超过数组长度 num_servers = min(len(available_dns["ipv4"]), len(available_dns["ipv6"])) if args.best_dns_num > num_servers: @@ -546,7 +522,9 @@ def main(): recommended_dns[ip_version], ip_version, available_dns ) - confirm = get_input_with_timeout("\n是否要设置系统DNS为推荐的最佳服务器?(y/n,10秒后自动执行): ", 10) + confirm = get_input_with_timeout( + "\n是否要设置系统DNS为推荐的最佳服务器?(y/n,10秒后自动执行): ", 10 + ) if confirm.lower() == "y": set_dns_servers(recommended_dns["ipv4"], recommended_dns["ipv6"]) logger.info("DNS服务器已更新") @@ -555,6 +533,7 @@ def main(): else: logger.warning("未找到合适的DNS服务器") + def is_admin() -> bool: """ 检查当前用户是否具有管理员权限 @@ -584,7 +563,41 @@ def run_as_admin(): os.execvp("sudo", ["sudo", "python3"] + sys.argv) sys.exit(0) + if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="DNS解析器和设置工具,请使用管理员权限运行" + ) + parser.add_argument("--debug", action="store_true", help="启用调试日志") + parser.add_argument( + "--show-availbale-list", + "--list", + action="store_true", + help="显示可用dns列表,通过 --num 控制娴熟数量", + ) + parser.add_argument( + "--best-dns-num", + "--num", + default=BEST_DNS_NUM, + type=int, + action="store", + help="显示最佳DNS服务器的数量", + ) + parser.add_argument( + "--algorithm", + "--mode", + choices=["region", "overall"], + default="region", + help="推荐最佳DNS的算法 (按区域或整体)", + ) + parser.add_argument( + "--show-resolutions", + "--show", + action="store_true", + help="显示域名解析结果", + ) + args = parser.parse_args() + if not is_admin(): logger.info("需要管理员权限来设置DNS服务器。正在尝试提升权限...") run_as_admin() diff --git a/BestHostsUpdater.py b/BestHostsUpdater.py index c47fd20..d9d29e5 100644 --- a/BestHostsUpdater.py +++ b/BestHostsUpdater.py @@ -30,7 +30,7 @@ def parse_args(): - parser = argparse.ArgumentParser(description="Hosts Updater Script") + parser = argparse.ArgumentParser(description="Hosts文件更新工具,请使用管理员权限运行") parser.add_argument( "--log-level", default="INFO", diff --git a/README.md b/README.md index 9decba9..56d930f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ # cnNetTool + +[![Release Version](https://img.shields.io/github/v/release/sinspired/cnNetTool?display_name=tag&logo=github&label=Release)](https://github.com/sinspired/cnNetTool/releases/latest) +[![GitHub repo size](https://img.shields.io/github/repo-size/sinspired/cnNetTool?logo=github) +](https://github.com/sinspired/cnNetTool) +[![GitHub last commit](https://img.shields.io/github/last-commit/sinspired/cnNetTool?logo=github&label=最后提交:)](ttps://github.com/sinspired/cnNetTool) + 基于Python的网络小工具。 针对某些区域DNS污染问题,自动筛选解析速度最快的 DNS 服务器、自动设置hosts文件(包括tinyManager刮削源tmdb.org、themoviedb.org,Google/chrome网页翻译等),支持Windows、Linux、MacOS。 @@ -16,11 +22,48 @@ pip install -r requirements.txt ``` 这将安装所有依赖项 +# 参数说明 + +**CloudflareBestIP** 可以接受以下参数: + +### DNS 服务器工具 `BestDnsUpdater.py` + +* --debug 启用调试日志 +* --show-availbale-list, --list 显示可用dns列表,通过 --num 控制娴熟数量 +* --best-dns-num BEST_DNS_NUM, --num 显示最佳DNS服务器的数量 +* --algorithm --mode {region,overall} 默认 `region` 平衡IPv4和ipv6 DNS +* --show-resolutions, --show 显示域名解析结果 + +### Hosts文件工具 `BestHostsUpdater.py` + +* --log-level 设置日志输出等级,'DEBUG', 'INFO', 'WARNING', 'ERROR' +* --num-fastest 设置选择的最快IP数量 +* --max-latency 设置允许的最大延迟(毫秒) + +命令行键入 `-h` `help` 获取帮助 + +`py BestDnsUpdater.py -h` + +`py BestHostsUpdater.py -h` + # 运行 -在项目目录运行,分别设置解析最快的DNS服务器,更新hosts文件。 +请使用管理员权限,在项目目录运行,分别设置解析最快的DNS服务器,更新hosts文件。 **接受传递参数,大部分时候直接运行即可**。 -```bsh +```bash py BestDnsUpdater.py py BestHostsUpdater.py ``` + +# 最新发行版下载 + +Windows下载可执行文件双击运行即可,注意使用管理员权限。linux系统使用sudo。 + +或在命令行设置参数运行: + +```pwsh +./BestDnsUpdater.exe --best-dns-num 10 +./BestHostsUpdater.exe --num-fastest 3 --max-latency 500 +``` + +[![Release Detail](https://img.shields.io/github/v/release/sinspired/cnNetTool?sort=date&display_name=release&logo=github&label=Release)](https://github.com/sinspired/cnNetTool/releases/latest) \ No newline at end of file