論文検索を自動化してメール通知【コード全文】

関数ごとにブロックを分けています。全てコピペすれば動くと思います。
この記事はコードのみなので、中身についてこちらを参照してください。

<details><summary>流れは大体こんな感じです（フローチャート）。</summary>

[![](https://mermaid.ink/img/pako:eNqVk11PGkEUhv8KmWs0RaGLa9KkCW3SCxMTvSpwMWUHIYFZsh9Gy5KwuxcqqbUXFmJNarCt1VI_kjaNtlR-zOkCXvkXenZYiBjbxN2b3XPe95lzzsyUSUZVGJHJkkZLudBiYjbFUzyEj2by0MTEo5DGqDLHDDobhINfkSvSPH9q8oyRV3mQvxnyNRY4h-Aegdvu7jhgt3oXZ-DU-p1tsHeu2-sxb_c92Jdgf75ub1h3Ae_GhkIWuAdIBeeHzz59DfaJd7LXb76ysszI5B5z5ckyLZjUYMn5uWcJP71V9y4bacEdUG8rBbmkseW8aurzxbyiJ72NTVHiyQBy2D3d7rfdtBjTADKmFwSrWz_rH2z1W_VufQ1sBxseFJWgBsXlFhguiR0VkuCuiR46N2sL6rotFug8z6pJ1IPzEdx1cFtQtcE5B_eDz3FrGPHebIL9Beyj9ADmW4SXvtAN9O6DWwfnQtiPwDnFj0DpC4IRqBmm64tsxUhiC95xw1tv4Ay6b797v_fTw75HImEyNMr1gj_vXudX__DbsJlRXKj0Ue9X1Xe9vU_gNkXlLcRfVe0_nebQp481XlCXIjiuY3DO_K1c--nVdtP_2QIxgoaFtql_2nxmsGFfm5i-eaLAPrfuOI0-7x4WsVKKkzApMg1zCt60sjh_xMixIksRGT8VlqVmwUiRFK-glJqGurDKM0Q2NJOFiVlScHqJPMU7WiRylhZ0jJYof66qY_9ELpMVIksPpcloPBaLxmdiEXylMFklcuzBpDQTwaA0LU3Hp6JSJUxeCkCk8hekTcGC?type=png)](https://mermaid.live/edit#pako:eNqVk11PGkEUhv8KmWs0RaGLa9KkCW3SCxMTvSpwMWUHIYFZsh9Gy5KwuxcqqbUXFmJNarCt1VI_kjaNtlR-zOkCXvkXenZYiBjbxN2b3XPe95lzzsyUSUZVGJHJkkZLudBiYjbFUzyEj2by0MTEo5DGqDLHDDobhINfkSvSPH9q8oyRV3mQvxnyNRY4h-Aegdvu7jhgt3oXZ-DU-p1tsHeu2-sxb_c92Jdgf75ub1h3Ae_GhkIWuAdIBeeHzz59DfaJd7LXb76ysszI5B5z5ckyLZjUYMn5uWcJP71V9y4bacEdUG8rBbmkseW8aurzxbyiJ72NTVHiyQBy2D3d7rfdtBjTADKmFwSrWz_rH2z1W_VufQ1sBxseFJWgBsXlFhguiR0VkuCuiR46N2sL6rotFug8z6pJ1IPzEdx1cFtQtcE5B_eDz3FrGPHebIL9Beyj9ADmW4SXvtAN9O6DWwfnQtiPwDnFj0DpC4IRqBmm64tsxUhiC95xw1tv4Ay6b797v_fTw75HImEyNMr1gj_vXudX__DbsJlRXKj0Ue9X1Xe9vU_gNkXlLcRfVe0_nebQp481XlCXIjiuY3DO_K1c--nVdtP_2QIxgoaFtql_2nxmsGFfm5i-eaLAPrfuOI0-7x4WsVKKkzApMg1zCt60sjh_xMixIksRGT8VlqVmwUiRFK-glJqGurDKM0Q2NJOFiVlScHqJPMU7WiRylhZ0jJYof66qY_9ELpMVIksPpcloPBaLxmdiEXylMFklcuzBpDQTwaA0LU3Hp6JSJUxeCkCk8hekTcGC)
</details>

# コード

全体の実行を定義する関数。
この関数に対してトリガーを設置しますが、設置方法は検索してみてください。
```js
function run() {
  let err_count = 0;

  //metaデータの読み込み
  let meta = readMeta(); 

  //ユーザー数に応じて繰り返す
  let max = meta.length;
  for (i=0; i<max; i++) {

    //メインの関数を実行
    try{
      mainFunction(meta, i, err_count);

    }catch(e){
      console.log(e.message);
      errorEmail(meta[i]);
    }
  }
}

```

各機能を担うそれぞれの関数を順番に呼び出して実行するための関数。
エラー時には自分自身を呼び出してやり直す（5回まで）。5回を超える場合には、実行をストップしてエラー通知メールを送信する。
```js
function mainFunction(meta, i, err_count){
  try{
  //metaから情報を取得
  user_id = meta[i].id;
  word = meta[i].word;
  email = meta[i].email;
  num = meta[i].num;
  stop = meta[i].stop;

  console.log(user_id, word, email, stop);

  //新しいPMIDの評価、logをobjectの配列で返す
  let log = fetchAndEvaluate(word, num ,user_id);

  //新規PMIDのデータ取得、メール送信
  fetchDataAndSendEmail(log, meta[i]);

  //ログをJSON形式で保存
  writeJson(log, user_id);
  err_count=0;

  }catch(e){
    err_count++ ;

    if (err_count<6){
    console.log('count:',err_count,'\n',e.message);
    mainFunction(meta, i, err_count);
    }else{
      console.log("Stopped due to error")
      errorEmail(meta[i]);
    }
  }
}
```

Googleドライブ内に保存したmetaファイルを読み込む。
metaファイルはJSON形式で作成し、検索ワードや送信先アドレスを格納する。
ファイル名は`meta.json`にしておく。
Googleドライブのフォルダidを調べて指定する。

metaファイルの構成：`"id"`, `"word"`, `"num"`, `"stop"`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655949/d380c4c6-8349-a55b-1331-87d7657426a4.png)

```js
function readMeta() {

  //メタファイルが保存されているフォルダidを指定
  folder = '******************';
  file = 'meta.json';

  const content = DriveApp.getFolderById(folder)
  .getFilesByName(file)
  .next()
  .getBlob()
  .getDataAsString("utf-8")

  return JSON.parse(content);
}

```

検索ワードにヒットしたPMIDを取得する。
前回のログを読み込んで新規かどうかを評価する。
新規なら`1`、新規じゃないなら`0`を記録する。
```js
//PubmedからPMIDを取得し、前回のPMIDと比較して新規かどうかを評価する
function fetchAndEvaluate(word, num ,user_id) {
  console.log('開始')////////////////////////////////////

  // 指定したword でPubmed検索 
  let query = Utilities.formatString('https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=%s&retmax=%s&retmode=json',word,num);

  //APIに接続、JSON形式で取得、読み取り可能な形式に変換
  let response = UrlFetchApp.fetch(query).getContentText();
  let response_json=JSON.parse(response);

  pmids = response_json['esearchresult']['idlist'].slice();

  //前回のPMIDを配列で取得
  let previous_pmids = previousPmids(user_id);

  //PMIDの比較、新しい場合　1　を記録
  let evaluation = pmids.map((id) =>{
    if(previous_pmids.indexOf(parseInt(id, 10)) == -1){
      return 1;
    }else{
      return 0;
    }
  });

  //PMIDSをオブジェクトに変換
  let log = pmids.reduce((acc, value, index) => {
  return {...acc, ['pmid' + index]: value};
  }, {} );
  
  //一時オブジェクトを作成して情報を入れる（あとで結合する）
  let temp = {
    evaluation: evaluation, 
    timestmp: Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss'), 
    error: ""
  };

  //オブジェクトを配列に入れて結合する
  log = [log, temp];

  return log
}

```

前回実行時のログを読み込んで、配列として返す。
上記`fetchAndEvaluate`関数内で呼び出されている。
```js
//ログを読み込んで前回のPMIDを取得する
function previousPmids(user_id) {
  let date = new Date();
  date.setDate(date.getDate() - 1)

  let timestmp = Utilities.formatDate(date, 'Asia/Tokyo', 'yyyyMMdd');

  let folder = '**************************';
  let file = Utilities.formatString('%s_%s', timestmp, String(user_id));

  const content = DriveApp.getFolderById(folder)
  .getFilesByName(file)
  .next()
  .getBlob()
  .getDataAsString("utf-8")

  let previous_pmids = JSON.parse(content)[0];
  previous_pmids = Object.values(previous_pmids).map((x) => parseInt(x,10));
  
  //console.log(previous_pmids);
  return previous_pmids
}
```

1.新規論文の情報を取得する。
2.Abstractの取得と翻訳の実行を行う。
3.最後に新規論文を通知するメールを送信する。
```js
function fetchDataAndSendEmail(log, meta) {
  let pmids = Object.values(log[0]);
  let evaluation = log[1].evaluation;

  console.log('evaluation:', evaluation);

  let data = pmids.map((id, i) => {
    if (evaluation[i] == 1){
      Utilities.sleep(0.12 * 1000)

      //pmidを用いてそれぞれの論文から情報を取得するためのurlを作成
      let url= 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&id='+id;

      json = JSON.parse(UrlFetchApp.fetch(url).getContentText());

      let obj = {
        pmid: id,
        pubdate: json['result'][pmids[i]]['pubdate'],
        title: json['result'][pmids[i]]['title'],
        journal_name: json['result'][pmids[i]]['fulljournalname'],
        issn: json['result'][pmids[i]]['issn'],
        link: 'https://pubmed.ncbi.nlm.nih.gov/'+pmids[i]+'/',
      }
      return obj;
    }else{
      return {};
    }
  }); 

  let abst = pmids.map((id, i) => {
    if (evaluation[i]==1){
      Utilities.sleep(0.12 * 1000)

      let url ='https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=XML&id='+id;
      let response_abst=UrlFetchApp.fetch(url).getContentText();
      let document = XmlService.parse(response_abst);
      let body = XmlService.getPrettyFormat().format(document);

      let processed = processText(body);
      let jpn = LanguageApp.translate(processed, 'en','ja');

      let obj = {
        abst: processed,
        abst_jpn: jpn
      }
      return obj
    }else{
      return {};
    }
  });

  data = data.map((each, i) => {
    data[i] = Object.assign(data[i],abst[i]);
    return data[i];
  });

  evaluation.map((val, i) => {
    if(val == 1){
      sendEmail(data[i], meta);
    }
  });
}

```
AabstractのHTMLタグや余計なスペースなどを除去して整形するための関数。
上記`fetchDataAndSendEmail`関数内で呼び出される。
```js
function processText(body){
    //abstrastの両端の文字位置を取得
    let start =[];
    let end =[];
    start = body.indexOf('<Abstract>')+10 ;
    end= body.indexOf('</Abstract>') ;

    //abstract の切り出し
    let raw_text = body.substring(start,end);
    let processed = raw_text.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'').replace(/[\r\n]+/g,'');

    while( processed.indexOf("  ") >=0){
      processed = processed.replace("  ","").slice()
    }
  return processed;
}
```
メールの送信を行うための関数。
上記`fetchDataAndSendEmail`関数内で呼び出される。
```js
function sendEmail(data, meta){

  //メールの送信設定
  subject='New paper';

//メール文面の作成
  let body = Utilities.formatString(
    'PMIDS: %s<br>Journal: %s<br>Publish date: %s<br><br>Tile:<br><b>%s</b><br><br>%s<br><br>Abstract:<br>%s<br><br>%s',
    data.pmid,
    data.journal_name,
    data.pubdate,
    data.title,
    data.link,
    data.abst,
    data.abst_jpn
    );

  address = meta.email;
  //Gmailの送信
  GmailApp.sendEmail(address,subject,body,{htmlBody:body});
  console.log(address);
}
```

今回の実行のログを残す。
ログ保存用のフォルダをGoogleドライブ内に作成し、以下の関数にフォルダidを指定する。
```js
//JSON形式でログを出力する
function writeJson(log, user_id=) {
  let timestmp = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyyMMdd');

  var json_log = JSON.stringify(log);

  var file = Utilities.formatString('%s_%s', timestmp, String(user_id));
  let folder ="************************";

  var blob = Utilities.newBlob("", "application/json", file);
  var file = blob.setDataFromString(json_log, "UTF-8");

  DriveApp.getFolderById(folder)
  .createFile(file);

  console.log('保存完了')
}
```

エラー時の通知メールを送信する。
```js
//エラーで実行だストップした際にメール通知する
function errorEmail(meta) {
  let address = "*******************" ;
  let subject = "Error Notification";
  let body = Utilities.formatString("Stopped due to error.\n(Run: %s).", meta.id);
  GmailApp.sendEmail(address,subject,body);
}
```