見出し画像

【GAS】Gmail取得ツール スプレッドシート コード全公開 かんたんな解説付き

結構、良い感じにコードが書けたので、公開して共有したいと思います。コードは全部公開します。スプレッドシートに少し手を加える必要がありますが、手の加え方も順を追って説明します。



条件

必要スキル:GASで何かしらのプログラムを実行できること
      パソコンの基本スキル
      Googleスプレッドシートの知識
      AND、OR関数の知識

必要な環境:Googleアカウント
      Gmailの受信箱が空ではないこと

解説を読むのに必要なスキル:プログラミングの基礎知識
              クラスの基礎知識


完成図

スプレッドシートの加工

必要なシート
「設定」シートの編集

コード

メイン.gsとクラス.gsを作成して、それぞれにコピペしてください。

スクリプトの設定

メイン.gs

function onOpen(){
  //カスタムバーの追加
  const menu = SpreadsheetApp.getUi()
  menu.createMenu("追加バー")
      .addItem("Gmail検索","searchGmail")
      .addToUi();
}

function onEdit(e){
  if (e.value == "TRUE"){
    //チェックボックスが選択されたら
    let sp = SpreadsheetApp.getActiveSpreadsheet();
    let sh = sp.getActiveSheet();
    if(sh.getName() == "設定"){
      let col = e.range.getColumn();
      let row = e.range.getRow();
      //チェックボックスの排他処理
      if (col == 2 && row == 1){
        //ORのキャンセルを外す
        sh.getRange("B2").setValue("FALSE");
      }else if(col == 2 && row == 2){
        //ANDのチェックを外す
        sh.getRange("B1").setValue("FALSE");
      }
    }
  }
}

//Gmailの受信トレイから検索する(メイン関数)
function searchGmail(){
  let sp = SpreadsheetApp.getActiveSpreadsheet();
  let sh_setting = sp.getSheetByName("設定");
  let sh_writing = sp.getSheetByName("メール");
  sh_writing.clear() //シートの初期化
  let write_List = []; //書き込みリスト
  let header = ["日付","FROM","件名","本文"]; //項目名
  write_List.push(header);
  //検索フラグの設定
  //AND:AND条件、OR:OR条件、NON:先頭一個
  let search_flg = "NON"
  if (sh_setting.getRange("B1").getValue()){
    search_flg = "AND";
  }else if(sh_setting.getRange("B2").getValue()){
    search_flg = "OR";
  }

  //D列の検索文字列の取得
  let lastRow = 1;
  if(sh_setting.getRange("D2").isBlank()){
    lastRow = 1
  }else{
    //D列の先頭行から下方向に取得する
    lastRow = sh_setting.getRange(1, 4).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
  }
  let querys = sh_setting.getRange("D1:D" + lastRow.toString()).getValues();
  
  //エラー処理
  if(querys[0][0] == "" || querys[0][0] == null){
    Browser.msgBox("エラー:D列の先頭行に検索文字列を入力してください。");
    return;
  }
  
  if(lastRow == 1){
    search_flg = "NON";
  }

  //クラスのインスタンスを作成
  let s = new SearchCondition(querys,search_flg);
  
  //クエリの作成
  let doQuery = s.query;
  //指定日以降(after)の入力があれば指定を追加
  let after = sh_setting.getRange("B4").getValue();
  if(!(after == "" || after == null)){
    doQuery += " AND after:" + setDate(after);
  }
  //指定日以前(before)の入力があれば指定を追加
  let before = sh_setting.getRange("B5").getValue();
  if(!(before == "" || before == null)){
    doQuery += " AND before:" + setDate(before);
  }
  //受信箱を指定する
  doQuery += " AND in:inbox"
  //メールの取得
  let threads = GmailApp.search(doQuery);
  threads.forEach(thread => {
    let messages = thread.getMessages();
    messages.forEach(message => {
      let m = message.getPlainBody();
      if(s.checkMessage(m)){
        //出力
        let list = [
          setDate(message.getDate()),
          message.getFrom(),
          message.getSubject(),
          m
        ]
        write_List.push(list);
      }
    });
  });

  //出力
  sh_writing.getRange(1,1,write_List.length,write_List[0].length).setValues(write_List);

  //垂直方向をtopに変更
  sh_writing.getRange("A:C").setVerticalAlignment("top");
}

/** 
 * 日付型をyyyy/MM/ddの文字列に変換
 * @param {Date} date - 日付型
 * @return {string} yyyy/MM/ddの文字列 
 **/
function setDate(date){
  return Utilities.formatDate(date,"Asia/Tokyo","yyyy/MM/dd");
}

クラス.gs

/**
 * 検索条件 AND,OR,NONに合わせて処理分岐するクラス
 **/
class SearchCondition{
  /**
   * 検索条件(querys)をflgのAND,OR,NONに合わせて処理分岐
   * @param {Array.<Array.<string>>} querys - 検索条件、2次元配列、0番目のみ使用
   * @param {string} flg - 検索フラグ,AND,OR,NONのみ設定可能
   **/
  constructor(querys,flg){
    this.flg = flg;
    switch (flg){
      case "NON":
        this.query = querys[0][0];
        break;
      case "AND":
      case "OR":
        this.query = makeQuery();
        this.searchList = makeSearchList();
    }
    //検索条件queryの結合
    function makeQuery(){
      //querysの長さが1以上の場合に走る
      let query = querys[0][0];
      for(let i = 1 ; i < querys.length ; i++){
        query += " " + flg + " " + querys[i][0];
      }
      return query
    }
    //検索リストを作成する
    function makeSearchList(){
      let list = [];
      for(let i = 0 ; i < querys.length; i++){
        list.push(querys[i][0]);
      }
      return list;
    }
  }
  /**
   * flgに合わせてmessageに検索条件の文字列が含まれているかの判定
   * @param {string} message - 検索対象の文字列
   * @return {boolean} true:含まれている。false:含まれていない。
   **/
  checkMessage(message){
    let query = this.query;
    let list = this.searchList;
    switch(this.flg){
      case "NON":
        return NonCheckMessage();
      case "AND":
        return AndCheckMessage();
      case "OR":
        return OrCheckMessage();
    }
    function NonCheckMessage(){
      if(message.indexOf(query) != -1){
        return true;
      }else{
        return false;
      }
    }
    function AndCheckMessage(){
      let search_flg = false;
      for(let i = 0 ; i < list.length; i++){
        if(message.indexOf(list[i] != -1)){
          search_flg = true;
        }else{
          search_flg = false;
          break;
        }
      }
      if(search_flg){
        return true;
      }else{
        return false;
      }
    }
    function OrCheckMessage(){
      for(let i = 0 ; i < list.length ; i++){
        if(message.indexOf(list[i]) != -1){
          return true;
        }
      }
      return false;
    }
  }
}

機能説明


「設定」シートのD列に入力された検索条件を元にGmailの受信トレイを検索して、本文に検索条件に合致する文字列がある場合、「メール」シートに出力する
検索期間をafter(以降)、before(以前)で指定することも可能
※指定なしも可
検索条件はAND、ORのどちらかを指定することが可能。指定が無い(チェックが無い)場合は、D1セルの値のみで検索する使用方法


使用方法

検索条件を「設定」シートに入力して右上の「追加バー」タブから「Gmail検索」を選択して実行

「追加バー」タブの「Gmail検索」

スプレッドシートの編集

1.「メール」シートと「設定」シートの作成
1-1.シートを追加
下部の「+」ボタンを押して、シートを追加する

シートを追加

1-2.シート名の変更方法
下部のシートタブを選択して右クリックして赤枠の「名前を変更」を選択し、名前を変える。「シート1」、「シート2」をそれぞれ「メール」と「設定」に変える

シート名を変更

1-3.「設定」シートの編集
1-3-1.項目名の編集
A1セル:AND、A2セル:OR、A4セル:after、A5セル:before、C1セル:検索条件

項目名の編集後

1-3-2.入力セルの色付け
Ctrlボタンを押しながら、入力セル(B1:B2、B4:B5、D1:D8※D列は適当な長さで良い)を選択し、背景色を設定する

入力セルを選択してセルの背景色を設定

1-3-3.チェックボックスの挿入
B1:B2セルを選択して「挿入」タブから「チェックボックス」を選択

「挿入」から「チェックボックス」

1-3-4.日付の入力制限
B4:B5セルを選択して、「データ」タブの「データの入力規則」を選択して、データの入力規則のサイドバーを表示

「データ」タブの「データの入力規則」

右端に「データの入力規則」というサイドバーが表示されるので、「+ルールを追加」を選択

「データの入力規則」サイドバーの「+ルールを追加」

範囲に適用が「'設定'!B4:B5」になっているのを確認して、条件の「プルダウン」を「有効な日付」に変更

「有効な日付」の入力規則に変更

設定出来たら、右下の完了ボタンを選択

入力規則を設定したセルをダブルクリックしてカレンダーが表示されたらOK

入力規則が「有効な日付」に設定されているか確認

コード解説

ここから下は、コードの内容に興味のある人向けです。

関数・クラス一覧

function一覧:
 onOpen() 追加バーの設定
 onEdit(e) チェックボックスの排他処理※ANDもしくはORが選択されたら逆のチェックを外す
 searchGmail() メイン関数 Gmailを検索条件で検索して出力する
 setDate(date) ユーティリティ関数 日付型を日付文字(yyyy/MM/dd、2023/08/05など)に変換する

Class:
 SearchCondition
  constructor(querys,flg) コンストラクター 検索条件の初期設定
   makeQuery() サブ関数 queryを作成
   makeSearchList() サブ関数 searchListを作成
   プロパティ flg AND条件、OR条件、もしくは、条件無しかを設定
         query (検索条件※GmailApp,search(query)に使用)
         searchList (検索文字列の配列)
 メソッド
  checkMessage(message) messageが検索条件を満たしているか判定
   NonCheckMessage() プライベート関数 条件無しの場合の処理
   AndCheckMessage() プライベート関数 AND条件の場合の処理
   OrCheckMessage()  プライベート関数 OR条件の場合の処理

onOpenやonEditやsetDateの説明は省きます。

流れの説明

SearchGmail()の処理
1.「メール」「設定」シートの取得、および、「メール」シートの初期化

 let sp = SpreadsheetApp.getActiveSpreadsheet();
 let sh_setting = sp.getSheetByName("設定");
 let sh_writing = sp.getSheetByName("メール");
 sh_writing.clear() //シートの初期化

2.書き込み用の配列の準備(ヘッダーを含む)

let write_List = []; //書き込みリスト
let header = ["日付","FROM","件名","本文"]; //項目名
write_List.push(header);

3.ANDやORのチェックボックスの状態からsearch_flgの設定

//検索フラグの設定
//AND:AND条件、OR:OR条件、NON:先頭一個
let search_flg = "NON"
if (sh_setting.getRange("B1").getValue()){
  search_flg = "AND";
}else if(sh_setting.getRange("B2").getValue()){
  search_flg = "OR";
}

4.検索文字列の取得 D1からD列の最終入力セルまで。空白がある場合は、その上。また、D1セルに入力が無い場合は、エラーを表示する。D2セルが空白の場合は、検索条件が無いとして、D1セルのみを検索条件に用いる。また、その場合は、検索条件search_flgを検索条件無しにしている。

//D列の検索文字列の取得
let lastRow = 1;
if(sh_setting.getRange("D2").isBlank()){
  lastRow = 1
}else{
  //D列の先頭行から下方向に取得する
  lastRow = sh_setting.getRange(1, 4).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
}
let querys = sh_setting.getRange("D1:D" + lastRow.toString()).getValues();
  
//エラー処理
if(querys[0][0] == "" || querys[0][0] == null){
  Browser.msgBox("エラー:D列の先頭行に検索文字列を入力してください。");
  return;
}
  
if(lastRow == 1){
  search_flg = "NON";
}

5.SeachConditionクラスのインスタンスの作成

//クラスのインスタンスを作成
let s = new SearchCondition(querys,search_flg);

6.検索条件doQuery(文字列、たとえば"Google OR コメダ AND after:2023/01/01 AND in:inbox"など)の作成

//クエリの作成
let doQuery = s.query;
//指定日以降(after)の入力があれば指定を追加
let after = sh_setting.getRange("B4").getValue();
if(!(after == "" || after == null)){
  doQuery += " AND after:" + setDate(after);
}
//指定日以前(before)の入力があれば指定を追加
let before = sh_setting.getRange("B5").getValue();
if(!(before == "" || before == null)){
  doQuery += " AND before:" + setDate(before);
}
//受信箱を指定する
doQuery += " AND in:inbox"

7.メールの取得

//メールの取得
let threads = GmailApp.search(doQuery);
threads.forEach(thread => {
  let messages = thread.getMessages();
  messages.forEach(message => {
    let m = message.getPlainBody();
    if(s.checkMessage(m)){
      //出力
      let list = [
        setDate(message.getDate()),
        message.getFrom(),
        message.getSubject(),
        m
      ]
      write_List.push(list);
    }
  });
});

ここが、肝なので、もうちょっと詳しく説明します。
GmailApp.search()はGmailを検索して合致するのをたくさん返してきます。
それをforEachで展開します。ここでは、threadsからthreadへ。
threadのgetMessages()(※複数形なのでこれもやはりたくさん返ってきます)でメッセージを取得して、forEachで展開します。ここでは、messagesからmessageへ。
messageのgetPlainBody()(※getBodyだとhtml形式で返ってきます)で文章を取得します。この時、SearchConditonクラスのcheckMessage()を使って文章が検索条件の文字列を含んでいるかを判定。AND、OR、もしくは、無しかは、ここでは意識する必要はありません。クラス側で判断(※プロパティのflgで判定)してもらいます。

条件が通れば、出力判定になり、出力内容をList(配列)に格納して、出力用配列write_Listに追加します。

ここの処理によってクラスを用いている意味があります。つまり、メール本文の文字列検索処理をクラスに任せることが出来るので、メイン関数では大まかな流れだけを意識すればよくなります。
しかし、説明の関係では、必要になると思うので、ここで、searchConditionクラスのcheckMessage()メソッドの中身を見ていきます。

/**
 * flgに合わせてmessageに検索条件の文字列が含まれているかの判定
 * @param {string} message - 検索対象の文字列
 * @return {boolean} true:含まれている。false:含まれていない。
 **/
checkMessage(message){
  let query = this.query;
  let list = this.searchList;
  switch(this.flg){
    case "NON":
      return NonCheckMessage();
    case "AND":
      return AndCheckMessage();
    case "OR":
      return OrCheckMessage();
  }
  function NonCheckMessage(){
    if(message.indexOf(query) != -1){
      return true;
    }else{
      return false;
    }
  }
  function AndCheckMessage(){
    let search_flg = false;
    for(let i = 0 ; i < list.length; i++){
      if(message.indexOf(list[i] != -1)){
        search_flg = true;
      }else{
        search_flg = false;
        break;
      }
    }
    if(search_flg){
      return true;
    }else{
      return false;
    }
  }
  function OrCheckMessage(){
    for(let i = 0 ; i < list.length ; i++){
      if(message.indexOf(list[i]) != -1){
        return true;
      }
    }
    return false;
  }
}

検索条件に使用していたsearch_flgがAndCheckMessage()で再び出てきて、しかも、違う使い方をしているので、変数の命名則としては、ダメな感じがしますが、いったん置いておきます。

大事なことは、Switch文を使うことで、使う時に条件が違う場合を意識する必要が無いということです。

NonCheckMessage()、AndCheckMessage()、OrCheckMessage()はそれぞれプライベートにしたかったので入れ子にしました(※ほかのやり方が分からなかった)。これで、外部(インスタンス)からは直接アクセスできなくなります。

8.出力と体裁を整える

//出力
sh_writing.getRange(1,1,write_List.length,write_List[0].length).setValues(write_List);

//垂直方向をtopに変更
sh_writing.getRange("A:C").setVerticalAlignment("top");

以上です。

いろいろと力不足な点も多いかと思いますが、ご容赦ください。

この記事が気に入ったらサポートをしてみませんか?