import os import glob import re import sys import urllib.request import urllib.parse import html # Add this new import! def get_channel_id(filename): match = re.search(r'\[(\d+)\]\.html$', filename) if match: return int(match.group(1)) return float('inf') def download_and_localize_images(target_dir, log_files): img_dir_name = "images" img_dir_path = os.path.join(target_dir, img_dir_name) os.makedirs(img_dir_path, exist_ok=True) downloaded_urls = {} img_counter = 0 print("Scanning log files for external images to download locally...") src_pattern = re.compile(r'src=["\'](https?://[^"\']+)["\']', re.IGNORECASE) for file_path in log_files: filename = os.path.basename(file_path) print(f" Processing image links in: {filename}") with open(file_path, "r", encoding="utf-8") as f: html_content = f.read() urls = src_pattern.findall(html_content) if not urls: continue modified = False for url in urls: if url.startswith(('data:', 'file:', './')) or url in downloaded_urls: if url in downloaded_urls: html_content = html_content.replace(url, downloaded_urls[url]) modified = True continue try: parsed_url = urllib.parse.urlparse(url) clean_path = parsed_url.path ext = os.path.splitext(clean_path)[1] if '?' in ext: ext = ext.split('?')[0] if not ext or len(ext) > 5: ext = ".png" img_counter += 1 local_img_name = f"img_{img_counter:04d}{ext}" local_img_path = os.path.join(img_dir_path, local_img_name) req = urllib.request.Request( html.unescape(url), headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'} ) with urllib.request.urlopen(req, timeout=10) as response, open(local_img_path, "wb") as out_file: out_file.write(response.read()) relative_local_path = f"{img_dir_name}/{local_img_name}" downloaded_urls[url] = relative_local_path html_content = html_content.replace(url, relative_local_path) modified = True except Exception as e: print(f" Warning: Could not download asset {url}... Error: {e}") if modified: with open(file_path, "w", encoding="utf-8") as f: f.write(html_content) print(f"Successfully cached {img_counter} image assets locally.") def build_discord_archive(target_dir, game_name): target_dir = os.path.abspath(target_dir) if not os.path.isdir(target_dir): print(f"Error: The directory '{target_dir}' does not exist.") return all_html_files = glob.glob(os.path.join(target_dir, "*.html")) exclude_files = {"index.html", "sidebar.html", "welcome.html", "public_index.html"} log_files = [f for f in all_html_files if os.path.basename(f) not in exclude_files] if not log_files: print(f"No Discord chat log HTML files found in '{target_dir}'.") return # Download image resources locally download_and_localize_images(target_dir, log_files) # Sort files chronologically by channel ID log_files.sort(key=lambda x: get_channel_id(os.path.basename(x))) print(f"Compiling Single Page Application layout for '{game_name}'...") sidebar_buttons = [] chat_panels = [] for idx, file_path in enumerate(log_files): filename = os.path.basename(file_path) clean_name = filename.split(" - ")[-1].split(" [")[0] if not clean_name or clean_name.endswith(".html"): clean_name = os.path.splitext(filename)[0] clean_name = clean_name.replace('"', '"') with open(file_path, "r", encoding="utf-8") as f: raw_html = f.read() body_match = re.search(r"]*>(.*)", raw_html, re.DOTALL | re.IGNORECASE) body_content = body_match.group(1) if body_match else raw_html panel_id = f"chan_panel_{idx}" btn_html = f' ' sidebar_buttons.append(btn_html) panel_html = f' ' chat_panels.append(panel_html) sidebar_content_block = "\n".join(sidebar_buttons) chat_panels_block = "\n".join(chat_panels) master_spa_tmpl = f""" {game_name} - Chat Logs

Welcome to the {game_name} Archive

Select a channel from the sidebar list on the left to view specific round logs. Your scroll positions will remain locked automatically as you switch.

{chat_panels_block}
""" output_path = os.path.join(target_dir, "index.html") with open(output_path, "w", encoding="utf-8") as f: f.write(master_spa_tmpl) print(f"\nSuccess! Built your instant-switch, scroll-preserving archive sheet at:\n{output_path}") if __name__ == "__main__": if len(sys.argv) > 2: path_input = sys.argv[1] game_input = sys.argv[2] else: path_input = input("Enter the path to your Discord logs directory: ").strip() game_input = input("Enter the name of the game: ").strip() build_discord_archive(path_input, game_input)