リンク切れチェックツール『幽黶』 ソースコード

『幽黶』はリンク切れチェックツールです。自サイトのアップロード前に確認することで記述ミス等によるリンク切れを防ぐことができます。

>>GUI外観 >>使い方

以下は『幽黶』のソースコード全文です:

 
        import os
        import re
        import tkinter as tk
        from tkinter import filedialog, messagebox, ttk
        from datetime import datetime
        
        class LinkCheckerApp:
            def __init__(self, root):
                self.root = root
                self.root.title("リンク切れチェックツール - 幽黶")
                self.root.geometry("800x800")
        
                self.selected_folder = ""
                self.ext_counts = {}
                self.ext_vars = {}
                self.results = []
        
                # オプション変数
                self.exclude_mailto = tk.BooleanVar(value=False)
                self.exclude_zip = tk.BooleanVar(value=False)
        
                self.create_widgets()
        
            def create_widgets(self):
                # フォルダ選択
                frame_top = ttk.Frame(self.root)
                frame_top.pack(padx=10, pady=5, fill="x")
        
                ttk.Button(frame_top, text="フォルダ選択", command=self.select_folder).pack(side="left")
                self.label_folder = ttk.Label(frame_top, text="(未選択)")
                self.label_folder.pack(side="left", padx=10)
        
                # 拡張子チェック欄(スクロール付き)
                self.ext_frame_outer = ttk.LabelFrame(self.root, text="拡張子別ファイル数(チェックして対象選択)")
                self.ext_frame_outer.pack(padx=10, pady=5, fill="x")
        
                canvas = tk.Canvas(self.ext_frame_outer, height=100)
                scrollbar = ttk.Scrollbar(self.ext_frame_outer, orient="vertical", command=canvas.yview)
                self.ext_frame = ttk.Frame(canvas)
        
                self.ext_frame.bind(
                    "<Configure>",
                    lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
                )
        
                canvas.create_window((0, 0), window=self.ext_frame, anchor="nw")
                canvas.configure(yscrollcommand=scrollbar.set)
        
                canvas.pack(side="left", fill="both", expand=True)
                scrollbar.pack(side="right", fill="y")
        
                # 探索オプション
                self.option_frame = ttk.LabelFrame(self.root, text="探索オプション")
                self.option_frame.pack(padx=10, pady=5, fill="x")
        
                ttk.Checkbutton(self.option_frame, text="mailto: を除く", variable=self.exclude_mailto).pack(side="left", padx=10)
                ttk.Checkbutton(self.option_frame, text="zipファイルへのリンクを除く", variable=self.exclude_zip).pack(side="left", padx=10)
        
                # 探索開始
                ttk.Button(self.root, text="探索開始", command=self.start_check).pack(pady=5)
        
                # 結果表示
                self.text_result = tk.Text(self.root, wrap="none")
                self.text_result.pack(padx=10, pady=5, expand=True, fill="both")
        
                # エクスポート
                ttk.Button(self.root, text="結果をエクスポート", command=self.export_result).pack(pady=5)
        
            def select_folder(self):
                folder = filedialog.askdirectory()
                if folder:
                    self.selected_folder = folder
                    self.label_folder.config(text=folder)
                    self.scan_extensions()
        
            def scan_extensions(self):
                self.ext_counts.clear()
                for root, _, files in os.walk(self.selected_folder):
                    for file in files:
                        ext = os.path.splitext(file)[1].lower()
                        if ext:
                            self.ext_counts[ext] = self.ext_counts.get(ext, 0) + 1
        
                # チェックボックス初期化
                for widget in self.ext_frame.winfo_children():
                    widget.destroy()
                self.ext_vars.clear()
        
                # 全選択・全解除ボタン
                control_frame = ttk.Frame(self.ext_frame)
                control_frame.grid(row=0, column=0, columnspan=5, sticky="w", pady=(0, 5))
        
                ttk.Button(control_frame, text="全てにチェック", command=self.check_all).pack(side="left", padx=5)
                ttk.Button(control_frame, text="全てチェックを外す", command=self.uncheck_all).pack(side="left", padx=5)
        
                # 拡張子チェックボックス(5列折り返し)
                columns = 5
                for idx, (ext, count) in enumerate(sorted(self.ext_counts.items())):
                    var = tk.BooleanVar(value=True)
                    chk = ttk.Checkbutton(self.ext_frame, text=f"{ext} ({count}件)", variable=var)
                    row = (idx // columns) + 1
                    col = idx % columns
                    chk.grid(row=row, column=col, sticky="w", padx=5, pady=2)
                    self.ext_vars[ext] = var
        
            def check_all(self):
                for var in self.ext_vars.values():
                    var.set(True)
        
            def uncheck_all(self):
                for var in self.ext_vars.values():
                    var.set(False)
        
            def start_check(self):
                self.results.clear()
                self.text_result.delete("1.0", "end")
        
                target_exts = [ext for ext, var in self.ext_vars.items() if var.get()]
                if not target_exts:
                    messagebox.showwarning("警告", "最低1つの拡張子を選択してください。")
                    return
        
                for root, _, files in os.walk(self.selected_folder):
                    for file in files:
                        ext = os.path.splitext(file)[1].lower()
                        if ext not in target_exts:
                            continue
        
                        filepath = os.path.join(root, file)
                        self.check_file(filepath)
        
                if self.results:
                    for line in self.results:
                        self.text_result.insert("end", line + "\n")
                else:
                    self.text_result.insert("end", "リンク切れは見つかりませんでした。")
        
            def check_file(self, filepath):
                try:
                    with open(filepath, encoding="utf-8", errors="ignore") as f:
                        for i, line in enumerate(f, 1):
                            for link in self.extract_links(line):
                                if self.exclude_mailto.get() and link.startswith("mailto:"):
                                    continue
                                if self.exclude_zip.get() and link.lower().endswith(".zip"):
                                    continue
        
                                target_path = self.resolve_path(filepath, link)
                                if target_path and not os.path.exists(target_path):
                                    rel_path = os.path.relpath(filepath, self.selected_folder)
                                    self.results.append(f"{rel_path} : {i}行目 : {link}")
                except Exception as e:
                    self.results.append(f"{os.path.basename(filepath)} の読み込み中にエラー: {e}")
        
            def extract_links(self, line):
                pattern = r'(?:href|src)\s*=\s*["\'](.*?)["\']'
                return [m for m in re.findall(pattern, line)
                        if not m.startswith(("http:", "https:", "javascript:", "#"))]
        
            def resolve_path(self, base_file, link):
                if os.path.isabs(link):
                    return os.path.join(self.selected_folder, link.lstrip("/\\"))
                else:
                    return os.path.normpath(os.path.join(os.path.dirname(base_file), link))
        
            def export_result(self):
                if not self.results:
                    messagebox.showinfo("情報", "エクスポートする結果がありません。")
                    return
                folder_name = os.path.basename(self.selected_folder.rstrip(os.sep))
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                filename = f"{folder_name}-{timestamp}.txt"
                desktop = os.path.join(os.path.expanduser("~"), "Desktop")
                filepath = os.path.join(desktop, filename)
                with open(filepath, "w", encoding="utf-8") as f:
                    for line in self.results:
                        f.write(line + "\n")
                messagebox.showinfo("保存完了", f"結果をデスクトップに保存しました:\n{filename}")
        
        if __name__ == "__main__":
            root = tk.Tk()
            
            ##タイトルバーアイコン表示用
            data = '''R0lGODdhEAAQAIcAAEzM6EDB5VS91z624lmtzE+uzEyp1kCl1kemwjqh4Eacuz6S0zWSzjZ7sC59vzJ5lSpnkCZiiiFgoRtemh1UgR5UeClQjSBTeCVNfRVTjBJLhglNjRxIfyJEgRhDchI9aiA4Yx44ZB43ZBQ4YRI5ZRwyWhAvTw0qSiEhOSAhOiAgOCAfOB8fOBUjPRUiOxQjPBIiOxQgNxIgNxIfNhApSg0mRQ4lRRAiQREhORAfNxEfNB4eNR0eNhweNRseNBMeMhIeMg8eNA8eMw0eMRscMRocMBcdMRQcMRIdMhEcLw4cMA0cLwwcLxkbMRgbMBgaLhgaLRYbLRcaLhcaLRQbLxUaLBQaLRMbLxMbLhUYKhUYKBQYKhQXKRQXJxIaLBAbLhAaKxEZKREYKREYJw8XJw8XJhIWJxIWJhIWJREWJhAWJQ8WJwgaPwgaMAsaLwkbLw0aLg4bLQsbLQoZKw0YKQoYKgkYKgkYKQsXKAoXKAkWKQgXKA0YJwsXJwsXJg4WJw0WJg4WJAwWJAsWJgsWJAgXJwkWJwkWJggWJhEVJREVJBAVJA8VJQ8VJBAUIQ8UIxATIRATIA4VJA0VJA4UIQwVJQ4TIQ4THw0THw8SHw8SHg4SHw4SHQ0SHQ0SHA0RGwwRHwwRHAwRGwsVJAsVIwsUIAkVJQkUIwsTIAsTHwoTIQoTHgkTIgkTHggVJggVJQgUJAgUIwgUIggTIQcTIQgRJAgRHgkSHQsRGwkRGwwQIwwQGgsQGQoQGQkQGgkQGQgQGggQGQsPGAoPGAkQGAgPGAoPFwkPFwgPFwgPFgIMNAQKHwUNHQoOFgkOFggOFgoOFQkOFQkNFQkNFAgOFQgNFAgNEwcOFQcNFAgMEwcMEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAEAAQAEAI/wCtYXtWLNgtQXToEGqVC9k1atUWURlF50YBAhMCAKhQy48qL2OclUFCyxQiO21oPLig55CSJL+aiZrSwwykTJskMbKUqEmWUM2qHUsVowWcOnXuyGESRAafYtQwHTllas6JDQcUKOMgwZasL4F4RbFiqUQHEQsg2BjyZ5KaKrimRRP2ScsOFCpWsCDSxZOxZtO0USO26ouLFzBw5BAShxSwZNSo+foxAxYsV4X2LGNmaImOUsl6YRkU69UbEx8EIHDgwVYeIKkaXTmFxw0JAwwyREgwgtUsMGEuGSEDCkMDNhQGaKjRpxIqK5SGofGRRgwICyF0AVrT6JETRcagCSPrVCRFDylbznB5wgMKp7/ZsklzZmwXp0iONO0S5iyatGkBAQA7''' 
            root.tk.call('wm', 'iconphoto', root._w, tk.PhotoImage(data=data))
            ####
            
            app = LinkCheckerApp(root)
            root.mainloop()
       
    

📦 ダウンロード 🏠 ホームへ