6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

話題の「ChatGPT✖️Pubmed新着論文」のLINE Botに検索機能もつける

Last updated at Posted at 2023-04-03

ピンク 緑 黄色 サロン LINE リッチメニュー (5).png

はじめに

ものづくりの先輩医師がこのような画期的なシステムを作ってくださいました。

自分の興味のある検索ワードで実装すると、毎日定時にPubmedから検索してChatGPTで要約したものが届きます。
スキマ時間で読めるのでとても便利!かつPubmedから検索なので実在しない論文は出てきません。

これを参考に
・LINEへの通知(二つ目の機能はつけない場合は途中を読み飛ばして最後だけ読んでください。)
・自動通知は固定の検索ワードにしておいて、ふと気になった検索ワードで検索できる機能
をつけました。

なるべく元のをいじらず省エネで作っています(笑)

スプレッドシートを準備

スクリーンショット 2023-04-03 15.06.56.png
こんな感じで準備してください。
C3セルに"=365*C2"と入力しておいてください。
シート名はmainにします(スクリプトと統一していればなんでも良いです)。

LINE通知の準備

ほぼ元のスクリプトと同様に書いて、通知の部分を書き換えます。
初めに、

let CHANNEL_ACCESS_TOKEN =
  "LINEのチャンネルアクセストークン";
let line_endpoint = "https://api.line.me/v2/bot/message/reply";
let sheet= SpreadsheetApp.openById("準備したスプレッドシートID").getSheetByName("main")

を追加します。
スプレッドシートIDは該当のスプレッドシートのURL https://docs.google.com/spreadsheets/d/******/edit の******部分です。

リプライメッセージ部分

function doPost(e) {
  let json = JSON.parse(e.postData.contents);
  let reply_token = json.events[0].replyToken;
  if (typeof reply_token === "undefined") {
    return;
  }
  if (json.events[0].type == "message") {
    const usermessage = json.events[0].message.text;
    if (usermessage == "他の論文を検索する") {
      sheet.getRange("A2").setValue(1);
      messages = [
        {
          type: "text",
          text:
            "「検索ワード」、「publishされた期間(年)を半角数字で」、「論文上限数(max13個)を半角数字で」の順番に入れてください(各項目ごとにメッセージを送信)",
        },
      ];
    } else {
      let count = parseInt(sheet.getRange("A2").getValue());
      if (count == 3) {
        sheet.getRange("D2").setValue(usermessage);
        let otherPUBMED_TERM = sheet.getRange("C3").getValue();
        const date = new Date();
        const past = new Date(
          date.getFullYear(),
          date.getMonth(),
          date.getDate() - otherPUBMED_TERM
        );
        let otherPUBMED_QUERY = sheet.getRange("B2").getValue();
        const ids = getotherPaperIDsOn(past, date, otherPUBMED_QUERY);
        let output = "検索結果のお知らせ\n\n";
        let paperCount = 0;

        let otherMAX_PAPER_COUNT = usermessage;
        for (let i = 0; i < ids.length; i++) {
          Utilities.sleep(1000);
          const id = ids[i];
          const pubmedUrl = `https://pubmed.ncbi.nlm.nih.gov/${id}`;
          const summary = getPaperSummaryByID(id);
          const title = summary.title;
          console.log(`id: ${id}, pubtype: ${summary.pubtype.join(",")}`);
          if (!checkPubtype(summary.pubtype)) {
            console.log("pubtype: NG");
            continue;
          }
          console.log("pubtype: OK");
          if (++paperCount > otherMAX_PAPER_COUNT) break;
          const abstract = getPaperAbstractByID(id);
          const input =
            "\n" + "title: " + title + "\n" + "abstract: " + abstract;
          const res = callChatGPT([
            {
              role: "user",
              content: PROMPT_PREFIX + "\n" + input,
            },
          ]);
          const paragraphs = res.choices.map((c) => c.message.content.trim());
          output += `${paragraphs.join("\n")}\n\n${pubmedUrl}\n\n\n`;
        }
        output = output.trim();
        messages = [
          {
            type: "text",
            text: output,
          },
        ];
        sheet.getRange("A2").setValue(0);
      } else {
        sheet.getRange(2, count + 1).setValue(usermessage);
        sheet.getRange("A2").setValue(count + 1);
      }
    }
    sendmessage(messages, reply_token);
  }
  return ContentService.createTextOutput(
    JSON.stringify({ content: "post ok" })
  ).setMimeType(ContentService.MimeType.JSON);
}

function getotherPaperIDsOn(past,date,otherPUBMED_QUERY) {
  
    const url = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&sort=pub_date&term=${otherPUBMED_QUERY}&mindate=${toYYYYMMDD(past)}&maxdate=${toYYYYMMDD(date)}`;
    const res = JSON.parse(UrlFetchApp.fetch(url).getContentText());
    return res.esearchresult.idlist;
}

function sendmessage(message, reply_token) {
  UrlFetchApp.fetch(line_endpoint, {
    headers: {
      "Content-Type": "application/json; charset=UTF-8",
      Authorization: "Bearer " + CHANNEL_ACCESS_TOKEN,
    },
    method: "post",
    payload: JSON.stringify({
      replyToken: reply_token,
      messages: message,
    }),
  });
}

仕組み

モードセル(A2)の数値で挙動を区別

①「他の論文を検索する」が送信されるとモード1
②モード1,2の時はセルにusermessageを格納し、モードの数字を1増やす
③モード3の時はこれまでのメッセージをスプレッドシートから抽出
 →Pubmed検索→ChatGPT API→結果をLINE送信

リッチメニューは作らなくても良い

私はリッチメニューを押したら「他の論文を検索する」のメッセージが送信され、そこからスタートする仕様にしましたが、「他の論文を検索する」のところを「検索」とか簡単なワードにして(何なら「あ」だけでもよい)、自分で入力するでも機能には影響ありません。

自動送信の部分をLINE通知に変更する方法

ちなみに、メール通知をLINE通知に変更する方法は、初めに

let CHANNEL_ACCESS_TOKEN =
  "LINEのチャンネルアクセストークン";

を追加した上で、元のスクリプトの「sendEmail(output);」部分を消して、

UrlFetchApp.fetch('https://api.line.me/v2/bot/message/broadcast', {
            method: 'post',
            headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
            },
            payload: JSON.stringify({
              messages: [
                {
                    type: 'text',
                    text: output
                },
                
              ]
              }),
          }) 

をコピペしてください。
LINEアカウントの作り方・GASとの連携はこちらから。

おわりに

各部分もっとスマートなやり方はあると思いますが、初心者かつ「動けばよい」精神でやっているのでこんな感じです。
スマートなやり方を教えてくださる方はぜひコメントをお願いいたします!

6
5
8

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
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?