Kanto transit chatbot với Node.js và mongoDb

Chatbotlà khái niệm không mới nhưng gần đây trở thành hot trend khi kết hợp cùng machine learningAI. Mình sẽ có một bài viết cụ thể hơn về kỹ thuật cũng như những cơ hội mà chatbot đem lại trong một bài khác. Trong khuôn khổ bài viết này xin phép mỳ ăn liền chatbot đơn giản mà mình mới làm trong một buổi tối gió và lạnh ở Tokyo.

Lấy cảm hứng từ việc ngày thứ 2 vừa rồi Tokyo có trận tuyết khủng bố nhất 30 năm trở lại đây và chiến thằng của U23 Việt Nam (=))) mình có xây dựng một chatbot đơn giản để gửi tin nhắn cảnh báo mỗi khi tàu điện trên các line (vùng Kanto) mình quan tâm nếu line có thay đổi trạng thái.

Đây cũng được hi vọng là viên gạch đầu tiên cho một chatbot hoàn chỉnh hơn với nhiều chức năng hỗ trợ cá nhân (auto dự báo thời tiết, viết mail report, xem giá cổ phiếu, bitcoin, rss,...) mà mình (nếu có thời gian và không lười) định sẽ xây dựng.

Ý tưởng

  • Flowchart cho chatbot đơn giản này.

flow-chart

  • Khi người dùng nhập tên line, chatbot sẽ lấy thông tin về line đó, gửi trả lại thông tin và từ đó người dùng sẽ subscribe line này, nếu dữ liệu có thay đổi chatbot sẽ gửi thông báo tới messenger của khách hàng qua việc thực hiện 1 cronjob để liên tục check dữ liệu.

  • Mình chọn facebook messenger platform cùng node.jsmongodb để mọi thứ trở nên đơn giản hơn =))

Ok, đã xong phần ý tưởng và công cụ, bắt tay vào làm.

Dữ liệu đầu vào

  • Đầu tiên chúng ta cần có một nguồn dữ liệu về traffic tàu điện ở vùng Kanto. Mình có thử search traffic API của Tokyo nhưng không tìm được nên mình chọn giải pháp sử dụng dữ liệu từ Yahoo transit. Như vậy cần một html parser để có thể lấy dữ liệu phục vụ cho chatbot.

Facebook chatbot

  • Chúng ta sẽ tạo một chatbot sử dụng Facebook messenger platform, sử dụng test-drive để mọi thứ trở nên đơn giản.

  • Các bạn có thể làm theo đến bước 5. Facebook có recommend sử dụng localtunnel, tuy nhiên mình thấy localtunnel thiếu ổn định và bị delay khá nhiều nên mình chuyển qua dùng một công cụ khác là ngrok. Các bạn có thể down tại đây, giải nén ra và dùng liền.

  • Sau khi chạy server (node app.js) chúng ta sẽ chạy lệnh

    ./ngrok http 5000
    

    cổng local 5000 đã có thể kết nối với facebook thông qua ngrok service.

  • Copy link tại giao diện ngrok (https method) và paste vào facebook test-drive, đến đây chúng ta đã công đoạn setup chatbot.

  • Bây giờ bắt đầu vọc code của fb, dữ liệu đầu vào sẽ là text (message) nên cần sửa hàm receivedMessage để trả về kết quả mong muốn. Bỏ qua các text đặc biệt, trường hợp default của vòng lặp switch chính là phần cần tuỳ biến.

app.js

  default:
        parseInfo(senderID, subcribeLine, msgFlg) // senderID: fbID của người dùng, subcribeLine: tên line người dùng nhập vào, msgFlg: 0:cronJob, 1: tin nhắn của người dùng.

Input của người dùng được lấy tại
app.js

  var messageText = message.text;

và chatbot cần phải tìm ra line chính xác từ đống này

và chỗ này nữa

Yahoo transit web parser

  • 'nodejs html parser' trên googlecheerio (info) là lựa chọn có vẻ tốt nhất, tiện thể import thêm mongodb và để lưu dữ liệu và node-cron(info) để tạo cron-job cho chatbot.
  npm install cheerio
  npm install mongo
  npm install node-cron
  npm install cron

app.js

  var mongoClient = require('mongodb').MongoClient;
  var url = "mongodb://localhost:27017/";
  var cheerio = require('cheerio');
  var CronJob = require('cron').CronJob;
  • Nắng đã có mũ, mưa đã có ô, không biết parse thế nào để ra cái mình cần đã có selectorgadget(info).

Với extension này mình tìm ra được '#mdAreaMajorLine td:nth-child(1)' là css selector của mục cần lấy, inspect thêm 1 lần thì mình đã lấy đầy đủ các thông tin sau:

  1. tên line (lineName) tại children[0].children[0].data
  2. link info page của line children[0].attribs.href
  3. tình trạng line (lineStatus) tại $('dt')[0].children[1].data hoặc $('dt')[0].children[2].data
  4. thông tin line (lineTrouble) tại $('#mdServiceStatus p')[0].children[0].data
  • Bóc tách được thông tin rồi chúng ta sẽ viết hàm parseInfo để lấy tất cả thông tin của line theo yêu cầu của người dùng.

app.js

      function parseInfo(senderID, subcribeLine , msgFlg) {
          request('https://transit.yahoo.co.jp/traininfo/area/4/', function (error, response, html) {
              if (!error && response.statusCode == 200) {
                  var found = false;
                  var $ = cheerio.load(html);
                  $('td:nth-child(1)').each(function (idx, elem) {
                      var lineName = elem.children[0].children[0].data;
                      if (lineName.includes(subcribeLine)) {
                          found = true;
                          request(elem.children[0].attribs.href, function (error, response, html) {
                              if (!error && response.statusCode == 200) {
                                  var $ = cheerio.load(html);
                                  var lineStatus = $('dt')[0].children[1].data || $('dt')[0].children[2].data;
                                  var lineTrouble = $('#mdServiceStatus p')[0].children[0].data
                                  handleRequest(senderID, lineName, lineStatus, lineTrouble, msgFlg);
                              }
                          });
                      }
                  });
                  if (!found){
                      sendTextMessage(senderID, subcribeLine + "の情報がありません。");
                  }
              }
          });
      }
  • Tiện tay viết luôn hàm xử lý việc gửi tin nhắn reply cho người dùng

app.js

    function handleRequest(senderID, lineName, lineStatus, lineTrouble, msgFlg){
        mongoClient.connect(url, function(err, db) {
            if (err) throw err;
            var dbo = db.db("chatbot");
            var query = { userId: senderID, subcribeLine: lineName };

            dbo.collection("rosen").find(query).toArray(function(err, result) {
                if (err) throw err;
                if (result[0] && result[0].lineStatus == lineStatus && result[0].lineTrouble == lineTrouble) {
                    // line info did not change
                    console.log("Did not change");
                    // if user send the msg (nếu là người dùng gửi thì sẽ gửi lại trạng thái dù không thay đổi)
                    if (msgFlg == 1){
                        var msg = '[' + result[0].subcribeLine.toString() + ']: ' + result[0].lineStatus.toString() + '\n' + result[0].lineTrouble.toString();
                        sendTextMessage(senderID, msg);
                    }
                } else if (result[0]) {
                    // line info has been changed
                    var newVal = { $set: { lineStatus: lineStatus, lineTrouble: lineTrouble } };
                    dbo.collection("rosen").updateOne(query, newVal, function(err, res) {
                        if (err) throw err;
                        console.log(senderID + lineName + " updated");
                        db.close();
                    });
                    var msg = '[' + lineName.toString() + ']: ' + lineStatus.toString() + '\n' + lineTrouble.toString();
                    sendTextMessage(senderID, msg);
                } else {
                    // user subcribe to new line
                    var myobj = { userId: senderID, subcribeLine: lineName, lineStatus: lineStatus, lineTrouble: lineTrouble };
                    dbo.collection("rosen").insertOne(myobj, function(err, res) {
                        if (err) throw err;
                        console.log(senderID + lineName + "inserted");
                    });
                    var msg = '[' + lineName.toString() + ']: ' + lineStatus.toString() + '\n' + lineTrouble.toString();
                    sendTextMessage(senderID, msg);
                }
                db.close();
            });
        });
    }
  • Test thử nào

Cron-job

  • Việc cuối cùng chúng ta cần tạo cronjob để chatbot của chúng ta (bản chất là node.js app) check trạng thái liên tục (mỗi phút một lần) và nếu có thay đổi sẽ gửi thông báo đến cho người dùng

app.js

    new CronJob('1 * * * * *', function() {
        mongoClient.connect(url, function(err, db) {
            if (err) throw err;
            var dbo = db.db("chatbot");
            dbo.collection("rosen").find({}).toArray(function(err, result) {
                if (err) throw err;
                result.forEach(function (res) {
                    parseInfo(res.userId, res.subcribeLine, 0);
                });
                db.close();
            });
        });
    }, null, true, 'Asia/Tokyo');
  • Test hoạt động lần cuối

Vậy là chúng ta đã hoàn thành xong một chatbot với chức năng tự động báo cáo tình trạng các line. Hiện tại thì chức năng của chatbot giao thông này vẫn còn rất đơn giản và ở mức tạm dùng được và đã được mình deploy tại https://chatbot.qmau.me . Một số chức năng đơn giản như xem và xoá subscribeLine sẽ được mình hoàn thiện nốt trong thời gian tới.

Để lại một likecomment để buổi tối của mình không lãng phí nhé. :3

Source code của project có tại đây. Feel free to use!

Ps: Tầm này chỉ muốn ở Hà Nội sang chơi với cháu rồi đi ăn bún đậu cùng đồng bào thôi :(

Refs

Link bài viết gốc có tại blog cá nhân :smile:
https://qmau.me/blog/post/kanto-transit-chatbot-voi-yahoo-transit-va-node-js

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Mầu Hà Quang

4 bài viết.
60 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
117 12
Lập trình viên sau khi phát triển xong một website, website chạy ổn định, không bug thì coi như đã hoàn thành. Câu chuyện của năm 2018 có đơn giả...
Mầu Hà Quang viết 6 tháng trước
117 12
White
26 8
Khi phát triển các dịch vụ web (web services), các lập trình viên cần có một quy ước chuẩn để các thành phần trong hệ thống có thể giao tiếp với nh...
Mầu Hà Quang viết 5 tháng trước
26 8
White
8 2
Tiếp tục (Link), liệu REST có vượt trội so với SOAP theo như ý kiến của tác giả dưới đây? (Ảnh) Trong bài viết này mình sẽ so sánh trực tiếp RES...
Mầu Hà Quang viết 4 tháng trước
8 2
Bài viết liên quan
White
41 7
Giới thiệu MongoDB là một giải pháp nosql database. Data được lưu ở dạng các bson document. Hỗ trợ vertical scaling và horizontal scaling, dynamic...
manhdung viết hơn 3 năm trước
41 7
White
1 0
Lâu lâu không động vào nodejs không biết mấy ông tool tiếc này đi đâu về đâu rồi. Trước đây thì mình vẫn có thể dùng istanbul với mocha đơn giản th...
Hoàng Duy viết 2 năm trước
1 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
4 bài viết.
60 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!