GASで自動化!Gmailから楽天証券の積立予定をGoogleカレンダーに登録する方法

プログラミング

こんにちは、だあしょ(@daasho_blog)です。

楽天証券で投資信託を積み立てていますが、毎月のどこかで積立予定日を知らせるメールが届きます。

これまでは何気なく見ていたのですが、ふとした時に「せっかくだからGoogleカレンダーに登録したいな」と思いました。

まぁ正直なところ長期投資を目的としているので、放置、意識しない、忘れる、くらいがちょうど良いのかもしれませんが。。。

とはいえ思い立ったが吉日ということで、ちょっと自動化してみます。

普段からGmailやGoogleカレンダーといったGoogleサービスには大変お世話になっています。

そのため、GAS(Google App Script)を使って自動化を目指してみます。

作業履歴と備忘録も兼ねてまとめておきます。

同じように考える人に少しでも参考になれば幸いです。



プロフィール

文学部日本文学科卒の超文系人間|飲食業界大手から転職してWebエンジニアに|メインはJava|フロントエンドも勉強中|技術のキャッチアップに忙しい毎日|趣味は読書・野球・ゴルフ・写真|座右の銘は『幸せとは、自分の人生を自分でコントロールすること』|2021/04/22~ブログスタート

だあしょをフォローする

完成したコード

そんなこんなで試行錯誤しながらも完成したコードがこちらです。

// メール検索の条件を定義(送信元と件名)
var SEARCH_QUERY = 'from:service@rakuten-sec.co.jp subject:【投資信託】次回積立予定をお知らせします';

// メール本文内のキーワードを定義
var KEYWORDS = {
  INVESTMENT_DATE: '積立予定日',
  STARTING_LINE: '対象の積立設定',
  ENDING_LINE: '「楽天キャッシュで積立」の取引スケジュールは、楽天証券サイトにてご確認ください。'
};

function getInvestmentScheduleFromGmail() {
  // デフォルトカレンダーを取得
  var calendar = CalendarApp.getDefaultCalendar();

  // メールを検索する(最新1通)
  var threads = GmailApp.search(SEARCH_QUERY, 0, 1);

  // メールが見つからない場合の処理
  if (threads.length === 0) {
    Logger.log('該当するメールが見つかりませんでした。');
    return;
  }

  // 各スレッド内のメッセージを処理
  threads.forEach(function(thread) {
    thread.getMessages().forEach(function(message) {
      var body = message.getPlainBody(); // メール本文の取得(プレーンテキスト)
      var lines = body.split("\n"); // 改行で本文を分割

      var investmentDate = null;  // メール本文から抽出した積立予定日
      var investmentDetails = [];  // メール本文から抽出した積立内容の詳細

      // メール本文から積立予定日を抽出
      lines.forEach(function(line) {
        if (line.includes(KEYWORDS.INVESTMENT_DATE)) {
          investmentDate = line.replace(KEYWORDS.INVESTMENT_DATE, '')
                                   .replace(/(\d{4})年(\d{1,2})月(\d{1,2})日/, '$1/$2/$3');
        }
      });
        
      // メール本文から積立内容セクションの開始と終了を特定
      var startIdx = lines.findIndex(line => line.includes(KEYWORDS.STARTING_LINE));
      var endIdx = lines.findIndex(line => line.includes(KEYWORDS.ENDING_LINE));

      // 積立内容セクションが存在する場合、内容を抽出
      if (startIdx >= 0 && endIdx > startIdx) {
        investmentDetails = lines.slice(startIdx + 1, endIdx);
      }
      
      // 積立予定日と詳細をログに出力
      Logger.log('積立予定日: ' + investmentDate);
      Logger.log('積立詳細: ' + investmentDetails.join('\n'));

      // 積立予定日が存在し、詳細もある場合のみカレンダーに登録
      if (investmentDate && investmentDetails.length > 0) {
        // 同じ日付のイベントが存在しないか確認
        var events = calendar.getEventsForDay(new Date(investmentDate));
        var eventTitle = '投信積立予定';
        if (events.some(event => event.getTitle() === eventTitle)) {
          Logger.log('同じ日付のイベントが既に存在します。');
        } else {
          // イベントの作成
          var event = calendar.createAllDayEvent(eventTitle, new Date(investmentDate), {
            description: investmentDetails.join('\n')
          });
          event.setColor(CalendarApp.EventColor.PALE_RED);  // 色を設定
          Logger.log('カレンダーにイベントを追加しました。')
        }
      }
    });
  });
}

各行の処理を整理

以下、コードの各行の処理を詳しく説明します。

メール検索の条件を定義

var SEARCH_QUERY = 'from:service@rakuten-sec.co.jp subject:【投資信託】次回積立予定をお知らせします';

検索クエリを定義します。

  • from:部分:送信元のメールアドレスを指定します。
  • subject:部分:メールの件名に含む文字列を指定します。

この場合、楽天証券からの投信積立予定に関するメールを検索します。

メール本文内のキーワードを定義

メール本文から必要な情報を抽出するためのキーワードを定義します。

var KEYWORDS = {
  INVESTMENT_DATE: '積立予定日',
  STARTING_LINE: '対象の積立設定',
  ENDING_LINE: '「楽天キャッシュで積立」の取引スケジュールは、楽天証券サイトにてご確認ください。'
};

1つずつ定数として定義しても良いですが、項目群がまとまっている方がきれいなので“KEYWORDS”としてまとめておきます。

  • INVESTMENT_DATE:積立予定日が記載されている部分を特定するためのキーワード。
  • STARTING_LINE:積立内容の詳細が始まる部分を示すキーワード。
  • ENDING_LINE:積立内容の詳細が終わる部分を示すキーワード。

デフォルトカレンダーを取得

Googleカレンダーのデフォルトカレンダーを取得します。

var calendar = CalendarApp.getDefaultCalendar();

カレンダーにイベントを追加するために使用します。

メールを検索する(最新1通)

GmailのAPIを使って、定義した検索クエリ (SEARCH_QUERY) に基づいてメールを検索します。

var threads = GmailApp.search(SEARCH_QUERY, 0, 1);
  • 第一引数:検索クエリを指定します。
  • 第二引数:検索結果の配列の開始位置を指定します。
  • 第三引数:検索結果の配列の終了位置を指定します。

つまり、今回の「0」と「1」は検索結果の最初の1通のみを取得するという意味です。

メールが見つからない場合の処理

検索結果にメールが見つからなかった場合の処理を加えておきます。

if (threads.length === 0) {
  Logger.log('該当するメールが見つかりませんでした。');
  return;
}

ログに「該当するメールが見つかりませんでした」と記録し、処理を終了させます。

各スレッド内のメッセージを処理

メールスレッド (thread) 内の各メッセージを取得します。

その後、取得した各メッセージに対して処理しています。

threads.forEach(function(thread) {
  thread.getMessages().forEach(function(message) {

スレッド内に複数のメッセージがある可能性があるため、すべてのメッセージに対して処理を行います。

メール本文の取得(プレーンテキスト)

メールの本文をプレーンテキスト形式で取得します。

var body = message.getPlainBody();

プレーンテキスト形式とは、HTMLメールなどの形式ではなく、純粋なテキスト形式です。

余計なHTMLなどが入ることで処理がややこしくなることを防ぎます。

改行で本文を分割

メール本文を改行 (\n) で分割し、各行を配列として格納します。

var lines = body.split("\n");

この配列を使って各行に対して処理を行います。

積立予定日と内容を格納する変数を定義

各変数を初期化しておきます。

var investmentDate = null;  // メール本文から抽出した積立予定日
var investmentDetails = [];  // メール本文から抽出した積立内容の詳細
  • investmentDate:メール本文から抽出する積立予定日を格納するための変数。初期値はnullです。
  • investmentDetails:積立設定の詳細を格納する配列。メール本文から抽出した内容をこの配列に追加します。

メール本文から積立予定日を抽出

まずは積立予定日を抽出します。

カレンダーに登録する際に指定する日付に使用します。

lines.forEach(function(line) {
  if (line.includes(KEYWORDS.INVESTMENT_DATE)) {
    investmentDate = line.replace(KEYWORDS.INVESTMENT_DATE, '')
                         .replace(/(\\d{4})年(\\d{1,2})月(\\d{1,2})日/, '$1/$2/$3');
  }
});

メールの各行 (lines) を順に確認し、積立予定日キーワード (KEYWORDS.INVESTMENT_DATE) を含む行を特定します。

特定した行からキーワードを削除し、日付の形式をYYYY/MM/DDに変換してinvestmentDateに格納します。

なお、日付の変換には正規表現を使用しました。

積立内容セクションの開始位置と終了位置を特定

カレンダーの本文に登録する積立内容セクションを抽出するため、開始位置と終了位置を特定します。

var startIdx = lines.findIndex(line => line.includes(KEYWORDS.STARTING_LINE));
var endIdx = lines.findIndex(line => line.includes(KEYWORDS.ENDING_LINE));

lines配列から、積立内容の詳細が始まる行 (KEYWORDS.STARTING_LINE) と終わる行 (KEYWORDS.ENDING_LINE) のインデックスをそれぞれ取得します。

積立内容セクションが存在する場合、情報を抽出

特定した積立内容セクションの範囲内にある情報を抽出します。

if (startIdx >= 0 && endIdx > startIdx) {
  investmentDetails = lines.slice(startIdx + 1, endIdx);
}

startIdxとendIdxが有効な範囲である場合、開始行の次の行から終了行の前までの行を抽出し、investmentDetails配列に格納します。

積立予定日と詳細をログに出力

抽出した積立予定日と積立設定の詳細をログに出力します。

Logger.log('積立予定日: ' + investmentDate);
Logger.log('積立詳細: ' + investmentDetails.join('\n'));

investmentDetails.join(‘\n’)で詳細を改行区切りで結合しています。

積立予定日が存在し、積立内容もある場合のみカレンダーに登録

抽出した情報をカレンダーに登録します。

if (investmentDate && investmentDetails.length > 0) {

積立予定日 (investmentDate) と積立内容の詳細 (investmentDetails) が有効な場合にのみ、次のカレンダー登録処理に進みます。

同じ日付のイベントが存在しないか確認

イベントの重複を避ける処理を加えておきます。

var events = calendar.getEventsForDay(new Date(investmentDate));
var eventTitle = '投信積立予定';
if (events.some(event => event.getTitle() === eventTitle)) {
  Logger.log('同じ日付のイベントが既に存在します。');
} else {

カレンダーの指定した日 (investmentDate) に既存のイベントを取得し、タイトルが「投信積立予定」のイベントが既に存在するかをチェックします。

既に同じタイトルのイベントが存在する場合、重複を避けるために新しいイベントは作成しません。

イベントの作成

同じ日付にイベントが存在しない場合、新しいイベントをカレンダーに作成します。

var event = calendar.createAllDayEvent(eventTitle, new Date(investmentDate), {
  description: investmentDetails.join('\n')
});
event.setColor(CalendarApp.EventColor.GREEN);  // 色を設定
Logger.log('カレンダーにイベントを追加しました。');
  • createAllDayEvent:終日イベントを作成します。
  • description:メールから抽出した積立詳細をイベントの説明として追加します。
  • setColor:イベントの色を設定します。この場合は緑色 (GREEN) です。

結果をログに出力

結果をログに出力しておきます。

Logger.log('カレンダーにイベントを追加しました。');

イベントの作成が成功した場合、ログに「カレンダーにイベントを追加しました。」というメッセージを出力します。

トリガーを設定

ここまでで作成したイベントを発火させるトリガーを設定します。

過去の受信状況を見た感じでは、投信の次回積立予定日のお知らせは、

  • 毎月16日(土日祝日関係なし)
  • 11:30前後

に来ているようなので、トリガーは毎月16日の12時~1時で設定しておきます。

実行結果

コードを書きながらテストで動かしている感じだと、問題なく動いているようでした。

※見せない方が良さそうな部分は加工して塗りつぶしています。

TODO:

この記事を書いて投稿した日が15日ですので、ちょうど明日、来月分が届く(はず)ので、実際に動くかどうか待ってみたいと思います。

===== 2024/10/16 追記=====

予想通り来月分のお知らせが11:59に届きました。

その後、設定した12時~13時の間にトリガーが発火しましたね。

問題なく登録されたようです。よかった。



まとめ

GASを使って楽天証券から送られてくる投信積立予定日をGoogleカレンダーに登録するスクリプトを作ってみました。

  • GASを使ってGmailのメール本文から情報を抽出し、Googleカレンダーに登録できる
  • GASのスクリプトを駆使するコストは多少あるが、慣れればそれほど問題ない
  • GASのトリガーを使えば、上記の処理が自動化できる

慣れない間は理解が難しかったですが、やっていく内にコツが掴めてきました。

今回作成したスクリプトをベースにして、他のメールにも色々応用できそうだなと思いました。

細かい部分の調整や作り込みとかエラー処理の追加とかはまた時間がある時に進めて行こうかなと。

楽天証券で積み立てているものは他にもあるので、その辺もどんどん登録してみたいと思います。

コメント

タイトルとURLをコピーしました