吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5422|回复: 65
收起左侧

[原创工具] 智能IP优选工具

  [复制链接]
huayi 发表于 2025-11-30 18:26
初衷:
经常遇到github进不去,或者TMM刮削不动的情况,上百度搜都是要去dnschecker.org找最新的解析IP。就感觉很麻烦。
于是我就想到了用千问AI写一个工具,自动处理这些。

功能:
解析网址获取最新的和最低延迟的IP,并写入到host文件里,达到可以直连的目的。
并且增加了一个网址预设功能,可以添加自己常用的切需要改host的网址。

下载链接:
https://iiicg.lanzouw.com/iwB7d3cixkxa


截图:
ScreenShot_2025-11-30_182200_186.jpg



源代码:
[Python] 纯文本查看 复制代码
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
import tkinter as tk
from tkinter import messagebox, simpledialog
import socket
import subprocess
import os
import sys
import threading
import json

# 尝试导入 dnspython
try:
    import dns.resolver
    USE_DNSPYTHON = True
except ImportError:
    USE_DNSPYTHON = False

# 预设文件路径(与脚本同目录)
PRESETS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "host_presets.json")

def load_presets():
    if os.path.exists(PRESETS_FILE):
        try:
            with open(PRESETS_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception:
            return ["github.com", "api.themoviedb.org", "www.themoviedb.org", "image.tmdb.org"]
    else:
        return ["github.com", "api.themoviedb.org", "www.themoviedb.org", "image.tmdb.org"]

def save_presets(presets):
    try:
        with open(PRESETS_FILE, 'w', encoding='utf-8') as f:
            json.dump(presets, f, indent=2, ensure_ascii=False)
    except Exception as e:
        print(f"保存预设失败: {e}")

def is_admin():
    try:
        return os.getuid() == 0
    except AttributeError:
        import ctypes
        return ctypes.windll.shell32.IsUserAnAdmin()

def flush_dns():
    try:
        if sys.platform == "win32":
            startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            startupinfo.wShowWindow = subprocess.SW_HIDE

            subprocess.run(
                ["ipconfig", "/flushdns"],
                check=True,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                startupinfo=startupinfo
            )
        elif sys.platform == "darwin":
            subprocess.run(["sudo", "killall", "-HUP", "mDNSResponder"], check=False, stdout=subprocess.DEVNULL)
            subprocess.run(["sudo", "dscacheutil", "-flushcache"], check=False, stdout=subprocess.DEVNULL)
        else:
            subprocess.run(["sudo", "systemd-resolve", "--flush-caches"], check=False, stdout=subprocess.DEVNULL)
            subprocess.run(["sudo", "nscd", "-i", "hosts"], check=False, stdout=subprocess.DEVNULL)
        return True
    except Exception:
        return False

def get_all_ips(domain):
    ips = set()
    if USE_DNSPYTHON:
        for nameserver in ['8.8.8.8', '1.1.1.1', '223.5.5.5']:
            resolver = dns.resolver.Resolver()
            resolver.nameservers = [nameserver]
            try:
                answers = resolver.resolve(domain, 'A', lifetime=3)
                for rdata in answers:
                    ips.add(rdata.to_text())
            except Exception:
                continue
    else:
        try:
            _, _, ip_list = socket.gethostbyname_ex(domain)
            ips.update(ip_list)
        except Exception:
            try:
                ip = socket.gethostbyname(domain)
                ips.add(ip)
            except Exception:
                pass
    return list(ips)

def ping_ip(ip, timeout=2):
    try:
        if sys.platform == "win32":
            startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            startupinfo.wShowWindow = subprocess.SW_HIDE

            result = subprocess.run(
                ["ping", "-n", "1", "-w", str(int(timeout * 1000)), ip],
                stdout=subprocess.PIPE,
                stderr=subprocess.DEVNULL,
                text=True,
                startupinfo=startupinfo
            )
            if result.returncode == 0:
                for line in result.stdout.splitlines():
                    if "平均" in line or "Average" in line:
                        import re
                        match = re.search(r"(\d+)(?:ms|毫秒)", line)
                        if match:
                            return int(match.group(1))
                        if "<1" in line:
                            return 1
            return None
        else:
            result = subprocess.run(
                ["ping", "-c", "1", "-W", str(timeout), ip],
                stdout=subprocess.PIPE,
                stderr=subprocess.DEVNULL,
                text=True
            )
            if result.returncode == 0:
                for line in result.stdout.splitlines():
                    if "time=" in line:
                        start = line.find("time=") + 5
                        end = line.find(" ", start)
                        delay = float(line[start:end])
                        return int(round(delay))
            return None
    except Exception:
        return None

def resolve_and_ping(domain, progress_callback, result_callback):
    ips = get_all_ips(domain)
    if not ips:
        result_callback([])
        return

    results = []
    total = len(ips)
    for i, ip in enumerate(ips):
        progress_callback(f"Ping {ip} ({i+1}/{total})...")
        delay = ping_ip(ip)
        results.append((ip, delay))
    results.sort(key=lambda x: (x[1] is None, x[1]))
    result_callback(results)

class SmartHostsApp:
    def __init__(self, root):
        self.root = root
        self.root.title("&#10024; 智能 Hosts 优化工具")
        self.selected_ip = None
        self.domain = ""
        self.presets = load_presets()
        self.create_widgets()

    def create_widgets(self):
        main_frame = ttk.Frame(self.root, padding=15)
        main_frame.pack(fill=BOTH, expand=YES)

        paned = ttk.Panedwindow(main_frame, orient=tk.HORIZONTAL)
        paned.pack(fill=BOTH, expand=YES, pady=(0, 10))

        left_frame = ttk.Labelframe(paned, text="&#128204; 常用域名", padding=10)
        paned.add(left_frame, weight=1)

        self.preset_listbox = tk.Listbox(
            left_frame,
            font=("Consolas", 10),
            selectmode=tk.SINGLE,
            exportselection=False
        )
        self.preset_listbox.pack(fill=BOTH, expand=YES, pady=(0, 10))
        self.preset_listbox.bind("<<ListboxSelect>>", self.on_preset_select)

        btn_frame_left = ttk.Frame(left_frame)
        btn_frame_left.pack(fill=X)

        ttk.Button(
            btn_frame_left,
            text="&#10133; 添加",
            bootstyle=SUCCESS,
            command=self.add_preset
        ).pack(side=LEFT, fill=X, expand=YES, padx=(0, 5))

        ttk.Button(
            btn_frame_left,
            text="&#128465; 删除",
            bootstyle=DANGER,
            command=self.remove_preset
        ).pack(side=LEFT, fill=X, expand=YES, padx=(5, 0))

        right_frame = ttk.Frame(paned)
        paned.add(right_frame, weight=2)

        title_label = ttk.Label(
            right_frame,
            text="智能 Hosts 优化工具",
            font=("Helvetica", 18, "bold"),
            bootstyle=PRIMARY
        )
        title_label.pack(pady=(0, 15))

        input_frame = ttk.Frame(right_frame)
        input_frame.pack(fill=X, pady=5)
        ttk.Label(input_frame, text="域名:", font=("Helvetica", 10)).pack(side=LEFT)
        self.domain_entry = ttk.Entry(input_frame, font=("Consolas", 11))
        self.domain_entry.pack(side=LEFT, fill=X, expand=YES, padx=(5, 10))
        self.domain_entry.focus()
        self.resolve_btn = ttk.Button(
            input_frame,
            text="&#128269; 查询并 Ping",
            bootstyle=SUCCESS,
            command=self.start_resolve
        )
        self.resolve_btn.pack(side=RIGHT)

        self.status_var = tk.StringVar(value="请输入域名或从左侧选择常用域名")
        status_label = ttk.Label(right_frame, textvariable=self.status_var, font=("Helvetica", 9), bootstyle=INFO)
        status_label.pack(pady=(5, 10))

        table_frame = ttk.Frame(right_frame)
        table_frame.pack(fill=BOTH, expand=YES, pady=10)

        columns = ("IP", "延迟 (ms)")
        self.tree = ttk.Treeview(
            table_frame,
            columns=columns,
            show="headings",
            selectmode="browse",
            bootstyle=PRIMARY
        )
        self.tree.heading("IP", text="IP 地址")
        self.tree.heading("延迟 (ms)", text="延迟 (ms)")
        self.tree.column("IP", width=180, anchor=W)
        self.tree.column("延迟 (ms)", width=120, anchor=CENTER)

        vsb = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.tree.yview)
        hsb = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL, command=self.tree.xview)
        self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)

        self.tree.grid(row=0, column=0, sticky=NSEW)
        vsb.grid(row=0, column=1, sticky=NS)
        hsb.grid(row=1, column=0, sticky=EW)

        table_frame.grid_rowconfigure(0, weight=1)
        table_frame.grid_columnconfigure(0, weight=1)

        self.tree.bind("<<TreeviewSelect>>", self.on_select)

        btn_frame = ttk.Frame(right_frame)
        btn_frame.pack(pady=15)

        self.apply_btn = ttk.Button(
            btn_frame,
            text="&#9989; 写入 Hosts",
            bootstyle=PRIMARY,
            state=DISABLED,
            command=self.apply_hosts
        )
        self.apply_btn.pack(side=LEFT, padx=5)

        self.flush_btn = ttk.Button(
            btn_frame,
            text="&#128260; 刷新 DNS",
            bootstyle=INFO,
            command=self.do_flush_dns
        )
        self.flush_btn.pack(side=LEFT, padx=5)

        footer = ttk.Label(
            right_frame,
            text="&#128161; 提示:点击左侧常用域名快速查询;延迟越低越好",
            font=("Helvetica", 8),
            bootstyle=SECONDARY
        )
        footer.pack(side=BOTTOM, anchor=W)

        self.refresh_preset_list()

    def refresh_preset_list(self):
        self.preset_listbox.delete(0, tk.END)
        for domain in self.presets:
            self.preset_listbox.insert(tk.END, domain)

    def on_preset_select(self, event):
        selection = self.preset_listbox.curselection()
        if selection:
            domain = self.preset_listbox.get(selection[0])
            self.domain_entry.delete(0, tk.END)
            self.domain_entry.insert(0, domain)
            self.start_resolve()

    def add_preset(self):
        domain = simpledialog.askstring("添加常用域名", "请输入域名(如:example.com):", parent=self.root)
        if domain:
            domain = domain.strip().lower()
            if domain and domain not in self.presets:
                self.presets.append(domain)
                save_presets(self.presets)
                self.refresh_preset_list()
            elif domain in self.presets:
                messagebox.showinfo("提示", "该域名已在列表中!", parent=self.root)

    def remove_preset(self):
        selection = self.preset_listbox.curselection()
        if not selection:
            messagebox.showwarning("操作失败", "请先选择一个域名!", parent=self.root)
            return
        idx = selection[0]
        domain = self.presets[idx]
        if messagebox.askyesno("确认删除", f"确定要删除「{domain}」吗?", parent=self.root):
            del self.presets[idx]
            save_presets(self.presets)
            self.refresh_preset_list()

    def on_select(self, event):
        selected = self.tree.selection()
        if selected:
            item = self.tree.item(selected[0])
            self.selected_ip = item["values"][0]
            self.apply_btn.config(state=NORMAL)

    def clear_table(self):
        for item in self.tree.get_children():
            self.tree.delete(item)

    def update_status(self, msg):
        self.status_var.set(msg)
        self.root.update_idletasks()

    def start_resolve(self):
        domain = self.domain_entry.get().strip()
        if not domain:
            messagebox.showwarning("输入错误", "请输入一个有效的域名!", parent=self.root)
            return
        self.domain = domain
        self.status_var.set("正在解析并 Ping 所有 IP,请稍候...")
        self.resolve_btn.config(state=DISABLED)
        self.apply_btn.config(state=DISABLED)
        self.clear_table()

        threading.Thread(
            target=resolve_and_ping,
            args=(domain, self.update_status, self.on_complete),
            daemon=True
        ).start()

    def on_complete(self, results):
        self.clear_table()
        if not results:
            self.status_var.set("&#10060; 未找到有效 IP")
            messagebox.showerror("错误", "无法解析该域名或所有 IP 均无响应。", parent=self.root)
        else:
            self.status_var.set(f"&#9989; 共找到 {len(results)} 个 IP(按延迟排序)")
            for ip, delay in results:
                disp_delay = str(delay) if delay is not None else "超时"
                self.tree.insert("", tk.END, values=(ip, disp_delay))
        self.resolve_btn.config(state=NORMAL)

    def apply_hosts(self):
        if not self.selected_ip or not self.domain:
            return

        if not is_admin():
            messagebox.showerror("权限不足", "请以管理员身份运行本程序才能修改 hosts 文件!", parent=self.root)
            return

        hosts_path = r"C:\Windows\System32\drivers\etc\hosts" if os.name == 'nt' else "/etc/hosts"
        new_entry = f"{self.selected_ip}\t{self.domain}"

        try:
            if os.path.exists(hosts_path):
                with open(hosts_path, 'r', encoding='utf-8') as f:
                    lines = f.read().splitlines()
            else:
                lines = []

            filtered_lines = []
            for line in lines:
                stripped = line.strip()
                if stripped and not stripped.startswith('#'):
                    parts = stripped.split()
                    if len(parts) >= 2 and parts[-1] != self.domain:
                        filtered_lines.append(line)
                else:
                    filtered_lines.append(line)

            filtered_lines.append(new_entry)
            content = "\n".join(filtered_lines) + "\n"

            with open(hosts_path, 'w', encoding='utf-8') as f:
                f.write(content)

            messagebox.showinfo("成功", f"已写入 hosts:\n{new_entry}", parent=self.root)
            self.do_flush_dns(silent=True)

        except Exception as e:
            messagebox.showerror("写入失败", f"错误: {e}", parent=self.root)

    def do_flush_dns(self, silent=False):
        if flush_dns():
            if not silent:
                messagebox.showinfo("成功", "DNS 缓存已刷新!", parent=self.root)
        else:
            if not silent:
                messagebox.showwarning("警告", "DNS 刷新失败(可能需要管理员权限)", parent=self.root)

if __name__ == "__main__":
    if not USE_DNSPYTHON:
        print("[提示] 未安装 dnspython,将使用基础 DNS 查询(可能只返回一个 IP)")
        print("建议运行: pip install dnspython")

    app = ttk.Window(
        title="智能 Hosts 优化工具",
        themename="darkly",
        size=(850, 600),
        resizable=(True, True)
    )
    SmartHostsApp(app)
    app.mainloop()

免费评分

参与人数 22吾爱币 +26 热心值 +22 收起 理由
houmasv + 1 谢谢@Thanks!
barry1204 + 1 + 1 谢谢@Thanks!
aabbcc123123 + 1 + 1 谢谢@Thanks!
KenDvD + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
guoxinpang + 1 + 1 热心回复!
Wiltord + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
小师叔 + 1 + 1 谢谢@Thanks!
deathaccount + 1 + 1 我很赞同!
xzhang8 + 1 + 1 谢谢@Thanks!
cnngtc + 2 + 1 可以的 试了一下还可以
my621 + 1 + 1 必须点赞。
xiakexing + 1 + 1 常用域名里面的域名是怎么找到的?
cydlongzhe + 1 + 1 谢谢@Thanks!
xxssvip + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
caoxuexin + 1 + 1 我很赞同!
anter999 + 1 + 1 热心回复!
4DJnUll + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
fsaac + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
confiant + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
pentium + 1 我很赞同!
zhangwei6929 + 1 + 1 感谢高手分享!~
风之暇想 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

xiaofeiTM233 发表于 2025-12-14 10:54
本帖最后由 xiaofeiTM233 于 2025-12-16 20:14 编辑

我个人是在itdog里面把解析出的ip复制,然后到本地的批量tcping工具里粘贴看延迟和丢包,这样筛选,可能会比本地解析好一些
补充,我用的批量ping工具 pinginfoview 站内有搬运

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
aabbcc123123 + 1 + 1 谢谢@Thanks!

查看全部评分

yanguichao 发表于 2025-12-3 15:02
这个github有时候真是让人一脸懵X,说是被墙了吧,有时候又能够打开,说没被墙吧,关键时候又会掉链子。
深蓝2015 发表于 2025-12-2 15:00
pentium 发表于 2025-12-2 16:26
感谢分享,支持有源代码的
wosiniuniu 发表于 2025-12-2 16:59
是做啥用的不太懂
 楼主| huayi 发表于 2025-12-2 17:53
wosiniuniu 发表于 2025-12-2 16:59
是做啥用的不太懂

解决某些无法打开或者打开慢的网站,把它们延迟最小的IP记录到host里,就可以打开了。
Du1223 发表于 2025-12-2 19:19
真不错,论坛有你是幸事
稻草瓶子 发表于 2025-12-3 07:50
本帖最后由 稻草瓶子 于 2025-12-3 09:58 编辑

好用。。。
yuball 发表于 2025-12-3 10:28
楼主你是个好人
asd1226 发表于 2025-12-3 10:44
感谢,,,这个真的需要。。
loverix 发表于 2025-12-3 10:55
感谢,感谢,这个是个好东西
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-6-30 07:23

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表