Skip to content

Instantly share code, notes, and snippets.

@hokkey
Last active January 23, 2026 16:06
Show Gist options
  • Select an option

  • Save hokkey/e61bf1cc57e388425b9f02012f6dd6b7 to your computer and use it in GitHub Desktop.

Select an option

Save hokkey/e61bf1cc57e388425b9f02012f6dd6b7 to your computer and use it in GitHub Desktop.
BathyScapheのthread形式をMarkdownに整形する - https://media-massage.net/works/project/bathyscaphe2markdown/
#!/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
#!/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