定期的に英語のニュース記事を読みたいなと思っているのですが、結構そのこと自体を忘れます。
LINE botで定期的に通知すれば、忘れずに毎日続けられると思い、GASとLINE botで定期通知botを作ってみました。
準備
準備として、①LINE botを作るためのLINE developer consoleからの設定、②GASのスクリプトプロパティへの環境変数保存、③GASの定期実行トリガーの保存、などがあります。
詳しくはここで解説しませんが、Messaging APIをLINE Developper Console上で有効にし、通知先のLINEアカウントのユーザIDをDevelopper Consoleまたはテスト関数的なものを作成して通知に使うbotから直接送信先のユーザIDを取得する必要があります。
余談:別のbotで取得した自分のユーザIDを取得して使おうとしたところ、永遠に下のエラーが出て原因がわからなかった...
{"message":"Failed to send messages"}
実行コード
ユーザID取得用
こちらはbotに対して適当なメッセージを送ることで、そのメッセージを送ってきた人のuser idを返すbotです。単純にニュース記事をbotに流すだけであれば不要ですが、こちらはWebhookとしてGASのコードを全体公開し、そのURLをLINE botのWebhook URLに指定する必要があります。
// LINE developersのメッセージ送受信設定に記載のアクセストークン
const LINE_TOKEN = PropertiesService.getScriptProperties().getProperty("LINE_ACCESS_TOKEN");
const LINE_URL = 'https://api.line.me/v2/bot/message/reply';
//ユーザーがメッセージを送信した時に下記を実行する
function doPost(e) {
const json = JSON.parse(e.postData.contents);
//replyToken…イベントへの応答に使用するトークン(Messaging APIリファレンス)
// https://developers.line.biz/ja/reference/messaging-api/#message-event
const reply_token = json.events[0].replyToken;
const userId = json.events[0].source.userId;
// 検証で200を返すための取り組み
if (typeof reply_token === 'underfined') {
return;
}
const option = {
'headers': {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + LINE_TOKEN,
},
'method': 'post',
'payload': JSON.stringify({
'replyToken': reply_token,
'messages': [{
'type': 'text',
'text': "Your UserId: "+ userId,
}],
}),
}
UrlFetchApp.fetch(LINE_URL,option);
return;
}
ニュース記事通知botのコード
sendMessage
をGASのトリガーに設定し、定期実行します。
// 設定値
const LINE_CHANNEL_ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty("LINE_ACCESS_TOKEN");
const USER_ID = PropertiesService.getScriptProperties().getProperty("USER_ID");
// リマインドメッセージを送信
function sendMessage() {
const option = {
'headers': {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN,
},
'method': 'post',
"muteHttpExceptions": true,
"validateHttpsCertificates": false,
"followRedirects": false,
'payload': JSON.stringify({
'to': USER_ID,
'messages': fetchAndSendTechCrunchArticles()
}),
}
try {
const response = UrlFetchApp.fetch('https://api.line.me/v2/bot/message/push', option);
Logger.log(`Response: ${response.getContentText()}`);
} catch (e) {
Logger.log('Error sending reminder:');
Logger.log(e);
throw e;
}
}
/**
* 記事を取得して送信
*/
function fetchAndSendTechCrunchArticles() {
try {
// TechCrunchのRSSフィードを取得(HTMLより軽量で構造化されている)
const rssUrl = 'https://techcrunch.com/feed/';
const response = UrlFetchApp.fetch(rssUrl);
if (response.getResponseCode() !== 200) {
return 'TechCrunchからの記事取得に失敗しました。';
return;
}
const xmlContent = response.getContentText();
const articles = parseRSSFeed(xmlContent);
if (articles.length === 0) {
return '記事が見つかりませんでした。'
return;
}
// 最新の記事を処理
const latestArticles = articles.slice(0, 5);
let messages = [];
latestArticles.forEach((article, index) => {
// タイトルと概要を翻訳
const translatedTitle = translateText(article.title);
const translatedDescription = translateText(article.description);
const message = `📰 記事 ${index + 1}\n\n` +
`🔤 原題: ${article.title}\n\n` +
`🇯🇵 和訳: ${translatedTitle}\n\n` +
`📝 概要: ${translatedDescription}\n\n` +
`🔗 リンク: ${article.link}\n\n` +
`📅 公開日: ${article.pubDate}`;
messages.push({
type: 'text',
text: message
});
});
// メッセージを送信
return messages
} catch (error) {
Logger.log(error);
return '記事の取得中にエラーが発生しました。'
}
}
/**
* RSSフィードを解析
*/
function parseRSSFeed(xmlContent) {
const articles = [];
try {
// XMLをパース
const xml = XmlService.parse(xmlContent);
const root = xml.getRootElement();
const channel = root.getChild('channel');
const items = channel.getChildren('item');
items.forEach(item => {
const title = item.getChildText('title') || '';
const link = item.getChildText('link') || '';
const description = item.getChildText('description') || '';
const pubDate = item.getChildText('pubDate') || '';
// HTMLタグを除去
const cleanDescription = description.replace(/<[^>]*>/g, '').substring(0, 200) + '...';
articles.push({
title: title.substring(0, 100), // タイトルを100文字に制限
link: link,
description: cleanDescription,
pubDate: formatDate(pubDate)
});
});
} catch (error) {
Logger.log('Error parsing RSS feed: ' + error.toString());
}
return articles;
}
/**
* Google Translate APIを使用してテキストを翻訳
*/
function translateText(text) {
if (!text || text.trim() === '') return '';
try {
const japanese = LanguageApp.translate(text, 'en', 'ja');
Logger.log(japanese);
return japanese; // 翻訳失敗時は元のテキストを返す
} catch (error) {
Logger.log('Translation error: ' + error.toString());
return text;
}
}
/**
* 日付をフォーマット
*/
function formatDate(dateString) {
try {
const date = new Date(dateString);
return Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm');
} catch (error) {
return dateString;
}
}
/**
* 手動でTechCrunch記事を取得するテスト関数
*/
function testFetchArticles() {
try {
const rssUrl = 'https://techcrunch.com/feed/';
const response = UrlFetchApp.fetch(rssUrl);
const xmlContent = response.getContentText();
const articles = parseRSSFeed(xmlContent);
console.log('取得した記事数:', articles.length);
articles.slice(0, 2).forEach((article, index) => {
console.log(`記事 ${index + 1}:`);
console.log('タイトル:', article.title);
console.log('翻訳タイトル:', translateText(article.title));
console.log('---');
});
} catch (error) {
console.log('テストエラー:', error.toString());
}
}
}
ちなみに今回は適当にTech Crunchから取得していますが、他のニュースサイトのRSS URLに置き換えても使えます。他にも使えそうなニュースサイトのRSSとしては以下のURLがありそうです。
Hacker News: https://news.ycombinator.com/rss
Ars Technica: http://feeds.arstechnica.com/arstechnica/index/
The Guardian: https://www.theguardian.com/uk/rss
LWN: https://lwn.net/headlines/newrss
BBC: http://feeds.bbci.co.uk/news/video_and_audio/news_front_page...