Vue+LIFF(LINE Front-end Framework)+GASでフォームを使ったBOTを作ってみた
目次
LIFF を使用したアプリケーションを何か作ってみたかったので、勤怠連絡の BOT を作ってみました。
開発準備
フロントは、jQuery でもよかったのですが、せっかくなので Vue.js の勉強のため Vue.js で構築しています。
Vue CLI3をインストール
まずは、Vue CLI3をインストールします。
インストール
npm install -g @vue/cli
# OR
yarn global add @vue/cli
作成(設定はデフォルトのままで大丈夫です)
vue create vue-liff-bot
パッケージのインストール
yarn install
bootstrap-vueを追加
今回は、BootStrap を利用するので、BootStrap もインストール
yarn add bootstrap-vue
開発スタート
以下で開発を始めます。
yarn run serve
bootstrap-vueの組み込み
main.js
にbootstrap-vue
を組み込みます。
import Vue from 'vue';
import App from './App.vue';
// BottstrapVueの読み込み
import BootstrapVue from 'bootstrap-vue';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap-vue/dist/bootstrap-vue.css';
Vue.config.productionTip = false;
// BootstrapVueの使用
Vue.use(BootstrapVue);
new Vue({
render: (h) => h(App)
}).$mount('#app');
Vue ファイルの作成
App.vue
まずは、App.vue を変更します。
<template>
<div id="app" class="pt-3 pb-3">
<input-form :ttl="title" />
</div>
</template>
<script>
// inputFormをComponentとして設定
import InputForm from '@/components/InputForm.vue';
export default {
name: 'app',
components: {
InputForm
},
data() {
return {
// titleをpropとしてComponentに渡す
title: '勤怠連絡 BOT'
};
}
};
</script>
<style>
#app {
color: #2c3e50;
}
</style>
InputForm.vue
続いて、InputForm.vue
を作成します。
<template>
<div class="container">
<!-- 親のpropsを受け取る -->
<h1 class="text-center pb-2">{{ ttl }}</h1>
<!-- submitイベントを無効に -->
<form @submit.prevent="">
<div class="form-group">
<label for="name">名前</label>
<input
type="text"
class="form-control"
id="name"
v-model="data.name"
@change="createMessage()"
/>
</div>
<div class="form-group">
<label for="reason">遅刻理由</label>
<select
class="form-control"
v-model="data.reason"
id="reason"
@change="createMessage()"
>
<option v-for="(reason, index) in reasons" :key="index">
{{ reason }}
</option>
</select>
</div>
<!-- 理由がその他の時のみ表示 -->
<div class="form-group" v-show="data.reason == 'その他'">
<input
type="text"
class="form-control"
id="reson_other"
@change="createMessage()"
placeholder="その他の場合は理由を書いてください。"
v-model="data.reason_other"
/>
</div>
<div class="form-group">
<label>種別</label>
<div class="form-check" v-for="(type, index) in types" :key="index">
<input
class="form-check-input"
type="radio"
v-model="data.schedule"
:id="index"
:value="type"
:checked="index != 0 ? '' : 'checked'"
@change="createMessage()"
/>
<label class="form-check-label" :for="index">{{ type }}</label>
</div>
</div>
<!-- 種別が遅刻しますの時のみ表示 -->
<div class="form-group" v-show="data.schedule == '遅刻します'">
<label>遅刻時間</label>
<div class="form-row align-items-center">
<div class="col-5 form-group">
<select
class="form-control"
v-model="data.time1"
@change="createMessage()"
>
<!-- createTimes()の配列を設定 -->
<option v-for="(time, index) in createTimes(0, 12)" :key="index">
{{ time }}
</option>
</select>
</div>
<div class="col-2 text-center form-group">
<span>~</span>
</div>
<div class="col-5 form-group">
<!-- createTimes()の配列を設定 -->
<select
class="form-control"
v-model="data.time2"
@change="createMessage()"
>
<option v-for="(time, index) in createTimes(1, 13)" :key="index">
{{ time }}
</option>
</select>
</div>
</div>
</div>
<div class="form-group">
<label for="message">メッセージ</label>
<!-- createMessageで生成されたデータを挿入 -->
<textarea
class="form-control"
id="message"
rows="5"
v-model="data.message"
></textarea>
</div>
<div class="form-group pt-2">
<button
class="btn btn-primary btn-lg btn-block"
id="send"
type="submit"
>
送信
</button>
</div>
</form>
</div>
</template>
<script>
export default {
name: 'InputForm',
props: {
ttl: String
},
data() {
return {
data: {
name: '',
reason: '',
reason_other: '',
schedule: '',
time1: '',
time2: '',
message: ''
},
// 遅刻理由の中身
reasons: ['電車遅延', '体調不良', '忘れ物', 'その他'],
// 種別の中身
types: [
'遅刻します',
'全休をいただきます',
'午前休をいただきます',
'午後休をいただきます'
],
// メッセージのテンプレート
text1: 'おはようございます。',
text2: 'よろしくお願いします。'
};
},
methods: {
// 時間のオプションを配列で生成
createTimes: function (init, to) {
const times = [];
for (let index = init; index < to; index++) {
times.push(`${index * 5}分`);
}
return times;
},
// inputに変更があった場合にメッセージの作成
createMessage: function () {
let text, text_reson, text_schedule;
// その他の場合は、reason_otherのデータを使用
if (this.data.reason == 'その他') {
text_reson = this.data.reason_other;
} else {
text_reson = this.data.reason;
}
// 遅刻の場合は時間に入力した時間を使用して生成
if (this.data.schedule == '遅刻します') {
text_schedule = ${this.data.time1}~${this.data.time2}ほど${this.data.schedule};
} else {
text_schedule = this.data.schedule;
}
// メッセージを生成
text = ${this.text1}${this.data.name}です。${text_reson}のため${text_schedule}。${this.text2};
// データに渡す
this.data.message = text;
}
}
};
</script>
input
、select
の変更を検知して、data
を更新+message
を都度生成します。
完成
以下のコマンドでビルドします。
yarn run build
BOT の設定
参考
LIFF の設定は以下の記事が大変参考になります。
LINE Bot : LINE Front-end Framework を liff npm パッケージで操作
設定
https://developers.line.me/ja/docs/liff/getting-started/を参考にして進めます。
- アクセストークンを作成してコピーしておく
- 一番下のYour user IDもコピーしておく
- Channel Secretも後ほど利用するので生成してコピーしておく
- Webhook送信
- BOTのグループトーク参加→利用するに設定は後ほど設定するので、利用するに設定
- 自動応答メッセージ→利用しない
- 友だち追加時あいさつ→利用しない
- LINEアプリへのQRコードがありますので、LINE アプリより追加してください。
LIFF を設定
コードの追加
まずは、コードを追加します。
※Vue に組み込みたかったのですが、うまく動作させることができず、外部ファイルで対応しました・・・。
public/index.html
の head
に以下を追加します。
<script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script>
<script src="./send.js"></script>
続いて、send.js
を新規作成します。場所は、public/send.js
です。
window.onload = function (e) {
// sendにclickイベントを追加
document.getElementById('send').addEventListener('click', function () {
// messageの中身を取得し、sendMessagesのtextに入れる
var ms = document.getElementById('message').value;
liff
.sendMessages([
{
type: 'text',
text: ms
}
])
.then(function () {
window.alert('送信完了');
// aleartでOKを押すと、自動でLIFFウィンドウが閉じる
liff.closeWindow();
})
.catch(function (error) {
window.alert('Error sending message: ' + error);
});
});
};
liff.sendMessage
で送信します。
公開
LIFFはhttpsが必要なため、わざわざサーバーを用意する必要のないGitHub Pagesで公開します。
まずは、buildします。
yarn run build
続いて
.gitignore
からdist
を外す- githubにリポジトリを作成
git init
git add .
git commit -m 'init'
git remote add origin https://github.com/xxxxxxx/xxxxxx.git
git push -u origin master
- gitにpushした後は、Settingsをクリック
- GitHub PagesのSourceをmaster branchにしてsave
これで、GitHub Pagesに公開されます。
公開された URL は、
https://<ユーザー名>.github.io/<リポジトリ名>/dist/
になります。
LIFF の設定
まずは LIFFのnpmパッケージをインストールします。
npm install -g liff
liff init
で初期化します。上記で取得したチャネルのアクセストークンを入れます。
liff init <アクセストークン>
LIFF アプリケーションの登録します。今回はtall
を指定します。
liff add https://<ユーザー名>.github.io/<リポジトリ名>/dist/ tall
戻り値から LIFF ID および Accessible URL をコピーしておきます。
[LIFF ID] xxxxxxxxxxxxxxx created
accessible uri : line://app/xxxxxxxxxxxxxxxx
liff send
コマンドでリンクを送付します。LINE アプリよりリンクをクリックしてテスト。上記で取得したLIFF IDとYour user IDを入れます。
liff send <LIFF ID> <Your user ID>
BOT より送られたリンクを確認して、フォームから送信を押してメッセージが表示されれば完了です!
ブラッシュアップ
リンクのクリックから起動させるのではなく、BOT のリッチメニューか LIFF を起動させると、より良いでしょう。
リッチメニューの実装は以下を参考にしてください。
https://developers.line.me/ja/docs/messaging-api/using-rich-menus/
- LINE@マネージャーからログインして作成した BOT を選択します。
- リッチメニューより、メニューを作成します。
- コンテンツ設定のリンクを上記で取得したAccessible URLに設定します。
これでメニューから LIFF を起動させることができます。
GAS の設定
参考
GAS の設定は以下の記事が参考になります。
設定手順
以下の手順で GAS を設定します。
- Googleアカウントにログイン
- Googleドライブを開く
- マイドライブをクリック
- その他→アプリを追加→Google Apps Script
- コードを記述 → 保存
- 公開→ウェブアプリケーションとして導入
- アプリケーションにアクセスできるユーザーを全員(匿名ユーザーを含む)に変更
- 公開をクリックし、出てきた現在のウェブ アプリケーションの URLをコピーする
- LINE Developersの対象 BOT のWebhook URLに先ほどコピーした GASのURLを張り付ける
以上で GAS を利用した Webhook の設定が完了です。
コード
スクリプトは、以下になります。
//LINE Developersで取得したアクセストークンを入れる
var CHANNEL_ACCESS_TOKEN = '<LINE BOT アクセストークン>';
// 送信先のグループID
var GROUP_ID = '<グループへ参加させて set を送ると送られる>';
// グループIDを取得するためのキーワード
var SETTING_MESSAGE = 'set';
var line_reply = 'https://api.line.me/v2/bot/message/reply';
var line_push = 'https://api.line.me/v2/bot/message/push';
//ポストで送られてくるので、送られてきたJSONをパース
function doPost(e) {
var webhook = JSON.parse(e.postData.contents).events[0];
//返信するためのトークン取得
var reply_token = webhook.replyToken;
if (typeof reply_token === 'undefined') {
return;
}
//送られたメッセージ内容を取得
var message = webhook.message.text;
var gid = webhook.source.groupId;
// ユーザーの会話(グループIDが存在しない)
if (typeof gid === 'undefined') {
// 確認用メッセージの返信
UrlFetchApp.fetch(line_reply, {
headers: {
'Content-Type': 'application/json; charset=UTF-8',
Authorization: 'Bearer ' + CHANNEL_ACCESS_TOKEN
},
method: 'post',
payload: JSON.stringify({
replyToken: reply_token,
messages: [
{
type: 'text',
text: 'ご利用ありがとうございます。以下のメッセージをグループへ送信しました。'
},
{
type: 'text',
text: message
}
]
})
});
// グループへメッセージをpush
UrlFetchApp.fetch(line_push, {
headers: {
'Content-Type': 'application/json; charset=UTF-8',
Authorization: 'Bearer ' + CHANNEL_ACCESS_TOKEN
},
method: 'post',
payload: JSON.stringify({
to: GROUP_ID,
messages: [
{
type: 'text',
text: message
}
]
})
});
// グループIDを取得する
} else if (message == SETTING_MESSAGE) {
// キーワードが等しい場合にグループID返信する
UrlFetchApp.fetch(line_reply, {
headers: {
'Content-Type': 'application/json; charset=UTF-8',
Authorization: 'Bearer ' + CHANNEL_ACCESS_TOKEN
},
method: 'post',
payload: JSON.stringify({
replyToken: reply_token,
messages: [
{
type: 'text',
text: 'グループIDは以下の通りです。'
},
{
type: 'text',
text: gid
}
]
})
});
}
return ContentService.createTextOutput(
JSON.stringify({ content: 'post ok' })
).setMimeType(ContentService.MimeType.JSON);
}
解説
メッセージにgroupIdが含まれるかで条件を分岐させています。
- 対ユーザーには送信メッセージのリピート(確認メッセージ)
- 対グループにユーザーから送信されたメッセージを送信
また、groupIdを取得するためには、
- BOT をグループに参加させる
set
を発言する- BOT がgroupIdを発信します
以上で取得できます。
取得したgroupIdは GAS 上のコードのGROUP_IDの箇所へ入れてください。
入れると、指定したグループへ発信できます。
まとめ
groupIdの取得やグループへの発信、対個人への発信などがやや面倒だと感じました。
また、グループに参加したときのイベントjoinを利用すればよかったのですが、うまくイベントを発生させることができず、setというキーワードを今回は利用しています。
そのほかにも、GAS を利用すれば、
- メールの送信
- スプレッドシートへのログの記載
などなど、色々と派生させることができると思いますので、色々カスタマイズできると思います。