こんにちは、開発部のレンです。
日本人たちは日本語が上手いですね。
自分の日本語能力を改善したいですが、暇の時でほぼゲームをやっているので、なかなか勉強する時間がなかったです。草
毎日電車で通勤してて、電車の中でゲームできないので、その時間ちょこちょこ勉強するようにしたいと思いました。
そのため、Messenger botを作ってみました。
Facebook Messenger でbotに声をかけると、新しい日本語の文法を返ってくれる感じです。
デモ:
botの作り方を説明させていただきます
準備
- Facebook developer account Facebook for Developers
- heroku account heroku
- Nodejsのナレッジ
- Headless Chrome: puppeteerのナレッジ puppeteer
全体の流れ
facebook messenger botの設定流れ
facebookのドキュメントはすごくわかりやすいので、上の6つのステップをすすめていただくと、botが作成できます。
Botの挙動は「Step 3. Webhookを設定する」ステップで決まるので、
この記事は「Webhookを設定する」ステップを詳しく話したいと思います。
Webhook設定ステップ
新しいNode.jsプロジェクトの作成
mkdir messenger-webhook // プロジェクトディレクトリを作成
cd messenger-webhook // 新しいディレクトリに移動
touch index.js // 空のindex.jsファイルを作成。
npm init // package.jsonを作成。すべての質問に対してデフォルトをそのまま使用します。
npm install express body-parser --save // express.js httpサーバーフレームワークモジュールをインストールし、// それらをpackage.jsonファイルのdependenciesセクションに追加。
npm install puppeteer --save // web crawlerを実装するため、pupperteerを追加
注意
日本語がうまくcrawlできなかったら、 fonts-ipafont-gothic fonts-ipafont-mincho
もインストールしてください
apt-get install fonts-ipafont-gothic fonts-ipafont-mincho
Webhookエンドポイントの追加
エンドユーザーが送ったメッセージはここに届きます
// Creates the endpoint for our webhook
app.post('/webhook', (req, res) => {
let body = req.body;
// Checks this is an event from a page subscription
if (body.object === 'page') {
// Iterates over each entry - there may be multiple if batched
body.entry.forEach(function(entry) {
// Gets the message. entry.messaging is an array, but
// will only ever contain one message, so we get index 0
let webhook_event = entry.messaging[0];
console.log(webhook_event);
});
// Returns a '200 OK' response to all requests
res.status(200).send('EVENT_RECEIVED');
} else {
// Returns a '404 Not Found' if event is not from a page subscription
res.sendStatus(404);
}
});
webhook_eventの中に、エンドユーザーが入力したテキストがあります。
webhook_event例:
{
sender: { id: '247802069227****' },
recipient: { id: '48593652514****' },
timestamp: 1577172836757,
message:
{ mid:
'm_UcB2ELDcx8frRDMMDl92x9b5ZSaR20LWFnrR1GAlt0DE48CLDlsdcVbC8y8AGPixdWO2SbBqBIQ5N44f****',
text: 'Hello world' }
}
日本語のBotの場合、ユーザーが next
を入力していただくときだけに日本語を返ってくるので、ロジックは以下のようになります
const crawler = require('./crawler');
app.post('/webhook', (req, res) => {
let body = req.body;
if (body.object === 'page') {
body.entry.forEach(function(entry) {
let webhook_event = entry.messaging[0];
console.log(webhook_event);
// Get the sender PSID
let sender_psid = webhook_event.sender.id;
console.log('Sender PSID: ' + sender_psid);
// Handles messages events
if (webhook_event.message) {
handleMessage(sender_psid, webhook_event.message); // ここです!
}
});
}
});
// Handles messages events
function handleMessage(sender_psid, received_message) {
let response;
// Check if the message contains text
if (!received_message.text) {
return;
}
if (received_message.text.toLowerCase() != "next") {
callSendAPI(sender_psid, {"text": "Type [next] to get a new grammar."});
}
else if (received_message.text && received_message.text.toLowerCase() == "next") {
// 'next'の場合、crawlerを走ります
(async () => {
crawler.crawl_grammar(sender_psid, callSendAPI)
})();
}
}
https://github.com/DucNguyenVan/bikae/blob/master/index.js
puppeteerを利用し、crawler.crawl_grammarを作る
async function crawl_grammar(sender_psid, callback){
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
if (!interceptedRequest.url().includes('bikae.net'))
interceptedRequest.abort();
else
interceptedRequest.continue();
});
// 日本語の文法があるページにアクセスする
await page.goto('https://bikae.net/ngu-phap/tong-hop-ngu-phap-n2/');
const grammar_href = await page.evaluate(() => {
var grammar_selectors = '.entry-content span a';
// 文法該当リンクを全部取得する
var sum = document.querySelectorAll(grammar_selectors).length
const rd_idx = (Math.random(0,sum/100)*100).toFixed();
// 各文法リンクの中に、一つの文法をrandomで選ぶ、
return document.querySelectorAll(grammar_selectors)[rd_idx].href;
})
// 選ばれたリンクにアクセスし、タイトルをGETする
await page.goto(grammar_href);
const grammar = await page.evaluate(() => {
return document.querySelector('.entry-header .entry-title').textContent
})
await browser.close();
// タイトル - 文法の該当リンクを返す:
// ~ に基づいて https://bikae.net/ngu-phap/ngu-phap-n2-ni-motoduite/
response = {
"text": `${grammar} \n ${grammar_href}`
}
callback(sender_psid, response)
};
https://github.com/DucNguyenVan/bikae/blob/master/crawler.js
残りやりたいこと
next
を入力しないで、定期時間で新しい日本語の文法を送る機能を実装しようと思ってましたが、
facebookのbussiness planではないとできないようですので、断念しました
最後
こちらのbotについて、source codeが公開しましたので、
使ってみたい方はどうぞ