Last active
January 23, 2026 16:06
-
-
Save hokkey/e61bf1cc57e388425b9f02012f6dd6b7 to your computer and use it in GitHub Desktop.
BathyScapheのthread形式をMarkdownに整形する - https://media-massage.net/works/project/bathyscaphe2markdown/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -e | |
| # バッチ変換用 | |
| # 第1引数: 過去ログディレクトリのルート | |
| ARCHIVE_ROOT="$1" | |
| # 第2引数: Markdown 出力先のルート | |
| OUTPUT_ROOT="$2" | |
| # 同一階層に単体用スクリプトを配置 | |
| THREAD2MD="./bs2md_single.sh" | |
| # 引数チェック | |
| if [ -z "$ARCHIVE_ROOT" ] || [ -z "$OUTPUT_ROOT" ]; then | |
| echo "Usage: $0 archive_root output_root" | |
| exit 1 | |
| fi | |
| ## ディレクトリ存在チェック | |
| if [ ! -d "$ARCHIVE_ROOT" ]; then | |
| echo "Archive root directory not found." | |
| exit 1 | |
| fi | |
| if [ ! -x "$THREAD2MD" ]; then | |
| echo "thread2md.sh not found or not executable." | |
| exit 1 | |
| fi | |
| # 出力先ルートを作成 | |
| mkdir -p "$OUTPUT_ROOT" | |
| # 各板ディレクトリを走査 | |
| find "$ARCHIVE_ROOT" -type f -name "*.thread" -print0 | | |
| while IFS= read -r -d '' thread_file; do | |
| board_name="$(basename "$(dirname "$thread_file")")" | |
| board_output_dir="$OUTPUT_ROOT/$board_name" | |
| mkdir -p "$board_output_dir" | |
| "$THREAD2MD" "$thread_file" "$board_output_dir" | |
| done |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # 単体ファイル変換用 | |
| # エラーが発生した時点でスクリプトを終了する | |
| set -e | |
| # 第1引数: BathyScaphe の thread ファイルへのパス | |
| THREAD_FILE="$1" | |
| # 第2引数: Markdown の出力先ディレクトリ | |
| OUTPUT_DIR="$2" | |
| # 引数が不足している場合は使い方を表示して終了 | |
| if [ -z "$THREAD_FILE" ] || [ -z "$OUTPUT_DIR" ]; then | |
| echo "Usage: $0 thread_file output_dir" | |
| exit 1 | |
| fi | |
| # thread ファイルが存在しない場合は終了 | |
| if [ ! -f "$THREAD_FILE" ]; then | |
| echo "Thread file not found." | |
| exit 1 | |
| fi | |
| # 出力先ディレクトリを作成(既に存在していても問題なし) | |
| mkdir -p "$OUTPUT_DIR" | |
| # 一次ファイル、一次スクリプトを作成 | |
| TMP_XML=$(mktemp -t bathyscaphe_thread).xml | |
| TMP_PY=$(mktemp -t bathyscaphe_thread).py | |
| # 一次ファイル、一次スクリプトを作成・失敗時はskip | |
| plutil -convert xml1 -o "$TMP_XML" "$THREAD_FILE" 2>/dev/null || true | |
| # Python スクリプトを一時ファイルとして生成 | |
| cat > "$TMP_PY" << 'PYEOF' | |
| import plistlib, sys, os, re, datetime | |
| xml_path = sys.argv[1] | |
| out_dir = sys.argv[2] | |
| try: | |
| with open(xml_path, "rb") as f: | |
| data = plistlib.load(f) | |
| except Exception as e: | |
| print(f"SKIP (plist parse error): {xml_path}", file=sys.stderr) | |
| sys.exit(0) | |
| # スレッド情報を取得 | |
| title = data.get("Title", "") | |
| board = data.get("BoardName", "") | |
| created = data.get("CreatedDate", "") | |
| modified = data.get("ModifiedDate", "") | |
| dat = data.get("dat", "") | |
| contents = data.get("Contents", []) | |
| # ファイル名に使用できない文字を除去し、 | |
| # 末尾の全角・半角スペースをトリムする関数 | |
| def sanitize(name): | |
| name = re.sub(r'[\/:*?"<>|]', '', name) | |
| name = re.sub(r'[ ]+$', '', name) | |
| return name | |
| # CreatedDate を YYYY-MM-DD 形式の日付文字列に変換する関数 | |
| def format_date(date_obj): | |
| if not date_obj: | |
| return "unknown_date" | |
| if isinstance(date_obj, datetime.datetime): | |
| return date_obj.strftime("%Y-%m-%d") | |
| if isinstance(date_obj, str): | |
| # 想定: YYYY-MM-DD HH:MM:SS | |
| return date_obj.split(" ")[0] | |
| return "unknown_date" | |
| # ファイル名を作成 | |
| date_prefix = format_date(created) | |
| filename_base = sanitize(f"{date_prefix}_{title}") | |
| if not filename_base: | |
| filename_base = f"{date_prefix}_thread" | |
| filename = f"{filename_base}_{dat}.md" | |
| out_path = os.path.join(out_dir, filename) | |
| # アンカー書き換え関数 | |
| def rewrite_anchor_links(html): | |
| """ | |
| read.cgi 形式のアンカーリンクを | |
| Markdown内アンカー(#レス番号)に変換し、 | |
| target="_blank" 属性を除去する | |
| """ | |
| # href を #レス番号 に変換 | |
| html = re.sub( | |
| r'href="[^"]*/read\.cgi/[^/]+/\d+/(\d+)"', | |
| r'href="#\1"', | |
| html | |
| ) | |
| # target="_blank" を削除(前後の空白も考慮) | |
| html = re.sub( | |
| r'\s*target="_blank"', | |
| '', | |
| html | |
| ) | |
| return html | |
| # Markdown ファイルを UTF-8 で出力 | |
| with open(out_path, "w", encoding="utf-8") as f: | |
| f.write(f"# {title}\n\n") | |
| f.write(f"{board}\n") | |
| f.write(f"作成日時: {created} 更新日時: {modified}\n") | |
| f.write(f"DAT: {dat}\n\n") | |
| # 各レスを順番に出力 | |
| for i, c in enumerate(contents, 1): | |
| f.write( | |
| f"##### <span id=\"{i}\">{i}</span> 名前: {c.get('Name','')} " | |
| f"Mail: {c.get('Mail','')} " | |
| f"投稿日: {c.get('Date','')} " | |
| f"ID:{c.get('ID','')}\n\n" | |
| ) | |
| msg = c.get("Message", "") | |
| msg = rewrite_anchor_links(msg) | |
| f.write("<pre>\n") | |
| f.write(msg) | |
| if not msg.endswith("\n"): | |
| f.write("\n") | |
| f.write("</pre>\n\n") | |
| # 出力されたファイルのパスを標準出力に表示 | |
| print(out_path) | |
| PYEOF | |
| # 生成した Python スクリプトを実行 | |
| /usr/bin/python3 "$TMP_PY" "$TMP_XML" "$OUTPUT_DIR" | |
| # 一時ファイルを削除 | |
| rm "$TMP_XML" "$TMP_PY" 2>/dev/null || true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment