6
2

More than 1 year has passed since last update.

なでしこさんでLINE Botを作りたい! ~おうむ返し+αのボットが日本語で作れたよ~

Last updated at Posted at 2021-09-16

 前々回でLINEボットの作り方は大体把握し、前回なでしこ3のプログラムをHerokuで動かすことが出来ることを確認できたので、なでしこさんで出来るよう張り切ってプラグインを作っていきます! と、頑張る話です。
 あるいは、と、思ったら、考えがだいぶ甘くて結構苦労しちゃった話し。
 または、例によってドシロウトが出来たの出来ないのと大騒ぎしながら、ぼやきまくる話し。

 でも一応、おうむ返し+αのボットまではできました☆
 頑張った!!

準備

 前々回前回で作成したアカウントや構築した環境は、あるものとします。
 引き続き、前回のnako3フォルダに作っていきます。

 あと、さすがにいきなりherokuさんに、いちいちアップしてお試しするのは面倒な上、リアルタイムにコンソールログがみれなかったりと不自由なことが多いので、まずは手元でお試し出来るようにします。

ngrokのインストール

 npmでグローバルインストールしたら、

npm i -g ngrok

 こんだけ!(数字はローカルホストのポート)

ngrok http 8888

 すると、こんな画面になるので、ForwardinghttpsのほうのURLをLINE DevelopersのWebhook URLに設定して上げればOK。

ngrok by @inconshreveable

Session Status                online
Session Expires               1 hour, 59 minutes
Version                       2.3.40
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://1e8a-2001-f73-87c0-1800-b839-b3de-2a8-a0d4.ngrok.io -> http://localhost:8888
Forwarding                    https://1e8a-2001-f73-87c0-1800-b839-b3de-2a8-a0d4.ngrok.io -> http://localhost:8888

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

 超簡単! と思ったんですがね・・・
 この画面、普通のコンソールの画面と違って文字が折り返さないんですよよよ・・・
 そして、URLが結構長いんですよよよよよ・・・
 そしてそして、コマンドプロンプトのウィンドウは、ふつーのウィンドウみたいに引っ張っても伸ばせないんですよね~。全部見えないじゃん!Σ(゜д゜;
 と、途方に暮れたのですが、コマンドプロンプトの左上のアイコンをクリックすると、メニューが出るんですよね。
 「プロパティ」→「レイアウト」で、ウィンドウのサイズを変えれば窓自体を大きくすることが出来、画面バッファのサイズを大きくすれば、表示領域が広がりスクロールバーが出る・・・ということを知りました。
 画面の色も、黒以外に変えられるんですね!!

 ともかく、Webhook URLを設定したら、この画面は開きっぱにしておきます。
 画面を閉じるか、ctrl+Cで終了するまで有効なので、Google Colabの時みたいに、毎回Webhook URLを設定し直さなくてOKなんですよね☆
(でも、たまにいつの間にか落ちてるコトあります(´д`;)

環境変数

 秘密鍵は、プログラムへ直に書くのは良くないので、環境変数から読むようにします。
 ポートもherokuさんではPORTという環境変数から読むことになっているので、同様に出来るよう環境変数に入れます。

SET LINE_CHANNEL_SECRET=(チャネルシークレット(短い方))
SET LINE_ACCESS_TOKEN=(アクセストークン(長い方))
SET PORT=8888

 SETで設定した環境変数は、そのコンソールだけで有効なので、一回閉じちゃったら次回はまた設定し直さなきゃなりません。
 コレをバッチにして、このバッチファイルを開くように設定したコマンドプロンプトのショートカットを作っておくと、超便利です☆

インストール

 line-bot-sdkexpressをインストール。

cd desktop/nako3
npm i @line/bot-sdk express

 なでしこ3には標準でexpressが入っており、前回Expressサーバーをお試しした時にはexpressはインストールしなくてよかったのですが、なでしこ3は依存関係を定義しただけで、作業ディレクトリには入っていないから、どうやらnadesiko3のsrcに入ってるプラグインからじゃないと使えないみたい(?)
 今回は、作業フォルダに置いた自前のプラグインからexpressも使いたい(後述)のです。
 とゆうわけで、めんどくさいので別途インストールしたほうが早い!(ヤケか;)

 一応、package.jsondependenciesに追加されたことを確認。

package.json
{
  "name": "nako3",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@line/bot-sdk": "^7.4.0",
    "express": "^4.17.1",
    "nadesiko3": "^3.2.25"
  }
}

 ちゃんと追加されていますね! おっけ~☆

プラグインを作るよ!

 desktop/nako3に、Plugin_linebot.jsを作ります。がんばる!

※基本、なでしこでしか、日本語でしかプログラムの出来ない人です。
 Javascriptのことが分かっている人は、てきとーに読み飛ばすか、こいつバカだなぁと、生温かく見守って下さい。あるいは、なんか間違っていたら、お教え下さい。

サーバー部分

 さて、webhookへのPOSTを受け取るのは、なでしこ3コンソール版(cnako3)の基本プラグイン「Webサーバ(Express)」で出来るだろうと思っていたのですが、どうもうまくいきませんでした。いきなりツラい・・・(´Д⊂ヽ

 サーバーのことが全然よく分かりませんが、取りあえず分かった問題その1。
 どうやら・・・ココによると・・・・・・っ、英語~っっ><
 ま、まあ、とりあえず・・・どうやらbody-parserなるものを使っちゃいけないような?

 line.middlewareがパースして署名の検証をするからbody-parserを使う必要はありません。っつーかline.middlewareより前にbody-parser使っちゃうと、署名とか解析出来なくなっちゃうからやめれ。どうしても使いたい場合は、line.middlewareの後でやってね。

 的な???(Google翻訳さんのイミフな訳を、ユキノ的解釈により超訳した物w 正しくは原文をお読み下さい)
 ところが、なでしこ3のplugin_expressでは、WEBサーバ起動の中で既に、POSTを自動的に処理するよう、body-parserを使っているんだよね。むむむ。

サーバー起動部分

 仕方がないので、サーバー起動部分から用意することにする。
 と言ってもよく分かりません。
 ほぼほぼplugin_expressからのコピペですけどね(汗)

 body-parserを使っている部分を削除して、代わりにこの中でline.Clientも作ることにして、ポートは環境変数を読むことになっているので、自動的に与えることにする。
 で、こんな感じ?

Plugin_linebot.js(一部)
  'PORT番号': { type: 'const', value: process.env.PORT }, // @PORTばんごう
  'LINEボット起動': { // @ポート番号でLINEボット用にExpressサーバを起動して成功したら『LINEボット起動した時』を実行する // @らいんぼっときどう
    type: 'func',
    josi: [''],
    fn: function (portno, sys) {
      let app = express()
      let client = new line.Client(config)
      let server = app.listen(portno, () => {
        const pno = server.address().port
        console.log('[URL] http://localhost:' + pno + 'でサーバー起動しました☆')
        sys.__webapp.get('/', (req, res) => res.send('LINEボットが起動しています☆')); //ブラウザ確認用

        const callback = sys.__v0['WEBサーバ:ONSUCCESS']
        if (callback) {callback(pno, sys)}
      })
      server.on('error', (e) => {
        const callback = sys.__v0['WEBサーバ:ONERROR']
        if (callback) {callback(e, sys)}
      })
      // memo
      sys.__webapp = app
      sys.__server = server
      sys.__client = client
      return server
    }
  },
  'LINEボット起動時': { // @LINEボット用にExpressサーバを起動して成功したらCALLBACKを実行する // @らいんぼっときどうしたとき
    type: 'func',
    josi: [''],
    fn: function (callback, sys) {
      sys.__v0['WEBサーバ:ONSUCCESS'] = callback
      portno = sys.__v0['PORT番号']
      return sys.__exec('LINEボット起動', [portno, sys])
    }
  },

 あってる???
 いちおう、コンソールに、

[URL] http://localhost:8888でサーバー起動しました☆

 て出るのを確認。
 そのhttp://localhost:8888を開いて、「LINEボットが起動しています☆」て出るのを確認。
 大丈夫そう。ま、コピペですから;

イベントを受信した時

 「/webhook」にWEBサーバPOSTした時みたいなことです。
 でも、WEBサーバPOST時は、引数がURIだけだけど、そうすると件のline.middlewareはどうしたら・・・? というのが問題の二つ目。
 サッパリ分からん(そもそもさーばーもじゃばすくりぷともよくわかってないからね;;;)

 でもまあ、LINE Botは、基本/webhookへのPOSTに対してあれこれするのがメインみたいだし、別になでしこ側でルーティング設定を作らなくてもいいのかな。
 これも完全にLINE Botようの命令として作ることにする。

Plugin_linebot.js(一部)
  'LINEイベント': { type: 'const', value: '' }, // @らいんいべんと
  'LINEイベント受信時': { // @webhookにイベントがPOSTされた時 // @らいんいべんとじゅしんしたとき
    type: 'func',
    josi: [''],
    fn: function (callback, sys) {
      sys.__webapp.post('/webhook', line.middleware(config), (req, res) => {
        res.status(200).end(); //エラー対策
        sys.__v0['LINEイベント'] = req.body.events
        if (sys.__v0['LINEイベント'].length === 0) {
          console.log('検証イベント受信しました☆'); //疎通確認用
          return;
        } else {
          console.log('イベント受信しました☆\n',sys.__v0['LINEイベント']); //Webhookの中身の確認用
        }
        callbackServerFunc(callback, req, res, sys)
      })
    }
  },

 取りあえずここまでで、「検証」押したら「成功」になって、コンソールにも「検証イベント受信しました☆」が表示されました。
 疎通は出来てるようです。やったね!

 そして、前のJavascriptでお試しした時の、handleEventでやってたような色々の処理は、なでしこさん側で行えるようにしたいワケです。
 受信したreq.body.eventsを、LINEイベントって変数に入れて、あれこれできるようにすることにする。

イベントの処理

メッセージオブジェクト

 メッセージオブジェクトは簡単に作りたい。
 送信出来るメッセージタイプ。こんなに種類があるようだ。
 取りあえずテキスト。

Plugin_linebot.js(一部)
  'LINEテキストメッセージ': { // @textのメッセージobjを作成する // @らいんてきすとめっせーじ
    type: 'func',
    josi: [''],
    fn: function (txt, sys) {
      return {type: 'text', text: txt}
    }
  },

メッセージ送信

 取りあえず返信(応答メッセージ)。
 他にもいろいろあるけど、プッシュ以外はよく分かんない;;;

Plugin_linebot.js(一部)
  'LINE返信': { // @返信先(replyToken)へ応答メッセージ(mes)を送信する(実際の送信は「LINEメッセージ送信」でまとめて行われる) // @らいんへんしん
    type: 'func',
    josi: [['', ''],['']],
    fn: function (replyToken, mes, sys) {
      return sys.__client.replyMessage(replyToken, mes);
    }
  },

 LINEイベントは、一件ずつとは限らず、配列で来るらしい。
 反復で順番に処理しても、非同期で順序が狂うやつ?

ぷろみす?

 Promise.allが、順番通りにやってくれるやつ??
 自分でお試ししても、イベントは一件ずつにしかならないから、よく分かんないんだよね;
 取りあえず、先のLINE返信ではreplyMessageを直接実行せずに返しておいて、なでしこ側で配列に格納してPromise.allに渡す的な???

Plugin_linebot.js(一部)
  'LINEメッセージ送信': { // @LINEメッセージをぷろみすで送信 // @らいんめっせーじそうしん
    type: 'func',
    josi: [['', '']],
    fn: function (promises, sys) {
      Promise.all(promises).then((response) => {
          console.log(`${response.length} 件のイベントを処理しました☆`);
      });
    }
  }

ユーザープロフィール取得

 取得しただけじゃ、非同期で後の処理に結果を反映出来ないやつ?
 「には構文」にすればいいのかな。非同期モードも逐次実行も使えないっぽいし・・・

Plugin_linebot.js(一部)
  'プロフィール取得時': { // @ユーザーのプロフィール情報を取得 // @ぷろふぃーるしゅとくしたとき
    type: 'func',
    josi: [[''],['']],
    fn: function (fn, userId, sys) {
      sys.__client.getProfile(userId).then((prf) => {
        sys.__v0['対象'] = prf
        console.log('ユーザープロフィール取得しました☆\n',prf); //確認用
        return fn(prf, sys)
      })
    }
  },

 これを追加したらエラーの嵐になって、ええっ、何か間違った?!?! と、慌てましたが、問題はなでしこ側でした。変数をグローバルで宣言してなかったために、無名関数の中に入った部分で返信先のreplyTokenとかが読めなくなって、空だか未定義だかが送られていただけでした(バカ;)
 あと、ボットをブロックしているユーザーからはプロフィール取得が出来ないので、イベント種別が「unfollow」だったら、プロフィール取得には行かないようにしなければいけなかった。
 プラグインがうまく出来てないと思い込んだせいで、随分ムダに時を費やしてしもうた( ;∀;)

なでしこ側のプログラム

 Webhookイベントオブジェクトだけ覚えたら、あとは簡単☆ て感じにするのが目標なんですけど、どうかな?

linebot.nako3
!「Plugin_linebot.js」を取り込む。

#---宣言----------
イベントリスト=空配列。C=0。
イベント種別=空。返信先=空。
ユーザーID=空。ユーザー名=空。
受信メッセージ=空。返信テキスト=空。

#---サーバー----------
LINEボット起動した時には
  「{PORT番号}でサーバ起動しました」と表示。
  LINEイベント受信時には
    イベント処理。
  ここまで。
ここまで。

●イベント処理
  C=0。
  LINEイベントを反復
   イベント種別=LINEイベント[対象キー]["type"]
   もし、イベント種別=「unfollow」ならば、続ける。//ブロックするとプロフィール取得が出来なくなって、コンソールにエラーが来るので、この段階で止める。
   返信先=LINEイベント[対象キー]["replyToken"]
   ユーザーID=LINEイベント[対象キー]["source"]["userId"]。
   ユーザーIDのプロフィール取得した時には
    ユーザー名=対象["displayName"]。
    イベント種別で条件分岐
     「follow」ならば、フォロー。。。
     「message」ならば、メッセージ。。。
    ここまで。
    もし、C=(イベントリストの配列要素数)ならば、イベントリストをLINEメッセージ送信。
   ここまで。
  ここまで。
ここまで。

●フォロー
  返信テキスト=「{ユーザー名}さん、はじめまして!{改行}お友達になってくれてありがとう☆」
  イベントリスト[対象キー]=返信テキストのLINEテキストメッセージを返信先へLINE返信。
  C=C+1。
ここまで。

●メッセージ
  メッセージ種別=LINEイベント[対象キー]["message"]["type"]
  もし、メッセージ種別=「text」ならば、
    受信メッセージ=LINEイベント[対象キー]["message"]["text"]
    もし、受信メッセージ=ユーザー名ならば、
      返信テキスト=「{ユーザー名}さんって、すてきな名前だね☆」
    違えば、もし、受信メッセージ=「なでしこ」ならば、
      返信テキスト=「誰でも簡単プログラマー」
    違えば、もし、受信メッセージ=「なでしこさん」ならば、
      返信テキスト=「呼んだ?」
    違えば、
      返信テキスト=受信メッセージ&「?」
    ここまで。
    イベントリスト[対象キー]=返信テキストのLINEテキストメッセージを返信先へLINE返信。
    C=C+1。
  ここまで。
ここまで。

 一応、友達追加した時のメッセージを加えたのと、メッセージはおうむがえしの他、「なでしこ」「なでしこさん」、あとユーザーのLINEでの表示名に反応するようにしてみました。
 こうゆう部分は、全て日本語でやりたかったわけなんです。
 うごいてます! イイ感じですね☆

Procfile

 そうそう、前回のHallo.nako3からlinebot.nako3に変更するの忘れず。

web: cnako3 linebot.nako3

Herokuにデプロイ

ログイン

 前回やったとうり。

heroku login

環境変数

 herokuさんの環境変数に、秘密鍵をセットします。

heroku config:set LINE_CHANNEL_SECRET=(チャネルシークレット(短い方))
heroku config:set LINE_ACCESS_TOKEN=(アクセストークン(長い方))

デプロイ

 前回やったとうり。

git init
git add .
git commit -m "(コメント)"
git push -u heroku master

 ・・・ああっ、エラー出る!><
 前回、実際にインストールすること無く手動でpackage.jsonになでしこさんを追加したのですが、今回line-bot-sdkなどをインストールした結果作成されたpackage-lock.jsonと、内容に齟齬があるよと言われてしまっているようです。そうだね、そうなるよね(´Д⊂ヽ
 package-lock.json消します!(ええっΣ(゜д゜;)
 (本当に消すのはさすがによろしくないと思われるので、どっかに待避します)

 再びデプロイ。おっと、成功成功♪
 一応、待避したpackage-lock.jsonを元に戻して・・・.gitignorepackage-lock.jsonを追加して管理対象外にしちゃいます。(ホントはこうゆうファイルこそ、Gitで管理して頂いた方が良さげな物のような気がしますが・・・でもま、実際のところ何一つインストールすること無しに、line-bot-sdkやexpressも手動でpackage.jsonに追記しただけでも、heroku上では問題なく動きますからっっ;)

※この事件については、その後発覚したコト(?!)も含めて、前回の記事へわりと重要な追記をしています。
 ってか、やっぱワタシは常にどっか抜けてるって話し(´・ω・`)

お試し

 さてさて気を取り直して、前々回やったとうり、LINE Developersで、「Messaging API設定」の「Webhook URL」にhttps://nadesiko3.herokuapp.com/webhookを登録して、「検証」。
 成功! よしよし、だいじょぶそうですね☆

 と、ゆうわけで友達登録。
215ybrvp.png
 で、こんな感じ。
linebot.jpg
 できた! やったね!!
 自分のLINEの登録名を送ると「すてきな名前だね」と言ってもらえてうれしい❤(単純;)
 「なでしこさん」なら「呼んだ?」と返事をしてくれ、「なでしこ」なら「誰でも簡単プログラマー」とゆう、なでしこの決め文句(?)、それ以外は全部おうむ返しに問い返してくるようになっているので、もしよろしければお試し下さい。

※「dyno 時間を節約するため、30 分間何も操作が行われないと、無料アプリケーションは自動的にスリープモードに切り替わります」・・・とゆうことで、初回の返信には少々時間が掛かる場合があります。
※30分以内置きに何か送り続けてこれを回避する裏技はあるようですが、ようするにdyno 時間を無駄遣いするワケです。だいたい人間ならそんなに返信早くないでしょ、とそのままにしてありますが、復帰に30秒以上掛かるとreplyTokenの有効期限が切れて初回のメッセージは既読スルーされてしまうそうです(´Д⊂ヽ
(実際のところは、スリープ状態になっているところへ送っても、復帰に30秒以上も掛かったことはまだありませんが、pushの併用で既読スルーを回避する的な裏技もあるようなのでそのうち試したい)

つづきます

 なでしこさんでLINE Bot作ってherokuに公開まで出来ました! やったね(≧▽≦)
 取りあえず最低限のことは出来るようになった気がする。
 プラグイン側ではテキストだけじゃなくスタンプや画像も送れるようにしたり、pushも出来るようにして、ボット自体ももそっといろんなコトできるようにしよう☆

なでしこさんでLINE Botを作りたい! ~なでしこの最新バージョンを答えてくれるLINE Bot~

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