텔레그램 채팅을 옵시디언으로 자동 변환하는 방법: 미디어 및 링크 처리 포함
이 변환기는 텔레그램 데스크톱의 JSON 내보내기 파일을 체계적으로 정리된 옵시디언 볼트로 전환합니다. 메시지를 분석하고, 미디어 파일을 복사하며, 텔레그램 형식을 깔끔한 마크다운으로 변환하고, 채팅 유형별로(연락처, 그룹, 채널) 구조화합니다. 이 솔루션은 신뢰성 높은 경로 해결을 위한 견고한 파일 인덱스를 구축하며, 중복된 파일명 충돌도 자연스럽게 처리합니다.
원본 텔레그램 내보내기에는 result.json(메타데이터), HTML 기반 chats/ 폴더(메시지 포함), 그리고 관련 미디어 파일이 포함됩니다. 목표 폴더 구조는 Telegram_Export/이며, 하위에 Contacts/, Groups/, Channels/ 폴더와 중앙 Index.md 파일이 위치합니다.
변환기 아키텍처
시스템은 다섯 가지 핵심 모듈로 구성됩니다: JSON 파서, 미디어 인덱서, 마크다운 변환기, 파일 매니저, 볼트 구조 생성기. 데이터 흐름은 다음과 같습니다: JSON → 파싱 → 인덱싱 → 변환 → 미디어가 노트 폴더에 복사된 옵시디언 볼트.
주요 구성 요소:
- 파서:
result.json에서 채팅, 메시지, 메타데이터를 추출합니다. - 인덱서:
.jpg,.mp4,.pdf등 확장자별로 미디어 파일의 검색 가능한 맵을 구축합니다. - 변환기: 다중 전략 미디어 조회 및 풍부한 텍스트 서식 규칙을 적용합니다.
- 파일 매니저: 중복 이름 제거와 함께 자산을 해당 노트 폴더로 복사합니다.
설정은 환경 변수로 관리됩니다: TELEGRAM_JSON_FILE, OBSIDIAN_OUTPUT_DIR, COPY_MEDIA.
import os
from pathlib import Path
JSON_FILE = os.getenv('TELEGRAM_JSON_FILE', 'result.json')
EXPORT_BASE = Path(os.getenv('TELEGRAM_EXPORT_BASE', '.'))
OUTPUT_DIR = Path(os.getenv('OBSIDIAN_OUTPUT_DIR', 'Telegram_Export'))
COPY_MEDIA = os.getenv('COPY_MEDIA', 'true').lower() == 'true'
GROUP_BY_DAY = os.getenv('GROUP_BY_DAY', 'true').lower() == 'true'
미디어 인덱싱 및 조회
patch.txt 파일은 ls -R 명령어로 생성되며, 텔레그램 내보내기 전체 디렉터리 트리를 기록합니다. 인덱서는 파일명, 상대 경로, 절대 경로를 모두 매핑해 빠르고 결정론적인 조회를 가능하게 합니다.
def index_media_from_patch(self):
media_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.webp', '.mp4', '.webm', '.pdf', '.zip', '.mp3'}
current_dir = None
with open(PATCH_FILE, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line.endswith(':'):
current_dir = line[:-1]
continue
if current_dir and any(line.endswith(ext) for ext in media_extensions):
full_path = Path(current_dir) / line
self.media_index[line] = full_path
if 'chats/' in str(full_path):
rel_path = str(full_path).split('chats/', 1)[-1]
self.media_index[rel_path] = full_path
미디어 조회는 네 가지 대체 전략을 사용합니다: 정확한 파일명 일치, 전체 경로 일치, 부분 경로 일치, EXPORT_BASE 기준 직접 상대 경로 해석.
충돌 처리를 통한 미디어 복사
파일은 공유 첨부 폴더가 아닌 각 노트 폴더 내부로 복사됩니다. 이를 통해 옵시디언의 기본 임베드 문법(![[file]])이 즉시 작동합니다. media_cache는 반복 조회를 가속화합니다. 이름 충돌 시 _1, _2와 같은 접미사가 자동으로 추가됩니다.
def copy_media_file(self, source_path: str, note_folder: Path = None) -> Optional[str]:
if not COPY_MEDIA or not source_path:
return None
source_file = self.find_media_file(source_path)
if not source_file or not source_file.exists():
return None
target_dir = note_folder if note_folder else OUTPUT_DIR / "Attachments"
target_dir.mkdir(parents=True, exist_ok=True)
target_file = target_dir / source_file.name
if target_file.exists():
stem = target_file.stem
suffix = target_file.suffix
counter = 1
while target_file.exists():
target_file = target_dir / f"{stem}_{counter}{suffix}"
counter += 1
shutil.copy2(source_file, target_file)
self.stats['media_files'] += 1
return source_file.name
텍스트 및 엔티티 파싱
HTML과 텔레그램 전용 엔티티(볼드, 링크, 스포일러 등)는 의미 있는 마크다운으로 변환됩니다. 전용 핸들러가 의미 보존과 옵시디언 호환성을 보장합니다.
def parse_text_entities(self, text: Union[str, List], entities: Optional[List[Dict]] = None) -> str:
if isinstance(text, str) and ('<' in text or '&' in text):
return self.html_to_markdown(text)
if entities and isinstance(text, str):
return self._process_entities(text, entities)
handlers = {
'bold': lambda t: f"**{t}**",
'italic': lambda t: f"*{t}*",
'code': lambda t: f"`{t}`",
'pre': lambda t: f"""
{t}
""",
'link': lambda t, e: f"[{t}]({e.get('url', '')})",
'spoiler': lambda t: f"\n> [!spoiler] {t}\n"
}
return str(text) if text else ""
메시지 서식 논리
각 메시지는 타임스탬프, 발신자, 서식화된 텍스트(엔티티 포함), 내장 미디어 참조를 포함합니다. photo, video, audio와 같은 키는 정확한 미디어 추출을 유발합니다.
처리 단계:
date및from필드 추출.text_entities로직을 사용해text파싱.- 미디어 키 감지 → 파일 복사 →
또는![[file]]링크 삽입. GROUP_BY_DAY활성화 시 날짜별 메시지 그룹화.
내장 통계 기능은 처리된 채팅, 메시지, 미디어 파일, 연락처 수를 실시간 추적합니다.
주요 설계 원칙
patch.txt인덱싱은 JSON 경로와 실제 파일 시스템 구조 간 불일치를 해소합니다.- 4단계 미디어 조회 전략으로 자산 해결률 95% 이상 달성.
- 노트별 미디어 복사는 옵시디언 임베드 기능의 완벽한 무결성을 보장합니다.
- 텔레그램의 풍부한 텍스트 기능(볼드, 코드 블록, 링크, 멘션, 스포일러)을 완전히 지원합니다.
- 환경 변수 기반 설정으로 프로덕션 배포 및 CI/CD 통합이 용이합니다.
— Editorial Team
아직 댓글이 없습니다.