MENU
  • プロフィール
  • 無料相談
  • サービス
    • オンライン自習講座
    • 単発レッスン
    • HP作成5回レッスン
  • 実績・お客さまの声
  • ブログ

    カテゴリー

    • 無料ブログから始める
    • デザインとあそぶ
    • 語学の旅
    • 文房具と整理術
    • WordPressブログの始め方
    • ブログと歩む
    • 困ったときのQ&A
    • 私を支えてくれる本
    • Web制作の学び
    • お知らせ
    • 活動実績・ご感想
    • WordPress&Webサポート
    • 私らしい世界を作る
    • 暮らしと思想

    タグ

    ameliaChatGPTHTML/CSSinstagramWPプラグインお気に入りアクセス解析ゲームセキュリティデザイン見本ドメインピアノブログ引っ越しホームページ制作実績レッスンご感想健康思想旅行自動化・ノーコード英語学習読書音楽

    アーカイブ

    • 2026年4月
    • 2026年3月
    • 2026年2月
    • 2026年1月
    • 2025年12月
    • 2025年11月
    • 2025年10月
    • 2025年9月
    • 2025年8月
    • 2025年7月
    • 2025年6月
    • 2025年5月
    • 2025年3月
    • 2025年2月
    • 2025年1月
    • 2024年12月
    • 2024年7月
    • 2024年3月
    • 2023年7月
    • 2023年4月
    • 2023年1月
    • 2022年12月
    • 2022年11月
    • 2022年8月
    • 2022年4月
    • 2022年3月
    • 2022年1月
    • 2021年12月
    • 2021年11月
    • 2021年10月
    • 2021年9月
    • 2021年8月
    • 2020年10月
    • 2020年8月
    • 2020年7月
    • 2020年6月
    • 2020年5月
    • 2020年1月
    • 2019年11月
    • 2019年9月
    • 2019年8月
    • 2019年2月
    • 2019年1月
    • 2018年11月
    • 2018年9月
    • 2018年8月
    • 2018年7月
    • 2018年6月
    • 2017年9月
    • 2017年7月
    ゼロから始めるWebと創作の記録
    0 Forest
    • プロフィール
    • 無料相談
    • サービス
      • オンライン自習講座
      • 単発レッスン
      • HP作成5回レッスン
    • 実績・お客さまの声
    • ブログ
    0 Forest
    • プロフィール
    • 無料相談
    • サービス
      • オンライン自習講座
      • 単発レッスン
      • HP作成5回レッスン
    • 実績・お客さまの声
    • ブログ
    1. ホーム
    2. 暮らしと思想
    3. Web制作の学び
    4. コンテンツの投稿予定をGoogleカレンダーに連携するプログラム

    コンテンツの投稿予定をGoogleカレンダーに連携するプログラム

    2026 4/10
    Web制作の学び
    2025-07-212026-04-10
    目次

    やりたいこと:Googleカレンダーでコンテンツの投稿予定をみたい

    私はNotionでコンテンツカレンダーをデータベースとして管理しています。
    これらをGoogleカレンダーでも見れるようなプログラムを作っていこうと思います。

    Screenshot

    NotionのDBの主な項目

    項目名プロパティの種類用途
    公開日日付イベントの開始日/時刻
    タイトルテキストカレンダーイベントの件名に使用
    コンテンツタイプマルチセレクト
    (WP記事、IG投稿、YB動画など)
    絵文字やカラー設定に使用(例:Instagramなら📷)
    ステータスステータス
    (アイデア段階、制作中、投稿済など)
    投稿準備状況などの確認に使用(イベント詳細など)
    イベントIDテキストGoogleカレンダーのeventIdを保存しておくため

    Notion API連携

    GAS接続用のインテグレーション設定

    1. 🔗 Notion Developersのページを開く
      https://www.notion.com/my-integrations
    2. 🔘「+ New integration」をクリック
    3. 必要事項を入力
      • Name:例)「GAS連携カレンダー」
      • Associated workspace:自分の作業スペースを選択

    • ✅「コンテンツを読み取る」「コンテンツを更新」にチェック(データ取得・更新のため)
    • 🔐「内部インテグレーションシークレット」をコピー(あとでGASで使います)
    Screenshot

    Notion DBにインテグレーションを接続する

    Notionで使いたい「データベースページ」を開く(例:コンテンツプランナー)

    右上「…」→「接続」

    Screenshot

    先ほど作成したインテグレーション(例:「GAS連携カレンダー」)を選択する。

    データベースIDの取得

    データベースページをブラウザで開く

    URLの形式が次のようになっています:
    例)https://www.notion.so/abc123def4567890ghij0123klmn4567?v=~~~

    この「英数字32桁の文字列」が データベースID です

    ●この後で使う項目
    NOTION_SECRET: インテグレーションで取得した「Secretトークン」
    DATABASE_ID : 32桁のノーションDBのID

    Google Apps Script(GAS)でNotion APIにアクセス

    接続テスト

    Googleドライブを開く

    「新規」→「その他」→「Google Apps Script」

    プロジェクト名を「Notion接続テスト」などに変更

    以下のコードを貼り付けて保存

    const NOTION_TOKEN = 'あなたのNotion統合のシークレットトークン'; // 例: secret_xxxxxx
    const DATABASE_ID = 'あなたのデータベースID'; // ハイフンなし32桁
    
    function testNotionConnection() {
      const url = `https://api.notion.com/v1/databases/${DATABASE_ID}`;
      const options = {
        method: "get",
        headers: {
          "Authorization": `Bearer ${NOTION_TOKEN}`,
          "Notion-Version": "2022-06-28", // 最新の安定版日付
          "Content-Type": "application/json",
        },
        muteHttpExceptions: true
      };
    
      const response = UrlFetchApp.fetch(url, options);
      const statusCode = response.getResponseCode();
      const content = response.getContentText();
    
      console.log(`📡 接続ステータス: ${statusCode}`);
      console.log(`📄 レスポンス内容: ${content}`);
    
      if (statusCode === 200) {
        const json = JSON.parse(content);
        console.log(`接続成功!データベース名: ${json.title[0]?.text?.content}`);
      } else {
        console.error("接続失敗。トークン、DB ID、共有設定を確認してください。");
      }
    }
    

    上のコードを保存(Ctrl + S)

    testNotionConnection() を選択して ▶️ 実行

    初回は 認証が求められます
    「権限を確認」→左下の詳細を表示→NotionDB接続テスト(安全ではないページ)に移動をクリック

    メニュー「表示」→「ログ」または Ctrl + Enter でログを表示

    接続成功!データベース名: ◯◯◯ が出ればOK!

    Notionデータが取得できるか確認

    const NOTION_DB_ID = 'ここにデータベースID'; 
    const NOTION_TOKEN = 'ここにシークレットトークン';
    const NOTION_VERSION = '2022-06-28';
    
    function fetchNotionData() {
      const url = `https://api.notion.com/v1/databases/${NOTION_DB_ID}/query`;
    
      const options = {
        method: 'post',
        headers: {
          'Authorization': `Bearer ${NOTION_TOKEN}`,
          'Notion-Version': NOTION_VERSION,
          'Content-Type': 'application/json',
        },
      };
    
      const response = UrlFetchApp.fetch(url, options);
      const data = JSON.parse(response.getContentText());
    
      const results = data.results;
    
      console.log(`取得件数: ${results.length}`);
      results.forEach((page, i) => {
        const props = page.properties;
    
        const title = props['タイトル']?.title?.[0]?.plain_text ?? 'タイトルなし';
        const date = props['公開日']?.date?.start ?? '日付なし';
        const contentType = props['コンテンツタイプ']?.multi_select?.map(tag => tag.name).join(', ') ?? 'タイプ未設定';
        const status = props['ステータス']?.status?.name ?? '未設定';
    
        console.log(`${i + 1}: ${date}|${contentType}|${status}|${title}`);
      });
    }
    

    うまくいくと、
    1: 2025-09-15|📷IG投稿|画像メディア作成済|無料相談お知らせ (1)
    2: 2025-09-01|🌿WP記事|投稿済|無料相談お知らせ (来月)

    「日付| コンテンツタイプ | ステータス | タイトル」が、行数分表示される。

    Googleカレンダーへ登録

    ステップ①:Googleカレンダーとの連携準備

    カレンダーIDはGoogleカレンダーの設定>カレンダーの統合>カレンダーIDに記載されている。

    xxxx@group.calendar.google.comという形式です。

    GASに貼り付けて保存

    registerFirstEventToCalendar() を手動実行

    カレンダーにイベントが1件追加されるか確認!

    const NOTION_API_KEY = 'Notionのトークン';
    const DATABASE_ID = 'データベースID';
    const CALENDAR_ID = 'カレンダーID'; // 例: xxxx@group.calendar.google.com
    
    const NOTION_API_URL = 'https://api.notion.com/v1/databases/' + DATABASE_ID + '/query';
    
    function registerFirstEventToCalendar() {
      const notionData = fetchNotionData();
      if (!notionData || notionData.length === 0) {
        console.log('Notionデータベースにデータが見つかりません');
        return;
      }
    
      const first = notionData[0];
    
      const title = first.title || 'タイトルなし';
      const contentType = first.contentType || '';
      const emojiPrefix = getEmojiPrefix(contentType);
      const start = new Date(first.date);
      const end = new Date(start.getTime() + 30 * 60 * 1000); // 30分後を終了時刻に
    
      const cal = CalendarApp.getCalendarById(CALENDAR_ID);
      const event = cal.createEvent(`${emojiPrefix}${title}`, start, end);
      console.log(`登録成功: ${event.getTitle()} / ${start}`);
    }
    
    // 🔧 Notionからデータを取得(必要な項目だけ)
    function fetchNotionData() {
      const options = {
        method: 'post',
        contentType: 'application/json',
        headers: {
          'Authorization': 'Bearer ' + NOTION_API_KEY,
          'Notion-Version': '2022-06-28'
        },
        payload: JSON.stringify({
          page_size: 1,
          sorts: [{ property: '公開日', direction: 'ascending' }]
        })
      };
    
      const res = UrlFetchApp.fetch(NOTION_API_URL, options);
      const data = JSON.parse(res.getContentText());
    
      return data.results.map(page => {
        const props = page.properties;
        return {
          title: props['タイトル']?.title?.[0]?.plain_text ?? '',
          contentType: props['コンテンツタイプ']?.multi_select?.[0]?.name ?? '',
          date: props['公開日']?.date?.start ?? null
        };
      });
    }
    
    // 🎨 絵文字プレフィックス(コンテンツタイプに応じて)
    function getEmojiPrefix(type) {
      if (type.includes('WP')) return '🌿';
      if (type.includes('IG')) return '📷';
      if (type.includes('YT')) return '📺';
      return '';
    }
    

    ②Notion DBとGoogleカレンダーを同期するコード

    そしてこちらができたコード。

    • 対象:今日〜来月末
    • Youtubeは青、Instagramはピンク、WordPressは緑、投稿前のコンテンツは灰色にセット
    • 予定の作成、更新(タイトル、日付、色分け)、削除を行う
    • エクセルシートにNotion→Googleカレンダーへの同期ログを書く。
    // 事前設定
    const NOTION_TOKEN = 'Notionトークン';
    const DATABASE_ID = 'データベースID'; 
    const CALENDAR_ID = 'カレンダーID';
    const TIMEZONE = 'Asia/Tokyo';
    
    const SPREADSHEET_ID = 'GoogleスプレッドシートのID';
    const SHEET_NAME = 'Googleスプレッドシートのシート名';
    
    
    /**
     * メイン関数:Notion→Googleカレンダー 同期(新規/更新/削除)
     */
    async function syncNotionToCalendar() {
      const calendar = CalendarApp.getCalendarById(CALENDAR_ID);
      const tz = Session.getScriptTimeZone();
      const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);
      sheet.clearContents();
      sheet.appendRow(['処理日時', '処理', 'タイトル','コンテンツタイプ', '開始時刻', 'ステータス', 'event_id']);
    
      const now = new Date();
      now.setHours(0, 0, 0, 0);
      const until = new Date(now.getFullYear(), now.getMonth() + 2, 0); // 翌月末まで
      const calendarEvents = calendar.getEvents(now, until);
      const calendarEventMap = new Map(calendarEvents.map(e => [e.getId(), e]));
    
      const notionItems = await fetchNotionPages();
      const notionEventIds = new Set();
    
      for (const item of notionItems) {
        const { title, contentType, status, date, eventId, pageId } = item;
        if (!date || status === 'アイデア段階') continue;
    
        const emoji = getEmoji(contentType);
        const finalTitle =`${emoji}${title}`;
        const colorId = getEventColor(contentType, status);
        const start = new Date(date);
        const end = new Date(start.getTime() + 30 * 60 * 1000);
    
        if (!eventId) {
          const ev = calendar.createEvent(finalTitle, start, end);
          ev.setColor(colorId);
          await updateNotionPage(pageId, ev.getId());
          sheet.appendRow([new Date(), '新規登録', finalTitle, contentType, formatDate(start, tz), status, ev.getId()]);
        } else {
          notionEventIds.add(eventId);
          const ev = calendar.getEventById(eventId);
          if (!ev) {
            const newEv = calendar.createEvent(finalTitle, start, end);
            newEv.setColor(colorId);
            await updateNotionPage(pageId, newEv.getId());
            sheet.appendRow([new Date(), '再作成', finalTitle, contentType, formatDate(start, tz), status, newEv.getId()]);
          } else {
              ev.setTitle(finalTitle);
              ev.setTime(start, end);
              ev.setColor(colorId);
              sheet.appendRow([new Date(), '更新', finalTitle, contentType, formatDate(start, tz), status, eventId]);
          }
        }
      }
    
      // カレンダーにあってNotionにないイベントは削除
      for (const [id, ev] of calendarEventMap.entries()) {
        if (!notionEventIds.has(id)) {
          ev.deleteEvent();
          sheet.appendRow([new Date(), '削除', ev.getTitle(), '-', formatDate(ev.getStartTime(), tz), '-', id]);
        }
      }
    }
    
    function formatDate(date, tz) {
      return Utilities.formatDate(date, tz, 'yyyy-MM-dd HH:mm');
    }
    
    
    // 🔹 Notionから全件取得
    async function fetchNotionPages() {
      const url = `https://api.notion.com/v1/databases/${DATABASE_ID}/query`;
    
      // 今日〜来月末を取得するためのISO文字列を生成
      const today = new Date();
      today.setHours(0, 0, 0, 0); // 今日の0時に設定
    
      const endOfNextMonth = new Date(today.getFullYear(), today.getMonth() + 2, 0); // 翌月末
    
      function formatDateToISO(date) {
        return date.toISOString().split('T')[0]; // "YYYY-MM-DD"形式にする
      }
    
      const payload = {
        page_size: 100,
        sorts: [{ property: '公開日', direction: 'ascending' }],
        filter: {
          property: '公開日',
          date: {
            on_or_after: formatDateToISO(today),
            on_or_before: formatDateToISO(endOfNextMonth)
          }
        }
      };
      const res = UrlFetchApp.fetch(url, {
        method: 'post',
        headers: {
          'Authorization': `Bearer ${NOTION_TOKEN}`,
          'Notion-Version': '2022-06-28',
          'Content-Type': 'application/json',
        },
        payload: JSON.stringify(payload),
      });
      const data = JSON.parse(res.getContentText());
    
      return data.results.map(p => {
        const props = p.properties;
        return {
          pageId: p.id,
          title: props['タイトル']?.title?.[0]?.plain_text ?? '',
          contentType: props['コンテンツタイプ']?.multi_select?.[0]?.name ?? '',
          status: props['ステータス']?.status?.name ?? '',
          date: props['公開日']?.date?.start,
          eventId: props['event_id']?.rich_text?.[0]?.plain_text ?? ''
        };
      });
    }
    
    // Notionにevent_idを書き戻す
    async function updateNotionPage(pageId, eventId) {
      const url = `https://api.notion.com/v1/pages/${pageId}`;
      const payload = {
        properties: {
          'イベントID': {
            rich_text: [{ type: 'text', text: { content: eventId } }]
          }
        }
      };
    
      UrlFetchApp.fetch(url, {
        method: 'patch',
        headers: {
          'Authorization': `Bearer ${NOTION_TOKEN}`,
          'Notion-Version': '2022-06-28',
          'Content-Type': 'application/json'
        },
        payload: JSON.stringify(payload)
      });
    }
    
    // 🔹 絵文字変換
    function getEmoji(type) {
      if (type.startsWith('🌿')) return '🌿';
      if (type.startsWith('📷')) return '📷';
      if (type.startsWith('📺')) return '📺';
      return '';
    }
    
    
    /**
     * 投稿ステータスと絵文字に応じた色を返す
     */
    function getEventColor(contentType, status) {
      if (status === '投稿済') {
        if (contentType.includes('🌿')) return '10'; // 緑:WP
        if (contentType.includes('📷')) return '4';  // 青(ピーコック):Instagram
        if (contentType.includes('📺')) return '7'; // 赤(フラミンゴ):YouTube
      }
      return '8'; // グレー(未投稿など)
    }

    こんな感じでカレンダーに追記される

    Screenshot

    しばらく運用してみて、うまくいきそうだったらアプリにしたいなぁ〜。

    Notion -> Googleカレンダー洗い替え方式

    Notionで前の行をコピーして作ると、
    しばらく使ってみて、Googleカレンダー側は閲覧にしか使わないのでシンプルな洗い替え方式に変更した。

    // 事前設定
    const NOTION_TOKEN = 'Notionトークン';
    const DATABASE_ID = 'データベースID'; 
    const CALENDAR_ID = 'カレンダーID';
    const TIMEZONE = 'Asia/Tokyo';
    
    const SPREADSHEET_ID = 'GoogleスプレッドシートのID';
    const SHEET_NAME = 'Googleスプレッドシートのシート名';
    
    
    /**
     * メイン関数:Notion→Googleカレンダー 同期(洗い替え)
     */
    async function syncNotionToCalendar() {
      const calendar = CalendarApp.getCalendarById(CALENDAR_ID);
      const tz = Session.getScriptTimeZone();
      const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);
    
      // 範囲:今日〜来月末
      const now = new Date();
      now.setHours(0, 0, 0, 0);
      const until = new Date(now.getFullYear(), now.getMonth() + 2, 0);
    
      // Step1: カレンダーイベント削除
      const existingEvents = calendar.getEvents(now, until);
      let deletedCount = 0;
      for (const ev of existingEvents) {
        ev.deleteEvent();
        deletedCount++;
      }
      
      // Step2: Notion取得&新規作成
      const notionItems = await fetchNotionPages();
      let createdCount = 0;
    
      for (const item of notionItems) {
        const { title, contentType, status, date } = item;
        if (!date || status === 'アイデア段階') continue;
    
        const emoji = getEmoji(contentType);
        const finalTitle = `${emoji}${title}`;
        const colorId = getEventColor(contentType, status);
        const start = new Date(date);
        const end = new Date(start.getTime() + 30 * 60 * 1000);
    
        if (start >= now) {
          calendar.createEvent(finalTitle, start, end).setColor(colorId);
          createdCount++;
        }
      }
    
      // Step3: ログ記録(処理日時、削除件数、追加件数)
      sheet.appendRow([
        Utilities.formatDate(new Date(), tz, 'yyyy-MM-dd HH:mm:ss'),
        deletedCount,
        createdCount
      ]);
    }
    
    
    // Notionから全件取得
    async function fetchNotionPages() {
      const url = `https://api.notion.com/v1/databases/${DATABASE_ID}/query`;
    
      // 今日〜来月末を取得するためのISO文字列を生成
      const today = new Date();
      today.setHours(0, 0, 0, 0); // 今日の0時に設定
    
      const endOfNextMonth = new Date(today.getFullYear(), today.getMonth() + 2, 0); // 翌月末
    
      function formatDateToISO(date) {
        return date.toISOString().split('T')[0]; // "YYYY-MM-DD"形式にする
      }
    
      const payload = {
        page_size: 100,
        sorts: [{ property: '公開日', direction: 'ascending' }],
        filter: {
          property: '公開日',
          date: {
            on_or_after: formatDateToISO(today),
            on_or_before: formatDateToISO(endOfNextMonth)
          }
        }
      };
      const res = UrlFetchApp.fetch(url, {
        method: 'post',
        headers: {
          'Authorization': `Bearer ${NOTION_TOKEN}`,
          'Notion-Version': '2022-06-28',
          'Content-Type': 'application/json',
        },
        payload: JSON.stringify(payload),
      });
      const data = JSON.parse(res.getContentText());
    
      return data.results.map(p => {
        const props = p.properties;
        return {
          pageId: p.id,
          title: props['タイトル']?.title?.[0]?.plain_text ?? '',
          contentType: props['コンテンツタイプ']?.multi_select?.[0]?.name ?? '',
          status: props['ステータス']?.status?.name ?? '',
          date: props['公開日']?.date?.start,
          eventId: props['event_id']?.rich_text?.[0]?.plain_text ?? ''
        };
      });
    }
    
    // 絵文字変換
    function getEmoji(type) {
      if (type.startsWith('🌿')) return '🌿';
      if (type.startsWith('📷')) return '📷';
      if (type.startsWith('📺')) return '📺';
      return '';
    }
    
    
    /**
     * 投稿ステータスと絵文字に応じた色を返す
     */
    function getEventColor(contentType, status) {
      if (status === '投稿済') {
        if (contentType.includes('🌿')) return '10'; // 緑:WP
        if (contentType.includes('📷')) return '4';  // 青(ピーコック):Instagram
        if (contentType.includes('📺')) return '7'; // 赤(フラミンゴ):YouTube
      }
      return '8'; // グレー(未投稿など)
    }
    Web制作の学び
    自動化・ノーコード
    よかったらシェアしてね!
    中谷恵美
    東京在住のフリーランス。システムエンジニア、ITコンサルタントを経て、現在はホームページ・ブログ作成、集客などの個人レッスンをしています。

    好きなものは、文房具、読書、ゲーム、イラスト、作曲など。やりたいことが色々。最近は語学学習にもハマってます。
    運動が苦手で体が硬いのが悩み。
    プロフィール
    WordPressで自分の場所を作りたい方へ

    女性向けのWordPressレッスンをご用意しています。
    何から始めたらいいか迷ったときは、無料相談で方向性を一緒に考えましょう✨
    あなたの状況に合わせてステップをご案内します。

    無料相談の詳細はこちら
    カテゴリー
    • お知らせ (1)
    • 活動実績・ご感想 (17)
    • WordPress&Webサポート (50)
      • 無料ブログから始める (10)
      • WordPressブログの始め方 (18)
      • 困ったときのQ&A (15)
    • 私らしい世界を作る (18)
      • デザインとあそぶ (4)
      • ブログと歩む (7)
    • 暮らしと思想 (62)
      • 語学の旅 (7)
      • 文房具と整理術 (6)
      • 私を支えてくれる本 (2)
      • Web制作の学び (25)
    人気記事
    • 【CSS】シンプルで女性らしい囲み枠デザイン48選
      2018-08-24
      私らしい世界を作る
    • 自分でできる。アメブロのおしゃれなカスタマイズまとめ
      2022-11-04
      無料ブログから始める
    • 【CSSコピペOK】おしゃれな引用blockquoteデザイン10選
      2018-09-13
      私らしい世界を作る
    • 箇条書きリストをおしゃれにするCSS10個
      2018-11-29
      私らしい世界を作る
    • ブログで使えるタイトル付き囲み枠のCSS
      2022-12-21
      私らしい世界を作る
    タグ
    amelia (3)ChatGPT (2)HTML/CSS (8)instagram (3)WPプラグイン (10)お気に入り (2)アクセス解析 (3)ゲーム (1)セキュリティ (2)デザイン見本 (4)ドメイン (3)ピアノ (1)ブログ引っ越し (1)ホームページ制作実績 (5)レッスンご感想 (8)健康 (2)思想 (2)旅行 (4)自動化・ノーコード (7)英語学習 (6)読書 (1)音楽 (3)
    目次