海外のニュースレターを、自動要約・翻訳してDiscordに飛ばす
せっかく海外のニュースレター(AI、知的生産管理)を購読したが、英語のため読む気が失せた...
という経験はないだろうか?
が、そもそも無料でやりたい...!月に20$も払えない
そんな迷える子羊に、このノウハウを送ります。
アーキテクチャーは以下の通り。
ニュースレター -> Gmail -> GASで自動購読 -> GASでOpenAI APIで翻訳/要約 -> Discord へ送信
Discordの設定
自分専用のDiscordサーバーがない方は、こちらの記事を参照
ボットから情報を受け取りたいチャネル(news-letterチャネル)を右クリックし、チャネルの編集(Edit channel)を行う
https://scrapbox.io/files/662a669117bcc80024532778.png
連携サービス(integration) -> create webhook
https://scrapbox.io/files/6629dfa27f78d10025f3edd6.png
「新しいウェブフック」を選択し、作成されたWebhookのURLをコピー(あとで使うので、どこかへ保存)
https://scrapbox.io/files/662a66e2d315450026e684c9.png
GASの設定
Google Driveの画面へ行く
任意のフォルダを作成(例えば、scriptsフォルダ)し、右クリック -> その他 -> Google Apps Scriptを押す
https://scrapbox.io/files/6629e0aace6915002455cae7.png
以下の画面が開くので、コードを書いていく。
https://scrapbox.io/files/6629e0e3d6c7ce00243a0432.png
無題のプロジェクトというプロジェクト名を、「海外の新着ニュースレターを翻訳/要約して、Discordへ」などに変更する
GASとDiscordを連結する
まずは、接続を確認
code:js
function myFunction() {
const payload =
{
"content": "Hello, World!"
};
const options =
{
"method": "post",
"payload": payload
};
UrlFetchApp.fetch(WEBHOOK_URL, options);
}
command Sで保存して、コードを実行
https://scrapbox.io/files/6629e2b78dbb5500246e46ae.png
権限を確認 -> gmailアカウントクリック -> 詳細 -> 安全ではないページに移動をクリック -> 許可
https://scrapbox.io/files/6629e2f9225b0800257971c8.png
GASの実行ログとともに、DiscordへもHello Worldが届いていることが確認される。
OpenAI KeyをGASへ設定
続いて、OpenAIのAPI keyを設定する。
API keyはこちらのリンクから取得する。
サイドバーのプロジェクトの設定をクリック
https://scrapbox.io/files/662a7176a393be0025b70e0d.png
スクロールするとスクリプトプロパティの項目が表示されるので、「スクリプトプロパティを追加」を選択。
プロパティに「OPENAI_API_KEY」と入れて、値に事前に取得したChatGPTのAPIキーをコピペし、「スクリプトプロパティを保存」をクリック。
https://scrapbox.io/files/6629ea36460b8c0025e572c3.png
Gmailで条件に合ったメールを、GASへ転送する
コードはこちら
code:js
// スクリプトプロパティにAPIキーを保存するには、スクリプトエディタで「ファイル」>「プロジェクトのプロパティ」>「スクリプトプロパティ」を選択し、OPENAI_API_KEYとしてAPIキーを追加してください。
const OPENAI_API_KEY =
PropertiesService.getScriptProperties().getProperty("OPENAI_API_KEY")
const OPENAI_API_MODEL = "gpt-3.5-turbo-0125"
const PROMPT_PREFIX = "あなたは情報教育、テクノロジーに詳しい教師です。\n以下のニュースレターを、タイトルと要約の2点をそれぞれ改行で分けて、日本語で説明してください。\n要点は3-5個程度、箇条書きでお願いします。\n## 出力形式 \n送信者: {送信者}\nタイトル: {タイトル}\n要約: {要約}\n日本語でお願いしますね。";
const WEBHOOK_URL = "" // 上でコピーしたWebhook urlを貼り付けてください。
// 検索クエリ(検索条件)を入れる
// ここは、各自皆様の都合で、書き換えてください。
const GMAIL_QUERY =
"is:unread from:Nick OR from:Tiago OR from:Ben's OR from:TLDR"
function searchGmailMessages(query) {
const threads = GmailApp.search(query)
Logger.log(スレッド件数:${threads.length})
if (threads.length === 0) {
return []
}
return threads.map((thread) => {
const messages = thread.getMessages()
})
}
function sendToDiscord(output) {
const payload = {
content: output,
}
const options = {
method: "post",
payload: payload,
}
UrlFetchApp.fetch(WEBHOOK_URL, options)
}
function callChatGPT(input) {
const messages = [
{
role: "user",
content: PROMPT_PREFIX + "\n" + input,
},
];
const options = {
"method": "post",
"headers": {
"Authorization": Bearer ${OPENAI_API_KEY},
"Content-Type": "application/json",
},
"payload": JSON.stringify({
model: OPENAI_API_MODEL,
messages,
}),
};
return JSON.parse(UrlFetchApp.fetch(OPENAI_API_ENDPOINT, options).getContentText());
}
function main() {
if (!OPENAI_API_KEY) {
console.log("ERROR: OPEN_API_KEY を指定してください")
return
}
const messages = searchGmailMessages(GMAIL_QUERY)
if (!messages.length) return
let output = "新着ニュースレターのお知らせ\n\n"
for (const message of messages) {
const sender = message.getFrom()
const title = message.getSubject()
// この入力文字エラーが解決できず、やむを得ず、replaceメソッドを使っている
// 4500文字以上を削除だと正常動作だが、5000文字以上にするとAPIエラーが返るため、4500文字としている。
const content = message.getPlainBody().replace(/\n\r/g, '').replace(/-+|\*+/g, '').replace(/\s\S{4500,}/g, ''); const input =
"\n" +
"sender:" +
sender +
"title: " +
title +
"\n" +
"content: " +
content
const res = callChatGPT(input)
console.log(content)
console.log(ChatGPTからのレスポンス: ${JSON.stringify(res, null, 2)})
const paragraphs = res.choices.map((c) => c.message.content.trim())
output += ${paragraphs.join("\n")}\n\n\n
// 転送が完了したら既読にする。
message.markRead()
}
output = output.trim()
console.log(最終結果: ${output})
sendToDiscord(output)
}
WEBHOOK_URLに、上で設定した値を貼り付ける
正常動作するか確認するために、エディタに戻り、main関数を実行する
https://scrapbox.io/files/662a865c41c0bc0024f092ca.png
うまくいったら、以下のようになる。
https://scrapbox.io/files/662a870284f84d00254b66e6.png
定期実行する
毎日決まったタイミングで処理を行うために、設定を行う。
左のサイドバーのトリガーを選択
https://scrapbox.io/files/662a874af31bd80023c2e9fa.png
右下のトリガーの追加から、以下の画面を設定する。(一例)
https://scrapbox.io/files/662a87dd7e55fc00247134d1.png
これで、毎朝午前11時-12時に、ニュースレターの翻訳/要約が届くようになった。
最後に、エディターからデプロイ -> 新しいデプロイ -> 歯車をクリック -> ウェブアプリを選択 -> デプロイで完成です。
https://scrapbox.io/files/662a880aaaffdc0023bf0595.png
お疲れ様でした!
備考
GASのOpenAIのAPIリクエストで、原因不明のエラーが起こった
入力文字の何らかが悪さをしているとわかったが、正規表現で様々な文字を削除しても効果がなかった。
4500文字以上の文字を全部削除にしたら問題なく動作した
5000文字以上の文字を全部削除にしたら、エラーが起きた。
入力長の関係だろうか...?
コードは汚なくなっており、今後原因が分かり次第、修正を加える
関連
参考資料