Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8695

LINEボットでGoogle Alertを通知

$
0
0

Google Alertは、キーワードを登録しておくと、ウェブ上の新着コンテンツを知らせてくれるサービスです。私は、今まで、メールで受信していたのですが、せっかくコンテンツを通知してくれたのに、時間がたつと埋もれてしまっていました。

そこで、新着コンテンツが来たタイミングでLINEボットで通知すると同時に、データベースに登録して、あとでも参照できるようにしてみます。

image.png

2か所でNode.jsを使います。
1つ目は、コンテンツの定期的な取得のため。
2つ目は、過去コンテンツの格納とLINEボット用のサーバです。

過去コンテンツは、MySQLサーバに格納しています。

もろもろはGitHubに上げておきました。

poruruba/GoogleAlert
 https://github.com/poruruba/GoogleAlert

流れ

①Googleアラートに、キーワードを登録しておきます。そうすると、RSSフィードのURLが取得できます。
②Node.jsなどで、定期的にRSSフィードからコンテンツを取得するとともに、Node.jsサーバにコンテンツ登録を依頼します。
③Node.jsサーバでは、コンテンツがすでにデータベースに登録されているか確認し、登録されていない場合はデータベースに登録します。それと同時に、LINEボットにコンテンツをメッセージとして送信します。
④ユーザは、LINEアプリにコンテンツがメッセージで送信されてきます。
⑤(必要に応じて)ユーザはLIFFアプリを起動し、Node.jsサーバからコンテンツ一覧を取得し表示します。

準備:Googleアラートにキーワードを登録

以下のサイトでキーワードを登録します。

Googleアラート
 https://www.google.co.jp/alerts

image.png

アラートを作成、と表示されているところにキーワードを入力します。
今回は、「ESP32」としてみました。オプションを表示となっている場合はクリックしてオプションを表示します。

image.png

ここで、配信先として、自身のGmailアドレスではなく、「RSSフィード」を選択します。
最後に、アラートを作成 を押下します。

そうすると、ESP32が追加され、無線のようなマークがでていますので、クリックします。
そうすると、RSSフィードが表示されました。
まだコンテンツの監視が始まったばかりで、コンテンツは1件もないです。ブラウザに表示されているこのURLを覚えておきます。

データベースの準備

以下のようなスキーマのテーブルを作成しました。

データベース名:googlealert

テーブル名:items
コンテンツを格納します。

image.png

テーブル名:members
LINEボットからコンテンツをメッセージ送信する先のユーザIDを格納します。

image.png

LINEボットの作成

すみませんが、以下の投稿を参考にしてください。

 LINEボットを立ち上げるまで。LINEビーコンも。

LINEボット名は「Googleアラート」にしてみました。

定期的なコンテンツの取得

定期的なコンテンツ取得は、GoogleアラートのRSSフィードを参照することで行います。
また、これから立ち上げるNode.jsサーバへコンテンツをHTTP Postしています。

RSSフィードの参照およびHTTP Postには以下のnpmモジュールを使っています。

rbren/rss-parser
 https://github.com/rbren/rss-parser

node-fetch/node-fetch
 https://github.com/node-fetch/node-fetch

cron_googlealert/index1.js
'use strict';constGOOGLE_ALERT_RSS_URL=process.env.GOOGLE_ALERT_RSS_URL||'【GoogleアラートのRSSフィードのURL】';constGOOGLE_ALERT_SEARCH_KEYWORD=process.env.GOOGLE_ALERT_SEARCH_KEYWORD||'【Googleアラートに指定したキーワード】';constbase_url="【Node.jsサーバのURL】";constfetch=require('node-fetch');const{URL,URLSearchParams}=require('url');constHeaders=fetch.Headers;constParser=require('rss-parser');constparser=newParser();(async()=>{varfeed=awaitparser.parseURL(GOOGLE_ALERT_RSS_URL);if(feed.items.length<=0)return;feed.items.forEach(item=>{console.log(item.title);});try{varcreated_at=newDate().getTime();for(vari=0;i<feed.items.length;i++){varitem=feed.items[i];console.log(item);varparam={keyword:GOOGLE_ALERT_SEARCH_KEYWORD,title:item.title,pubDate:item.pubDate,contentSnippet:item.contentSnippet,id:item.id,link:item.link,created_at:created_at};awaitdo_post(base_url+'/linebot-googlealert-push',param);}}catch(error){console.error(error);}})();functiondo_post(url,body){constheaders=newHeaders({"Content-Type":"application/json; charset=utf-8"});returnfetch(newURL(url).toString(),{method:'POST',body:JSON.stringify(body),headers:headers}).then((response)=>{if(!response.ok)throw'status is not 200';returnresponse.json();});}

以下の部分を、各自の環境に合わせて変更します。これから立ち上げるNode.jsサーバのURLです。

【Node.jsサーバのURL】

Node.jsサーバの実装

とりあえず、以下ダウンロードしてNode.jsサーバを立ち上げます。

unzip GoogleAlert-master.zip
cd GoogleAlert-master
mkdir cert
npm install

HTTPSである必要がありまして、SSL証明書をcertフォルダに置きます。フォルダ名は、app.jsを見ればわかります。
起動は以下の通りです。

$ node app.js

RSSフィードされたコンテンツを受信する部分を抜粋します。

server/api/controllers/linebot-googlealert/index.js
exports.handler=async(event,context,callback)=>{if(event.path=='/linebot-googlealert-push'){varbody=JSON.parse(event.body);varsql_query=`SELECT id FROM items WHERE id = '${body.id}'`;const[rows]=awaitdbconn.query(sql_query);varindex=rows.findIndex(rows_item=>body.id==rows_item.id);if(index<0){varsql_insert=`INSERT INTO items (id, keyword, content, pubDate, created_at) VALUES ('${body.id}', '${body.keyword}', '${JSON.stringify(body)}', '${newDate(body.pubDate).getTime()}', ${body.created_at})`;awaitdbconn.query(sql_insert);varsql_select=`SELECT memberId FROM members`;const[rows]=awaitdbconn.query(sql_select);varmessage=app.createSimpleCard(body.title,'キーワード: '+body.keyword,body.contentSnippet,'ブラウザで開く',{type:'uri',uri:body.link});rows.forEach(row=>{app.client.pushMessage(row.memberId,message);});}returnnewResponse({});}else

以下の部分を環境に合わせて変更します。

server/api/controllers/linebot-googlealert/index.js
constDB_HOST='【MySQLサーバのホスト名】';constDB_USER='【MySQLサーバのユーザ名】';constDB_PASSWORD="【MySQLサーバのパスワード】";constDB_PORT=3306;constDB_DATABASE="googlealert";

上記のうち、以下の部分がLINEボットとしてメッセージ送信する部分です。

server/api/controllers/linebot-googlealert/index.js
varmessage=app.createSimpleCard(body.title,'キーワード: '+body.keyword,body.contentSnippet,'ブラウザで開く',{type:'uri',uri:body.link});rows.forEach(row=>{app.client.pushMessage(row.memberId,message);});

以下の部分を環境に合わせて変更します。

server/api/controllers/linebot-googlealert/index.js
constconfig={channelAccessToken:'【LINEボットのチャネルアクセストークン(長期)】',channelSecret:'【LINEボットのチャネルシークレット】',};

上記のシークレットを変更しないと、LINEボットのWebhook設定で、Webhook URLの検証が成功しないです。

コンテンツ取得とLINE通知を試してみる。

それでは、LINEボットを自身のスマホのLINEアプリから登録しましょう。
登録が完了すると、LINEボットがそれを認識し、LINEユーザのユーザIDをデータベースに登録します。

image.png

以下の部分です。

server/api/controllers/linebot-googlealert/index.js
app.follow(async(event,client)=>{varmemberId=(event.source.type=='user')?event.source.userId:event.source.groupId;varsql_insert=`INSERT INTO members (memberId, type) VALUES ('${memberId}', '${event.source.type}')`;awaitdbconn.query(sql_insert);});app.unfollow(async(event,client)=>{varmemberId=event.source.type=='user'?event.source.userId:event.source.groupId;varsql_delete=`DELETE FROM members WHERE memberId = '${memberId}' AND type = '${event.source.type}'`;awaitdbconn.query(sql_delete);});exports.fulfillment=app.lambda();

そして、定期的なコンテンツ取得として用意したcron_googlealert/index1.jsを起動します。

起動に便利な、シェルスクリプトを用意しました。

cron_googlealert/index1.sh
#!/bin/shexport GOOGLE_ALERT_RSS_URL="【GoogleアラートのRSSフィードのURL】"export GOOGLE_ALERT_SEARCH_KEYWORD=" 【Googleアラートに指定したキーワード】"cd /home/XXXX/projects/node/cron_googlealert
/home/XXXX/.nvm/versions/node/v12.19.0/bin/node index.js

環境に合わせて以下を変更します。後者は、「ESP32」でした。

【Googleアラートに指定したキーワード】
【GoogleアラートのRSSフィードのURL】

$cd cron_googlealert
$chmod +x index1.sh
$ ./index1.sh

(まだコンテンツは見つかっていないかもしれません。気長に待ちましょう)

別のキーワードですが以下のようにDBに登録され、LINEにも通知されます。

image.png

同時に、LINEアプリにも通知が届いているかと思います。

image.png

あとは、これをCronで起動すればよいです。例えば、1時間ごとに。

$crontab -e
★以下を入力★
15 * * * * /home/XXXX/projects/node/cron_googlealert/index1.sh

コンテンツ一覧表示するLIFFアプリ

普通のWebページでもよいのですが、せっかくなのでLIFFアプリにして、LINEアプリ内で表示できるようにします。

LIFFアプリの登録には、LINE Developersで作ったMessaging APIのチャネルではなく、LINEログインのチャネルが必要です。

LINE Developers
https://developers.line.biz/console/

登録が完了すると、LIFF IDが割り当たります。

Node.jsサーバの以下の部分を書き換えます。

server/api/controllers/linebot-googlealert/index.js
constLIFF_ID="【LINEのLIFF-ID】";

image.png

画面はこんな感じです。

image.png

HTMLはこんな感じです。

public/googlealert/index.html
<!DOCTYPE html><htmllang="ja"><head><metahttp-equiv="Content-Type"content="text/html; charset=utf-8"/><metahttp-equiv="Content-Security-Policy"content="default-src * data: gap: https://ssl.gstatic.com 'unsafe-eval' 'unsafe-inline'; style-src * 'unsafe-inline'; media-src *; img-src * data: content: blob:;"><metaname="format-detection"content="telephone=no"><metaname="msapplication-tap-highlight"content="no"><metaname="apple-mobile-web-app-capable"content="yes"/><metaname="viewport"content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width"><!-- jQuery (necessary for Bootstrap's JavaScript plugins) --><script src="https://code.jquery.com/jquery-1.12.4.min.js"integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ"crossorigin="anonymous"></script><!-- Latest compiled and minified CSS --><linkrel="stylesheet"href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu"crossorigin="anonymous"><!-- Optional theme --><linkrel="stylesheet"href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css"integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ"crossorigin="anonymous"><!-- Latest compiled and minified JavaScript --><script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd"crossorigin="anonymous"></script><linkrel="stylesheet"href="css/start.css"><script src="js/methods_bootstrap.js"></script><script src="js/components_bootstrap.js"></script><script src="js/vue_utils.js"></script><script src="dist/js/vconsole.min.js"></script><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script><linkrel="stylesheet"href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css"><script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script><title>Google Alert</title></head><body><divid="top"class="container"><buttonclass="btn btn-default pull-right"v-on:click="list_update">更新</button><h1>Google Alert</h1><br><h2>本日のアイテム</h2><divclass="panel panel-default"v-for="(value, index) in item_list_today"><divclass="panel-heading"><h3>{{value.content.title}}</h3></div><divclass="panel-body"><spanclass="pull-left">pubDate: {{new Date(value.pubDate).toLocaleString()}}</span><spanclass="pull-right">keyword: {{value.keyword}}</span><br><br>
            {{value.content.contentSnippet}}
          </div><divclass="panel-footer text-right"><aclass="pull-left"v-bind:href="value.content.link">ブラウザで開く</a>いいね数:{{value.likes}}
            <buttonclass="btn btn-default btn-sm"v-on:click="change_likes(value, true)"></button><buttonclass="btn btn-default btn-sm"v-on:click="change_likes(value, false)"></button></div></div><hr><h2>過去のアイテム</h2><divclass="form-inline"><buttonclass="btn btn-default"v-on:click="list_update_default">今月</button><selectclass="form-control"v-model.number="target_year"v-on:change="list_update"><optionv-for="(value, index) in target_year_list"v-bind:value="value">{{value}}年</option></select><selectclass="form-control"v-model.number="target_month"v-on:change="list_update"><optionvalue="0">通年</option><optionv-for="(value, index) in [1,2,3,4,5,6,7,8,9,10,11,12]"v-bind:value="value">{{value}}月</option></select><selectclass="form-control"v-model.number="has_likes"><optionvalue="1">いいね有のみ</option><optionvalue="0">すべて</option></select></div><tableclass="table table-striped"><thead><tr><th>keyworkd</th><th>title</th><th>pubDate</th><th>いいね</th></tr></thead><tbody><trv-for="(value, index) in item_list"v-if="has_likes==0||value.likes>0"><td>{{value.keyword}}</td><td><av-bind:href="value.content.link">{{value.content.title}}</a></td><td>{{new Date(value.pubDate).toLocaleString()}}</td><td>{{value.likes}}
                    <buttonclass="btn btn-default btn-xs"v-on:click="change_likes(value, true)"></button><buttonclass="btn btn-default btn-xs"v-on:click="change_likes(value, false)"></button></td></tr></tbody></table><!-- for progress-dialog --><progress-dialogv-bind:title="progress_title"></progress-dialog></div><script src="js/start.js"></script></body>

Javascriptはこんな感じです。

public/googlealert/js/start.js
'use strict';//var vConsole = new VConsole();constbase_url="【Node.jsサーバのURL】";varvue_options={el:"#top",data:{progress_title:'',// for progress-dialogitem_list_today:[],item_list:[],target_month:0,target_year:0,target_year_list:[],has_likes:0},computed:{},methods:{list_update_default:asyncfunction(){this.target_month=this.now.getMonth()+1;this.target_year=this.now.getFullYear();returnthis.list_update();},list_update_today:asyncfunction(today){varparam={};varlist=awaitdo_post(base_url+"/linebot-googlealert-list",param);for(vari=0;i<list.length;i++)list[i].content=JSON.parse(list[i].content);this.item_list_today=list;},list_update:asyncfunction(){varparam={year:this.target_year,month:this.target_month,};varlist=awaitdo_post(base_url+"/linebot-googlealert-list",param);for(vari=0;i<list.length;i++)list[i].content=JSON.parse(list[i].content);this.item_list=list;},change_likes:asyncfunction(target,increment){console.log(target);vartarget_likes=(increment)?(target.likes+1):(target.likes-1);if(target_likes<0)target_likes=0;varparam={id:target.id,likes:target_likes};awaitdo_post(base_url+"/linebot-googlealert-likes",param);vart1=this.item_list.find(item=>item.id==target.id);if(t1)this.$set(t1,"likes",target_likes);vart2=this.item_list_today.find(item=>item.id==target.id);if(t2)this.$set(t2,"likes",target_likes);},},created:function(){},mounted:asyncfunction(){proc_load();this.now=newDate();for(vari=0;i<5;i++)this.target_year_list.push(this.now.getFullYear()-i);this.target_month=this.now.getMonth()+1;this.target_year=this.now.getFullYear();this.list_update_today();this.list_update();}};vue_add_methods(vue_options,methods_bootstrap);vue_add_components(vue_options,components_bootstrap);varvue=newVue(vue_options);functiondo_post(url,body){constheaders=newHeaders({"Content-Type":"application/json; charset=utf-8"});returnfetch(newURL(url).toString(),{method:'POST',body:JSON.stringify(body),headers:headers}).then((response)=>{if(!response.ok)throw'status is not 200';returnresponse.json();});}

Node.jsサーバ側では、それにこたえられるように、以下のエンドポイントを用意しています。
一覧の取得といいねカウントです。一覧の取得では、本日のコンテンツ、月ごとのコンテンツ、年ごとのコンテンツ、のようにフィルタリングして返しています。

server/api/controllers/linebot-googlealert/index.js
if(event.path=='/linebot-googlealert-list'){varbody=JSON.parse(event.body);varstartTime;varendTime;if(!body.year||!body.month){vartoday=newDate();today.setHours(0,0,0,0);startTime=today.getTime();vartomorrow=newDate(today);tomorrow.setDate(today.getDate()+1);endTime=tomorrow.getTime();}elseif(body.year&&body.month==0){varthisYear=newDate();thisMonth.setFullYear(body.year);thisMonth.setMonth(0);thisMonth.setDate(1);thisMonth.setHours(0,0,0,0);startTime=thisYear.getTime();varnextYear=newDate(thisYear);nextMonth.setFullYear(thisYear.getFullYear()+1);endTime=nextYear.getTime();}else{varthisMonth=newDate();thisMonth.setFullYear(body.year);thisMonth.setMonth(body.month-1);thisMonth.setDate(1);thisMonth.setHours(0,0,0,0);startTime=thisMonth.getTime();varnextMonth=newDate(thisMonth);nextMonth.setMonth(thisMonth.getMonth()+1);endTime=nextMonth.getTime();}varsql_select=`SELECT * FROM items WHERE pubDate >= ${startTime} AND pubDate < ${endTime} ORDER BY pubDate DESC`;const[rows]=awaitdbconn.query(sql_select);returnnewResponse(rows);}elseif(event.path=='/linebot-googlealert-likes'){varbody=JSON.parse(event.body);varsql_update=`UPDATE items SET likes = ${body.likes} WHERE id = '${body.id}'`;awaitdbconn.query(sql_update);returnnewResponse({});}

以上


Viewing all articles
Browse latest Browse all 8695

Trending Articles