使用Google App Script製作Line聊天機器人:教你如何輕鬆統計訂餐數量
1.緣起:
本文分享如何使用Google Apps Scripts 搭配 Google sheets 連接 line 做聊天機器人。 會有這製作這個機器人的想法主要是因為,每次訂便當的時候都要一個一個統計不同便當的數量及總金額,需要花費較多時間,因此才有製作這個聊天機器人的想法來解決這個問題。 但筆者還未學過line bot的製作,所以筆者就上網自學製作出這個聊天機器人,因為在自學的過程中受惠於各位前輩的分享,所以這個專案完成的時候我也決定分享出來。
2.需使用之工具:
Line developer帳號 、Google Sheet、Google Apps Script(簡稱GAS)
3.開始製作:
3.1資料庫部分:
使用Google Sheet作為資料庫
優點: 1.方便且只要有Google帳號的人都可以使用。
缺點:僅小專案使用,大專案較不適合。
3.2程式碼部分:
meal.gs
// 和Google試算表連動
var SpreadSheet = SpreadsheetApp.getActive();
var Sheet_parameter = SpreadSheet.getSheetByName("參數");
var Sheet_origin = SpreadSheet.getSheetByName("原始資料");
var Sheet_order_meal = SpreadSheet.getSheetByName("資料整理");
var Sheet_result = SpreadSheet.getSheetByName("結果");
var date = new Date();
var CHANNEL_ACCESS_TOKEN=Sheet_parameter.getRange(5,3).getValue()
function doPost(e) {
var msg = JSON.parse(e.postData.contents);
try {
// 取出 replayToken 和 發送的訊息文字
var replyToken = msg.events[0].replyToken;
var userMessage = msg.events[0].message.text;
var userID = msg.events[0].source.userId;
if (typeof replyToken === 'undefined') {
return;
}
var Sheet_origin_length = Sheet_origin.getLastRow()
// 特殊指令的處理
switch(userMessage) {
// 清空
case 'clear':
case '/clear':
case '清空':
case '清除':
clear_all_report();
//send_msg(CHANNEL_ACCESS_TOKEN, replyToken, '已清空');
Sheet_parameter.getRange(2,1).setValue('off');
return;
// 列出當前回報情況
case '/list':
case 'list':
case '結單':
case '結果':
send_msg(CHANNEL_ACCESS_TOKEN, replyToken ,Sheet_result.getRange(1,1).getValue());
return;
//開始統計(將參數設為on)
case '開始':
clear_all_report();
Sheet_parameter.getRange(2,1).setValue('on');
return;
//寄送結果至電子信箱
case 'Email':
case '電子郵件':
case 'email':
case 'mail':
send_email()//寄送電子信箱
return
//啟動加1模式
case '加1模式':
case '加ㄧ模式':
case '+1模式':
case '+一模式':
clear_all_report();
Sheet_parameter.getRange(2,2).setValue('on');
return;
default:
console.log('not special command')
}
//replace用法:使用反斜線(/)表示需替換之符號,如/ 表示需替換空白,
// /g表示替換全部,如/ /g表示替換全部字串中的空白符號,如多個條件則使用or(|)
//console.log(userMessage_edit_split.length)
// 訊息內容資料分割
userMessage_edit = userMessage.replace(/\r?\n|\r/g,',')//取代換行
userMessage_edit = userMessage_edit.replace(/(×)|(x)|(X)/g,'*')//取代乘(x)符號
userMessage_edit = userMessage_edit.replace(/ /g,'')//取代空白
userMessage_edit_split = userMessage_edit.split(',')//從,做分割
// 當參數表中顯示為on才開始統計,以免統計錯誤
if(Sheet_parameter.getRange(2, 1).getValue()=='on'){
//輸入原始資料分頁
var username = JSON.parse(getusername(CHANNEL_ACCESS_TOKEN,userID)).displayName//取得輸入人員名稱
Sheet_origin.getRange(Sheet_origin_length + 1, 1).setValue(Sheet_origin_length);//填入項次欄位
Sheet_origin.getRange(Sheet_origin_length + 1, 3).setValue(userMessage);//填入內容欄位
Sheet_origin.getRange(Sheet_origin_length + 1, 2).setValue(username);//填入訊息發送者欄位
Sheet_origin.getRange(Sheet_origin_length + 1, 4).setValue(date);//填入時間欄位
//輸入統計資料分頁
data_arange(userMessage,username)
Sheet_result.getRange(1,1).setValue(print_report_list(Sheet_order_meal,Sheet_parameter))
}else if(Sheet_parameter.getRange(2, 2).getValue()=='on'){
//----------------------------加1模式-----------------------------------
//輸入原始資料分頁
var username = JSON.parse(getusername(CHANNEL_ACCESS_TOKEN,userID)).displayName//取得輸入人員名稱
Sheet_origin.getRange(Sheet_origin_length + 1, 1).setValue(Sheet_origin_length);//填入項次欄位
Sheet_origin.getRange(Sheet_origin_length + 1, 3).setValue(userMessage);//填入內容欄位
Sheet_origin.getRange(Sheet_origin_length + 1, 2).setValue(username);//填入訊息發送者欄位
Sheet_origin.getRange(Sheet_origin_length + 1, 4).setValue(date);//填入時間欄位
//輸入統計資料分頁
add_one_mode(userMessage,username)
Sheet_result.getRange(1,1).setValue(print_report_list(Sheet_order_meal,Sheet_parameter))
}/*else{
send_msg(CHANNEL_ACCESS_TOKEN, replyToken,'統計未開始,請先輸入 開始 或 加1模式 ');
}*/
}
catch(err) {
console.log(err);
}
}
//加1模式訊息處理
function add_one_mode(message,name){
message = message.replace(/(×)|(x)|(X)|(\+)/g,'*')
mesg_split = message.replace(/ /g,'')
meal_number = Sheet_order_meal.getRange(3,4).getValue()
meal = mesg_split.split('*')
if(isNaN(parseInt(meal_number))==true){
total_number=parseInt(meal[1])//計算總數量
}else{
total_number=parseInt(meal_number)+parseInt(meal[1])//計算總數量
}
Sheet_order_meal.getRange(3,4).setValue(total_number)
Sheet_order_meal.getRange(3,5).setValue(Sheet_order_meal.getRange(3,5).getValue()
+ '\r\n' + name + '*' + meal[1])
//計算目前總金額
total_money = parseInt(total_number)*parseInt(Sheet_order_meal.getRange(3,3).getValue())
Sheet_order_meal.getRange(3,7).setValue(total_money)
}
//一般模式訊息處理
//輸入之格式需限定如多個品項用換行
//輸入之格式須為 餐點 價錢 x 數量
function data_arange(message,name){
message = message.replace(/\r?\n|\r/g,',')
message = message.replace(/(×)|(x)|(X)|(\+)/g,'*')
message = message.replace(/ /g,'')
mesg_split = message.split(',')
//replace用法:使用反斜線(/)表示需替換之符號,如/ 表示需替換空白,
// /g表示替換全部,如/ /g表示替換全部字串中的空白符號,如多個條件則使用or(|)
console.log(mesg_split.length)
console.log(mesg_split)
for(i=0; i < mesg_split.length; i++){
meal = mesg_split[i].split('*')
var Sheet_order_meal_length= Sheet_order_meal.getLastRow()
var check_index = NaN //用於判斷資料庫是否已存在該品名
//console.log(meal)
for(ii=0; ii < Sheet_order_meal_length; ii++){
meal_name = Sheet_order_meal.getRange(ii+1,2).getValue()
meal_number = Sheet_order_meal.getRange(ii+1,4).getValue()
//判斷資料庫中是否已存在此餐點
if(meal_name == determine_money(meal[0])[0]){
check_index=ii
break
}
}
//餐點未存在資料庫
if(isNaN(check_index)){
//輸入項次欄位
Sheet_order_meal.getRange(Sheet_order_meal_length + 1,1).setValue(Sheet_order_meal_length - 1)
//輸入餐點欄位
Sheet_order_meal.getRange(Sheet_order_meal_length + 1,2).setValue(determine_money(meal[0])[0])
//輸入價錢欄位
Sheet_order_meal.getRange(Sheet_order_meal_length + 1,3).setValue(determine_money(meal[0])[1])
//輸入數量欄位
Sheet_order_meal.getRange(Sheet_order_meal_length + 1,4).setValue(meal[1])
Sheet_order_meal.getRange(Sheet_order_meal_length + 1,5).setValue(name+ '*' + meal[1])
//計算目前總金額
total_money = parseInt(determine_money(meal[0])[1])*parseInt(meal[1])
Sheet_order_meal.getRange(Sheet_order_meal_length + 1,7).setValue(total_money)
}else{//餐點已存在資料庫
total_number=parseInt(meal_number)+parseInt(meal[1])//計算總數量
Sheet_order_meal.getRange(check_index+1,4).setValue(total_number)
Sheet_order_meal.getRange(check_index+1,5).setValue(Sheet_order_meal.getRange(check_index+1,5).getValue()
+ ',' + name + '*' + meal[1])
//計算目前總金額
total_money = parseInt(total_number)*parseInt(Sheet_order_meal.getRange(check_index+1,3).getValue())
Sheet_order_meal.getRange(check_index + 1,7).setValue(total_money)
}
}
}
//取得訊息中之價錢
function determine_money(meal){
if(isNaN(Number(meal.substr(-3)))==false){
meal_name = meal.split(meal.substr(-3))[0]
meal_money = Number(meal.substr(-3))
}else if(isNaN(Number(meal.substr(-2)))==false){
meal_name = meal.split(meal.substr(-2))[0]
meal_money = Number(meal.substr(-2))
}
return [meal_name,meal_money]
}
//取得輸入人之名稱
function getusername(CHANNEL_ACCESS_TOKEN,userID){
var url = "https://api.line.me/v2/bot/profile/" + userID;
return UrlFetchApp.fetch(url, {
'headers': {
'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
},
'method': 'get',
});
}
function print_report_list(Sheet_order_meal,Sheet_parameter) {
report = '';
for(i = 2; i <Sheet_order_meal.getLastRow(); i++){
report = report + Sheet_order_meal.getRange(i+1, 2).getValue() + '*'
+ Sheet_order_meal.getRange(i+1, 4).getValue()+ '\r\n';
}
list_url = '結果顯示頁面\r\n'+Sheet_parameter.getRange(2, 3).getValue();
report = report +'-------\r\n'+'總金額'+ Sheet_order_meal.getRange(2, 9).getValue()+ '\r\n'
+'總數量'+ Sheet_order_meal.getRange(2, 10).getValue()+ '\r\n' + list_url;
return report;
}
function clear_all_report() {
for(var i= 0; i<100000000;i++ ){
if (Sheet_origin.getLastRow()==1){
break
}
else{
Sheet_origin.deleteRow(Sheet_origin.getLastRow())
}
}
for(var i= 0; i<1000000;i++ ){
if (Sheet_order_meal.getLastRow()==2){
break
}
else{
Sheet_order_meal.deleteRow(Sheet_order_meal.getLastRow())
}
}
Sheet_result.getRange(1,1).setValue('無資料')
Sheet_parameter.getRange(2,2).setValue('off');
Sheet_parameter.getRange(2,1).setValue('off');
}
function send_msg(token, replyToken, text) {
var url = 'https://api.line.me/v2/bot/message/reply';
UrlFetchApp.fetch(url, {
'headers': {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + token,
},
'method': 'post',
'payload': JSON.stringify({
'replyToken': replyToken,
'messages': [{
'type': 'text',
'text': text,
}],
}),
});
}
// 連結HTML檔案
function doGet(){
var x=HtmlService.createTemplateFromFile("Index");
var y= x.evaluate();
// 避免無法顯示在網際網路上
var z = y.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
return z;
}
//取得日期
function getDate(){
dataValues = [
date.getFullYear(),
date.getMonth() + 1,
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
];
get_date = dataValues[0]+'/'+dataValues[1]+'/'+dataValues[2]+'訂餐統計'
console.log(get_date)
return get_date
}
// 抓試算表裡的資料
function getData(){
var Sheet_origin_data=Sheet_origin.getDataRange();
var Sheet_order_meal_data = Sheet_order_meal.getDataRange();
return [Sheet_order_meal_data.getValues(),Sheet_origin_data.getValues()];
}
function send_email(){
let emailAddress = Sheet_parameter.getRange(9, 3).getValue();
let subject = getDate();
let message = Sheet_result.getRange(1, 1).getValue();
MailApp.sendEmail(emailAddress, subject, message);
}
//用於google試算表中之資料清除功能
function datacleanall(){
for(var i= 0; i<100000000;i++ ){
if (Sheet_origin.getLastRow()==1){
break
}
else{
Sheet_origin.deleteRow(Sheet_origin.getLastRow())
}
}
for(var i= 0; i<100000000;i++ ){
if (Sheet_order_meal.getLastRow()==2){
break
}
else{
Sheet_order_meal.deleteRow(Sheet_order_meal.getLastRow())
}
}
Sheet_result.getRange(1,1).setValue('無資料')
Sheet_parameter.getRange(2,1).setValue('off')
Sheet_parameter.getRange(2,2).setValue('off')
}
//建立google sheets的功能
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu("資料庫功能")
.addItem("資料清除","datacleanall")
.addToUi();
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>訂餐統計結果</title>
<style>
table,
th,
td {
border: 1px solid black;
}
tr{
text-align: center;
}
table {
width: 100%;
border-collapse: collapse;
}
</style>
</head>
<body>
<h1><?= getDate() ?></h1>
<h2>!!請點選重新整理,以取得最新統計結果</h2>
<h2>統計結果:</h2>
<? var data = getData(); ?>
<table>
<? for (var i = 0; i < data[0].length; i++) { ?>
<tr>
<? for (var j = 0; j < data[0][i].length; j++) { ?>
<td>
<?= data[0][i][j] ?>
</td>
<? } ?>
</tr>
<? } ?>
</table>
<br>
<h2>原始訊息內容:</h2>
<table>
<? for (var i = 0; i < data[1].length; i++) { ?>
<tr>
<? for (var j = 0; j < data[1][i].length; j++) { ?>
<td>
<?= data[1][i][j] ?>
</td>
<? } ?>
</tr>
<? } ?>
</table>
</body>
</html>
3.3如何連接line bot:
詳細的連結方法可以參考這篇
3.4如何測試:
在寫程式的過程中由於需要測試所寫之函數功能是否符合需求,但如果要發布告line bot再進行測試將會較麻煩,因此筆者使用的測試方式為,直接指定函式所需資料,接著直接在GAS中執行函式確定函式所要處理之功能沒問題後再將原本直接指定的資料換成由line bot接收到的資料。
3.5本程式使用規定!!
一、可使用之指令: 輸入以下雙引號(” “)內之紅色文字 :
- 清除資料庫之資料: “clear”,”/clear”,”清空”或”清除”
- 開始統計(一般模式):”開始”
- 列出結果:”/list”,”list”,”結單”或”結果”
- 開始統計(加1模式):”加1模式”,”加ㄧ模式”,”+1模式”或”+一模式”
- 寄送email:”Email”,”email”,”mail”,”電子郵件”
二、輸入之資料格式(請遵守此格式輸入,否則程式會有問題): 非常重要!!!!!!!!!!
★一般模式:
- 一行一個品項輸入之格式需如多個品項用換行
- 每個品項之輸入格式須為: 餐點 價錢 x 數量
- 輸入之品項中不要有小圖示(表情符號)
★加1模式:
- 輸入之內容僅輸入 + 數量
- 不要用貼圖取代加1
- 不要有小圖示(表情符號)
三、操作說明:
- po完餐點資訊後即可輸入”開始”或”加1模式”相關指令,開始進行統計,如未輸入則不會統計。
- 一般正常留言並遵守第二節之規則即可,如有違反第二節之規則,則可能會導致統計錯誤。
- 結單後輸入列出結果之相關指令即可。
- 參數貼入如下說明
4.程式連結:
4.1程式github連結
訂餐統計機器人,非商業用途歡迎至我的Github下載Google Sheet檔案
5.參考資料:
[2]做個 LINE 機器人記錄誰 +1!群組 LINE Bot 製作教學與分享 (jcshawn.com)
[4]GitHub – GarrettTW/LineBot_ReportMessage: 利用Line機器人來擷取群組內的特格式訊息,最後可整理並輸出成軍中回報的特定訊息