Obsidian のノートから Google Sheets を読み書きする。

Markdown 内に spread_sync(...) と書けばセルの値が表示される。コードブロックで範囲を表として埋め込める。editable: true を付ければ表をその場で編集し、ボタン一つでシートに書き戻せる。Drive の更新時刻で書き込み前にコンフリクト検出する。

Obsidian 1.4+ Desktop のみ MIT 59 tests passing

できること

読み取り・書き戻し・編集が一つのプラグインに収まっている。

インライン読み取り
バッククォートで囲んだ spread_sync("id", "Sheet1", "B3") が、Reading View でセル値に展開される。
範囲を表として
```spread-sync``` ブロックで範囲を Markdown テーブルに描画する。ヘッダ、転置、名前付き範囲、シート全体に対応。
表をその場で編集
editable: true を付けるとセルが編集可能になる。変更点は黄色でハイライトされ、テーブル下の Push ボタンで values:batchUpdate によりまとめて反映される。
明示的な書き込み
```spread-write``` ブロックとコマンド Push this block で、意図したセルだけを安全に送信する。
コンフリクト検出
書き込み前に Drive の modifiedTime を確認する。シートが外で変更されていた場合は上書き・キャンセルを尋ねる。
PKCE OAuth
自分の GCP プロジェクトで作った Desktop OAuth クライアントを使う。refresh token は Electron safeStorage で暗号化保存される。
キャッシュとリフレッシュ
LRU キャッシュ、stale-while-revalidate、手動 refresh コマンド。編集中は自動で再取得を停止する。
エラー表示
用途別バッジ(re-auth / no access / Excel format など)で、何が起きたか即座にわかる。429 は指数バックオフでリトライ。

導入

現時点では手動インストール。後日 Community Plugins に登録予定。

# Vault のプラグインディレクトリへ clone
cd /path/to/your/vault/.obsidian/plugins
git clone https://github.com/yut0takagi/obsidian-spread-sync.git spread-sync
cd spread-sync
npm install
npm run build

Obsidian の Settings → Community plugins から Spread Sync を有効化する。

OAuth クライアントを用意する

自分の GCP プロジェクトで Desktop OAuth クライアントを作り、その認証情報を設定タブに入力する。所要時間 5 分。

  1. Google Cloud プロジェクトを開く

    Cloud Console から新規作成、または既存プロジェクトを選択。

  2. API を有効化する

    APIs & Services → Library で Google Sheets API と Google Drive API を有効化。

  3. OAuth consent screen

    User type は External。スコープに .../auth/spreadsheets.../auth/drive.metadata.readonly を追加。Publishing status は In production にする(Testing のままだと refresh token が 7 日で失効する)。

  4. OAuth クライアントを作成

    Credentials → Create credentials → OAuth client ID → Application type は Desktop app。Client ID と Client secret をコピー。

  5. Obsidian の設定タブに入力

    Settings → Spread Sync → OAuth credentials に貼り付け、Sign in with Google を押下する。ブラウザで認可後、完了。

Desktop OAuth の client_secret は Google の公式ドキュメントが「機密保持できない」と明言している(すべてのデスクトップアプリのバイナリに同梱されるため)。ローカル設定に保存することは公式ライブラリと同じ扱い。

使い方

単一値はインラインで、範囲はコードブロックで埋め込む。書き込みは別構文で明示する。

インラインセル

今月の店舗数は `spread_sync("<spreadsheet-id>", "Sheet1", "B3")` 店です。

範囲を表として

```spread-sync
id: <spreadsheet-id>
sheet: Sheet1
range: A1:D10
header: true        # 既定 true
transpose: false    # 既定 false
```

編集可能なテーブル

editable: true を追加するとセルをクリックして編集できる。変更したセルが黄色でハイライトされ、Push ボタンで values:batchUpdate によりまとめて反映する。

```spread-sync
id: <spreadsheet-id>
sheet: Sheet1
range: A1:D10
editable: true
```

シート全体

```spread-sync
id: <spreadsheet-id>
sheet: Sheet1
```

名前付き範囲

```spread-sync
id: <spreadsheet-id>
sheet: Sheet1
named: MonthlyKPI
```

エイリアス

設定で kpi → <long-id> と登録すれば、以後は "@kpi" で参照できる。シートを移動しても MD は触らなくてよい。

```spread-sync
id: "@kpi"
sheet: Sheet1
range: A1:C5
```

書き込みブロック

カーソルをブロック内に置き、Command Palette から Spread Sync: Push this block。範囲書き込みは value を YAML 配列で渡す。

```spread-write
id: <spreadsheet-id>
sheet: Sheet1
target: B3
value: 42
mode: replace        # または "append"
```

コマンド

すべて Command Palette(Cmd / Ctrl + P)から呼び出す。

コマンド動作
Refresh current file開いているファイルのキャッシュを無効化し、再取得する
Refresh all open files開いているすべての Markdown ファイルに対して同じことを行う
Refresh by spreadsheetid / URL / @alias を入力し、そのスプレッドシート配下のキャッシュをまとめて無効化する
Push this blockカーソル位置の spread-write ブロックをシートへ送信する

エラーバッジ

失敗時はバッジとツールチップで原因を提示する。

バッジ意味
⚠ re-authトークン期限切れ、または未サインイン。クリックで再認証フローへ。
⚠ no access403 / 404。シートの共有設定または API の有効化を確認。
⚠ Excel format対象が .xlsx として Drive に保存されている。「ファイル → Google スプレッドシートとして保存」で変換する。
⚠ stale 12m直近の取得に失敗、12 分前のキャッシュを表示中。
⚠ offlineネットワーク未接続。

アーキテクチャ

独立してテスト可能な 4 層に分かれている。

┌──────────────────────────────────────────────────┐ │ Renderer インライン/ブロック postprocessor │ │ 編集可能テーブル ウィジェット │ ├──────────────────────────────────────────────────┤ │ Auth OAuth (PKCE) · safeStorage │ │ localhost loopback callback │ ├──────────────────────────────────────────────────┤ │ Sheets+Drive 純粋な HTTP (https.request) │ │ read · batchRead · write · batchWrite │ │ modifiedTime · 429 backoff │ ├──────────────────────────────────────────────────┤ │ Storage LRU cache · debounced persist │ └──────────────────────────────────────────────────┘