6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

楽天のAPIを使ってマスクを買えるBotを作ってみた[LINEBotリッチメニュー]

Last updated at Posted at 2020-04-01

##はじめに

前回の楽天市場のAPIを使って買えるマスクを提案してくれるLINEBotを作ってみた - Qiitaをブラッシュアップしました。

##概要
前回できたものは、
LINEBotにほしいもの、「マスク」と入力したら
楽天市場から「マスク」を検索して、絞り込み検索で、購入可能最安価でソート最低金額○円以上最高金額○円以下の商品を商品画像付きで3商品くらい返してくれるBot
結果として出来上がったものの、楽天の商品名ってSEO対策のせいでやたらと長くてかなりエリアを取る、3つ出したのにとても見比べられず実用性が低かったのでリッチメニューを使って使いやすくしようと思いました。

作りたいもの

マスクにセグメントされた楽天市場の商品を3つ引っ張ってきて

  1. カルーセルパネルで開く
  2. 複数検索
  3. 商品名を3行くらいに収める
  4. 金額かレビューを出す
  5. (マスクは)怪しいショップが多かったので店舗名を出す
  6. ngrokで動かしているので何かにデプロイする

作れなかったところ

  1. ○ → カルーセルパネルで開く
  2. ○ → 複数検索してるっぽいみため
  3. ○ → 商品名を3行くらいに収める
  4. ○ → 金額かレビューを出す
  5. ○ → (マスクは)怪しいショップが多かったので店舗名を出す
  6. × → ngrokで動かしているのでHerokuにデプロイする

###環境
Node.js v13.7.0
MacBook Pro macOS Mojave
Visual Studio Code v1.43.1

###できたもの

「マスクある?」と質問することで、楽天市場の中で在庫あり商品、最安価の商品名と商品URLと商品画像のあるマスクをカルーセルパネルを使って横スクロールで商品を提案してくれます。


カルーセルパネルに入れる情報

楽天市場API↓を使って

https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706?format=json&rakutenAPiurl&keyword=mask&genreId=501143&availability=1&sort=%2BitemPrice&minPrice=1000&maxPrice=4000&carrier=2&hits=3&applicationId=***

Image from Gyazo
上記のカルーセルに入れる情報を作ります。

//APIが深いので前半をまとめる
    const item = items[i].Item;
//商品画像(中サイズ)
    const itemimg = `${item.mediumImageUrls[0].imageUrl}`;
//商品URL
    const producturl = `${item.itemUrl}`;
//商品名
    const productlabellg = `${item.itemName}`;
//商品名を54文字以下にして...をつける
    const productlabel = productlabellg.slice(0, 54) + "...";
//商品金額
    const productprice = `¥${item.itemPrice}`;
//レビュー平均値
    const producavg = `${item.reviewAverage}`;
//ショップ名
    const productshop = `${item.shopName}`;
// LINEに入力した文字
    const own = `${ownword}`;

これを使って、カルーセルパネルに設定していく
Image from Gyazo

let columns = [];

let carousel =
    {
      "thumbnailImageUrl": itemimg,
      "imageBackgroundColor": "#FFFFFF",
      "title": own,
      "text": productlabel,
      "defaultAction": {
        "type": "uri",
        "label": "View detail",
        "uri": producturl
      },
      "actions": [
        {
          "type": "message",
          "label": productprice,
          "text": productprice
        },
        {
          "type": "uri",
          "label": productshop,
          "uri": producturl
        }
      ]
    };
    columns.push(carousel);
  }

これを3つ分作りたいのでFor文の中に入れちゃいます。

全コード(ngrok)

node.js
'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');
const PORT = process.env.PORT || 3000;
const rakutenAPiurl = "https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706?format=json";
const rakutenAppId = '***';
const guidecat = 'https://i.gyazo.com/97840790b257952c89c59e7c176e114c.png';

const config = {
  channelSecret: '***',
  channelAccessToken: '***'
};

const app = express();

app.post('/webhook', line.middleware(config), (req, res) => {
  console.log(req.body.events);
  Promise
    .all(req.body.events.map(handleEvent))
    .then((result) => res.json(result));
});

const client = new line.Client(config);

function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  let mes = event.message.text;
  if (mes.indexOf('') > -1) {
    getNodeVer(event.source.userId, mes);
    return client.replyMessage(event.replyToken, [{
      type: 'image',
      originalContentUrl: guidecat,
      previewImageUrl: guidecat
    },
    { type: "text", text: '在庫があって安いのはこれだよ〜' }]);

  } else {
    // mes = event.message.text;

    return client.replyMessage(event.replyToken, [{
      type: 'image',
      originalContentUrl: guidecat,
      previewImageUrl: guidecat
    },
    { type: "text", text: '〇〇ある?って聞いてほしいな' }]);
  }

}

const getNodeVer = async (userId, mes) => {

  let ownword = mes.split('ある?');
  console.log('mes->' + mes + 'ownword->' + ownword);
  const res = await axios.get(rakutenAPiurl + '&keyword=' + encodeURIComponent(ownword) + '&genreId=501143&availability=1&sort=%2BitemPrice&minPrice=1000&maxPrice=4000&carrier=2&hits=3&applicationId=' + rakutenAppId);
  const items = res.data.Items;
  let columns = [];

  for (let i = 0, len = items.length; i < len; i++) {
    const item = items[i].Item;
    const itemimg = `${item.mediumImageUrls[0].imageUrl}`;
    const producturl = `${item.itemUrl}`;
    const productlabellg = `${item.itemName}`;
    const productlabel = productlabellg.slice(0, 54) + "...";
    const productprice = ${item.itemPrice}`;
    const producavg = `${item.reviewAverage}`;
    const productshop = `${item.shopName}`;
    const own = `${ownword}`;

    let carousel =
    {
      "thumbnailImageUrl": itemimg,
      "imageBackgroundColor": "#FFFFFF",
      "title": own,
      "text": productlabel,
      "defaultAction": {
        "type": "uri",
        "label": "View detail",
        "uri": producturl
      },
      "actions": [
        {
          "type": "message",
          "label": productprice,
          "text": productprice
        },
        {
          "type": "uri",
          "label": productshop,
          "uri": producturl
        }
      ]
    };
    columns.push(carousel);
  }

 await client.pushMessage(userId, {
    "type": "template",
    "altText": "this is a carousel template",
    "template": {
      "type": "carousel",
      "columns": columns,
      "imageAspectRatio": "rectangle",
      "imageSize": "cover"
    }
  });
}

app.listen(PORT);
console.log(`Server running at ${PORT}`);

このコードをngrokで動かすと、動きます!

しかしこれをHerokuに持っていくと失敗。。。
課題なのでタイムオーバー。。。
Herokuにはdeployできてるのになぁ。。。
何が起こっているのかな

Image from Gyazo

Herokuを有料にしないといけないとかmにつけたのですが、Heroku側のSSL認証問題なのでしょうか。
[参考URL]
Heroku SSL
https://devcenter.heroku.com/articles/ssl

###now

Herokuは諦めてnowで試してみた

1時間でLINE BOTを作るハンズオンをベースにしたけどうまくいかなかったのでProtoOutの校長先生がさらに書いていただいて、
LINE BOTで天気を返すサンプルがngrokで動いてnowで動かない件を参考に書き換えたら動きましたー!!!!

node.js
'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');
const PORT = process.env.PORT || 3000;
const rakutenAPiurl = "https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706?format=json";
const rakutenAppId = '***';
const guidecat = 'https://i.gyazo.com/97840790b257952c89c59e7c176e114c.png';

const config = {
  channelSecret: '***',
  channelAccessToken: '***'
};

const app = express();

app.post('/webhook', line.middleware(config), (req, res) => {
  console.log(req.body.events);
  Promise
    .all(req.body.events.map(handleEvent))
    .then((result) => res.json(result));
});

const client = new line.Client(config);

async function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  let mes = event.message.text;
  let linemes = mes.indexOf('ある?');

  if ( linemes < 0) {
    return client.replyMessage(event.replyToken, [{
      type: 'image',
      originalContentUrl: guidecat,
      previewImageUrl: guidecat
    },
    { type: "text", text: '〇〇ある?って聞いてほしいな' }]);
  }

  await client.replyMessage(event.replyToken, [{
    type: 'image',
    originalContentUrl: guidecat,
    previewImageUrl: guidecat
  },
    {
    type: "text", text: '在庫があって安いのはこれだよ〜' }]);
    return getRakuten(event.source.userId, mes);
}
const getRakuten = async (userId, mes) => {

  let ownword = mes.split('ある?');
  console.log('mes->' + mes + 'ownword->' + ownword);
  const res = await axios.get(rakutenAPiurl + '&keyword=' + encodeURIComponent(ownword) + '&genreId=501143&availability=1&sort=%2BitemPrice&minPrice=1000&maxPrice=4000&carrier=2&hits=3&applicationId=' + rakutenAppId);
  const items = res.data.Items;
  let columns = [];
  for (let i = 0, len = items.length; i < len; i++) {
    const item = items[i].Item;
    const msg = `${item.itemName}\n ${item.itemUrl}\n¥${item.itemPrice}`;
    const itemimg = `${item.mediumImageUrls[0].imageUrl}`;
    const producturl = `${item.itemUrl}`;
    const productlabellg = `${item.itemName}`;
    const productlabel = productlabellg.slice(0, 54) + "...";
    const productprice = ${item.itemPrice}`;
    const producavg = `${item.reviewAverage}`;
    const productshop = `${item.shopName}`;
    const own = `${ownword}`;

    let carousel =
    {
      "thumbnailImageUrl": itemimg,
      "imageBackgroundColor": "#FFFFFF",
      "title": own,
      "text": productlabel,
      "defaultAction": {
        "type": "uri",
        "label": "View detail",
        "uri": producturl
      },
      "actions": [
        {
          "type": "message",
          "label": productprice,
          "text": productprice
        },
        {
          "type": "uri",
          "label": productshop,
          "uri": producturl
        }
      ]
    };
    columns.push(carousel);

  }//for

  return client.pushMessage(userId, {
      "type": "template",
      "altText": "this is a carousel template",
      "template": {
        "type": "carousel",
        "columns": columns,
        "imageAspectRatio": "rectangle",
        "imageSize": "cover"
      }
    });
}

(process.env.NOW_REGION) ? module.exports = app : app.listen(PORT);
console.log(`Server running at ${PORT}`);


感想

やり進めていくとFlex Messageの存在を知りました。。。こっちの方がやりたかった。。。
次はこれをやろう。

Deployはやっぱりハマってしまった。
頑張ろうー。

参考サイト

LINE Messaging APIのテンプレートメッセージをまとめてみる | Developers.IO
LINEのBot開発 超入門(前編) ゼロから応答ができるまで - Qiita

6
3
1

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
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?