Google Calendar와 Obsidian을 연동한 자동 미팅록 관리 시스템 구축기

소개

시도하고자 했던 것과 그 이유를 알려주세요.

Google Calendar는 미팅록을 Google Docs로 관리하기 쉽게 자동으로 연결 가능합니다. Google Docs는 협업에는 훌륭하지만, 지식 체계 구축에는 한계가 있다고 느낍니다. 무엇보다 내 데이터에 대한 주도권을 갖고 싶었습니다.

마크다운 파일로 문서를 관리하면 다음과 같은 장점이 있습니다:

  • 완전한 데이터 소유권: 로컬 파일로 저장되어 플랫폼 종속성 없음

  • 버전 관리 용이: Git으로 변경 이력 추적 가능

  • 높은 이식성: 어떤 에디터에서든 열 수 있는 순수 텍스트 파일

  • 강력한 연결성: Obsidian의 백링크, 그래프 뷰로 지식 네트워크 구축

  • 자유로운 커스터마이징: 플러그인과 CSS로 완전한 환경 제어

그래서 Google Calendar의 미팅 일정을 자동으로 감지하여 마크다운 파일로 미팅록을 생성하고, 이를 Obsidian에서 체계적으로 관리하는 시스템을 구축하기로 했습니다.

이 시스템의 목표는:

  1. Google Calendar는 일정 관리 도구로만 사용

  2. 실제 미팅록은 마크다운으로 작성하여 데이터 주도권 확보

  3. Obsidian을 통해 미팅록을 개인 지식 베이스와 연결

진행 방법

어떤 도구를 사용했고, 어떻게 활용하셨나요?

사용한 도구:

  • Google Apps Script (2개 프로젝트)

  • Google Calendar API

  • Google Drive API

  • Obsidian (Google Drive 동기화)

  • Obsidian URI Protocol

1단계: Calendar 이벤트 모니터링 및 노트 생성 스크립트

30분마다 실행되는 트리거를 설정하여 향후 2시간 내의 미팅을 감지하고 자동으로 노트를 생성합니다.

핵심 기능:

// 미팅 확인 및 처리
function checkUpcomingMeetings() {
  const now = new Date();
  const later = new Date(now.getTime() + CONFIG.LOOKAHEAD_HOURS * 60 * 60 * 1000);
  
  const events = CalendarApp.getDefaultCalendar()
    .getEvents(now, later);
  
  events.forEach(event => {
    // 참석자가 있고 아직 처리되지 않은 미팅만 처리
    if (event.getGuestList().length > 0 && !isProcessed(event)) {
      try {
        createMeetingNotes(event);
      } catch (error) {
        console.error(`미팅 노트 생성 실패: ${event.getTitle()}`, error);
      }
    }
  });
}

미팅록 템플릿 생성:

function generateNoteContent(event) {
  // YAML frontmatter와 함께 구조화된 마크다운 생성
  return `---
date: ${dateStr}
time: "${Utilities.formatDate(startTime, 'Asia/Seoul', 'HH:mm')}"
type: meeting
title: "${title}"
participants:
${guests.map(g => `  - ${g.getEmail()}`).join('\n')}
tags: [meeting, ${dateStr.substring(0,7)}]
---

# ${title}

## 📅 미팅 정보
- **날짜:** ${dateStr}
- **시간:** ${startTime} - ${endTime}
- **장소:** ${location}

## 👥 참석자
${guests.map(g => `- ${g.getGuestStatus() === 'YES' ? '✅' : '❓'} ${g.getName()}`).join('\n')}

## 📋 안건
${description}

## 📝 회의 내용

## 🎯 액션 아이템
- [ ] 

## 💡 주요 결정사항
`;
}

2단계: Obsidian Deep Link 리다이렉트 서비스

Google Calendar는 보안상 obsidian:// 프로토콜을 직접 지원하지 않아, 중간 리다이렉트 서비스를 구현했습니다.

function doGet(e) {
  const params = e.parameter;
  const vault = params.vault || '';
  const file = params.file || '';
  
  const obsidianUri = `obsidian://open?vault=${encodeURIComponent(vault)}&file=${encodeURIComponent(file)}`;
  
  // 사용자 친화적인 HTML 페이지 생성
  const html = `
    <!DOCTYPE html>
    <html>
    <head>
        <title>Obsidian 노트 열기</title>
        <style>
            .container {
                background: white;
                padding: 40px;
                border-radius: 20px;
                text-align: center;
                box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
            }
            
            .btn-primary {
                padding: 16px 32px;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                border-radius: 12px;
                font-weight: 600;
                cursor: pointer;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <h1>미팅 노트 열기</h1>
            <button onclick="openObsidian()" class="btn-primary">
                📝 Obsidian에서 열기
            </button>
        </div>
        
        <script>
            function openObsidian() {
                window.location.href = ${JSON.stringify(obsidianUri)};
            }
            
            // 페이지 로드 시 자동 시도
            window.addEventListener('load', () => {
                setTimeout(() => {
                    const iframe = document.createElement('iframe');
                    iframe.style.display = 'none';
                    iframe.src = ${JSON.stringify(obsidianUri)};
                    document.body.appendChild(iframe);
                }, 500);
            });
        </script>
    </body>
    </html>
  `;
  
  return HtmlService.createHtmlOutput(html);
}

3단계: Calendar 이벤트에 링크 추가

생성된 노트의 링크를 Calendar 이벤트 설명에 자동 추가:

function updateEventWithLinks(event, file, dateStr, fileName) {
  const driveLink = file.getUrl();
  const filePath = `meeting_note/${dateStr}/${fileName.replace('.md', '')}`;
  
  // Redirect Service를 통한 Obsidian 링크
  const obsidianLink = `${REDIRECT_SERVICE_URL}?app=obsidian&vault=${CONFIG.OBSIDIAN_VAULT_NAME}&file=${encodeURIComponent(filePath)}`;
  
  const linksSection = `
📝 미팅 노트:
<a href="${driveLink}">📁 Google Drive에서 보기</a>
<a href="${obsidianLink}">📝 Obsidian에서 열기</a>
━━━━━━━━━━━━━━━━━━━`;

  event.setDescription(currentDesc + linksSection);
}

실제 Calendar 이벤트 화면:

결과와 배운 점

배운 점과 나만의 꿀팁

  1. 파일명 안전 변환의 중요성: Calendar 에서 사용가능한 문자와 Obsidian 파일 명에서 사용가능한 문자가 달라 중간에 변경작업이 필요함

  2. Event ID 기반 중복 처리 방지: 이벤트 설명에 마커를 추가하여 중복 생성 방지

    function isProcessed(event) {
      return event.getDescription().includes('📝 미팅 노트:');
    }
    
  3. 시간대 처리: 한국 시간대 명시적 설정으로 일관성 유지

    Utilities.formatDate(date, 'Asia/Seoul', 'yyyy-MM-dd')
    

과정 중에 겪은 시행착오

  1. Deep Link 직접 연결 실패: Google Calendar의 보안 정책으로 obsidian:// 프로토콜 직접 사용 불가

    • 해결: 별도 Apps Script 웹앱으로 리다이렉트 서비스 구현

  2. 파일명 특수문자 문제: 미팅 제목의 '/', ':', '?' 등이 파일 시스템 오류 발생

    • 해결: 포괄적인 sanitize 함수 구현

도움이 필요한 부분

  • Obsidian 모바일 앱에서의 Deep Link 처리 개선 방법

  • 미팅 종료 후 자동으로 녹화 링크나 액션 아이템 동기화

앞으로의 계획

  1. AI 통합: Google Meeting 전사 Obsidian으로 자동 업데이트하는 파이프라인 구성

  2. Slack 연동: 액션 아이템을 Slack으로 자동 전송

  3. 템플릿 다양화: 미팅 유형별(1:1, 팀미팅, 고객미팅) 템플릿 자동 선택

4

뉴스레터 무료 구독

👉 이 게시글도 읽어보세요