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)