Python 廣告阻擋腳本

Created on: December 7, 2024

Answered using OpenAI 01 by OpenAI01

Question

寫一個用python 給window使用的block廣告腳本
1. 用dns設定 (ad_block_dns_ipv4 = ["94.140.14.14"] ad_block_dns_ipv6 = ["2a10:50c0::1"])
2.要有介面 可開可關
3.重開軟件後要知道是否正在開住 block廣告

Thought

重启记忆功能

用户需求界面控制广告阻止器,并且重启后记得开关状态。界面可能使用Tkinter或PyQt。

思考 花的時間為 2 秒

Answer

要在 Windows 上使用 Python 編寫一個廣告阻擋腳本,並滿足以下需求:

  1. 使用指定的 DNS 設定

    • IPv4: 94.140.14.14
    • IPv6: 2a10:50c0::1
  2. 提供一個圖形介面 (GUI),允許用戶啟用或禁用廣告阻擋。

  3. 記憶狀態,在軟件重啟後仍能知道廣告阻擋是否啟用。

以下是一個完整的 Python 腳本示例,使用 Tkinter 作為 GUI 框架,並使用 subprocess 模組來執行 netsh 命令以更改 DNS 設定。此外,狀態會保存在一個簡單的 JSON 文件中,以便在軟件重啟後保持狀態。

注意事項

  • 此腳本需要以管理員權限運行,因為它需要更改系統的網路設定。
  • 這個腳本僅適用於使用 netsh 管理的網路介面,可能需要根據具體的網路配置進行調整。
  • 確保在執行前備份原始的 DNS 設定,以便在需要時恢復。
import os import subprocess import tkinter as tk from tkinter import messagebox import json # 設定檔案路徑 CONFIG_FILE = "ad_block_config.json" # 廣告阻擋 DNS 設定 AD_BLOCK_DNS_IPV4 = ["94.140.14.14"] AD_BLOCK_DNS_IPV6 = ["2a10:50c0::1"] # 預設 DNS 設定(根據需要調整) DEFAULT_DNS_IPV4 = ["8.8.8.8", "8.8.4.4"] DEFAULT_DNS_IPV6 = ["2001:4860:4860::8888", "2001:4860:4860::8844"] def is_admin(): try: return os.getuid() == 0 except AttributeError: # Windows 不支援 os.getuid,使用 ctypes import ctypes try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False def get_network_adapters(): """取得所有有線和無線網路介面的名稱""" cmd = 'netsh interface show interface' result = subprocess.run(cmd, capture_output=True, text=True, shell=True) interfaces = [] for line in result.stdout.splitlines(): if "Connected" in line or "Disconne" in line: parts = line.split() name = ' '.join(parts[3:]) interfaces.append(name) return interfaces def set_dns(interfaces, ipv4, ipv6): for adapter in interfaces: # 設定 IPv4 DNS cmd_ipv4 = f'netsh interface ip set dns name="{adapter}" static {ipv4[0]} primary' subprocess.run(cmd_ipv4, shell=True) for dns in ipv4[1:]: cmd_add = f'netsh interface ip add dns name="{adapter}" {dns} index=2' subprocess.run(cmd_add, shell=True) # 設定 IPv6 DNS cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" static {ipv6[0]} primary' subprocess.run(cmd_ipv6, shell=True) for dns in ipv6[1:]: cmd_add = f'netsh interface ipv6 add dnsservers name="{adapter}" {dns} index=2' subprocess.run(cmd_add, shell=True) def reset_dns(interfaces): for adapter in interfaces: # 設定 DNS 為自動獲取 cmd_ipv4 = f'netsh interface ip set dns name="{adapter}" dhcp' subprocess.run(cmd_ipv4, shell=True) cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" source=dhcp' subprocess.run(cmd_ipv6, shell=True) def load_config(): if not os.path.exists(CONFIG_FILE): return {"ad_block_enabled": False} with open(CONFIG_FILE, "r") as f: return json.load(f) def save_config(config): with open(CONFIG_FILE, "w") as f: json.dump(config, f) def toggle_ad_block(): config = load_config() interfaces = get_network_adapters() if not interfaces: messagebox.showerror("錯誤", "未找到網路介面。") return if config.get("ad_block_enabled", False): # 禁用廣告阻擋,恢復預設 DNS reset_dns(interfaces) config["ad_block_enabled"] = False status_var.set("廣告阻擋已禁用") else: # 啟用廣告阻擋,設定廣告阻擋 DNS set_dns(interfaces, AD_BLOCK_DNS_IPV4, AD_BLOCK_DNS_IPV6) config["ad_block_enabled"] = True status_var.set("廣告阻擋已啟用") save_config(config) def update_status(): config = load_config() if config.get("ad_block_enabled", False): status_var.set("廣告阻擋已啟用") else: status_var.set("廣告阻擋已禁用") def main(): if not is_admin(): messagebox.showerror("權限不足", "請以管理員身份運行此程序。") return root = tk.Tk() root.title("廣告阻擋工具") root.geometry("300x150") root.resizable(False, False) global status_var status_var = tk.StringVar() update_status() status_label = tk.Label(root, textvariable=status_var, pady=20) status_label.pack() toggle_button = tk.Button(root, text="切換廣告阻擋", command=toggle_ad_block, width=20) toggle_button.pack(pady=10) root.mainloop() if __name__ == "__main__": main()

腳本說明

  1. 管理員權限檢查

    • 使用 is_admin() 函數檢查腳本是否以管理員身份運行。如果不是,顯示錯誤訊息並終止程序。
  2. 取得網路介面

    • 使用 netsh interface show interface 命令列出所有網路介面,並過濾出已連接的介面名稱。
  3. 設定 DNS

    • set_dns() 函數使用 netsh 命令將指定的 IPv4 和 IPv6 DNS 設定應用到所有網路介面。
    • reset_dns() 函數將 DNS 設定恢復為自動獲取(DHCP)。
  4. 配置管理

    • 使用 JSON 文件 (ad_block_config.json) 來儲存廣告阻擋的狀態 (ad_block_enabled)。
    • load_config()save_config() 函數分別負責讀取和寫入配置文件。
  5. 圖形介面 (GUI)

    • 使用 Tkinter 建立一個簡單的窗口,顯示當前的廣告阻擋狀態,並提供一個按鈕來切換狀態。
    • 當用戶點擊按鈕時,toggle_ad_block() 函數會根據當前狀態啟用或禁用廣告阻擋,並更新配置文件和 GUI 顯示。
  6. 狀態更新

    • 每次啟動時,update_status() 函數會根據配置文件的內容設置狀態標籤,確保重啟後仍能反映當前狀態。

使用說明

  1. 安裝 Python: 確保已安裝 Python 3.x,並將其添加到系統的 PATH 環境變數中。

  2. 保存腳本: 將上述腳本保存為 ad_blocker.py

  3. 以管理員身份運行

    • 右鍵點擊命令提示符或 PowerShell,選擇「以管理員身份運行」。
    • 瀏覽到腳本所在目錄,運行 python ad_blocker.py
  4. 操作 GUI

    • 程序啟動後,會顯示當前廣告阻擋的狀態。
    • 點擊「切換廣告阻擋」按鈕以啟用或禁用廣告阻擋。
  5. 自動啟動(可選): 若希望此腳本在每次 Windows 啟動時自動運行,可以將其添加到「啟動」文件夾,或使用任務排程器設置。

額外建議

  • 恢復原始 DNS: 如果您不確定原始的 DNS 設定,建議在第一次啟用廣告阻擋前手動記錄當前的 DNS 設定,或修改腳本以自動備份和恢復原始設定。

  • 錯誤處理: 根據實際使用情況,可能需要添加更多的錯誤處理,例如處理 netsh 命令失敗的情況,或網路介面變更時的情況。

  • 多語言支持: 根據用戶需求,可以擴展腳本以支持多種語言的界面。

希望這個腳本能滿足您的需求,並幫助您在 Windows 上有效地阻擋廣告!

Question

要以管理員身份開 請開腳本時要求要管理員權限
請不要再有錯 思考多點 詳細一點

Thought

Ensuring administrative privileges

I’m crafting a Windows script with ctypes to confirm admin rights at startup, re-running as admin if needed, and managing DNS settings efficiently.

Facilitating rerun as admin

I’m outlining code to re-run a script as admin using ctypes, handle user cancellation, manage DNS settings, and ensure the config file path is absolute.

Thought about admin rights handling 花的時間為 4 秒

Answer

下面的範例程式碼是以 Windows 平台為目標,以 Python 撰寫的廣告阻擋腳本。該腳本在啟動時會自動判斷是否以管理員 (Administrator) 權限執行,如非管理員將嘗試以管理員身份重新啟動程式。程式利用 netsh 指令設定 DNS,以達到阻擋廣告的效果,同時提供簡易的圖形化介面 (GUI) 來切換廣告阻擋狀態。程式會將狀態資訊儲存在 JSON 檔案中,以便重啟後能保持先前的啟動或關閉狀態。

功能說明

  1. 自動要求管理員權限
    若非以管理員身份執行,程式將嘗試透過 ShellExecute 提升權限並重新執行自己。

  2. 使用特定的 DNS 設定來阻擋廣告
    預設使用下列 DNS:

    • IPv4: 94.140.14.14
    • IPv6: 2a10:50c0::1
  3. 可在程式介面中開啟或關閉廣告阻擋
    按下按鈕即可切換狀態,無需手動修改系統設定。

  4. 狀態持久化
    使用 ad_block_config.json 檔案儲存當前狀態,以便下次程式重啟時能直接知道上次的狀態。

  5. 回復機制
    當您關閉廣告阻擋時,腳本會將 DNS 設定恢復為自動取得 (DHCP),以確保不影響正常網路使用。

確保系統需求

  • 請確保電腦上已安裝 Python 3.x 並加入 PATH。
  • 執行本程式前,請確保您具備網路管理的基礎知識。如對原始 DNS 設定有特殊需求,請先備份設定或自行調整程式碼。
  • 本程式需在 Windows 平台上執行,並利用 netsh 指令來修改 DNS。

原始碼範例

請將下列程式碼儲存為 ad_blocker.py,以便後續執行:

import os import sys import ctypes import subprocess import tkinter as tk from tkinter import messagebox import json # 設定檔案名稱 CONFIG_FILE = "ad_block_config.json" # 廣告阻擋用的 DNS 設定 (可根據需要調整) AD_BLOCK_DNS_IPV4 = ["94.140.14.14"] AD_BLOCK_DNS_IPV6 = ["2a10:50c0::1"] # 若未啟用廣告阻擋時,預設將 DNS 設定為自動取得 # (此處不預設特定 DNS,直接使用 DHCP) # 若您希望恢復特定的自訂 DNS,可在這裡增加預設的 DNS。 # 本程式將在關閉時以 DHCP 模式還原 DNS。 def run_as_admin(): """ 若目前非以管理員權限執行,嘗試以管理員身份重新執行本程式。 成功後程式會自行結束原進程並由新進程接管。 若使用者拒絕提升權限,將顯示錯誤訊息並終止程式。 """ try: # 在 Windows 上,IsUserAnAdmin() 可用來判斷是否為管理員 if ctypes.windll.shell32.IsUserAnAdmin(): return True else: # 若非管理員,嘗試以管理員模式重新啟動 params = " ".join(sys.argv[1:]) # ShellExecuteW執行提權 h_instance = ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__ + " " + params, None, 1) if h_instance <= 32: # 使用者可能拒絕提權或發生錯誤 messagebox.showerror("錯誤", "需要以系統管理員身份執行此程式。請重新嘗試並允許提升權限。") sys.exit(1) else: # 成功提權後,原程式結束,等待新程式執行 sys.exit(0) except Exception as e: messagebox.showerror("錯誤", f"嘗試提升權限時發生錯誤:{e}") sys.exit(1) def load_config(): """ 載入程式配置檔(JSON格式),用來記錄廣告阻擋的啟用狀態。 若檔案不存在,使用預設狀態。 """ if not os.path.exists(CONFIG_FILE): return {"ad_block_enabled": False} try: with open(CONFIG_FILE, "r", encoding="utf-8") as f: return json.load(f) except: # 若讀取失敗,回傳預設 return {"ad_block_enabled": False} def save_config(config): """ 將當前狀態寫回配置檔,以便下次程式重啟時仍保有狀態。 """ with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=4) def get_network_adapters(): """ 使用 netsh 指令取得目前系統的網路介面名稱清單。 僅處理「已連線」或「已斷線」的介面。 """ cmd = 'netsh interface show interface' result = subprocess.run(cmd, capture_output=True, text=True, shell=True) if result.returncode != 0: messagebox.showerror("錯誤", "無法取得網路介面列表,請確認系統環境。") return [] interfaces = [] for line in result.stdout.splitlines(): if "Connected" in line or "Disconnected" in line or "連線" in line or "斷線" in line: parts = line.split() if len(parts) >= 4: name = ' '.join(parts[3:]) interfaces.append(name) return interfaces def set_dns(interfaces, ipv4_list, ipv6_list): """ 為所有指定的網路介面設定靜態 DNS。 ipv4_list、ipv6_list為包含 DNS 伺服器的列表。 """ for adapter in interfaces: # 設定 IPv4 DNS (第一個為 Primary) if ipv4_list: cmd_ipv4 = f'netsh interface ip set dns name="{adapter}" static {ipv4_list[0]} primary' subprocess.run(cmd_ipv4, shell=True) for i, dns in enumerate(ipv4_list[1:], start=2): cmd_add = f'netsh interface ip add dns name="{adapter}" {dns} index={i}' subprocess.run(cmd_add, shell=True) # 設定 IPv6 DNS (第一個為 Primary) if ipv6_list: cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" static {ipv6_list[0]} primary' subprocess.run(cmd_ipv6, shell=True) for i, dns in enumerate(ipv6_list[1:], start=2): cmd_add = f'netsh interface ipv6 add dnsservers name="{adapter}" {dns} index={i}' subprocess.run(cmd_add, shell=True) def reset_dns(interfaces): """ 將所有指定的網路介面的 DNS 設定恢復為自動取得 (DHCP)。 """ for adapter in interfaces: cmd_ipv4 = f'netsh interface ip set dns name="{adapter}" dhcp' subprocess.run(cmd_ipv4, shell=True) cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" source=dhcp' subprocess.run(cmd_ipv6, shell=True) def toggle_ad_block(): """ 切換廣告阻擋狀態。 若目前已啟用,則禁用並恢復為 DHCP。 若目前未啟用,則設定廣告阻擋 DNS。 """ config = load_config() interfaces = get_network_adapters() if not interfaces: messagebox.showerror("錯誤", "未找到可用的網路介面,無法修改 DNS 設定。") return if config.get("ad_block_enabled", False): # 已啟用廣告阻擋,現在將其關閉並恢復為 DHCP reset_dns(interfaces) config["ad_block_enabled"] = False status_var.set("廣告阻擋已禁用") else: # 尚未啟用廣告阻擋,現在設定廣告阻擋 DNS set_dns(interfaces, AD_BLOCK_DNS_IPV4, AD_BLOCK_DNS_IPV6) config["ad_block_enabled"] = True status_var.set("廣告阻擋已啟用") save_config(config) def update_status(): """ 根據配置檔更新介面上的狀態顯示。 """ config = load_config() if config.get("ad_block_enabled", False): status_var.set("廣告阻擋已啟用") else: status_var.set("廣告阻擋已禁用") def main(): # 首先嘗試以管理員身份執行 if not run_as_admin(): # 若無法取得管理員權限,直接結束 return # 建立 Tkinter 視窗 root = tk.Tk() root.title("廣告阻擋工具") root.geometry("300x150") root.resizable(False, False) global status_var status_var = tk.StringVar() update_status() status_label = tk.Label(root, textvariable=status_var, pady=20, font=("Arial", 12)) status_label.pack() toggle_button = tk.Button(root, text="切換廣告阻擋", command=toggle_ad_block, width=20, font=("Arial", 10)) toggle_button.pack(pady=10) root.mainloop() if __name__ == "__main__": main()

使用步驟

  1. 安裝 Python
    確保系統已安裝 Python 3.x,並且已將 Python 加入系統路徑 (PATH)。

  2. 執行腳本
    在儲存 ad_blocker.py 的資料夾中,以滑鼠右鍵點選「在此處開啟命令提示字元」或 PowerShell(以系統管理員身份執行)。

    執行指令:

    python ad_blocker.py
    

    若您直接執行而非以系統管理員身份啟動終端,程式將自動嘗試提升權限並重新啟動自己。如果使用者拒絕提權,程式將顯示錯誤訊息並結束。

  3. 切換狀態
    程式啟動後會顯示目前的廣告阻擋狀態 (啟用或禁用)。按下「切換廣告阻擋」按鈕即可在啟用和禁用之間切換。

  4. 重啟程式維持狀態
    當您關閉程式後,再次啟動時,程式會讀取 ad_block_config.json 狀態檔,並自動顯示先前狀態。

備註

  • 執行此程式須謹慎,因為它將修改系統的 DNS 設定。
  • 建議在初次使用前備份原本的 DNS 設定或在程式中加入您想要的預設 DNS,以便日後快速恢復。
  • 若遇到任何錯誤或想要客製化 DNS 列表,請依實際需求修改程式碼中對 DNS 的設定。

此程式碼已盡力考慮常見問題並增加詳細註解,希望能滿足您的需求並在使用時不再出現錯誤。

Question

閃退

Thought

Enhancing user experience

I'm thinking through how to avoid abrupt script closures by addressing potential issues with the run_as_admin logic, especially when encountering 閃退 on some Windows systems.

Assessing alternatives

I’m considering fallback methods when run_as_admin issues arise, like using ctypes for messagebox instead of tkinter, and ensuring __file__ is accurately accessed.

Ensuring clarity

I’m refining the run_as_admin logic to handle scenarios without tkinter, including using sys.executable and sys.argv, and providing clear console messages for elevation issues.

Thought about handling administrator permissions 花的時間為 6 秒

Answer

下面提供一個更為穩定的範例,解決可能的「閃退」問題。常見原因可能是:

  1. 未以管理員權限執行
    原本程式在檢測到非管理員後,會嘗試以管理員模式重新啟動。如果使用者拒絕或者出現路徑問題,可能導致程式立即結束(看似閃退)。
    改善方法:在要求管理員權限時,不使用 messagebox ,直接使用印出錯誤並 sys.exit() ,確保程式不會在沒有 Tkinter 視窗的狀態下嘗試顯示對話框,造成意料外行為。

  2. __file__ 變數問題
    部分執行環境中 __file__ 可能不存在,我們改用 os.path.abspath(sys.argv[0]) 獲取程式自身路徑,以確保能正確提權。

  3. 在 run_as_admin 前就顯示對話框
    為避免在提升權限過程中使用 Tkinter 的對話框,run_as_admin 中若提升失敗,改以 print 顯示錯誤訊息並 sys.exit() 結束程式。
    當提權成功後,再開始 Tkinter 界面相關動作。

下方程式碼經過調整,可降低「閃退」發生的機率。

import os import sys import ctypes import subprocess import tkinter as tk from tkinter import messagebox import json CONFIG_FILE = "ad_block_config.json" AD_BLOCK_DNS_IPV4 = ["94.140.14.14"] AD_BLOCK_DNS_IPV6 = ["2a10:50c0::1"] def is_admin(): # 使用 IsUserAnAdmin 檢查是否有管理員權限 try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False def run_as_admin(): """ 若目前非管理員,嘗試以系統管理員身份重新啟動本程式。 如果使用者拒絕或發生錯誤,將直接結束程式。 """ if is_admin(): return True # 準備以管理員身份重新執行本檔案 script = os.path.abspath(sys.argv[0]) params = " ".join(sys.argv[1:]) h_instance = ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, f'"{script}" {params}', None, 1) if h_instance <= 32: # 提權失敗或使用者拒絕 print("需要以系統管理員身份執行此程式。請重新嘗試並允許提升權限。") sys.exit(1) else: # 成功提權後,原程式結束,等待新程式執行 sys.exit(0) def load_config(): if not os.path.exists(CONFIG_FILE): return {"ad_block_enabled": False} try: with open(CONFIG_FILE, "r", encoding="utf-8") as f: return json.load(f) except: return {"ad_block_enabled": False} def save_config(config): with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=4) def get_network_adapters(): cmd = 'netsh interface show interface' result = subprocess.run(cmd, capture_output=True, text=True, shell=True) if result.returncode != 0: return [] interfaces = [] for line in result.stdout.splitlines(): # 尋找線上或離線的介面行 # "Connected" / "Disconnected" 或本地化語言表述 if ("Connected" in line or "Disconnected" in line or "連線" in line or "斷線" in line): parts = line.split() if len(parts) >= 4: name = ' '.join(parts[3:]) interfaces.append(name) return interfaces def set_dns(interfaces, ipv4_list, ipv6_list): for adapter in interfaces: if ipv4_list: cmd_ipv4 = f'netsh interface ip set dns name="{adapter}" static {ipv4_list[0]} primary' subprocess.run(cmd_ipv4, shell=True) for i, dns in enumerate(ipv4_list[1:], start=2): cmd_add = f'netsh interface ip add dns name="{adapter}" {dns} index={i}' subprocess.run(cmd_add, shell=True) if ipv6_list: cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" static {ipv6_list[0]} primary' subprocess.run(cmd_ipv6, shell=True) for i, dns in enumerate(ipv6_list[1:], start=2): cmd_add = f'netsh interface ipv6 add dnsservers name="{adapter}" {dns} index={i}' subprocess.run(cmd_add, shell=True) def reset_dns(interfaces): for adapter in interfaces: cmd_ipv4 = f'netsh interface ip set dns name="{adapter}" dhcp' subprocess.run(cmd_ipv4, shell=True) cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" source=dhcp' subprocess.run(cmd_ipv6, shell=True) def toggle_ad_block(): config = load_config() interfaces = get_network_adapters() if not interfaces: messagebox.showerror("錯誤", "未找到可用的網路介面,無法修改 DNS 設定。") return if config.get("ad_block_enabled", False): # 已啟用,現在停用 reset_dns(interfaces) config["ad_block_enabled"] = False status_var.set("廣告阻擋已禁用") else: # 未啟用,現在啟用 set_dns(interfaces, AD_BLOCK_DNS_IPV4, AD_BLOCK_DNS_IPV6) config["ad_block_enabled"] = True status_var.set("廣告阻擋已啟用") save_config(config) def update_status(): config = load_config() if config.get("ad_block_enabled", False): status_var.set("廣告阻擋已啟用") else: status_var.set("廣告阻擋已禁用") def main(): # 確保以管理員身份執行 run_as_admin() # 到此程式若能繼續,表示已獲得管理員權限 root = tk.Tk() root.title("廣告阻擋工具") root.geometry("300x150") root.resizable(False, False) global status_var status_var = tk.StringVar() update_status() status_label = tk.Label(root, textvariable=status_var, pady=20, font=("Arial", 12)) status_label.pack() toggle_button = tk.Button(root, text="切換廣告阻擋", command=toggle_ad_block, width=20, font=("Arial", 10)) toggle_button.pack(pady=10) root.mainloop() if __name__ == "__main__": main()

程式使用步驟

  1. 確保已安裝 Python 3.x 並加入 PATH。
  2. ad_blocker.py 所在的資料夾中,開啟終端(建議一開始就以系統管理員身份開啟命令提示字元或 PowerShell)。
    如果未以管理員身份開啟,程式將嘗試自動提權。若使用者拒絕提權或發生錯誤,將直接在終端顯示訊息並結束。
  3. 執行 python ad_blocker.py
  4. 在程式視窗中使用「切換廣告阻擋」按鈕即可控制是否使用特定 DNS 來阻擋廣告。
  5. 程式結束後再次啟動時,會讀取先前的狀態並顯示。

此版本程式碼在自動提權流程中避免使用 tkinter 的對話框,以減少在沒有 GUI 主視窗的情況下呼叫 messagebox 而導致的潛在問題。如果仍有「閃退」情況,請檢查:

  • 是否拒絕提權窗口的要求(UAC對話框)
  • Python 環境或路徑是否正確
  • 是否有殺毒軟體或群組原則限制

若有需要,可在程式中增加 print() 調試資訊,以確保流程正常。

Question

它的輸出
設定的 DNS 伺服器不正確或不存在。



設定的 DNS 伺服器不正確或不存在。



設定的 DNS 伺服器不正確或不存在。



設定的 DNS 伺服器不正確或不存在。




Thought

Setting DNS configurations

I noticed that the code attempts to configure DNS settings for all interfaces, including disconnected ones, and might be setting an incorrect DNS server or a mismatched format, such as using IPv6 in an IPv4 environment.

Identifying potential issues

The code might be setting DNS on unrecognized or inactive interfaces, including IPv6 on an adapter without IPv6 support. The solution is to ignore IPv6 if unnecessary or verify interface names.

Clarifying DNS settings

I'm refining the code to distinguish between IPv4 and IPv6 DNS settings, ensuring correct command syntax, and using appropriate parameters for different Windows versions.

Ensuring compatibility

I’m making sure the code uses modern netsh commands for IPv4 and IPv6 settings, verifying correct interface names and adapters, and eliminating any extraneous spaces.

Running manual tests

Checking netsh commands in the command prompt to identify issues with DNS server reachability or syntax errors, while ensuring compatibility with the user's Windows environment.

Switching gears

I’m considering skipping IPv6 if not needed, verifying netsh support, and ensuring system recognition of DNS servers and interface names in non-English systems.

Thought about DNS configuration issues 花的時間為 10 秒

Answer

您看到「設定的 DNS 伺服器不正確或不存在。」的錯誤訊息,多半是由於 netsh 指令使用錯誤、介面名稱不符或環境不支援造成。以下是可能原因和改進方法:

  1. 指令使用方式
    舊版做法常用 netsh interface ip set dns,但在較新環境中建議分別使用:

    • IPv4 用 netsh interface ipv4 set dnsservers
    • IPv6 用 netsh interface ipv6 set dnsservers

    如果繼續使用 netsh interface ip set dns 可能會在部分環境出現錯誤。

  2. 網路介面名稱
    程式會從 netsh interface show interface 中取得介面名稱。如果介面名稱中有空格、特殊字元,或者該介面實際上不支援設定 DNS,可能會導致錯誤。
    建議印出介面列表檢查,或手動指定您確定可用的介面名稱測試。

  3. IPv6 問題
    如果系統未啟用 IPv6 或介面不支援 IPv6,設定 IPv6 DNS 可能失敗。若不需要 IPv6,可考慮直接略過設定 IPv6 DNS。

  4. DNS Server 位址有誤
    雖然像 94.140.14.142a10:50c0::1 是知名的 AdGuard DNS,但請確定系統能辨識這些位址格式。
    您可嘗試以手動在命令列中輸入指令來測試,例如:

    netsh interface ipv4 set dnsservers name="Ethernet" static 94.140.14.14 primary

    看看是否能正常執行。如果不能,表示該 DNS 位址或介面名稱有問題。

以下是改良後的程式碼範例,將 IPv4 和 IPv6 設定分別以較新命令處理,並在設定前列印介面名稱供檢查。若您只需要 IPv4,可註解掉 IPv6 部分。

import os import sys import ctypes import subprocess import tkinter as tk from tkinter import messagebox import json CONFIG_FILE = "ad_block_config.json" # 廣告阻擋DNS AD_BLOCK_DNS_IPV4 = ["94.140.14.14"] # 可加其他,如 ["94.140.14.14", "8.8.8.8"] AD_BLOCK_DNS_IPV6 = ["2a10:50c0::1"] def is_admin(): try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False def run_as_admin(): if is_admin(): return True script = os.path.abspath(sys.argv[0]) params = " ".join(sys.argv[1:]) h_instance = ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, f'"{script}" {params}', None, 1) if h_instance <= 32: print("需要以系統管理員身份執行此程式。請重新嘗試並允許提升權限。") sys.exit(1) else: sys.exit(0) def load_config(): if not os.path.exists(CONFIG_FILE): return {"ad_block_enabled": False} try: with open(CONFIG_FILE, "r", encoding="utf-8") as f: return json.load(f) except: return {"ad_block_enabled": False} def save_config(config): with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=4) def get_network_adapters(): cmd = 'netsh interface show interface' result = subprocess.run(cmd, capture_output=True, text=True, shell=True) if result.returncode != 0: return [] interfaces = [] for line in result.stdout.splitlines(): # 篩出正常的介面行 # "Connected"/"Disconnected" 或本地語言 if ("Connected" in line or "Disconnected" in line or "連線" in line or "斷線" in line): parts = line.split() if len(parts) >= 4: name = ' '.join(parts[3:]) interfaces.append(name) return interfaces def set_dns(interfaces, ipv4_list, ipv6_list): for adapter in interfaces: # 設定 IPv4 DNS if ipv4_list: # 清空 IPv4 DNS 設定,再用 static 設第一個為 primary cmd_ipv4 = f'netsh interface ipv4 set dnsservers name="{adapter}" static {ipv4_list[0]} primary' subprocess.run(cmd_ipv4, shell=True) # 加入其他 IPv4 DNS for i, dns in enumerate(ipv4_list[1:], start=2): cmd_add = f'netsh interface ipv4 add dnsservers name="{adapter}" address={dns} index={i}' subprocess.run(cmd_add, shell=True) # 設定 IPv6 DNS if ipv6_list: cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" static {ipv6_list[0]} primary' subprocess.run(cmd_ipv6, shell=True) for i, dns in enumerate(ipv6_list[1:], start=2): cmd_add = f'netsh interface ipv6 add dnsservers name="{adapter}" address={dns} index={i}' subprocess.run(cmd_add, shell=True) def reset_dns(interfaces): for adapter in interfaces: # 將 IPv4 DNS 設為自動 cmd_ipv4 = f'netsh interface ipv4 set dnsservers name="{adapter}" source=dhcp' subprocess.run(cmd_ipv4, shell=True) # 將 IPv6 DNS 設為自動 cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" source=dhcp' subprocess.run(cmd_ipv6, shell=True) def toggle_ad_block(): config = load_config() interfaces = get_network_adapters() if not interfaces: messagebox.showerror("錯誤", "未找到可用的網路介面,無法修改 DNS 設定。") return # 列印介面名稱,供偵錯使用 print("發現的網路介面:", interfaces) if config.get("ad_block_enabled", False): reset_dns(interfaces) config["ad_block_enabled"] = False status_var.set("廣告阻擋已禁用") else: set_dns(interfaces, AD_BLOCK_DNS_IPV4, AD_BLOCK_DNS_IPV6) config["ad_block_enabled"] = True status_var.set("廣告阻擋已啟用") save_config(config) def update_status(): config = load_config() if config.get("ad_block_enabled", False): status_var.set("廣告阻擋已啟用") else: status_var.set("廣告阻擋已禁用") def main(): run_as_admin() root = tk.Tk() root.title("廣告阻擋工具") root.geometry("300x150") root.resizable(False, False) global status_var status_var = tk.StringVar() update_status() status_label = tk.Label(root, textvariable=status_var, pady=20, font=("Arial", 12)) status_label.pack() toggle_button = tk.Button(root, text="切換廣告阻擋", command=toggle_ad_block, width=20, font=("Arial", 10)) toggle_button.pack(pady=10) root.mainloop() if __name__ == "__main__": main()

建議測試步驟

  1. 手動測試指令:
    在 CMD(以系統管理員身份)輸入:

    netsh interface ipv4 set dnsservers name="Ethernet" static 94.140.14.14 primary

    如果此處就出錯,請檢查網路介面名稱是否正確 ("Ethernet" 是範例,請以程式輸出或您實際介面為準)。

  2. 確認介面名稱正確性:
    在程式跑出「發現的網路介面: ...」後,檢查輸出清單內的名稱是否與系統在「控制台 -> 網路連線」中顯示的名稱相符。如不相符或有奇怪字元,請嘗試該名稱能否手動執行 netsh 指令。

  3. 如果不需要 IPv6:
    如果您不需要設定 IPv6 DNS,可將 set_dns 函數中有關 IPv6 的設定程式碼註解掉或移除,避免不必要的錯誤。

透過以上修正與檢查,問題「設定的 DNS 伺服器不正確或不存在。」應該能獲得改善。

Question

成功了 但我想你更改它檢測是否已啟用不用建立一個文件

Thought

调整脚本内容
我正在修改脚本,让它能通过检测当前的DNS设置,避免使用独立的配置文件来确认广告拦截状态。这样可以简化环境设置,确保脚本直接有效。

调整脚本以适应新需求
开始时,我先列出所有网络适配器,检查DNS设置。如果所有适配器都设置了广告拦截DNS服务器,那么就把广告拦截状态标记为已启用。

思考 花的時間為 幾秒

Answer

很高興聽到腳本已經成功運行!接下來,我們將改進腳本,使其在啟動時不再依賴外部配置文件來檢測廣告阻擋的狀態,而是直接通過檢查當前網路介面的 DNS 設定來判斷是否已啟用廣告阻擋。這樣可以簡化流程,減少潛在的錯誤來源。

主要改進點

  1. 移除配置文件:不再使用 ad_block_config.json 來保存狀態。
  2. 動態檢測狀態:啟動時通過檢查每個網路介面的 DNS 設定來判斷廣告阻擋是否啟用。
  3. 優化介面檢測:確保檢測所有相關的網路介面,並處理多個介面的情況。
  4. 更詳細的錯誤處理與日誌:增加更多的日誌輸出,方便後續調試與監控。

完整改進後的腳本

請將下列程式碼保存為 ad_blocker.py,並以管理員身份運行:

import os import sys import ctypes import subprocess import tkinter as tk from tkinter import messagebox import json # 廣告阻擋DNS設定 AD_BLOCK_DNS_IPV4 = ["94.140.14.14"] AD_BLOCK_DNS_IPV6 = ["2a10:50c0::1"] def is_admin(): """ 檢查程式是否以管理員身份執行。 """ try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False def run_as_admin(): """ 若非以管理員身份執行,則嘗試以管理員身份重新啟動程式。 """ if is_admin(): return True # 獲取程式絕對路徑 script = os.path.abspath(sys.argv[0]) params = " ".join([f'"{arg}"' for arg in sys.argv[1:]]) try: # 使用 ShellExecuteW 提權 h_instance = ctypes.windll.shell32.ShellExecuteW( None, "runas", sys.executable, f'"{script}" {params}', None, 1) if h_instance <= 32: # 提權失敗或使用者拒絕 messagebox.showerror("錯誤", "需要以系統管理員身份執行此程式。請重新嘗試並允許提升權限。") sys.exit(1) else: # 成功提權後,原程式結束,等待新程式執行 sys.exit(0) except Exception as e: messagebox.showerror("錯誤", f"嘗試提升權限時發生錯誤:{e}") sys.exit(1) def get_network_adapters(): """ 使用 netsh 指令取得目前系統的網路介面名稱清單。 僅處理「已連線」或「已斷線」的介面。 """ cmd = 'netsh interface show interface' result = subprocess.run(cmd, capture_output=True, text=True, shell=True) if result.returncode != 0: messagebox.showerror("錯誤", "無法取得網路介面列表,請確認系統環境。") return [] interfaces = [] for line in result.stdout.splitlines(): # 篩選出包含 "Connected" 或 "Disconnected" 的行,考慮中英文 if any(status in line for status in ["Connected", "Disconnected", "連線", "斷線"]): parts = line.split() if len(parts) >= 4: # 介面名稱可能包含空格,因此需合併後面的部分 name = ' '.join(parts[3:]) interfaces.append(name) return interfaces def set_dns(interfaces, ipv4_list, ipv6_list): """ 為所有指定的網路介面設定靜態 DNS。 ipv4_list、ipv6_list 為包含 DNS 伺服器的列表。 """ for adapter in interfaces: # 設定 IPv4 DNS if ipv4_list: # 設定第一個 DNS 為 primary cmd_ipv4 = f'netsh interface ipv4 set dnsservers name="{adapter}" static {ipv4_list[0]} primary' result_ipv4 = subprocess.run(cmd_ipv4, shell=True, capture_output=True, text=True) if result_ipv4.returncode != 0: print(f"設定 IPv4 DNS 失敗: {result_ipv4.stderr}") # 設定其他 DNS for i, dns in enumerate(ipv4_list[1:], start=2): cmd_add = f'netsh interface ipv4 add dnsservers name="{adapter}" address={dns} index={i}' result_add = subprocess.run(cmd_add, shell=True, capture_output=True, text=True) if result_add.returncode != 0: print(f"添加 IPv4 DNS 失敗: {result_add.stderr}") # 設定 IPv6 DNS if ipv6_list: cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" static {ipv6_list[0]} primary' result_ipv6 = subprocess.run(cmd_ipv6, shell=True, capture_output=True, text=True) if result_ipv6.returncode != 0: print(f"設定 IPv6 DNS 失敗: {result_ipv6.stderr}") for i, dns in enumerate(ipv6_list[1:], start=2): cmd_add = f'netsh interface ipv6 add dnsservers name="{adapter}" address={dns} index={i}' result_add = subprocess.run(cmd_add, shell=True, capture_output=True, text=True) if result_add.returncode != 0: print(f"添加 IPv6 DNS 失敗: {result_add.stderr}") def reset_dns(interfaces): """ 將所有指定的網路介面的 DNS 設定恢復為自動取得 (DHCP)。 """ for adapter in interfaces: # 重置 IPv4 DNS cmd_ipv4 = f'netsh interface ipv4 set dnsservers name="{adapter}" source=dhcp' result_ipv4 = subprocess.run(cmd_ipv4, shell=True, capture_output=True, text=True) if result_ipv4.returncode != 0: print(f"重置 IPv4 DNS 失敗: {result_ipv4.stderr}") # 重置 IPv6 DNS cmd_ipv6 = f'netsh interface ipv6 set dnsservers name="{adapter}" source=dhcp' result_ipv6 = subprocess.run(cmd_ipv6, shell=True, capture_output=True, text=True) if result_ipv6.returncode != 0: print(f"重置 IPv6 DNS 失敗: {result_ipv6.stderr}") def is_ad_block_enabled(interfaces): """ 檢查是否已啟用廣告阻擋。 透過檢查所有網路介面的 DNS 是否匹配廣告阻擋 DNS 來判斷。 如果所有介面均設置為廣告阻擋 DNS,則返回 True,否則返回 False。 """ for adapter in interfaces: # 檢查 IPv4 cmd_ipv4 = f'netsh interface ipv4 show dnsservers name="{adapter}"' result_ipv4 = subprocess.run(cmd_ipv4, shell=True, capture_output=True, text=True) if result_ipv4.returncode != 0: print(f"檢查 IPv4 DNS 失敗: {result_ipv4.stderr}") return False dns_ipv4 = [] for line in result_ipv4.stdout.splitlines(): line = line.strip() if line and not line.startswith("DNS servers configured"): dns_ipv4.append(line.split()[0]) # 檢查 IPv6 cmd_ipv6 = f'netsh interface ipv6 show dnsservers name="{adapter}"' result_ipv6 = subprocess.run(cmd_ipv6, shell=True, capture_output=True, text=True) if result_ipv6.returncode != 0: print(f"檢查 IPv6 DNS 失敗: {result_ipv6.stderr}") return False dns_ipv6 = [] for line in result_ipv6.stdout.splitlines(): line = line.strip() if line and not line.startswith("DNS servers configured"): dns_ipv6.append(line.split()[0]) # 比較 DNS 是否匹配廣告阻擋 DNS if not (dns_ipv4 and dns_ipv4[0] in AD_BLOCK_DNS_IPV4): return False if not (dns_ipv6 and dns_ipv6[0] in AD_BLOCK_DNS_IPV6): return False return True def toggle_ad_block(): """ 切換廣告阻擋狀態。 如果已啟用,則禁用;如果未啟用,則啟用。 """ interfaces = get_network_adapters() if not interfaces: messagebox.showerror("錯誤", "未找到可用的網路介面,無法修改 DNS 設定。") return # 檢查當前狀態 enabled = is_ad_block_enabled(interfaces) if enabled: # 已啟用,現在禁用 reset_dns(interfaces) status_var.set("廣告阻擋已禁用") else: # 未啟用,現在啟用 set_dns(interfaces, AD_BLOCK_DNS_IPV4, AD_BLOCK_DNS_IPV6) status_var.set("廣告阻擋已啟用") def update_status(): """ 根據當前 DNS 設定更新介面上的狀態顯示。 """ interfaces = get_network_adapters() if not interfaces: status_var.set("無法檢測狀態") return enabled = is_ad_block_enabled(interfaces) if enabled: status_var.set("廣告阻擋已啟用") else: status_var.set("廣告阻擋已禁用") def main(): # 首先,確保以管理員身份運行 if not is_admin(): run_as_admin() # 如果已經是管理員,繼續執行 root = tk.Tk() root.title("廣告阻擋工具") root.geometry("300x150") root.resizable(False, False) global status_var status_var = tk.StringVar() update_status() status_label = tk.Label(root, textvariable=status_var, pady=20, font=("Arial", 12)) status_label.pack() toggle_button = tk.Button(root, text="切換廣告阻擋", command=toggle_ad_block, width=20, font=("Arial", 10)) toggle_button.pack(pady=10) # 增加一個刷新按鈕,以便手動刷新狀態 refresh_button = tk.Button(root, text="刷新狀態", command=update_status, width=20, font=("Arial", 10)) refresh_button.pack(pady=5) root.mainloop() if __name__ == "__main__": main()

程式說明

  1. 管理員權限檢查與提權

    • 使用 is_admin() 函數檢查程式是否以管理員身份運行。
    • 如果不是,使用 run_as_admin() 函數嘗試以管理員身份重新啟動程式。若使用者拒絕提權,則顯示錯誤訊息並退出。
  2. 動態檢測廣告阻擋狀態

    • is_ad_block_enabled() 函數通過檢查所有網路介面的 DNS 設定來判斷廣告阻擋是否啟用。只有當所有介面的主要 DNS 設定與廣告阻擋 DNS 匹配時,才認為已啟用。
  3. 設定與重置 DNS

    • set_dns() 函數為所有指定的網路介面設置靜態 DNS(包括 IPv4 和 IPv6)。
    • reset_dns() 函數將所有指定的網路介面的 DNS 設定恢復為自動取得(DHCP)。
  4. GUI 介面

    • 使用 Tkinter 建立一個簡單的窗口,顯示當前廣告阻擋的狀態,並提供一個按鈕來切換狀態。
    • 增加了一個「刷新狀態」按鈕,方便用戶在外部改動 DNS 設定後手動刷新顯示的狀態。
  5. 錯誤處理與日誌

    • netsh 命令執行失敗時,會在控制台輸出錯誤訊息,便於調試。
    • 如果無法取得網路介面或修改 DNS,會顯示相應的錯誤訊息。

使用步驟

  1. 安裝 Python

    • 確保系統已安裝 Python 3.x,並且已將 Python 加入系統路徑 (PATH)。
  2. 保存並執行腳本

    • 將上述程式碼保存為 ad_blocker.py
    • 右鍵點擊 ad_blocker.py,選擇「以管理員身份運行」。
    • 或者,通過命令提示字元以管理員身份運行:
      python ad_blocker.py
  3. 操作 GUI

    • 啟動後,程式會自動檢測當前廣告阻擋狀態並顯示。
    • 點擊「切換廣告阻擋」按鈕即可啟用或禁用廣告阻擋。
    • 如需手動刷新狀態,可以點擊「刷新狀態」按鈕。

測試與驗證

  1. 手動測試 DNS 設定

    • 在命令提示字元中,以管理員身份執行以下指令,確認能正確設置 DNS:
      netsh interface ipv4 set dnsservers name="Ethernet" static 94.140.14.14 primary netsh interface ipv6 set dnsservers name="Ethernet" static 2a10:50c0::1 primary
    • 替換 "Ethernet" 為您的實際網路介面名稱。
  2. 檢查介面名稱

    • 確保腳本中 get_network_adapters() 函數正確檢測到所有有效的網路介面。
    • 如有必要,可在腳本中加入 print("發現的網路介面:", interfaces) 來調試介面名稱。
  3. 處理特殊網路介面

    • 如果系統中有虛擬網路介面(如 VPN、虛擬機等),可能需要根據實際情況進行篩選或處理。

額外建議

  • 自動啟動

    • 若希望此腳本在每次 Windows 啟動時自動運行,可以將其添加到「啟動」文件夾,或使用任務排程器設置。
  • 日誌記錄

    • 為了更好地監控腳本運行狀況,可以考慮將錯誤訊息和操作記錄到日誌文件中。
  • 多語言支持

    • 根據需求,可以擴展腳本以支持多種語言的介面。
  • 更靈活的 DNS 設定

    • 若需要使用多個廣告阻擋 DNS,或允許用戶自定義 DNS,則可進一步擴展腳本功能。

希望這個改進版的腳本能夠更好地滿足您的需求,並在使用過程中更加穩定與可靠。如果有任何其他問題或需要進一步的功能擴展,請隨時告知!

Share this Q&A