LINE Notify×GAS で楽天カード明細をLINEに自動通知してみた【WIZAPPエンジニアブログvol.2】

LINE Notifyとは

「LINE Notify」とはWebサービスと連携すると、LINEga提供する公式アカウント”LINE Notify”から通知が届くサービスです。複数のサービスと連携可能で、グループでも通知を受信することが可能です。

  • 利用料金は完全無料です。
  • ユーザーは、1時間あたり1,000回までの通知しか受け取れないという回数制限があります。
  • 「広告」、「販売促進」、「スパム的行為」などの目的での使用は禁止されています。(LINE Notify利用規約

以下実装に際し、「LINE Notify API Document」を参考に導入して設定してみます。

Google Apps Script (GAS)とは

Google Apps Script(GAS)は、Googleが提供するプログラミング言語です。HTMLやJavaScriptなどの身近なWeb言語を用いて、GmailやGoogleスプレッドシートなどのGoogleサービスを自動化することができます。

GASを使うには、Google Workspace アカウント(または 無償の Google アカウント)とウェブブラウザーのみが必要です。少量のコードを書くだけで Google のサーバー上で動作させることができます。

GAS公式ドキュメント

  • 利用料金は完全無料です。
GASの活用事例
  • スプレッドシートの自動化
  • Googleフォームからのデータの収集や処理
  • Gmailの自動化(例:自動返信、特定のメールの処理)
  • Googleドキュメントの自動化(例:自動作成、テンプレートの作成)など

完成イメージ(機能・作成背景)

機能

  • 楽天カードが使用されたら、使用履歴をLINEに通知する。
  • 家族グループにLINE Notifyアカウントを追加し、自動通知を行う。
  • Gmail経由で送信された利用履歴メールを元に集計する。
  • 一度送信されたものは重複して送信されない。
  • 毎日定時刻にバッチ実行し、1日分のメールを集計して、自動通知する。
  • 送信項目は①利用日(決済日)②利用先③利用者:本人/家族(家族カード)④利用金額⑤支払月を表示。

作成に至る背景・経緯

我が家では妻に家計管理をやってもらっている中で、楽天カードの明細を毎月スクリーンショットを撮って今月の決済金額および決済明細を送信していました。その一方、明細量が多いため、何枚ものスクリーンショットを撮影・送信するのに、時間と労力のコストがかかり、手間がかかっていた中、これを自動化できれば、スクリーンショットの手間がなくなると考え、このアプリシステムの開発を検討に至ります。

楽天カードでは、「カードお知らせメール」という機能があり、事前設定しておくと決済内容をメールで通知してくれる機能があります。それをGmail宛に設定し、GASと連携することで自動化を行うことができます。

実装方法

開発環境構築

clasp

clasp は、GAS をローカルで開発できるように作成された CLI ツールです。clasp を使用すれば、自分の好きなエディタで TypeScript で開発することが可能です。

様々な記事を拝見した中で、最終行き着いたのが以下の記事でしたので、ご参考に環境構築が可能です。

https://panda-program.com/posts/clasp-typescript

実装

楽天メール取得

/**
 * Gmail の受信ボックスから楽天決済案内メールを取得します。
 * @returns メール情報
 */
const getRakutenMail = (): string[] | undefined => {
    // 例として、現在の日付を取得し、前日のメールを検索するクエリを生成する
    const today = getToday();
    const searchQuery = generateSearchQuery(today);
    const threads = GmailApp.search(searchQuery, 0, 100);

    if (!threads || threads.length === 0) {
        Logger.log('該当するスレッドが見つかりませんでした');
        return undefined;
    }
    const messages = threads.map(thread => thread.getMessages()[0].getPlainBody());
    return messages;
};

/**
 * Gmailの検索クエリを生成する関数
 * @param {Date} date 対象の日付
 * @returns {string} Gmailの検索クエリ
 */
function generateSearchQuery(date) {
    const subject = 'カード利用のお知らせ';
    const yesterdayString = Utilities.formatDate(getYesterday(date), Session.getScriptTimeZone(), 'yyyy/MM/dd');
    return `subject:"${subject}" -:"速報版" after:${yesterdayString}`;
}

/**
 * 現在の日付を取得する関数
 * @returns {Date} 現在の日付
 */
function getToday() {
    return new Date();
}

/**
 * 指定された日付の前日を取得する関数
 * @param {Date} date 対象の日付
 * @returns {Date} 前日の日付
 */
function getYesterday(date) {
    const yesterday = new Date(date);
    yesterday.setDate(date.getDate() - 1);
    return yesterday;
}

メールの取得は、GmailApp.search を使用します。

第一引数が、メールを取得するクエリになりますが、今回は以下条件でクエリを定義しています。

  • 件名が「カード利用のお知らせ」と一致していること(※メール件名はカード利用のお知らせは、本人のみ、家族会員のみ、両方の3パターンあり、全て取得したいので、あえてタイトルに一致しているかどうかで判別)
  • 速報版という文字列は除外すること

Gmailの検索クエリはさまざまあるので、自分の用途に合わせてお使いください。

(参考:https://support.google.com/mail/answer/7190?hl=ja

取得したメッセージから、getPlainBody()のメソッド発火すると、メール本文を文字列で取得できます。

参考までにメール本文が以下の通りです。

次にフィールド項目を設定します。

この文字列から、■利用日: ~ ■ ご利用明細のご確認 までを一塊となるように、正規表現で取得して、各情報をパースしています。

/**
 * メール本文から決済履歴の情報を抽出し、決済情報オブジェクトを取得します。
 * @param message メール本文
 * @returns 決済情報オブジェクト
 */
const parseMessage = (message: string): PaymentInfo[] => {
    const paymentInfoList: PaymentInfo[] = [];
    const matched: RegExpMatchArray | null = message.match(/■利用日(?:(?!■利用日|■ご利用明細のご確認).)+/gs);
    if (matched) {
      for (const paymentMessage of matched) {
        const m = new Message(paymentMessage);
        paymentInfoList.push(new PaymentInfo(m.getUseDay(), m.getUseStore(), m.getUser(), m.getAmount(), m.getPayMonth()));
      }
    }

    return paymentInfoList;
};

export class Message {
    message: string;

    constructor(message: string) {
        this.message = message;
    }

    private extractPaymentInfo = (prefix: string): string => {
        const matched: RegExpMatchArray | null = this.message.match(`${prefix}.+`);
        return matched ? matched[0].replace(prefix, "") : "";
    };

    getUseDay(): string {
        return this.extractPaymentInfo("■利用日:");
    }

    getUseStore(): string {
        return this.extractPaymentInfo("■利用先: ");
    }

    getUser(): string {
        return this.extractPaymentInfo("■利用者: ");
    }

    getAmount(): string {
        return this.extractPaymentInfo("■利用金額: ");
    }

    getPayMonth(): string {
        return this.extractPaymentInfo("■支払月: ");
    }
}

export class PaymentInfo {
    private useDay: string;
    private useStore: string;
    private user: string;
    private amount: string;
    private payMonth: string;

    constructor(useDay: string, useStore: string, user: string, amount: string, payMonth: string) {
        this.useDay = useDay;
        this.useStore = useStore;
        this.user = user;
        this.amount = amount;
        this.payMonth = payMonth;
    }

    // Getter メソッドなど、必要に応じて追加
    getUseDay(): string {
        return this.useDay;
    }

    getUseStore(): string {
        return this.useStore;
    }

    getUser(): string {
        return this.user;
    }

    getAmount(): string {
        return this.amount;
    }

    getPayMonth(): string {
        return this.payMonth;
    }
}

LINE通知

LINE通知 は、GAS から Messaging API の push-message を POST することでメッセージを送信しています。

function notifyRakutenMail() {
    const token = LINE_NOTIFY_TOKEN; //LINENotifyで発行されるトークンを入力
    const lineNotifyApi = 'https://notify-api.line.me/api/notify';

    // Gmailからメールを取得
    const mailBodies = getRakutenMail();

    if (!mailBodies) {
        Logger.log('メールが見つかりませんでした');
        return;
    }

    for (const mailBody of mailBodies) {
        // メール本文から決済情報を抽出
        const paymentInfoList = parseMessage(mailBody);

        // LINE Notifyに送信するメッセージを構築
        let message = "\nカードが使われたよ!\n見てね!\n↓↓↓↓↓↓\n";
        for (const paymentInfo of paymentInfoList) {
            message += `\n■利用日: ${paymentInfo.getUseDay()}`
                    + `\n■利用先: ${paymentInfo.getUseStore()}`
                    + `\n■利用者: ${paymentInfo.getUser()}`
                    + `\n■利用金額: ${paymentInfo.getAmount()}`
                    + `\n■支払月: ${paymentInfo.getPayMonth()}\n\n`;
        }

        const options : GoogleAppsScript.URL_Fetch.URLFetchRequestOptions =
        {
            "method"  : "post",
            "payload" : {"message": message},
            "headers" : {"Authorization":"Bearer " + token}
        };

        UrlFetchApp.fetch(lineNotifyApi, options);
    }
}

動作確認

clasp run で、ローカルPCから GAS を実行

GAS を実行する際、GAS の画面から 実行ボタンを押すことで、実行できますが、clasp run を実行することで、ローカルからも実行することが可能です。

実行ログも clasp log --watch で確認できます。

clasp run を実行するためには、事前準備が必要です。以下をご参照ください。

https://github.com/google/clasp/blob/master/docs/run.md#prerequisites

GAS のデプロイ設定で定期実行バッチを回す

ローカルPCで動作確認が問題なければ、定期バッチの実行を設定を行います。

この設定を行うことで、毎日のメールを集計し定期的にLINE通知が実行されるようになります。

おわりに

今回実装を終え、日常生活における効率化に繋げることができました。

一点注意点としては、本実装は楽天からの明細通知メールの内容をベースに正規表現で取得しているため、万一メールの形式や内容に変更があった場合はそれに合わせて修正を行う必要があるので注意してください。

また、一部動作について動かない場合などはChat GPTなどを駆使して実装してみてください。

さらに、今回はLINE Notifyで実装しましたが、この他にもLINE Messaging APIを用いてビジネスアカウントでの通知なども可能です。詳細気になる方は調べてみてください。

WIZAPPは、引き続きお客さまの業務効率化や生産性向上、集客強化のためにWEBシステムでサポートさせていただきます。

以上、WIZAPPエンジニアブログの第二弾でした!