瞬間日記のメールエクスポート形式のテキストをobsidian向けmarkdownに変換するpythonスクリプト例

仕様

import re
import os
import ctypes
from datetime import datetime, timedelta
from pathlib import Path

def parse_diary_entries(text):
    """日記テキストを日付ごとに分割してパース"""
    # 日付パターン
    date_pattern = r'-\s+(\w+),\s+(\w+)\s+(\d+),\s+(\d+)\s+(\d+):(\d+)'
    
    # 月名を数字に変換
    months = {
        'January': 1, 'February': 2, 'March': 3, 'April': 4,
        'May': 5, 'June': 6, 'July': 7, 'August': 8,
        'September': 9, 'October': 10, 'November': 11, 'December': 12
    }
    
    entries = []
    
    # すべての日付位置を見つける
    matches = list(re.finditer(date_pattern, text))
    
    for i, match in enumerate(matches):
        day_of_week, month, day, year, hour, minute = match.groups()
        
        month_num = months.get(month)
        if not month_num:
            continue
        
        # 日付オブジェクトを作成
        date = datetime(int(year), month_num, int(day), int(hour), int(minute))
        
        # 本文の開始位置(日付行の終わり)
        content_start = match.end()
        
        # 本文の終了位置(次の日付の開始、または文末)
        if i + 1 < len(matches):
            content_end = matches[i + 1].start()
        else:
            content_end = len(text)
        
        # 本文を抽出
        content = text[content_start:content_end].strip()
        
        entries.append({
            'date': date,
            'day_of_week': day_of_week,
            'time': f"{hour}:{minute}",
            'content': content
        })
    
    return entries

def convert_to_markdown(entry_data):
    """エントリデータをMarkdown形式に変換"""
    date_iso = entry_data['date'].strftime('%Y-%m-%d')
    
    # 前日と翌日の日付を計算
    prev_date = (entry_data['date'] - timedelta(days=1)).strftime('%Y-%m-%d')
    next_date = (entry_data['date'] + timedelta(days=1)).strftime('%Y-%m-%d')
    
    # Markdownファイルの内容を生成
    markdown = ""
    
    # 前後の日付へのリンク
    markdown += f"[[{prev_date}]] | [[{next_date}]]\n\n"
    
    # 本文の改行を保持(Markdownでは行末に2つのスペースで改行)
    content_lines = entry_data['content'].split('\n')
    for line in content_lines:
        if line.strip():
            markdown += line + "  \n"
        else:
            markdown += "\n"
    
    # タグを追加
    markdown += "\n#diary\n"
    
    return date_iso, markdown

def set_file_times_windows(filepath, timestamp):
    """Windowsでファイルの作成日時、更新日時、アクセス日時を設定"""
    # Windows API の定義
    kernel32 = ctypes.windll.kernel32
    
    # 定数
    GENERIC_WRITE = 0x40000000
    FILE_SHARE_READ = 0x00000001
    FILE_SHARE_WRITE = 0x00000002
    OPEN_EXISTING = 3
    FILE_ATTRIBUTE_NORMAL = 0x80
    
    # ファイルハンドルを開く
    handle = kernel32.CreateFileW(
        str(filepath),
        GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        None,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        None
    )
    
    if handle == -1:
        print(f"ファイルを開けませんでした: {filepath}")
        return
    
    try:
        # datetimeをWindowsのFILETIME形式に変換
        # FILETIMEは1601年1月1日からの100ナノ秒単位
        unix_epoch = datetime(1970, 1, 1)
        windows_epoch = datetime(1601, 1, 1)
        
        # Unixエポックからの秒数を取得
        delta = timestamp - windows_epoch
        # 100ナノ秒単位に変換
        filetime = int(delta.total_seconds() * 10000000)
        
        # FILETIME構造体を作成
        class FILETIME(ctypes.Structure):
            _fields_ = [
                ("dwLowDateTime", ctypes.c_uint32),
                ("dwHighDateTime", ctypes.c_uint32)
            ]
        
        ft = FILETIME()
        ft.dwLowDateTime = filetime & 0xFFFFFFFF
        ft.dwHighDateTime = filetime >> 32
        
        # ファイル時刻を設定(作成日時、アクセス日時、更新日時)
        kernel32.SetFileTime(
            handle,
            ctypes.byref(ft),  # 作成日時
            ctypes.byref(ft),  # アクセス日時
            ctypes.byref(ft)   # 更新日時
        )
    finally:
        kernel32.CloseHandle(handle)

def process_diary(input_text, output_dir='diary'):
    """日記テキストを処理してファイルに出力"""
    # 出力ディレクトリを作成
    Path(output_dir).mkdir(exist_ok=True)
    
    # エントリをパース
    entries = parse_diary_entries(input_text)
    
    # 日付順にソート
    entries.sort(key=lambda x: x['date'])
    
    for entry_data in entries:
        # Markdownに変換
        filename, markdown_content = convert_to_markdown(entry_data)
        
        # ファイルに書き出し
        output_path = Path(output_dir) / f"{filename}.md"
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(markdown_content)
        
        # Windowsでファイルのタイムスタンプを設定
        set_file_times_windows(output_path, entry_data['date'])
        
        print(f"作成: {output_path}")

# メイン処理
if __name__ == "__main__":
    # diary.txtを読み込み
    try:
        with open('diary.txt', 'r', encoding='utf-8') as f:
            diary_text = f.read()
        
        print("diary.txtを読み込みました")
        print("変換を開始します...\n")
        
        # 変換実行
        process_diary(diary_text)
        
        print("\n変換が完了しました!")
        
    except FileNotFoundError:
        print("エラー: diary.txtが見つかりません")
        print("スクリプトと同じディレクトリにdiary.txtを配置してください")
    except Exception as e:
        print(f"エラーが発生しました: {e}")