@clawhub-ibluewind-6a21c5c94a
Google Calendar API ���� �� 조�, ��, ��, �� �리. OAuth 2.0 �� ��. ���� �� �린��� ��� ���고 �리�� � ��.
---
name: andrew-google-calendar
description: Google Calendar API ���� �� 조�, ��, ��, �� �리. OAuth 2.0 �� ��. ���� �� �린��� ��� ���고 �리�� � ��.
---
# Google Calendar
## Overview
Google Calendar API 를 ���� ���� ��� 조�, ��, ��, ���� � �� ������. OAuth 2.0 ��� ���� ���� �� �린�� �근����.
## Setup
### 1. OAuth ������� �� ��
```bash
# Google Cloud Console �� OAuth ������� �� ����
# https://console.cloud.google.com/apis/credentials
# �� ��� � ����리� 복�
cp ~/Downloads/client_secret_*.json ~/.google-credentials.json
```
### 2. �존� ��
```bash
pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib
```
### 3. �� ����
```bash
cd /Users/andrew/.openclaw/workspace/google-calendar
python3 scripts/oauth_setup.py
```
첫 ��� ����� �리고 Google ���� �그�� � ��� ����� ����.
## Capabilities
### �� 조�
**��� �� ��:**
```
"�� 7 � ��� 보��"
"�� ��� ��?"
"�� 주 �� 목� �려�"
```
**��� 기� 조�:**
```
"4 � 15 ���� 20 ��� ��� 보��"
```
### �� ��
**� �� ��:**
```
"�� �� 2 �� � 미� �� ����, 1 �� ��, Zoom ��"
"�� 주 ��� 10 �� dentist ��, 30 �"
```
### �� ��
**�� �경:**
```
"�� �� 2 � 미�� �� 3 �� ���"
"�� �목� '� 미�'�� '����� ��� 미�'�� �경���"
```
### �� ��
**�� 취�:**
```
"�� �� 2 � 미� 취����"
```
## Usage Examples
### �� 1: ��� �� 조�
```python
from scripts.calendar_ops import list_events, format_event
# �� 7 � �� 조�
events = list_events(max_results=10)
for event in events:
print(format_event(event))
```
### �� 2: � �� ��
```python
from scripts.calendar_ops import create_event
from datetime import datetime, timedelta
# �� �� 2 � �� ��
start = datetime.now() + timedelta(days=1, hours=14)
end = start + timedelta(hours=1)
event = create_event(
summary="� 미�",
start_time=start.isoformat(),
end_time=end.isoformat(),
description="주� ����� ���",
location="Zoom"
)
```
### �� 3: �린� 목� ��
```python
from scripts.oauth_setup import list_calendars
calendars = list_calendars()
for cal in calendars:
print(f"{cal['summary']} - {cal['accessRole']}")
```
## Files Structure
```
google-calendar/
��� SKILL.md
��� scripts/
� ��� oauth_setup.py # OAuth 2.0 �� � ���� �리
� ��� calendar_ops.py # Calendar API �� ����
��� references/
```
## Security Notes
- OAuth ����� `~/.google-calendar-token.pickle` � �����
- ������� ��� `~/.google-credentials.json` � �����
- � ���� `.gitignore` � ����� ����
- �� ��: `https://www.googleapis.com/auth/calendar` (�기/�기 �체 �근)
## Troubleshooting
**"OAuth ������� �� ��� ����" ��:**
- Google Cloud Console �� OAuth 2.0 ������� ��를 �� ����
- `client_secret_XXXXXX.json` ��� `~/.google-credentials.json` �� 복�
**�� ���:**
- ���� ��� ���고 ���: `rm ~/.google-calendar-token.pickle`
- Google Cloud Console �� API ��� ��
**�� ��:**
- OAuth �� �면�� ��� �� ��
- ����� �� � ���
FILE:scripts/calendar_ops.py
#!/usr/bin/env python3
"""
Google Calendar 연산 함수들
일정 조회, 생성, 수정, 삭제 등
"""
from datetime import datetime, timedelta
from oauth_setup import get_calendar_service, list_calendars
def list_events(calendar_id='primary', date_from=None, date_to=None, max_results=10):
"""
일정 목록 조회
Args:
calendar_id: 캘린더 ID (기본: 'primary')
date_from: 시작 날짜 (YYYY-MM-DD 형식 또는 datetime 객체)
date_to: 종료 날짜 (YYYY-MM-DD 형식 또는 datetime 객체)
max_results: 최대 결과 수
Returns:
일정 목록
"""
service = get_calendar_service()
# 날짜 처리
if date_from:
if isinstance(date_from, str):
date_from = datetime.strptime(date_from, '%Y-%m-%d')
time_min = date_from.isoformat() + 'Z'
else:
time_min = datetime.utcnow().isoformat() + 'Z'
if date_to:
if isinstance(date_to, str):
date_to = datetime.strptime(date_to, '%Y-%m-%d')
time_max = date_to.isoformat() + 'Z'
else:
time_max = (datetime.utcnow() + timedelta(days=7)).isoformat() + 'Z'
# API 호출
events_result = service.events().list(
calendarId=calendar_id,
timeMin=time_min,
timeMax=time_max,
maxResults=max_results,
singleEvents=True,
orderBy='startTime'
).execute()
events = events_result.get('items', [])
return events
def create_event(summary, start_time, end_time, description='', location='', attendees=None, calendar_id='primary'):
"""
새 일정 생성
Args:
summary: 일정 제목
start_time: 시작 시간 (datetime 객체 또는 'YYYY-MM-DDTHH:MM:SS' 형식)
end_time: 종료 시간 (datetime 객체 또는 'YYYY-MM-DDTHH:MM:SS' 형식)
description: 설명
location: 위치
attendees: 참석자 이메일 목록 (예: ['[email protected]'])
Returns:
생성된 일정 객체
"""
service = get_calendar_service()
# 시간 처리
if isinstance(start_time, datetime):
start_time = start_time.isoformat()
if isinstance(end_time, datetime):
end_time = end_time.isoformat()
event = {
'summary': summary,
'start': {
'dateTime': start_time,
'timeZone': 'Asia/Seoul'
},
'end': {
'dateTime': end_time,
'timeZone': 'Asia/Seoul'
},
'description': description,
'location': location
}
if attendees:
event['attendees'] = [{'email': email} for email in attendees]
created_event = service.events().insert(
calendarId=calendar_id,
body=event
).execute()
return created_event
def update_event(event_id, summary=None, start_time=None, end_time=None,
description=None, location=None, calendar_id=None):
"""
일정 수정
Args:
event_id: 수정할 일정 ID
summary: 새 제목 (선택)
start_time: 새 시작 시간 (선택)
end_time: 새 종료 시간 (선택)
description: 새 설명 (선택)
location: 새 위치 (선택)
calendar_id: 이동할 캘린더 ID (선택 - 이동시 필요)
Returns:
수정된 일정 객체
"""
service = get_calendar_service()
# 실제 이벤트 ID (recurring event 인 경우 _ 날짜 부분 제거)
base_event_id = event_id.split('_')[0] if '_' in event_id else event_id
# 기존 일정 가져오기 (기본 캘린더에서)
event = service.events().get(
calendarId='primary',
eventId=base_event_id
).execute()
# 업데이트
if summary:
event['summary'] = summary
if start_time:
if isinstance(start_time, datetime):
start_time = start_time.isoformat()
event['start'] = {'dateTime': start_time, 'timeZone': 'Asia/Seoul'}
if end_time:
if isinstance(end_time, datetime):
end_time = end_time.isoformat()
event['end'] = {'dateTime': end_time, 'timeZone': 'Asia/Seoul'}
if description is not None:
event['description'] = description
if location is not None:
event['location'] = location
# 캘린더 이동시: 원본에서 삭제하고 새 캘린더에 생성
if calendar_id and calendar_id != 'primary':
# 반복 일정 인스턴스면 ID 관련 필드 제거하고 새로 생성 (충돌 방지)
if 'id' in event:
del event['id']
if 'iCalUID' in event:
del event['iCalUID']
if 'sequence' in event:
del event['sequence']
# 새 캘린더에 생성
new_event = service.events().insert(
calendarId=calendar_id,
body=event
).execute()
# 원본 삭제 (base_event_id 사용)
try:
service.events().delete(
calendarId='primary',
eventId=base_event_id
).execute()
except:
pass # 이미 삭제되었거나 단일 인스턴스일 수 있음
return new_event
else:
updated_event = service.events().update(
calendarId='primary',
eventId=base_event_id,
body=event
).execute()
return updated_event
def delete_event(event_id):
"""
일정 삭제
Args:
event_id: 삭제할 일정 ID
"""
service = get_calendar_service()
service.events().delete(
calendarId='primary',
eventId=event_id
).execute()
return True
def format_event(event):
"""
일정 포맷팅 (출력용)
"""
summary = event.get('summary', 'No title')
start = event['start'].get('dateTime', event['start'].get('date', 'Unknown'))
end = event['end'].get('dateTime', event['end'].get('date', 'Unknown'))
location = event.get('location', '')
# 시간 포맷팅
try:
if 'T' in start:
start = datetime.fromisoformat(start.replace('Z', '+00:00'))
start = start.strftime('%m/%d %H:%M')
if 'T' in end:
end = datetime.fromisoformat(end.replace('Z', '+00:00'))
end = end.strftime('%m/%d %H:%M')
except:
pass
return f" • {summary} | {start} - {end} {'📍 ' + location if location else ''}"
def main():
"""
테스트용 메인 함수
"""
print("📅 Google Calendar 테스트\n")
# 캘린더 목록
print("=== 캘린더 목록 ===")
calendars = list_calendars()
for cal in calendars[:3]: # 상위 3 개만
print(f" • {cal['summary']}")
# 일정 목록
print("\n=== 향후 7 일 일정 ===")
events = list_events(max_results=5)
if events:
for event in events:
print(format_event(event))
else:
print(" 예정된 일정이 없습니다.")
# 새 일정 생성 테스트
# create_event(
# summary="테스트 회의",
# start_time=(datetime.now() + timedelta(days=1)).isoformat(),
# end_time=(datetime.now() + timedelta(days=1, hours=1)).isoformat(),
# description="테스트용 일정입니다",
# location="Zoom"
# )
# print("\n✅ 새 일정 생성 완료!")
if __name__ == '__main__':
main()
FILE:scripts/oauth_setup.py
#!/usr/bin/env python3
"""
OAuth 2.0 인증 설정 및 토큰 관리
Google Calendar API 접근을 위한 인증 처리
"""
import os
import pickle
from pathlib import Path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
# 권한 범위 (Calendar 읽기/쓰기)
SCOPES = ['https://www.googleapis.com/auth/calendar']
# 토큰 저장 경로
TOKEN_FILE = Path.home() / '.google-calendar-token.pickle'
CREDENTIALS_FILE = Path.home() / '.google-credentials.json'
def authenticate():
"""
OAuth 2.0 인증 수행 및 Credentials 반환
처음 실행시 브라우저에서 로그인 진행
"""
creds = None
# 기존 토큰이 있으면 로드
if TOKEN_FILE.exists():
with open(TOKEN_FILE, 'rb') as token:
creds = pickle.load(token)
# 토큰이 없거나 만료되었으면 새로 인증
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
# 리프레시
creds.refresh(Request())
else:
# OAuth 클라이언트 키가 있어야 함
if not CREDENTIALS_FILE.exists():
raise FileNotFoundError(
f"OAuth 클라이언트 키 파일이 없습니다.\n"
f"Google Cloud Console 에서 다운로드한 client_secret_*.json 파일을\n"
f"{CREDENTIALS_FILE} 로 복사해주세요."
)
# OAuth 흐름 시작
flow = InstalledAppFlow.from_client_secrets_file(
str(CREDENTIALS_FILE), SCOPES
)
creds = flow.run_local_server(port=8080)
# 토큰 저장
with open(TOKEN_FILE, 'wb') as token:
pickle.dump(creds, token)
return creds
def get_calendar_service():
"""
Google Calendar API 서비스 객체 반환
"""
creds = authenticate()
service = build('calendar', 'v3', credentials=creds)
return service
def list_calendars():
"""
사용자의 모든 캘린더 목록 조회
"""
service = get_calendar_service()
calendar_list = service.calendarList()
result = calendar_list.list(maxResults=25).execute()
calendars = result.get('items', [])
return calendars
def main():
"""
인증 테스트 및 캘린더 목록 출력
"""
try:
print("🔐 인증 중...")
calendars = list_calendars()
print(f"\n✅ 총 {len(calendars)} 개의 캘린더가 있습니다:\n")
for cal in calendars:
summary = cal.get('summary', 'Unknown')
access_role = cal.get('accessRole', 'unknown')
primary = " (기본)" if cal.get('primary') else ""
print(f" • {summary}{primary} [{access_role}]")
print("\n✅ 인증 성공!")
except FileNotFoundError as e:
print(f"\n❌ {e}")
print("\n설정 방법:")
print("1. Google Cloud Console 에서 OAuth 클라이언트 키 다운로드")
print("2. 파일을 ~/.google-calendar-credentials.json 으로 복사")
print("3. 다시 실행")
except Exception as e:
print(f"\n❌ 오류 발생: {e}")
if __name__ == '__main__':
main()
Google Tasks API ���� �� (Task) �리. OAuth 2.0 �� ��. ���� �� � 목�� 조�, ��, ��, �� �리�� � ��.
---
name: andrew-google-tasks
description: Google Tasks API ���� �� (Task) �리. OAuth 2.0 �� ��. ���� �� � 목�� 조�, ��, ��, �� �리�� � ��.
---
# Google Tasks
## Overview
Google Tasks API 를 ���� ���� �� � (Tasks) � 조�, ��, ��, �� �리�� � �� ������. OAuth 2.0 ��� ���� ���� Tasks � �근����.
## Setup
### 1. OAuth ������� �� ��
�미 구� �린�, ��� ���과 ��� �� ��� ������:
```bash
# �� ��� �미 ���� ��면 ��
ls ~/.google-credentials.json
```
### 2. �존� ��
```bash
pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib
```
### 3. �� ����
```bash
cd /Users/andrew/.openclaw/workspace/google-tasks
python3 scripts/tasks_ops.py
```
첫 ��� ����� �리고 Google ���� �그� � ��� ����� ����.
## Capabilities
### �� 목� 조�
**���� �� 목� ��:**
```
"� �� � 목� 보��"
"�� �� �� �� �� ��?"
```
### � �� ��
**� �� � ��:**
```
"�� 미� ���� � �����, ��� �� �� 2 �"
"����� 보고�를 ����� ��, �모: 5 ��� ��"
```
### �� �� �리
**�� ��:**
```
"�� 미� �� ��� �����"
```
### �� ��
**�� �� �경:**
```
"����� 보고� ���� �� 주 ���� ���"
```
### �� ��
**�� 취�:**
```
"���� �� �����"
```
## Usage Examples
### �� 1: �� 목� 조�
```python
from scripts.tasks_ops import list_tasks, format_task
# 기본 목�� �� 조�
tasks = list_tasks('@default')
for task in tasks:
print(format_task(task))
```
### �� 2: � �� ��
```python
from scripts.tasks_ops import create_task
# � �� ��
task = create_task(
tasklist_id='@default',
title='����� 보고� ��',
notes='5 ��� ��, �����',
due='2026-04-20T17:00:00+09:00'
)
print(f"�� �� ��: {task['title']}")
```
### �� 3: �� �� �리
```python
from scripts.tasks_ops import complete_task
# �� ��
task_id = '��_ID_�기�'
complete_task('@default', task_id)
print("�� �� �리�!")
```
### �� 4: �� 목� 목� 조�
```python
from scripts.tasks_ops import list_tasklists
tasklists = list_tasklists()
for tl in tasklists:
print(f"{tl['title']} - {tl['id']}")
```
## Files Structure
```
google-tasks/
��� SKILL.md
��� scripts/
��� tasks_ops.py # Tasks API �� ����
```
## Security Notes
- OAuth ����� `~/.google-tasks-token.pickle` � �����
- ������� ��� `~/.google-credentials.json` � ����� (�린�, ��� ���과 공�)
- � ���� `.gitignore` � ����� ����
- �� ��: `https://www.googleapis.com/auth/tasks` (Tasks �체 �근)
## Troubleshooting
**"OAuth ������� �� ��� ����" ��:**
- `~/.google-credentials.json` ��� ��� ��
- 구� �린� ��� �� � �미 ��� �� �����
**�� ���:**
- ���� ��� ���고 ���: `rm ~/.google-tasks-token.pickle`
**�� ��:**
- ����� �� � ���: `rm ~/.google-tasks-token.pickle && python3 scripts/tasks_ops.py`
## Integration with Other Google Skills
Same OAuth credentials (`~/.google-credentials.json`) are shared with `google-calendar` and `google-sheets` skills, so you only need to authenticate once!
FILE:scripts/tasks_ops.py
#!/usr/bin/env python3
"""
Google Tasks 연산 함수들
작업 (Task) 생성, 조회, 수정, 완료 처리 등
"""
from pathlib import Path
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
import pickle
# 권한 범위
SCOPES = ['https://www.googleapis.com/auth/tasks']
# 인증 파일 경로
CREDENTIALS_FILE = Path.home() / '.google-credentials.json'
TOKEN_FILE = Path.home() / '.google-tasks-token.pickle'
def authenticate():
"""
OAuth 2.0 인증 수행 및 Credentials 반환
"""
creds = None
# 기존 토큰이 있으면 로드
if TOKEN_FILE.exists():
with open(TOKEN_FILE, 'rb') as token:
creds = pickle.load(token)
# 토큰이 없거나 만료되었으면 새로 인증
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
if not CREDENTIALS_FILE.exists():
raise FileNotFoundError(
f"OAuth 클라이언트 키 파일이 없습니다.\n"
f"{CREDENTIALS_FILE} 파일을 준비해주세요."
)
flow = InstalledAppFlow.from_client_secrets_file(
str(CREDENTIALS_FILE), SCOPES
)
creds = flow.run_local_server(port=8083)
# 토큰 저장
with open(TOKEN_FILE, 'wb') as token:
pickle.dump(creds, token)
return creds
def get_tasks_service():
"""
Google Tasks API 서비스 객체 반환
"""
creds = authenticate()
service = build('tasks', 'v1', credentials=creds)
return service
def list_tasklists():
"""
사용자의 모든 작업 목록 (Task Lists) 조회
"""
service = get_tasks_service()
result = service.tasklists().list(maxResults=100).execute()
tasklists = result.get('items', [])
return tasklists
def list_tasks(tasklist_id='@default'):
"""
특정 작업 목록의 모든 작업 조회
Args:
tasklist_id: 작업 목록 ID (@default: 기본 목록 사용)
Returns:
작업 목록
"""
service = get_tasks_service()
result = service.tasks().list(tasklist=tasklist_id, showCompleted=False).execute()
tasks = result.get('items', [])
return tasks
def create_task(tasklist_id, title, notes='', due=None, parent=None):
"""
새 작업 생성
Args:
tasklist_id: 작업 목록 ID
title: 작업 제목
notes: 메모/설명
due: 마감일 (ISO 8601 형식, 예: '2026-04-20T10:00:00+09:00')
parent: 부모 작업 ID (하위 작업일 경우)
Returns:
생성된 작업 객체
"""
service = get_tasks_service()
task = {
'title': title,
'notes': notes
}
if due:
task['due'] = due
if parent:
task['parent'] = parent
created_task = service.tasks().insert(
tasklist=tasklist_id,
body=task
).execute()
return created_task
def update_task(tasklist_id, task_id, title=None, notes=None, due=None, status=None):
"""
작업 수정
Args:
tasklist_id: 작업 목록 ID
task_id: 작업 ID
title: 새 제목 (선택)
notes: 새 메모 (선택)
due: 새 마감일 (선택)
status: 새 상태 ('needsAction' 또는 'completed')
Returns:
수정된 작업 객체
"""
service = get_tasks_service()
# 기존 작업 가져오기
task = service.tasks().get(tasklist=tasklist_id, task=task_id).execute()
# 업데이트
if title:
task['title'] = title
if notes is not None:
task['notes'] = notes
if due:
task['due'] = due
if status:
task['status'] = status
updated_task = service.tasks().update(
tasklist=tasklist_id,
task=task_id,
body=task
).execute()
return updated_task
def complete_task(tasklist_id, task_id):
"""
작업 완료 처리
Args:
tasklist_id: 작업 목록 ID
task_id: 작업 ID
Returns:
완료된 작업 객체
"""
return update_task(tasklist_id, task_id, status='completed')
def delete_task(tasklist_id, task_id):
"""
작업 삭제
Args:
tasklist_id: 작업 목록 ID
task_id: 작업 ID
"""
service = get_tasks_service()
service.tasks().delete(tasklist=tasklist_id, task=task_id).execute()
return True
def format_task(task, indent=0):
"""
작업 포맷팅 (출력용, 하위 작업 포함)
"""
prefix = ' ' * indent
title = task.get('title', 'No title')
status = '✅' if task.get('status') == 'completed' else '⬜'
due = task.get('due', '')
notes = task.get('notes', '')
# 마감일 포맷팅
if due:
try:
due = due.split('T')[0] # 날짜 부분만 추출
except:
pass
line = f"{prefix}{status} {title}"
if due:
line += f" 📅 {due}"
lines = [line]
if notes and indent < 2:
lines.append(f"{prefix} 📝 {notes}")
return '\n'.join(lines)
def main():
"""
테스트용 메인 함수
"""
print("✅ Google Tasks 테스트\n")
try:
# 작업 목록 목록
print("=== 작업 목록 (Task Lists) ===")
tasklists = list_tasklists()
if tasklists:
for tl in tasklists:
print(f" • {tl['title']} ({tl['id']})")
else:
print(" 작업 목록이 없습니다.")
# 기본 작업 목록의 작업들
print("\n=== 기본 작업 목록의 작업 ===")
tasks = list_tasks('@default')
if tasks:
for task in tasks:
print(format_task(task))
else:
print(" 할 일이 없습니다! 🎉")
print("\n✅ 인증 및 연결 성공!")
except FileNotFoundError as e:
print(f"\n❌ {e}")
except Exception as e:
print(f"\n❌ 오류 발생: {e}")
if __name__ == '__main__':
main()
Google Sheets API ���� ������� �기/�기, ��, ��맷� �리. OAuth 2.0 �� ��. ���� 구� ����� ����를 조��고 ���� � ��.
---
name: andrew-google-sheets
description: Google Sheets API ���� ������� �기/�기, ��, ��맷� �리. OAuth 2.0 �� ��. ���� 구� ����� ����를 조��고 ���� � ��.
---
# Google Sheets
## Overview
Google Sheets API 를 ���� ���� �������를 조�, ��, ���� � �� ������. OAuth 2.0 ��� ���� ���� Google Sheets � �근����.
## Setup
### 1. OAuth ������� �� ��
�미 구� �린�� ��� �� ��� ������:
```bash
# �� ��� �미 ���� ��면 ��
ls ~/.google-credentials.json
```
### 2. �존� ��
```bash
pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib
```
### 3. �� ����
```bash
cd /Users/andrew/.openclaw/workspace/skills/google-sheets
python3 scripts/oauth_setup.py
```
첫 ��� ����� �리고 Google ���� �그� � ��� ����� ����.
## Capabilities
### ������� �기
**���� 조�:**
```
"���� ��� '�무��' � �근 10 � 보��"
"A1 ��� D10 �� ���� ���"
```
**��� ��� 조�:**
```
"'2026 � 4 �' ���� 모� ���� 보��"
```
### ������� �기
**���� ��:**
```
"�무�� ���� � �목 ��: 'OpenClaw ��', ��� '2026-04-21', ��� '2026-04-21', ��� '100%'"
```
**���� ��:**
```
"�무��� 6 �째 � ���� '100%' � ��������"
```
### ������� �리
**� ������� ��:**
```
"� ������� '����� �리' ����"
```
**��� 목� ��:**
```
"� 구� ��� 목� 보��"
```
## Usage Examples
### �� 1: ������� 목� 조�
```python
from scripts.sheets_ops import list_spreadsheets
# ���� 모� ������� 목�
sheets = list_spreadsheets()
for sheet in sheets:
print(f"{sheet['name']} - {sheet['spreadsheetId']}")
```
### �� 2: ��� �� �기
```python
from scripts.sheets_ops import read_range
# ��� ���� �� �기
data = read_range('SPREADSHEET_ID', 'Sheet1!A1:D10')
for row in data:
print(row)
```
### �� 3: ���� �기
```python
from scripts.sheets_ops import write_range
# ��� ��� ���� �기
write_range(
spreadsheet_id='SPREADSHEET_ID',
range_name='Sheet1!A1:D1',
values=[['���', '���', '���', '���']]
)
```
### �� 4: ���� �� (Append)
```python
from scripts.sheets_ops import append_rows
# � � ��
append_rows(
spreadsheet_id='SPREADSHEET_ID',
range_name='Sheet1!A:D',
values=[['� ��', '2026-04-21', '', '0%']]
)
```
### �� 5: � ������� ��
```python
from scripts.sheets_ops import create_spreadsheet
# � ������� ��
new_sheet = create_spreadsheet('�무��')
print(f"�� ��: {new_sheet['spreadsheetId']}")
```
## Files Structure
```
google-sheets/
��� SKILL.md
��� scripts/
��� oauth_setup.py # OAuth 2.0 �� � ���� �리
��� sheets_ops.py # Sheets API �� ����
```
## Security Notes
- OAuth ����� `~/.google-sheets-token.pickle` � �����
- ������� ��� `~/.google-credentials.json` � ����� (�린�� 공�)
- � ���� `.gitignore` � ����� ����
- �� ��: `https://www.googleapis.com/auth/spreadsheets` (������� �체 �근)
## Troubleshooting
**"OAuth ������� �� ��� ����" ��:**
- `~/.google-credentials.json` ��� ��� ��
- 구� �린� ��� �� � �미 ��� �� �����
**�� ���:**
- ���� ��� ���고 ���: `rm ~/.google-sheets-token.pickle`
**�� ��:**
- ����� �� � ���: `rm ~/.google-sheets-token.pickle && python3 scripts/oauth_setup.py`
## Integration with Other Google Skills
Same OAuth credentials (`~/.google-credentials.json`) are shared with `google-calendar` and `google-tasks` skills, so you only need to authenticate once!
FILE:scripts/oauth_setup.py
#!/usr/bin/env python3
"""
OAuth 2.0 인증 설정 및 토큰 관리
Google Sheets API 접근을 위한 인증 처리
"""
import os
import pickle
from pathlib import Path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
# 권한 범위 (Sheets 읽기/쓰기)
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
# 토큰 저장 경로
TOKEN_FILE = Path.home() / '.google-sheets-token.pickle'
CREDENTIALS_FILE = Path.home() / '.google-credentials.json'
def authenticate():
"""
OAuth 2.0 인증 수행 및 Credentials 반환
처음 실행시 브라우저에서 로그인 진행
"""
creds = None
# 기존 토큰이 있으면 로드
if TOKEN_FILE.exists():
with open(TOKEN_FILE, 'rb') as token:
creds = pickle.load(token)
# 토큰이 없거나 만료되었으면 새로 인증
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
# 리프레시
creds.refresh(Request())
else:
# OAuth 클라이언트 키가 있어야 함
if not CREDENTIALS_FILE.exists():
raise FileNotFoundError(
f"OAuth 클라이언트 키 파일이 없습니다.\n"
f"Google Cloud Console 에서 다운로드한 client_secret_*.json 파일을\n"
f"{CREDENTIALS_FILE} 로 복사해주세요."
)
# OAuth 흐름 시작
flow = InstalledAppFlow.from_client_secrets_file(
str(CREDENTIALS_FILE), SCOPES
)
creds = flow.run_local_server(port=8081)
# 토큰 저장
with open(TOKEN_FILE, 'wb') as token:
pickle.dump(creds, token)
return creds
def get_sheets_service():
"""
Google Sheets API 서비스 객체 반환
"""
creds = authenticate()
service = build('sheets', 'v4', credentials=creds)
return service
def get_drive_service():
"""
Google Drive API 서비스 객체 반환 (스프레드 시트 목록용)
"""
creds = authenticate()
service = build('drive', 'v3', credentials=creds)
return service
def list_spreadsheets():
"""
사용자의 모든 스프레드시트 목록 조회
"""
drive_service = get_drive_service()
# 스프레드시트 파일만 검색
results = drive_service.files().list(
q="mimeType='application/vnd.google-apps.spreadsheet'",
pageSize=25,
fields="files(id, name, createdTime, modifiedTime)"
).execute()
files = results.get('files', [])
return files
def main():
"""
인증 테스트 및 스프레드시트 목록 출력
"""
try:
print("🔐 인증 중...")
spreadsheets = list_spreadsheets()
print(f"\n✅ 총 {len(spreadsheets)} 개의 스프레드시트가 있습니다:\n")
for sheet in spreadsheets:
name = sheet.get('name', 'Unknown')
created = sheet.get('createdTime', 'Unknown')[:10]
print(f" • {name}")
print(f" ID: {sheet['id']}")
print(f" 생성일: {created}\n")
print("✅ 인증 성공!")
except FileNotFoundError as e:
print(f"\n❌ {e}")
print("\n설정 방법:")
print("1. Google Cloud Console 에서 OAuth 클라이언트 키 다운로드")
print("2. 파일을 ~/.google-credentials.json 으로 복사")
print("3. 다시 실행")
except Exception as e:
print(f"\n❌ 오류 발생: {e}")
if __name__ == '__main__':
main()
FILE:scripts/sheets_ops.py
#!/usr/bin/env python3
"""
Google Sheets 연산 함수들
스프레드시트 읽기, 쓰기, 생성, 수정 등
"""
from datetime import datetime
from oauth_setup import get_sheets_service, get_drive_service, list_spreadsheets
def read_range(spreadsheet_id, range_name):
"""
스프레드시트의 특정 범위 읽기
Args:
spreadsheet_id: 스프레드시트 ID
range_name: 범위 명 (예: 'Sheet1!A1:D10')
Returns:
2 차원 데이터 목록
"""
service = get_sheets_service()
result = service.spreadsheets().values().get(
spreadsheetId=spreadsheet_id,
range=range_name
).execute()
values = result.get('values', [])
return values
def write_range(spreadsheet_id, range_name, values, value_input_option='USER_ENTERED'):
"""
스프레드시트의 특정 범위 쓰기
Args:
spreadsheet_id: 스프레드시트 ID
range_name: 범위 명 (예: 'Sheet1!A1:D1')
values: 쓸 데이터 (2 차원 목록)
value_input_option: 값 입력 옵션 (RAW 또는 USER_ENTERED)
Returns:
업데이트된 정보
"""
service = get_sheets_service()
body = {
'values': values
}
result = service.spreadsheets().values().update(
spreadsheetId=spreadsheet_id,
range=range_name,
valueInputOption=value_input_option,
body=body
).execute()
return result
def append_rows(spreadsheet_id, range_name, values, value_input_option='USER_ENTERED'):
"""
스프레드시트에 행 추가
Args:
spreadsheet_id: 스프레드시트 ID
range_name: 범위 명 (예: 'Sheet1!A:D')
values: 추가할 데이터 (2 차원 목록)
value_input_option: 값 입력 옵션
Returns:
추가된 정보
"""
service = get_sheets_service()
body = {
'values': values
}
result = service.spreadsheets().values().append(
spreadsheetId=spreadsheet_id,
range=range_name,
valueInputOption=value_input_option,
insertDataOption='INSERT_ROWS',
body=body
).execute()
return result
def batch_read(spreadsheet_id, ranges):
"""
여러 범위 한 번에 읽기
Args:
spreadsheet_id: 스프레드시트 ID
ranges: 범위 목록 (예: ['Sheet1!A1:D10', 'Sheet2!A1:C5'])
Returns:
각 범위의 데이터 딕셔너리
"""
service = get_sheets_service()
result = service.spreadsheets().values().batchGet(
spreadsheetId=spreadsheet_id,
ranges=ranges
).execute()
value_ranges = result.get('valueRanges', [])
return value_ranges
def batch_write(spreadsheet_id, value_ranges, value_input_option='USER_ENTERED'):
"""
여러 범위 한 번에 쓰기
Args:
spreadsheet_id: 스프레드시트 ID
value_ranges: [{'range': 'Sheet1!A1:B2', 'values': [...]}, ...]
value_input_option: 값 입력 옵션
Returns:
업데이트된 정보
"""
service = get_sheets_service()
body = {
'valueInputOption': value_input_option,
'data': value_ranges
}
result = service.spreadsheets().values().batchUpdate(
spreadsheetId=spreadsheet_id,
body=body
).execute()
return result
def create_spreadsheet(title, sheet_title='Sheet1'):
"""
새 스프레드시트 생성
Args:
title: 스프레드시트 제목
sheet_title: 초기 시트 제목
Returns:
생성된 스프레드시트 정보
"""
service = get_sheets_service()
spreadsheet = {
'properties': {
'title': title
},
'sheets': [
{
'properties': {
'title': sheet_title
}
}
]
}
result = service.spreadsheets().create(
body=spreadsheet
).execute()
return result
def get_spreadsheet_info(spreadsheet_id):
"""
스프레드시트 메타데이터 조회
Args:
spreadsheet_id: 스프레드시트 ID
Returns:
스프레드시트 정보
"""
service = get_sheets_service()
result = service.spreadsheets().get(
spreadsheetId=spreadsheet_id
).execute()
return result
def clear_range(spreadsheet_id, range_name):
"""
특정 범위 내용 지우기
Args:
spreadsheet_id: 스프레드시트 ID
range_name: 범위 명
"""
service = get_sheets_service()
result = service.spreadsheets().values().clear(
spreadsheetId=spreadsheet_id,
range=range_name
).execute()
return result
def find_spreadsheet_by_name(name):
"""
이름으로 스프레드시트 찾기
Args:
name: 스프레드시트 이름 (일부 일치)
Returns:
일치하는 스프레드시트 목록
"""
spreadsheets = list_spreadsheets()
matches = []
for sheet in spreadsheets:
if name.lower() in sheet.get('name', '').lower():
matches.append(sheet)
return matches
def format_row(row_data, headers):
"""
딕셔너리 데이터를 행 데이터로 변환
Args:
row_data: {'header1': 'value1', 'header2': 'value2'}
headers: ['header1', 'header2', ...]
Returns:
['value1', 'value2', ...]
"""
return [row_data.get(h, '') for h in headers]
def main():
"""
테스트용 메인 함수
"""
print("📊 Google Sheets 테스트\n")
# 스프레드시트 목록
print("=== 스프레드시트 목록 ===")
spreadsheets = list_spreadsheets()
if spreadsheets:
for sheet in spreadsheets[:5]: # 상위 5 개만
print(f" • {sheet['name']}")
print(f" ID: {sheet['id']}")
else:
print(" 스프레드시트가 없습니다.")
# 특정 스프레드시트 읽기 테스트
# if spreadsheets:
# test_id = spreadsheets[0]['id']
# print(f"\n=== 첫 번째 스프레드시트 데이터 ===")
# data = read_range(test_id, 'Sheet1!A1:D10')
# for row in data:
# print(row)
# 새 스프레드시트 생성 테스트
# new_sheet = create_spreadsheet('테스트 스프레드시트')
# print(f"\n✅ 새 스프레드시트 생성 완료: {new_sheet['spreadsheetId']}")
if __name__ == '__main__':
main()