This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 5 years have passed since last update.

ジーズアカデミー Node.js授業 3日目

Last updated at Posted at 2017-07-21
作成: Lab3期 2017/7/21

ちなみにLAB2期の3日目はこんな感じだったみたい

http://qiita.com/n0bisuke/private/f9061fef456eb1f0608a

アンケート結果

左: 1回目 <-> 右: 2回目

コードのレビューもあって楽しかったです。
昨夜、復習、予習をしておいたので、ある程度ついていくことはできました。ES2015の授業の時はわからなかったことが、今になって色々わかってきたので嬉しいです。来週が楽しみです。ありがとうございました。
nodeに興味持てました!色々作ってみたくなりました!
次回はボット以外でお願いします!笑
ハンズオン+解説の組み合わせは楽しい!
面白かった。ソースコードのレビューとか講評とかあると嬉しいです。
少しコード自体の理解ができてない気がします。
最後の方スピードが上がって付いていくのギリギリでした!

宿題の答え合わせ

リッチテキスト利用+応用課題(expressとaxios)

npm i --save express body-parser axios
app.js
'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');

const app = express()
app.use(bodyParser());

const PORT = process.env.PORT || 3000;
const PAGE_ACCESS_TOKEN = 'とーくん';
const BASEURL = 'https://graph.facebook.com';
const PATH = '/v2.6/me/messages?access_token='+PAGE_ACCESS_TOKEN;

const sendButtonMessage = (recipientId, messageText) => {
    const postDataStr = JSON.stringify({
        recipient: { id: recipientId },
        message: { 
            attachment: {
                type: "template",
                payload: {
                    template_type: "button",
                    text: "What do you want to do next?",
                    buttons: [{
                        type: "web_url",
                        url: "https://petersapparel.parseapp.com",
                        title: "Show Website"
                    },{
                        type: "postback",
                        title: "Start Chatting",
                        payload: "USER_DEFINED_PAYLOAD"
                    }]
                }
            }
        }
    });

    return axios.request({
        method: 'post',
        baseURL: BASEURL,
        headers: {
            'Content-Type': 'application/json; charset=UTF-8',
            'Content-Length': Buffer.byteLength(postDataStr),
            'Accept': 'application/json'
        },
        url: PATH,
        data: postDataStr
    });
}

app.get('/',(req, res) => res.send(text));

// POST /login gets urlencoded bodies 
app.post('/', (req, res) => {
    if (!req.body) return;
    const data = req.body;
    const event = data.entry[0].messaging[0];
    if (data.object === 'page' && event.message) {
        //メッセージ受信時の処理
        const senderID = event.sender.id;
        const messageText = event.message.text;
        if(messageText === ''){
            console.log('メッセージが取得できない');
            return;
        }
        console.log("Message data: ", event.message);
        sendButtonMessage(senderID, messageText)
        .then((body)=>{
            console.log('返信完了');
            console.log(body);
        }).catch((error) => {
            console.log(error.data.error);
            console.log(error.data);
        });
    }else{
        console.log("Webhook received unknown event: ", event);
    }

    res.send('success');
})

app.listen(3000);

こんな感じ

非同期処理の復習

↑のコードに天気機能を組み合わせてみましょう

app.js
'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');

const app = express()
app.use(bodyParser());

const PORT = process.env.PORT || 3000;
const PAGE_ACCESS_TOKEN = 'とーくん';
const BASEURL = 'https://graph.facebook.com';
const PATH = '/v2.6/me/messages?access_token='+PAGE_ACCESS_TOKEN;

const sendButtonMessage = (recipientId, messageText) => {
    const postDataStr = JSON.stringify({
        recipient: { id: recipientId },
        message: { 
            attachment: {
                type: "template",
                payload: {
                    template_type: "button",
                    text: messageText,
                    buttons: [{
                        type: "web_url",
                        url: "https://petersapparel.parseapp.com",
                        title: "Show Website"
                    },{
                        type: "postback",
                        title: "Start Chatting",
                        payload: "USER_DEFINED_PAYLOAD"
                    }]
                }
            }
        }
    });

    return axios.request({
        method: 'post',
        baseURL: BASEURL,
        headers: {
            'Content-Type': 'application/json; charset=UTF-8',
            'Content-Length': Buffer.byteLength(postDataStr),
            'Accept': 'application/json'
        },
        url: PATH,
        data: postDataStr
    });
}

app.get('/',(req, res) => res.send(text));

// POST /login gets urlencoded bodies 
app.post('/', (req, res) => {
    if (!req.body) return;
    const data = req.body;
    const event = data.entry[0].messaging[0];
    if (data.object === 'page' && event.message) {
        //メッセージ受信時の処理
        const senderID = event.sender.id;
        const messageText = event.message.text;
        if(messageText === ''){
            console.log('メッセージが取得できない');
            return;
        }

        if(messageText === '天気'){
            const URL = 'http://weather.livedoor.com/forecast/webservice/json/v1?city=400040';
            axios.get(URL)
            .then((response) => {
                console.log(response.data.description.text);
                console.log("Message data: ", event.message);
                sendButtonMessage(senderID, response.data.description.text)
                .then((body)=>{
                    console.log('返信完了');
                    console.log(body);
                }).catch((error) => {
                    console.log(error.data.error);
                    console.log(error.data);
                });
            })
            .catch((error) => {
                console.log(error);
            });
        }

    }else{
        console.log("Webhook received unknown event: ", event);
    }

    res.send('success');
})

app.listen(3000);

変更点は天気というテキストを受け取ったらweather.livedoor.comにリクエストを飛ばして、その結果をBOTがしゃべるテキストにいれこんでいる点

コールバック地獄を見直す

もとのコード

app.js
省略

        if(messageText === '天気'){
            const URL = 'http://weather.livedoor.com/forecast/webservice/json/v1?city=400040';
            axios.get(URL)
            .then((response) => {
                console.log(response.data.description.text);
                console.log("Message data: ", event.message);
                sendButtonMessage(senderID, response.data.description.text)
                .then((body)=>{
                    console.log('返信完了');
                    console.log(body);
                }).catch((error) => {
                    console.log(error.data.error);
                    console.log(error.data);
                });
            })
            .catch((error) => {
                console.log(error);
            });
        }
省略

Promiseでthen繋ぎ

app.js
省略

        if(messageText === '天気'){
            const URL = 'http://weather.livedoor.com/forecast/webservice/json/v1?city=400040';
            axios.get(URL)
            .then((response) => {
                return sendButtonMessage(senderID, response.data.description.text)
            })
            .then((body)=>{
                console.log('返信完了', body.data);
            }).catch((error) => {
                console.log(error.data);
            });
        }

省略

Async/Await

app.js
省略

        if(messageText === '天気'){
            (async () => {
                const URL = 'http://weather.livedoor.com/forecast/webservice/json/v1?city=400040';
                const response = await axios.get(URL);
                const body = await sendButtonMessage(senderID, response.data.description.text);
                console.log('返信完了', body.data); 
            })()
            .catch((error) => {
                console.log(error.data);
            });
        }

省略

Promise化してくれるやつ

http://2ality.com/2017/05/util-promisify.html

リアルタイム通信/Socket.io

リアルタイム通信技術

  • ポーリング
  • WebSocket
  • WebRTC
  • MQTT

いまさら聞けないWebSocketとSocket.IOの基礎知識&インストール (1/2)

Socket.io

  • WebSocketを使いやすく
  • ひと世代築いたライブラリ
  • クロスブラウザ
  • ルーム機能などの拡張

触ってみよう/チャット作り

mkdir mychat
cd mychat
npm init -y
npm i --save socket.io express

1. app.jsの作成

app.js
'use strict';

const app = require('express')();
const http = require('http').Server(app);
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('<h1>Hello world</h1>');
});

http.listen(PORT, () => {
  console.log(`listening on *:${PORT}`);
});

2. 確認 localhost:3000


3. index.htmlを読み込むようにする

変更

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

4. index.htmlの作成

index.html
<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
  </body>
</html>

5. app.jsにsocket.ioのコードを追加

app.js
'use strict';

const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket) => {
  console.log('a user connected');
});

http.listen(PORT, () => {
  console.log(`listening on *:${PORT}`);
});

6. index.htmlにコード追記

</body>の直前に追記しましょう。

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io();
</script>

7. クライアント/サーバーの接続確認

ここで一旦リアルタイム通信っぽいのができるはずです。

8. 接続が切れた時の処理

io.on('connection', (socket) => {
  console.log('a user connected');
  socket.on('disconnect', () => {
    console.log('user disconnected');
  });
});

9. フォームから送信時の処理

index.htmlを編集

    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
    <script>
    $(function () {
        var socket = io();
        $('form').submit(function(){
        socket.emit('chat message', $('#m').val());
        $('#m').val('');
        return false;
        });
    });
    </script>

10. サーバー側も受信時の処理

  • app.js
io.on('connection', (socket) => {
  console.log('a user connected');
  socket.on('chat message', (msg) => {
    console.log(`message: ${msg}`);
  });
  socket.on('disconnect', () => {
    console.log('user disconnected');
  });
});

11. 複数人に情報を発信/ブロードキャスト

  • app.js
    socket.on('chat message', (msg) => {
        io.emit('chat message', msg);
        console.log(`message: ${msg}`);
    });
  • index.html
<script>
  $(function () {
    var socket = io();
    $('form').submit(function(){
      socket.emit('chat message', $('#m').val());
      $('#m').val('');
      return false;
    });
    socket.on('chat message', function(msg){
      $('#messages').append($('<li>').text(msg));
    });
  });
</script>

12. ブラウザを複数開いて確認

ほかのサーバーに接続してみる

  • index.htmlを変更
<script src="/socket.io/socket.io.js"></script>

<script src="https://4f20dd49.ngrok.io/socket.io/socket.io.js"></script>
  • index.htmlを変更2
var socket = io();

var socket = io('http://4f20dd49.ngrok.io');

すると...

こんなイメージ

Electronでデスクトップアプリを作ってみよう

Electronとは

はろーElectron

試してみよう。

Clone the Quick Start repository

$ git clone https://github.com/electron/electron-quick-start

Go into the repository

$ cd electron-quick-start
$ ls
LICENSE.md   index.html   package.json
README.md    main.js      renderer.js

Install the dependencies and run

$ npm install && npm start

表示されました?

ファイルの確認

表側

  • index.html
  • renderer.js

裏側

  • main.js

index.htmlを確認

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    <!-- All of the Node.js APIs are available in this renderer process. -->
    We are using Node.js <script>document.write(process.versions.node)</script>,
    Chromium <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
  </body>

  <script>
    // You can also require other files to run in this process
    require('./renderer.js')
  </script>
</html>

適当にいじってみよう。

      <style>
        body{
          background-color: red;
        }
      </style>

とか

  • ちゃっとっぽい見栄えとか
<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
  </body>
</html>

基本Webサイトと同じ感覚で作れる

main.jsを確認

main.js
const electron = require('electron')
// Module to control application life.
const app = electron.app
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow

const path = require('path')
const url = require('url')

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow

function createWindow () {
  // Create the browser window.
  mainWindow = new BrowserWindow({width: 800, height: 600})

  // and load the index.html of the app.
  mainWindow.loadURL(url.format({
    pathname: path.join(__dirname, 'index.html'),
    protocol: 'file:',
    slashes: true
  }))

  // Open the DevTools.
  // mainWindow.webContents.openDevTools()

  // Emitted when the window is closed.
  mainWindow.on('closed', function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null
  })
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', function () {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) {
    createWindow()
  }
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

NodeSchool

  • Node.jsの授業
npm install -g learnyounode
  • Electronの授業
npm install -g elementary-electron
  • NodeBots

アンケート

宿題

管理しているFacebook BOTに連絡がきたら表示してくれるデスクトップアプリ

ヒント: 前回のFacebook メッセンジャーBOT、今回やったSocekt.ioElectronを組み合わせるとできます。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up