
Google Calendar
- 1 installs
- Updated June 27, 2026
- mz038197/vanscoding-skills
Manages Google Calendar events (create, read, update, delete, list, reminders) via the Google Calendar API using OAuth2 or service-account auth.
About
Lets an agent or script operate Google Calendar to create, query, update, and delete events and set reminders through the Calendar API. A developer uses it to automate meetings and sync schedules from external systems.
- OAuth2 (recommended) or service-account with domain delegation
- Create, read, update, delete, and list events plus reminders
Google Calendar by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,983 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mz038197/vanscoding-skills --skill google-calendarAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| Last updated | June 27, 2026 |
| Repository | mz038197/vanscoding-skills ↗ |
What it does
Manages Google Calendar events (create, read, update, delete, list, reminders) via the Google Calendar API using OAuth2 or service-account auth.
Files
Google Calendar Skill
此 Skill 用於讓 agent 或自動化腳本操作 Google Calendar:新增/修改/刪除事件、查詢事件、設定提醒與日曆共享等。
適用情境
- 自動化建立會議、同步行程至團隊日曆
- 從外部系統產生事件(如表單回覆、任務系統)
- 建立提醒或排程例行工作
先決條件
1. 啟用 Google Calendar API(Google Cloud Console)。 2. 選擇驗證方式:
- Service Account(適合服務型資源或已在 Google Workspace 設定 domain-wide delegation):需將目標日曆分享給 service account 或啟用代表使用者授權。
- OAuth2 用戶授權(適合代表使用者存取私人日曆):需建立 OAuth client ID 並完成授權流程取得 token.json。
3. 安裝相依套件:
- pip install google-api-python-client google-auth google-auth-oauthlib
授權與憑證(簡要說明)
優先推薦:OAuth2(使用者授權)
- 為什麼優先:對於需要在使用者個人日曆建立事件(包含邀請參與者並寄送邀請郵件)的情境,OAuth2(Installed App / Desktop)是最直接且安全的方式。OAuth2 會以使用者身分建立/邀請事件,並可避免 service account 對邀請參與者的限制。
- 簡易流程(建議步驟):
1. 在 Google Cloud Console 建立 OAuth 2.0 Client ID(應用類型:Desktop app 或 Other)。 2. 下載 client_secret.json 並放到工作目錄: C:/Users/<user>/.nanobot/workspace/client_secret.json 3. 在 repo 或 workspace 建立一個簡單的 CLI(範例:oauth_cli.py)。 4. 在本機執行: python oauth_cli.py authorize (或印出授權 URL 在另一台有瀏覽器的機器開啟),完成授權後會產生 token.json。 5. token.json 檔案建議放置: C:/Users/<user>/.nanobot/workspace/token.json(並加入 .gitignore)。 6. 之後 CLI 用 token.json 建立事件(sendUpdates='all')即可發送邀請給參與者。
- 建議 scope: https://www.googleapis.com/auth/calendar.events
備援選擇:Service Account(適合後端/系統日曆)
- 優點:適合後端/無人值守的工作流程(如批次建立事件、將系統產生的行程寫入共用日曆)。
- 限制:service account 無法以一般使用者身分直接邀請外部參與者(除非在 Workspace 設定 domain-wide delegation)。若要讓 service account 存取某個私人/個人日曆,需由該日曆的擁有者把日曆分享給 service account 的 client_email。
- 金鑰檔建議放置: C:/Users/<user>/.nanobot/workspace/credentials.json,或透過環境變數 GOOGLE_APPLICATION_CREDENTIALS 指定。
建議檔案結構(repo)
- D:\Work\Python\nanobot\nanobot\skills\google-calendar\
- SKILL.md <-- 本檔
- metadata.yaml 或 SKILL.yaml(若需)
- scripts/ <-- 放可執行的 CLI 與 helper scripts
- cli.py
- helpers.py
- samples/ <-- 範例輸出或測試檔
使用範例(CLI/腳本)
- oauth_cli.py(已提供範例腳本,位於 skills/google-calendar/scripts/oauth_cli.py):
- 授權(會開啟本機瀏覽器):
- python scripts/oauth_cli.py authorize
- 或印出授權 URL(在無瀏覽器環境或要在其他機器完成授權時使用):
- python scripts/oauth_cli.py auth_url
- 在有瀏覽器的機器開啟連結,完成授權並取得 code
- 回到執行環境後使用: python scripts/oauth_cli.py finish --code <AUTH_CODE>
- 建立事件(會使用 token.json,並向與會者發出邀請):
- python scripts/oauth_cli.py create --calendar primary --start 2026-03-05T10:00:00+08:00 --end 2026-03-05T11:00:00+08:00 --summary "會議" --attendees "a@example.com,b@example.com" --location "會議室"
- 列出事件:
- python scripts/oauth_cli.py list --calendar primary --time-min 2026-03-05T00:00:00+08:00 --time-max 2026-03-06T00:00:00+08:00
- 備援:若使用 service account 的 CLI(例如 scripts/cli_service.py),請確保 credentials.json 放置於 workspace 並以 GOOGLE_APPLICATION_CREDENTIALS 指定或程式讀取該檔。
範例檔案路徑(workspace):
- C:/Users/<user>/.nanobot/workspace/
- client_secret.json <-- OAuth client
- token.json <-- 授權後的使用者憑證
- credentials.json <-- (備援)Service account 金鑰
備註:在使用 oauth_cli.py 時,請先安裝相依套件: pip install google-api-python-client google-auth google-auth-oauthlib
建議使用的 OAuth 範圍(Scopes)
- 最常用: https://www.googleapis.com/auth/calendar.events (建立/修改/刪除活動)
- 讀取事件: https://www.googleapis.com/auth/calendar.events.readonly
- 完整存取(包含日曆設定): https://www.googleapis.com/auth/calendar
實作注意事項
- 時區處理:事件時間要包含時區或以 RFC3339 格式(例如 2026-03-05T10:00:00+08:00)。
- 誤刪風險:刪除/更新操作請在腳本加入確認或 soft-delete 機制(例如先標記再真正刪除)。
- 權限與分享:若使用 service account,請把日曆分享給 service account 的 email,或使用 domain-wide delegation。
- 日配額:長時間大量建立事件時注意 API quota,建議批次與節流。
安全與隱私
- 不要將 client_secret.json 或 service account 金鑰放在公開 repo。使用 CI 時,透過 Secret 管理注入。
- 限制權限範圍(scopes)以降低風險。
- 審查 scripts 中的外部依賴,避免任意命令執行(尤其當 Skill 可能被外部上傳時)。
在 agent/CI 中的部署建議
- 在 container 或 CI 裡透過環境變數注入金鑰路徑或 JSON 內容。
- 安裝相依: pip install -r requirements.txt
- 若需要互動式 OAuth:在本地或有瀏覽器的環境完成授權後把 token.json 上傳到執行環境(或透過安全流程交換 token)。
"""
OAuth CLI for Google Calendar (installed app flow)
Usage:
python oauth_cli.py authorize
python oauth_cli.py create --calendar primary --start 2026-02-27T18:00:00+08:00 --end 2026-02-27T19:00:00+08:00 --summary "標題" --attendees a@example.com,b@example.com --location "地點"
python oauth_cli.py list --calendar primary --time-min 2026-02-27T17:00:00+08:00 --time-max 2026-02-27T20:00:00+08:00
Place your OAuth client secret at:
C:/Users/<user>/.nanobot/workspace/client_secret.json
Authorize will open a browser to let you login and consent; token.json will be saved to the same workspace.
"""
import os
import sys
import json
import argparse
from datetime import datetime, timedelta
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
WORKSPACE = os.path.join(os.path.expanduser('~'), '.nanobot', 'workspace')
CLIENT_SECRET_PATH = os.path.join(WORKSPACE, 'client_secret.json')
TOKEN_PATH = os.path.join(WORKSPACE, 'token.json')
SCOPES = ['https://www.googleapis.com/auth/calendar.events']
def authorize():
if not os.path.exists(CLIENT_SECRET_PATH):
raise SystemExit(f'找不到 OAuth client_secret.json,請放到: {CLIENT_SECRET_PATH}')
flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_PATH, SCOPES)
try:
creds = flow.run_local_server(port=0)
except Exception as e:
# fallback to console flow
print('無法使用 run_local_server(可能無法開啟瀏覽器),改用 console flow,請在終端貼上授權 url 並輸入 code。')
creds = flow.run_console()
with open(TOKEN_PATH, 'w', encoding='utf-8') as f:
f.write(creds.to_json())
print('授權完成,token 已儲存至', TOKEN_PATH)
def print_auth_url():
if not os.path.exists(CLIENT_SECRET_PATH):
raise SystemExit(f'找不到 OAuth client_secret.json,請放到: {CLIENT_SECRET_PATH}')
flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_PATH, SCOPES)
auth_url, _ = flow.authorization_url(prompt='consent')
print('請在本機瀏覽器開啟以下 URL,完成授權後會取得 code:')
print(auth_url)
def finish_with_code(code: str):
if not os.path.exists(CLIENT_SECRET_PATH):
raise SystemExit(f'找不到 OAuth client_secret.json,請放到: {CLIENT_SECRET_PATH}')
flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_PATH, SCOPES)
try:
creds = flow.fetch_token(code=code)
except Exception as e:
raise SystemExit(f'用 code 換 token 失敗: {e}')
# InstalledAppFlow.fetch_token returns a dict; construct Credentials
creds_obj = Credentials.from_authorized_user_info(creds, SCOPES)
with open(TOKEN_PATH, 'w', encoding='utf-8') as f:
f.write(creds_obj.to_json())
print('授權完成,token 已儲存至', TOKEN_PATH)
def load_credentials():
creds = None
if os.path.exists(TOKEN_PATH):
creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
if creds and creds.expired and creds.refresh_token:
try:
creds.refresh(Request())
with open(TOKEN_PATH, 'w', encoding='utf-8') as f:
f.write(creds.to_json())
except Exception as e:
print('刷新 token 失敗,需重新授權:', e)
creds = None
return creds
def build_service(creds):
return build('calendar', 'v3', credentials=creds)
def cmd_list(args):
creds = load_credentials()
if not creds:
raise SystemExit('未授權,請先執行: python oauth_cli.py authorize')
service = build_service(creds)
time_min = args.time_min or datetime.utcnow().isoformat() + 'Z'
time_max = args.time_max or (datetime.utcnow() + timedelta(days=7)).isoformat() + 'Z'
resp = service.events().list(calendarId=args.calendar, timeMin=time_min, timeMax=time_max, singleEvents=True, orderBy='startTime').execute()
items = resp.get('items', [])
print(json.dumps(items, ensure_ascii=False, indent=2))
def cmd_create(args):
creds = load_credentials()
if not creds:
raise SystemExit('未授權,請先執行: python oauth_cli.py authorize')
service = build_service(creds)
if not args.start or not args.end or not args.summary:
raise SystemExit('請提供 --start --end --summary')
event = {
'summary': args.summary,
'description': args.description or '',
'start': {'dateTime': args.start, 'timeZone': args.timezone},
'end': {'dateTime': args.end, 'timeZone': args.timezone},
}
if args.location:
event['location'] = args.location
if args.attendees:
event['attendees'] = [{'email': e.strip()} for e in args.attendees.split(',') if e.strip()]
# sendUpdates='all' will send invitations/updates to attendees
created = service.events().insert(calendarId=args.calendar, body=event, sendUpdates='all').execute()
print(json.dumps(created, ensure_ascii=False, indent=2))
def main():
p = argparse.ArgumentParser(prog='oauth_cli')
sub = p.add_subparsers(dest='cmd')
a = sub.add_parser('authorize')
a.set_defaults(func=lambda args: authorize())
au = sub.add_parser('auth_url')
au.set_defaults(func=lambda args: print_auth_url())
f = sub.add_parser('finish')
f.add_argument('--code', required=True)
f.set_defaults(func=lambda args: finish_with_code(args.code))
l = sub.add_parser('list')
l.add_argument('--calendar', default='primary')
l.add_argument('--time-min')
l.add_argument('--time-max')
l.set_defaults(func=cmd_list)
c = sub.add_parser('create')
c.add_argument('--calendar', default='primary')
c.add_argument('--start', required=True)
c.add_argument('--end', required=True)
c.add_argument('--summary', required=True)
c.add_argument('--description')
c.add_argument('--attendees')
c.add_argument('--location')
c.add_argument('--timezone', default='Asia/Taipei')
c.set_defaults(func=cmd_create)
args = p.parse_args()
if not args.cmd:
p.print_help()
return
try:
args.func(args)
except Exception as e:
raise SystemExit(f'錯誤: {e}')
if __name__ == '__main__':
main()