自分が投稿した記事に、どなたかがLGTM(いいね)してくれると、やっぱりうれしくて、書いたかいがあったなあと、元気をもらえます。
そこで、30分ごとに、LGTM(いいね)数とフォロワー数をウォッチして、増えていたら、LINEに通知と、自宅にあるGoogleHomeスマートスピーカにしゃべってもらおうと思います。
コロナの影響でずっと在宅勤務なので、ちょっとしたアクセントにもなります。
ちなみに、GoogleHomeスマートスピーカは、これから立ち上げるcron実行PCと同じネットワークにある必要があります。
#通知の準備
LINE通知には、LINE Notifyを使いました。
そのためには、パーソナルアクセストークンが必要です。
以下にアクセスして、アクセストークンの発行(開発者向け)をします。
https://notify-bot.line.me/my/
グループに発行してもよいですし、1:1でLINE Notifyから通知を受け取る でもよいです。
そうすると、43文字程度のパーソナルアクセストークンが発行されます。あとで、使います。
Google Homeスマートスピーカからしゃべってもらうためには、スマートスピーカのIPアドレスが必要です。
スマートスピーカと連携済みのAndroidのGoogle Homeアプリから、デバイス設定を選ぶと、一番下の情報 というところに、IPアドレスがありますのでメモっておきます。
#現在のLGTM(いいね)数とフォロワー数の取得
Qiita APIを見てみたのですが、記事ごとのLGTM(いいね)数は取得できますが、合計数をとれるようなAPIは見当たりませんでした。
そこで、QiitaのWebページからスクレーピングします。
スクレーピングには、npmのcheerioを使いました。
また、Webページの取得には、node-fetchを使いました。
npm install cheerio
npm install node-fetch
class=” UserCounterList__UserCounterItem-sc-******” のような感じのHTMLエレメントがあるので、その近辺を探しています。
(2020/10/10 修正)
と思ったら、取得したHTMLのmeta情報に、投稿数・LGTM数・フォロワー数がありました。なのでそこのスクレイピングに変更しました。
ソースはこんな感じ。
const cheerio = require('cheerio');
const fetch = require('node-fetch');
const { URL, URLSearchParams } = require('url');
async function get_qiita_state(userid){
return do_get('https://qiita.com/' + userid, {})
.then((text) =>{
const $ = cheerio.load(text);
/*
var state = {};
var root = $("[class^='UserCounterList__UserCounterItem-sc-']");
root.each((i, elem) =>{
var label = $(elem).children("[class^='UserCounterList__UserCounterItemLabel-sc-']").text();
var num = parseInt($(elem).children("[class^='UserCounterList__UserCounterItemCount-sc-']").text());
state[label] = num;
});
*/
var description = $("meta[name='description']").attr("content");
var description_list = description.split(' ');
var state = {
Items: parseInt(description_list[1]),
Contributions: parseInt(description_list[3]),
Followers: parseInt(description_list[5]),
};
// Posts, Contributions, Followers
console.log(state);
return state;
});
}
function do_get(url, qs) {
var params = new URLSearchParams(qs);
var url2 = new URL(url);
url2.search = params;
return fetch(url2.toString(), {
method: 'GET',
})
.then((response) => {
if (!response.ok)
throw 'status is not 200';
return response.text();
});
}
関数get_qiita_state()の呼び出しにより、
{ Posts : 投稿数, Contributions : LGTM数, Followers : フォロワー数 }
が返ってきます。
#LINE通知する
関数line_notifyに、通知したいメッセージと、さきほどのパーソナルアクセストークンを指定します。
const fetch = require('node-fetch');
const { URL, URLSearchParams } = require('url');
const Headers = fetch.Headers;
function line_notify(message, token){
var params = {
message: message
};
return do_post_urlencoded_token('https://notify-api.line.me/api/notify', params, token);
}
function do_post_urlencoded_token(url, params, token) {
const headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization' : 'Bearer ' + token });
var body = new URLSearchParams(params);
return fetch(new URL(url).toString(), {
method: 'POST',
body: body,
headers: headers
})
.then((response) => {
if (!response.ok)
throw 'status is not 200';
return response.json();
})
}
#GoogleHomeスマートスピーカにしゃべらせる
関数homeSpeechに、メッセージと、スマートスピーカのIPアドレスを指定すると、数秒後にスマートスピーカがしゃべります。
以下のnpmモジュールを使います。
・castv2-client
・google-tts-api
const Client = require('castv2-client').Client;
const MediaReceiver = require('castv2-client').DefaultMediaReceiver;
const googletts = require('google-tts-api');
function homeSpeech(text, host) {
return googletts(text, 'ja-JP', 1)
.then(function(url) {
return playUrl(url, host);
});
}
function playUrl(url, host) {
return new Promise((resolve, reject) => {
var client = new Client();
client.connect(host, () => {
client.launch(MediaReceiver, (err, player) => {
if( err ){
console.log('Error: %s', err.message);
client.close();
return reject(err);
}
var media = {
contentId: url,
contentType: 'audio/mp3',
streamType: 'BUFFERED'
};
player.load(media, { autoplay: true }, (err, status) =>{
client.close();
resolve('Device notified');
});
});
});
client.on('error', (err) =>{
console.log('Error: %s', err.message);
client.close();
reject(err);
});
})
}
本体
以上の関数を呼び出すメイン部です。
いくつか、ちょっと特殊処理を入れています。
LGTM数とフォロワー数は、以前のチェック時からの増加をみて、LINEとスマートスピーカから通知をしています。以前のチェック時の数は、ファイルに保存しています。
ファイル名として、QIITA_STATE_FILE を指定し、読み出しと更新用の関数をstate_read、state_updateを用意しました。
また、夜中に通知されても困るので、現在時刻を見て、NOTIFY_RANGE_HOURS の配列に示した時間の時だけ通知するようにしました。
const fs = require('fs');
const QIITA_USERID = process.env.QIITA_USERID || '【チェックしたいQiitaユーザ名】';
const GOOGLE_DEVICE_ADDRESS = process.env.DEVICE_ADDRESS || '【GoogleHomeスマートスピーカwのIPアドレス】';
const QIITA_STATE_FILE = process.env.QIITA_STATE_FILE || './data/qiita_state.json';
const LINE_PERSONAL_ACCESS_TOKEN = process.env.LINE_PERSONAL_ACCESS_TOKEN || '【LINE Notifyのパーソナルアクセストークン】';
const NOTIFY_RANGE_HOURS = process.env.NOTIFY_RANGE || [ 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0, 1 ]
get_qiita_state(QIITA_USERID)
.then(async (state) =>{
var prev_state = state_read();
var date = new Date();
var hour = date.getHours();
if( NOTIFY_RANGE_HOURS.includes(hour) ){
state_update(state);
var message = "";
if( state.Contributions > prev_state.Contributions )
message += 'いいねが' + (state.Contributions - prev_state.Contributions ) + '件増えたよ。';
if( state.Followers > prev_state.Followers ){
if( message != "" )
message += "\n";
message += 'フォロワーが' + (state.Followers - prev_state.Followers ) + '人増えたよ。';
}
if( message != "" ){
await homeSpeech(message, GOOGLE_DEVICE_ADDRESS);
await line_notify("\n" + message, LINE_PERSONAL_ACCESS_TOKEN);
}else{
console.log('no change');
}
}
})
.catch(error =>{
console.log(error);
});
function state_read(){
try{
return JSON.parse(fs.readFileSync(QIITA_STATE_FILE, 'utf8'));
}catch(error){
return {};
}
}
function state_update(state){
fs.writeFileSync(QIITA_STATE_FILE, JSON.stringify(state), 'utf8');
}
ということで、以下を環境に合わせて変更してください。
・QIITA_USERID:チェックしたいQiitaユーザ名
・GOOGLE_DEVICE_ADDRESS:GoogleHomeスマートスピーカwのIPアドレス
・QIITA_STATE_FILE:現在のLGTM数の保存先ファイル名
・LINE_PERSONAL_ACCESS_TOKEN:LINE Notifyのパーソナルアクセストークン
・NOTIFY_RANGE_HOURS:通知されてもよい時間(時)の配列
#cron化
シェルスクリプトを作成します。
以下例です。
vi index.sh
chmod +x index.sh
#!/bin/sh
cd /home/XXXXX/projects/node/cron_lgtm
/home/XXXXX/.nvm/versions/node/v8.12.0/bin/node index.js
あとはcronに登録するだけです。以下は30分ごとにチェックする場合です。
crontab -e
0,30 * * * * /home/XXXXX/projects/node/cron_lgtm/index.sh
以上