60 min
aihara
  1. 1. 概要
    1. 1.1. 機能
  2. 2. アプリの作成
    1. 2.1. Frontend
    2. 2.2. Backend
  3. 3. アプリのカスタマイズ
    1. 3.1. Frontend
    2. 3.2. Backend
  4. 4. デプロイ
    1. 4.1. サービス
    2. 4.2. Backend
    3. 4.3. Frontend
  5. 5. ダウンロード
  6. 6. Version

このチュートリアルでは、LINEのチャットボットとチャット画面を作る方法を説明します。チャットの内容は全て記録されていて、過去のチャット友達別に内容を見ることができます。

概要

## 素材

  • テンプレート: Chat
  • APIロジック: Line Messaging API, MongoDB

機能

  • LINEのチャットボット
  • LINEのフォロワーのメッセージを管理するチャット用のUI
  • LINEのメッセージを全て記録するMongoDBのデータストア

アプリの作成

K5 Playgroundにアクセスします。

Frontend

  1. K5 Playgroundで、Chatテンプレートを選択します。

Backend

  1. 左メニューのWebAPIsからwebhookという名前のWebAPIを新規作成してPOSTメソッドを有効化します。

  2. 作成したPOST webhookを左メニューから選択します。

    1. LINE Messaging API Receive MessageAPIロジックを右側のMessagingから追加します。

    2. MongoDB Save DocumentAPIロジックを右側のDatabaseから追加して、修正します。

      This application handles a follow event as a opening a stream where you can talk with the follower. In this code, we query follower’s name by adding getProfile method and save it by using the APIロジック.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      function getProfile(userId) {
      var accessToken = '[ACCESS TOKEN]';
      var options = {
      url: 'https://api.line.me/v2/bot/profile/' + userId,
      json: true,
      method: 'get',
      headers: {
      'Authorization': 'Bearer ' + accessToken,
      },
      };
      return new Promise(function(resolve, reject) {
      Request(options, function(error, response, body) {
      if (!error && response.statusCode == 200) {
      resolve(body);
      }
      });
      });
      }

      function addStream(event, profile) {
      var collectionName = 'sample_stream';
      var document = {
      stream_id: event.source.userId,
      name: profile.displayName,
      };
      var url = 'mongodb://' + config.datasource.user + ':' + config.datasource.password + '@' + config.datasource.host + ':' + config.datasource.port + '/' + config.datasource.database;

      MongoClient.connect(url, function(err, db) {
      if (err) {
      // @todo handle error
      throw err;
      }
      db.collection(collectionName).save(document, function(err, result) {
      db.close();
      if (err) {
      // @todo handle error
      throw err;
      }
      return;
      });
      });
      }

      var lineEvents = JSON.parse(req.body);

      lineEvents.events.forEach(function(event) {
      if(event.type === 'follow') {
      getProfile(event.source.userId).then(function(profile) {
      addStream(event, profile);
      });
      }
      });
      next({});
    3. MongoDB Save Document APIロジックを追加して次のように修正します。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      function getProfile(userId) {
      var accessToken = '[ACCESS TOKEN]';
      var options = {
      url: 'https://api.line.me/v2/bot/profile/' + userId,
      json: true,
      method: 'get',
      headers: {
      'Authorization': 'Bearer ' + accessToken,
      },
      };
      return new Promise(function(resolve, reject) {
      Request(options, function(error, response, body) {
      if (!error && response.statusCode == 200) {
      resolve(body);
      }
      });
      });
      }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function saveMessage(event, profile) {
var collectionName = 'sample_message'; // @todo set collection name
var document = {
stream_id: event.source.userId,
user_id: profile.displayName,
message: event.message.text,
replyMessage_id: '',
}; // @todo set document data for insert
var url = 'mongodb://' + config.datasource.user + ':' + config.datasource.password + '@' + config.datasource.host + ':' + config.datasource.port + '/' + config.datasource.database;
return new Promise(function(resolve, reject) {
MongoClient.connect(url, function(err, db) {
if (err) {
// @todo handle error
reject(err);
}
db.collection(collectionName).save(document, function(err, result) {
db.close();
if (err) {
// @todo handle error
reject(err);
}
resolve(result);
});
});
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
var lineEvents = JSON.parse(req.body);
var promises = [];
lineEvents.events.forEach(function(event) {
if(event.type === 'message' && event.message.type === 'text') {
promises.push(getProfile(event.source.userId).then(function(profile) {
return saveMessage(event, profile);
}));
}
});

Promise.all(promises).then(function() {
next({});
});
  1. LINE Messaging API Send Message APIロジックを追加して、次のように修正します。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var faqs = [
    {
    question: 'url',
    answer: 'http://playground.cloud.global.fujitsu.com',
    },
    {
    question: 'How are you',
    answer: 'I\'m fine!',
    }
    ];
    var lineEvents = JSON.parse(req.body);
    var answers = [];
    var promises = [];
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    function sendMessage(userId, text) {
    var message = [{
    "type": "text",
    "text": text,
    }];
    var options = {
    url: 'https://api.line.me/v2/bot/message/push',
    method: 'post',
    headers: {
    'Authorization': 'Bearer ' + accessToken,
    },
    json: {
    "to": userId,
    "messages": message,
    }
    };
    return new Promise(function(resolve, reject) {
    Request(options, function(error, response, body) {
    if (!error && response.statusCode == 200) {
    resolve(body);
    } else {
    res.end(JSON.stringify(error));
    reject(error);
    }
    });
    });
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
lineEvents.events.forEach(function(event) {
if(event.type === 'message' && event.message.type === 'text') {
faqs.forEach(function(faq) {
if(event.message.text.includes(faq.question)) {
promises.push(sendMessage(event.source.userId, faq.answer));
answers.push({
stream_id: event.source.userId,
user_id: 'bot',
message: faq.answer,
replyMessage_id: '',
});
}
});
}
});
Promise.all(promises).then(function() {
next(answers);
});
  1. MongoDB Save Document APIロジックをDatabaseメニューから追加します。

    1
    2
    3
    4
    if (!results.length) { 
    next({});
    return;
    }
1
next({});
  1. POST sample_messagesを修正します。

    1. LINE Messaging API Send MessageAPIロジックをStream Send MessageAPIロジックの下に追加して、次のように修正します。

      1
      var messageReceiverId = req.body.stream_id;
1
"text": req.body.message,
  1. アプリをダウンロードします

アプリのカスタマイズ

Frontend

  1. app\components\StreamList\StreamList.jsを次のように修正します。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    // ~~ code omitted ~~

    static propTypes = {
    currentStreamId: PropTypes.string,
    streamList: PropTypes.arrayOf(
    PropTypes.shape({
    stream_id: PropTypes.string,

    // ~~ code omitted ~~

    componentWillMount() {
    // push task to the stack
    const tasks = [
    () => { // task: select default stream
    if (!this.props.currentStreamId &&
    this.props.streamList.length > 0) {
    // select top of stream list for default stream
    const stream = this.props.streamList[0];
    StreamActionCreators.selectStream(stream);
    MessageActionCreators.getMessages({ streamId: stream.stream_id });

    // ~~ code omitted ~~

    handleCreateStream = () => {
    StreamActionCreators.createStream({
    name: (this.newstreamName ? this.newstreamName.value : 'new stream'),
    resolve: (stream) => {
    this.newstreamName.value = '';
    MessageActionCreators.getMessages({ streamId: stream.stream_id });

    // ~~ code omitted ~~

    handleSelectStream(stream) {
    StreamActionCreators.selectStream(stream);
    MessageActionCreators.getMessages({ streamId: stream.stream_id });

    // ~~ code omitted ~~

    const streamListItems = streamList.map(stream => (
    <ListItem
    style={styles.streamListItem}
    onClick={() => this.handleSelectStream(stream)}
    key={`StreamList-Stream-${stream.stream_id}`}
    value={stream.stream_id}

    // ~~ code omitted ~~
  2. app\components\StreamContent\StreamContent.jsを次のように編集します。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    // ~~ code omitted ~~

    static propTypes = {
    currentStream: PropTypes.shape({
    stream_id: PropTypes.string,

    // ~~ code omitted ~~

    componentWillReceiveProps(nextProps) {
    if (this.props.currentStream.stream_id !==
    nextProps.currentStream.stream_id) {

    // ~~ code omitted ~~

    this.setState({
    timerId: setInterval(() => {
    MessageActionCreators.getMessages({
    streamId: nextProps.currentStream.stream_id,

    // ~~ code omitted ~~

    <HorizontalLayout style={styles.entry}>
    <VerticalLayout expandable style={styles.entryForm}>
    <input
    type="text"
    ref={(component) => { this.userId = component; }}
    placeholder={'Name'}
    style={styles.userId}
    tabIndex={0}
    />
    <textarea
    ref={(component) => { this.message = component; }}
    placeholder={'Message'}
    style={styles.message}
    tabIndex={0}
    onKeyDown={
    event => this.handleEnterMessage(event, currentStream.stream_id, replyMessageId)

    // ~~ code omitted ~~

    <VerticalLayout style={styles.entrySubmit}>
    <RaisedButton002
    label="post"
    onClick={() => this.handleSubmitMessage(currentStream.stream_id, replyMessageId)}

Backend

  1. api/swagger.jsonを次のように修正します。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    "/webhook": {
    "post": {
    "description": "",
    "parameters": [{
    "name": "data",
    "in": "body",
    "schema": {
    "type": "string"
    }
    }],
    "responses": {
    "200": {
    "description": ""
    }
    },
    "operationId": "webhookPOST",
    "x-swagger-router-controller": "Chat"
    }
    }
  2. config.jsに必要な情報を記入します。

デプロイ

サービス

  • MongoDBを用意して、次のcollectionsを作成してください。
    • sample_message
    • sample_stream

Backend

1
2
cd backend
cf push [APP_NAME]

Frontend

1
2
3
4
5
6
cd frontend
npm install
set API_URL=[Backend URL] # for Windows
export API_URL=[Backend URL] # for Mac/Linux
npm run build
cf push [APP_NAME] -p public

ダウンロード

Version

  • Chat Template: v1.0.0