1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

記事投稿キャンペーン 「エンジニア×非エンジニアのコミュニケーション」

【0からGASを学ぶ】GASを用いてGoogleカレンダー+メール送信で翌日の予定を通知しよう

Last updated at Posted at 2023-11-03

はじめに

本シリーズでは、GASの始め方や便利な使い方、ビジネス活用まで幅広く解説します。シリーズをひと通り読んでいただければ、あなたもきっとGASマスターになれるはずです。

シリーズの対象者

  • そもそもGASってなんだかわからない
  • GASを学びたいけど何から始めればいいかわからない方
  • GASはわかり始めたけど、もっと活用ができないかと模索している方
  • とにかくGoogleが好き! という方

前回記事

スプレッドシートで遊ぶ

では早速始めていきましょう。【0からGASを学ぶ】シリーズの第8回は「GASを用いてGoogleカレンダー+メール送信で翌日の予定を通知しよう」です。前回に引き続きPDFエクスポートを使った操作をやってみようと思います。これまで、GoogleカレンダーGmailをGASから操作しました。また第5回では、スプレッドシートのデータを評価し、メールを自動送信しました。これらを組み合わせてみましょう。

今回やること

  1. Googleカレンダーの翌日のイベントをすべての取得する。
  2. イベントをスプレッドシートに一覧でまとめる。
  3. スプレッドシートをPDF変換する。
  4. 変換したPDF(翌日の予定一覧)を添付ファイルとして自分自身にメールを送信する。
  5. 毎日、翌日の予定一覧が届くようにトリガー登録する。

事前準備

カレンダーに予定を作成

すでに予定がびっしりな方は不要ですが、今回やることではイベントが取得できないと面白くないので、何かしら作っておきましょう。

Googleカレンダーでイベントを作成する際は、「予定」と「タスク」があります。しかしながら、これまでお伝えしたCalendarAppでは前者の「予定」しか取得することはできません。「タスク」の取得はまた今度!

カレンダーテンプレートの準備

スプレッドシートでカレンダーテンプレートを適当に作成しましょう。私は以下のような様式を作成しました。

PDF出力場所の作成

「今回やること」にも記載しましたが、途中でスプレッドシートをPDF変換するため、その出力先を作成しておきましょう。

IDの取得

スプレッドシートはコンテナバインド型で開くためIDは不要ですが、PDF出力先のIDは事前に取得しておきましょう。

プログラム開始

GASエディタを開く

準備したカレンダーテンプレートからコンテナバインド型でGASプログラムを記述していきましょう。こちらを参考にGASエディタを起動してください。では、どんどんいきますよ、ついてきてください。

STEP.1 Googleカレンダーの翌日のイベントをすべての取得する

Qiita008.gs
function Qiita008_SpreadSheet() {
  // カレンダークラスを用いて、IDを指定してGoogleカレンダーにアクセスする
  const wCalendar = CalendarApp.getCalendarById('*******************@gmail.com');

  let wToday = new Date()
      , wTriggerDate = new Date();
  // 対象日付を翌日にセットする
  wTriggerDate.setDate(wToday.getDate()+1);

  // 対象日付のイベントを取得する
  const wTomorrowEvent = wCalendar.getEventsForDay(wTriggerDate);
  // 各イベントの開始・終了時間等の詳細をログに出力する
  wTomorrowEvent.forEach(function(wEvent){
    console.log(Utilities.formatDate(wEvent.getStartTime(), 'JST', 'HH:mm:ss'));
    if (wEvent.isAllDayEvent()) {
      console.log('23:59:59')
    } else {
      console.log(Utilities.formatDate(wEvent.getEndTime(), 'JST', 'HH:mm:ss'));
    }
    console.log(wEvent.getTitle());
    console.log(wEvent.getLocation());
    console.log(wEvent.getDescription());
  });
}
実行結果
23:09:27	お知らせ	実行開始
23:09:28	情報	00:00:00
23:09:28	情報	23:59:59
23:09:28	情報	終日の予定1
23:09:28	情報	お台場, 日本、東京都 お台場
23:09:28	情報	
23:09:28	情報	08:30:00
…
23:09:28	お知らせ	実行完了

まずは翌日の予定をすべて取得できました。CalendarEventクラスは非常に扱いやすいクラスです。詳しくは以下をご覧ください。

STEP.2 イベントをスプレッドシートに一覧でまとめる

Qiita008.gs
const COL = {
  NUM: 1
  , START: 2
  , END: 3
  , TITLE: 4
  , LOCATION: 5
  , DETAIL: 6
}

function Qiita008_SpreadSheet() {
  // カレンダークラスを用いて、IDを指定してGoogleカレンダーにアクセスする
  const wCalendar = CalendarApp.getCalendarById('*******************@gmail.com');
  // スプレッドシートクラスを用いて、バインドしているスプレッドシートにアクセスする 
  const wSpread = SpreadsheetApp.getActiveSpreadsheet(); 
  const wSheet = wSpread.getSheets()[0];

  let wToday = new Date()
      , wTriggerDate = new Date();
  // 対象日付を翌日にセットする
  wTriggerDate.setDate(wToday.getDate()+1);

  let wPasteLst = new Array();
  // スプレッドシート内の既存データをクリアする
  let wLstRow = wSheet.getLastRow();
  if (wLstRow>1) wSheet.getRange(2, COL.NUM, wLstRow-1, COL.DETAIL).clearContent();
  
  // 対象日付のイベントを取得する
  const wTomorrowEvent = wCalendar.getEventsForDay(wTriggerDate);
  // 各イベントの開始・終了時間等の詳細を貼り付け用のリストに設定する
  wTomorrowEvent.forEach(function(wEvent, eIdx){
    wPasteLst.push([
      eIdx+1
      , Utilities.formatDate(wEvent.getStartTime(), 'JST', 'HH:mm:ss')
      , wEvent.isAllDayEvent() ? '23:59:59' : Utilities.formatDate(wEvent.getEndTime(), 'JST', 'HH:mm:ss')
      , wEvent.getTitle()
      , wEvent.getLocation()
      , wEvent.getDescription()
    ]);
  });
  wSheet.getRange(2, COL.NUM, wPasteLst.length, wPasteLst[0].length).setValues(wPasteLst);
}

こちらを実行すると、先ほどまではログに出力したカレンダーの内容がスプレッドシートに書き込まれます。

ゴールは近いですね。

wEvent.isAllDayEvent() ? '23:59:59' : Utilities.formatDate(wEvent.getEndTime(), 'JST', 'HH:mm:ss')
この箇所、少しだけテクニックを入れてあります。
isAllDayEventつまり終日予定の場合かそうでないかで配列に設定する値を変えたいんです。しかしながら、配列の値をセットしている途中にif文を記載することはできません(1要素ずつのpushなら別ですが)。
そこで三項演算子を使って、isAllDayEventの返却値によって値をセットしています。三項演算子は

条件式 ? 条件式がtrueの場合に評価される式 : 条件式がfalseの場合に評価される式;

といった構文となっており、配列の中だとしても柔軟に設定を行うことができます。

STEP.3 スプレッドシートをPDF変換する

Qiita008.gs
const COL = {
  NUM: 1
  , START: 2
  , END: 3
  , TITLE: 4
  , LOCATION: 5
  , DETAIL: 6
}

const PDF_OUTDIR = DriveApp.getFolderById('1rwVxGOY14fcQcJEwTFV-_IDXsaBSVy5A');

function Qiita008_SpreadSheet() {
  // カレンダークラスを用いて、IDを指定してGoogleカレンダーにアクセスする
  const wCalendar = CalendarApp.getCalendarById('*******************@gmail.com');
  // スプレッドシートクラスを用いて、バインドしているスプレッドシートにアクセスする 
  const wSpread = SpreadsheetApp.getActiveSpreadsheet(); 
  const wSheet = wSpread.getSheets()[0];

  let wToday = new Date()
      , wTriggerDate = new Date();
  // 対象日付を翌日にセットする
  wTriggerDate.setDate(wToday.getDate()+1);

  let wPasteLst = new Array();
  // スプレッドシート内の既存データをクリアする
  let wLstRow = wSheet.getLastRow();
  if (wLstRow>1) wSheet.getRange(2, COL.NUM, wLstRow-1, COL.DETAIL).clearContent();
  
  // 対象日付のイベントを取得する
  const wTomorrowEvent = wCalendar.getEventsForDay(wTriggerDate);
  // 各イベントの開始・終了時間等の詳細を貼り付け用のリストに設定する
  wTomorrowEvent.forEach(function(wEvent, eIdx){
    wPasteLst.push([
      eIdx+1
      , Utilities.formatDate(wEvent.getStartTime(), 'JST', 'HH:mm:ss')
      , wEvent.isAllDayEvent() ? '23:59:59' : Utilities.formatDate(wEvent.getEndTime(), 'JST', 'HH:mm:ss')
      , wEvent.getTitle()
      , wEvent.getLocation()
      , wEvent.getDescription()
    ]);
  });
  wSheet.getRange(2, COL.NUM, wPasteLst.length, wPasteLst[0].length).setValues(wPasteLst);

  let wPdfId = createPdf(wSpread.getId()
                         , wSheet.getSheetId()
                         , 'A1%3AF'+(wPasteLst.length+1)
                         , Utilities.formatDate(wTriggerDate, 'JST', 'yyyyMMdd')+'の予定');
}

function createPdf(spreadId, sheetId, rangeStr, fileName){
  // PDF変換するためのベースURLを作成する
  let wUrl = `https://docs.google.com/spreadsheets/d/${spreadId}/export?gid=${sheetId}&exportFormat=pdf`;
  let wPdfOpt = '&size=A4'                      // 用紙サイズ
              + '&portrait=true'                // 用紙の向き true:縦向き / false:横向き
              + '&fitw=true'                    // ページ幅を用紙にフィットさせるか true:フィットさせる / false:原寸大
              + '&top_margin=0.50'              // 上の余白
              + '&right_margin=0.50'            // 右の余白
              + '&bottom_margin=0.50'           // 下の余白
              + '&left_margin=0.50'             // 左の余白
              + '&horizontal_alignment=CENTER'  // 水平方向の位置
              + '&vertical_alignment=TOP'       // 垂直方向の位置
              + '&range='+rangeStr;             // 変換範囲

  // headersにアクセストークンを格納する
  let wOtions = {
    headers: {
      'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`
    }
  }; 
  // PDFを作成する
  let wBlob = UrlFetchApp.fetch(wUrl+wPdfOpt, wOtions).getBlob().setName(fileName + '.pdf');

  //PDFを指定したフォルダに保存する
  return PDF_OUTDIR.createFile(wBlob).getId();
}

実行すると、事前準備で作成したPDF出力先に以下のようなPDFが作成されます。

ここでポイントとなるのは、前回と同様にPDF変換用のURL部https://docs.google.com/spreadsheets/d/${spreadId}/export?gid=${sheetId}&exportFormat=pdf およびその他変換オプションとなります。

コード内でも一部は使用しましたが、変換オプションには以下のパラメタを使用することができます。

パラメタ 役割 設定例
size 用紙サイズ A3, A4 など
portrait 用紙の向き true:縦向き / false:横向き
fitw ページ幅を用紙にフィットさせるか true:フィットさせる / false:原寸大
top_margin 上の余白 0, 0.5, 1.0など
right_margin 右の余白 0, 0.5, 1.0など
bottom_margin 下の余白 0, 0.5, 1.0など
left_margin 左の余白 0, 0.5, 1.0など
horizontal_alignment 水平方向の位置 LEFT, CENTER, RIGHT
vertical_alignment 垂直方向の位置 TOP, MIDDLE, BOTTOM
range セル範囲 A1%3AF5
%3Aは:を意味します。つまり例はA1:F5をURIencodingしているということです
scale 表示スケール 1:標準100% / 2:幅に合わせる / 3:高さに合わせる / 4:ページに合わせる
printtitle スプレッドシート名をPDFに表示するか true:表示する / false:表示しない
sheetnames シート名をPDFに表示するか true:表示する / false:表示しない
pagenum ページ番号を表示するか true:表示する / false:表示しない
gridlines グリッドラインを表示するか true:表示する / false:表示しない
fzr 固定行を表示するか true:表示する / false:表示しない
fzc 固定列を表示するか true:表示する / false:表示しない

STEP.4 変換したPDF(翌日の予定一覧)を添付ファイルとして自分自身にメールを送信する

これは簡単です。STEP.3まででPDFはできているので、それを添付するだけです。

Qiita008.gs
const COL = {
  NUM: 1
  , START: 2
  , END: 3
  , TITLE: 4
  , LOCATION: 5
  , DETAIL: 6
}

const PDF_OUTDIR = DriveApp.getFolderById('1rwVxGOY14fcQcJEwTFV-_IDXsaBSVy5A');

function Qiita008_SpreadSheet() {
  // カレンダークラスを用いて、IDを指定してGoogleカレンダーにアクセスする
  const wCalendar = CalendarApp.getCalendarById('*******************@gmail.com');
  // スプレッドシートクラスを用いて、バインドしているスプレッドシートにアクセスする 
  const wSpread = SpreadsheetApp.getActiveSpreadsheet(); 
  const wSheet = wSpread.getSheets()[0];

  let wToday = new Date()
      , wTriggerDate = new Date();
  // 対象日付を翌日にセットする
  wTriggerDate.setDate(wToday.getDate()+1);

  let wPasteLst = new Array();
  // スプレッドシート内の既存データをクリアする
  let wLstRow = wSheet.getLastRow();
  if (wLstRow>1) wSheet.getRange(2, COL.NUM, wLstRow-1, COL.DETAIL).clearContent();
  
  // 対象日付のイベントを取得する
  const wTomorrowEvent = wCalendar.getEventsForDay(wTriggerDate);
  // 各イベントの開始・終了時間等の詳細を貼り付け用のリストに設定する
  wTomorrowEvent.forEach(function(wEvent, eIdx){
    wPasteLst.push([
      eIdx+1
      , Utilities.formatDate(wEvent.getStartTime(), 'JST', 'HH:mm:ss')
      , wEvent.isAllDayEvent() ? '23:59:59' : Utilities.formatDate(wEvent.getEndTime(), 'JST', 'HH:mm:ss')
      , wEvent.getTitle()
      , wEvent.getLocation()
      , wEvent.getDescription()
    ]);
  });
  wSheet.getRange(2, COL.NUM, wPasteLst.length, wPasteLst[0].length).setValues(wPasteLst);
  
  // スプレッドシートをPDFにエクスポートする
  let wPdfId = createPdf(wSpread.getId()
                         , wSheet.getSheetId()
                         , 'A1%3AF'+(wPasteLst.length+1)
                         , Utilities.formatDate(wTriggerDate, 'JST', 'yyyyMMdd')+'の予定');
  
  // 自分自身のメールアドレス宛にPDFを送信する
  GmailApp.sendEmail(
    '*******************@gmail.com'
    , '【自動送信メール】明日の予定'
    , '' // 自分あてなので本文はなくても笑
    , {attachments: DriveApp.getFileById(wPdfId).getBlob()}
  );
}

function createPdf(spreadId, sheetId, rangeStr, fileName){
  // PDF変換するためのベースURLを作成する
  let wUrl = `https://docs.google.com/spreadsheets/d/${spreadId}/export?gid=${sheetId}&exportFormat=pdf`;
  let wPdfOpt = '&size=A4'                      // 用紙サイズ
              + '&portrait=true'                // 用紙の向き true:縦向き / false:横向き
              + '&fitw=true'                    // ページ幅を用紙にフィットさせるか true:フィットさせる / false:原寸大
              + '&top_margin=0.50'              // 上の余白
              + '&right_margin=0.50'            // 右の余白
              + '&bottom_margin=0.50'           // 下の余白
              + '&left_margin=0.50'             // 左の余白
              + '&horizontal_alignment=CENTER'  // 水平方向の位置
              + '&vertical_alignment=TOP'       // 垂直方向の位置
              + '&range='+rangeStr;             // 変換範囲

  // headersにアクセストークンを格納する
  let wOtions = {
    headers: {
      'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`
    }
  }; 
  // PDFを作成する
  let wBlob = UrlFetchApp.fetch(wUrl+wPdfOpt, wOtions).getBlob().setName(fileName + '.pdf');

  //PDFを指定したフォルダに保存する
  return PDF_OUTDIR.createFile(wBlob).getId();
}

こちらを実行するとメールにて予定が送られてきましたよね。これでひとまずは完成です。

が!!!! 翌日の予定一覧が欲しいときにGASエディタを開いていちいち実行というのはめんどくさいですよね。そこで、今回つくったメソッドが毎日定時に実行されるようスケジュール登録していきましょう。

STEP.5 毎日、翌日の予定一覧が届くようにトリガー登録する

手順 イメージ
1 GASエディタの左メニューから「トリガー」をクリックします。
2 トリガー一覧画面に切り替わったら右下の「トリガーを追加」をクリックします。
3 出てきたメニュー上で右イメージにあるように「日付ベースのタイマー」と右下の「保存」をクリックします。
※時刻はお好きな時間を選択してください。
4 「保存」をクリック後、例のごとく権限設定のダイアログが出ますので許可すると、右イメージのようにトリガーが追加されていることを確認できます。

これで毎日上記で登録した時刻にメソッドが動き出し、翌日の予定を自身に送信してくれるようになりました。

このやり方の場合、[時刻を選択]で選択できる時間には幅があり、例えば22:00きっかりにという指定ができません。でも、スケジュール実行ならそうしたいですよね。安心してください、できます。次回、そういった小技をお伝えしていきます。

おわりに

お疲れ様でした。
第8回は「GASを用いてGoogleカレンダー+メール送信で翌日の予定を通知しよう」ということで、前回に引き続きPDF変換およびメール送信、さらにはトリガー登録と、より実業務で活用できそうなことをちりばめてみました。次回は、私がこれまで習得してきたGASのちょっとした小技をいくつか紹介します。あえて言っておきます、神回です。
記事を読んで、「良いな」や「今後に期待できる!」と感じて頂けたらいいねフォローコメントいただけると幸いです。それではまた次回をお楽しみに!

ブログでより詳しく解説しています!

以下画像をクリックしてブログにアクセス!!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?