ChatGPTで論文を要約しブログに投稿する

# これは時代が変わるぞという実感がやってきました。

ChatGPTが新着論文を要約し毎朝メールしてくれる仕組みの作り方 | Antaa Slide @antaa__jp #Antaa https://slide.antaa.jp/article/view/d0de1bd8ea414420 

Pubmedというアメリカの論文サイトで検索して、その結果をChatGPTに送って、要約・翻訳した結果をまとめてメールするという仕組みです。

-スライドにあったGASをコピー
-ChatGPTのAPIを取得
-API、メアド、検索キーワード、タイトルを変更
-毎朝実行する
（3時間）

新着論文が和訳されてメールがきて、おおーっ、やばいくらい感動しました！
ものすごく苦労してきた事が簡単に・・・

せっかくなので、メールだけではなく、スプレッドシートに内容をストック（3時間）
宛先を個人ではなく、ブログ宛に変更。（3時間）

それにしてもプログラミングって時間どろぼうですね。


```test.js

// OpenAI の API keys (https://platform.openai.com/account/api-keys)
const OPENAI_API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// ChatGPT に渡す命令
const PROMPT_PREFIX = "あなたはADHDに詳しい心理学者です。以下の論文を、タイトルと要約の2点をそれぞれ改行で分けて日本語で説明してください。要点は箇条書きで。";

// 結果メールの送信先
const EMAIL_RECIPIENT = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// 結果メールのタイトル
const EMAIL_SUBJECT = "PubMedの新着論文の要約";
// 結果メールの送信者の名前
const EMAIL_SENDER = "PubMed要約bot";

// PubMed の検索クエリ
const PUBMED_QUERY = "ADHD";
// PubMed の対象の記事タイプ（以下のいずれかのタイプとマッチする論文のみをヒットさせる ※ 期待したように記事タイプが判定されない場合も多い）
const PUBMED_PUBTYPES = ["Journal Article", "Books and Documents", "Clinical Trial", "Meta-Analysis", "Randomized Controlled Trial", "Review", "Systematic Review"];
// PubMed の検索対象日数
const PUBMED_TERM = 1;
// PubMed の検索時のヒット論文で要約する論文の本数の上限（多ければ多いだけ、ChatGPT API の token を多く消費する）
const MAX_PAPER_COUNT = 100;

//記録用のスプレッドシート
const sheet = SpreadsheetApp.getActiveSheet();

function main() {
    if (!OPENAI_API_KEY) {
        console.log("ERROR: OPEN_API_KEY を指定してください");
        return;
    }
    const today = new Date();
    const yesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - PUBMED_TERM);
    const ids = getPaperIDsOn(yesterday);

    let output = "[:contents]\n\n";
    let paperCount = 0;

    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 > MAX_PAPER_COUNT) break;
        const abstract = getPaperAbstractByID(id);
        const input = "\n" + "title: " + title + "\n" + "abstract: " + abstract;
        const res = callChatGPT(input);
        console.log(res);
        const paragraphs = res.choices.map((c) => c.message.content.trim());

        //はてぶ記法で先頭に"*"を追記 
        output += "*" + `${paragraphs.join("\n")}\n\n${pubmedUrl}\n\n\n`;
        //スプレッドシートに追記
        sendSheet(paragraphs,pubmedUrl);
    }
    if (paperCount === 0) {
        output += "検索条件に合致した新着論文はありませんでした。";
    }
    output = output.trim();
    sendEmail(output);
 
}

function toYYYYMMDD(date) {
    return [date.getFullYear(), date.getMonth() + 1, date.getDate()].join("/");
}

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

function getPaperSummaryByID(id) {
    const url = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&id=${id}`;
    console.log(url);
    const res = JSON.parse(UrlFetchApp.fetch(url).getContentText());
    return res.result[id];
}

function getPaperAbstractByID(id) {
    const url = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=xml&id=${id}`;
    const xml = UrlFetchApp.fetch(url).getContentText();
    const match = xml.match(/<Abstract>(.*?)<\/Abstract>/);
    return match ? match[1] : "";
}

function checkPubtype(pubtypes) {
    const common = pubtypes.filter(x => PUBMED_PUBTYPES.indexOf(x) !== -1);
    return common.length > 0;
}

function callChatGPT(input) {
    const messages = [
        {
            role: "user",
            content: PROMPT_PREFIX + "\n" + input,
        },
    ];

    const url = "https://api.openai.com/v1/chat/completions";

    const options = {
        "method": "post",
        "headers": {
            "Authorization": `Bearer ${OPENAI_API_KEY}`,
            "Content-Type": "application/json",
        },
        "payload": JSON.stringify({
            model: "gpt-3.5-turbo",
            messages,
        }),
    };

    return JSON.parse(UrlFetchApp.fetch(url, options).getContentText());
}

function sendEmail(body) {
    const options = { name: EMAIL_SENDER };
    
    //件名に日付をいれたい
    const date1 = new Date().toLocaleDateString("ja-JP", {year: "numeric",month: "2-digit",
   day: "2-digit"});
    GmailApp.sendEmail(EMAIL_RECIPIENT, date1 +" "+ EMAIL_SUBJECT, body, options);
}

function sendSheet(body,url) {
  let date = new Date();
  value = [date,url,body];
  value = value.flat();
  //スプレッドシートの最終行に挿入
  sheet.appendRow(value);
}
```