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

    カテゴリー

    • WordPress
    • ブログ・SNS運営
    • お知らせ
    • 活動実績・ご感想
    • 思考ログ
    • プログラミング
    • アクセス解析・マーケティング
    • 自動化・ノーコード
    • 遊び・制作ログ

    タグ

    ameliaChatGPTGASGTMHTML/CSSinstagramSEOSWELLWPプラグインZapierアメブロエックスサーバーセキュリティデザイン見本便利ツール初回無料相談制作実績単発レッスン継続レッスン

    アーカイブ

    • 2025年10月
    • 2025年9月
    • 2025年8月
    • 2025年7月
    • 2025年6月
    • 2025年3月
    • 2025年1月
    • 2024年12月
    • 2024年3月
    • 2023年7月
    • 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年4月
    • 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の場所
    EmiのWebノート
    • プロフィール
    • 無料相談
    • サービス
      • オンライン自習講座
      • 単発レッスン
      • HP作成5回レッスン
    • 実績・お客さまの声
    • ブログ
    • お問合せ
    EmiのWebノート
    • プロフィール
    • 無料相談
    • サービス
      • オンライン自習講座
      • 単発レッスン
      • HP作成5回レッスン
    • 実績・お客さまの声
    • ブログ
    • お問合せ
    1. ホーム
    2. 自動化・ノーコード
    3. コンテンツの投稿予定をGoogleカレンダーに連携するプログラム

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

    2025 7/21
    自動化・ノーコード
    2025-07-21
    目次

    やりたいこと: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'; // グレー(未投稿など)
    }
    自動化・ノーコード
    GAS
    よかったらシェアしてね!
    中谷恵美
    東京在住のフリーランス。システムエンジニア、ITコンサルタントを経て、現在はホームページ・ブログ作成、集客などのレッスンをしています。

    趣味は、文房具、ゲーム。最近は英語学習中。運動が苦手で体が硬いのが悩み。最近、腰痛改善にピラティスを始めた。
    プロフィール
    人気記事
    • 【CSSコピペOK】おしゃれな引用blockquoteデザイン10選
      2018-09-13
      ブログ・SNS運営
    • 【CSS】シンプルで女性らしい囲み枠デザイン48選
      2018-08-24
      ブログ・SNS運営
    • 自分でできる。アメブロのおしゃれなカスタマイズまとめ
      2022-11-04
      ブログ・SNS運営
    • Node.js+ExpressでMySQLに接続して一覧表示する
      2020-04-25
      プログラミング
    • ブログで使えるタイトル付き囲み枠のCSS
      2022-12-21
      ブログ・SNS運営

     

    カテゴリー
    • WordPress (33)
    • ブログ・SNS運営 (25)
    • お知らせ (4)
    • 活動実績・ご感想 (17)
    • 思考ログ (3)
    • プログラミング (13)
    • アクセス解析・マーケティング (2)
    • 自動化・ノーコード (6)
    • 遊び・制作ログ (5)
    タグ
    amelia (3)ChatGPT (1)GAS (1)GTM (1)HTML/CSS (14)instagram (3)SEO (1)SWELL (1)WPプラグイン (5)Zapier (3)アメブロ (14)エックスサーバー (4)セキュリティ (1)デザイン見本 (4)便利ツール (2)初回無料相談 (1)制作実績 (5)単発レッスン (1)継続レッスン (6)
    目次