iT邦幫忙

2021 iThome 鐵人賽

DAY 12
0
自我挑戰組

從無到有打造驗證碼共享的 Line 機器人系列 第 12

幫 Line Bot 加上身份驗證(3)

昨天將產出的驗證碼寫進了 Google Sheet,但我們還需要另一個功能:輸入驗證碼找出所在Row,並且在同一列的其他 column 寫入綁定的 userId 和 綁定時間。這樣使用者在 Line Bot 輸入驗證碼後,我們才能知道綁定這個驗證碼的使用者是誰。

在 Google Sheet 尋找特定欄位的資料

因為產出的驗證碼放在 verification_code 的第一行,所以我們可以取出這一行每個儲存格的值,再查找目標驗證碼的index,從而可以得知該驗證碼所在列號。

新增一個 tagVerificationCode.gs 內容如下:

function tagVerificationCode() {
  var target = 'a55969eb';
  // 連接到 verification_code sheet
  var sheet = ReadMailAndInsertToGoogleSheet.connectToSheet('verification_code');
  // 取得從 A1 開始到最後一列有資料的 AX 的所有值,X = getLastRow()
  var columnValues = sheet.getRange(1, 1, sheet.getLastRow(), 1).getValues();
  // 利用 findIndex 遍歷 columnValues 查找 target 驗證碼,找到的話回傳 index,否則回傳 -1
  var searchResult = columnValues.findIndex((element)=>element[0]==target);
  Logger.log(searchResult);
}

將 target 的值改為想查找的驗證碼,然後執行看看結果:
search result 01

然後進一步將 tagVerificationCode.gs 修改如下:

function tagVerificationCode(target, userId) {
  var sheet = ReadMailAndInsertToGoogleSheet.connectToSheet('verification_code');
  var columnValues = sheet.getRange(1, 1, sheet.getLastRow(), 1).getValues();
  var searchResult = columnValues.findIndex((element)=>element[0]==target);
  if (searchResult !== -1) {
    searchResult++;
    var targetRange =  sheet.getRange(searchResult, 2, 1, 2);
    targetRange.setValues([[userId, new Date()]]);
  }
}

修改後記得儲存,然後選取要執行的函式為 testTagVerificationCode
按下執行後查看結果:
search result 02

試算表結果:
search result 03

將查找的功能抽成共用 function

先前有提過,Google Sheet 雖然可以當成簡易資料庫使用,但其實是沒有 Query & Insert 的概念的。而我們剛剛完成的 tagVerificationCode.gs 裡用來查找某行資料有沒有這個值,其實就有點類似 Query 的概念,所以很適合抽成共用 function 方便之後重複使用。

Read Mail 專案內新增 searchColumnValue.gs

function searchColumnValue(sheet, columnName, target) {
  Logger.log('start to searchColumnValue');
  // 先取得標題列的值
  var headerRowValue = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
  // 查看 columnName 在標題列的 index
  var columnIndex = headerRowValue[0].indexOf(columnName);
  if (columnIndex !== -1) {
    columnIndex++;
    // 抓出 columnName 那行所有的值
    var columnValues = sheet.getRange(2, columnIndex, sheet.getLastRow(), 1).getValues();
    // 回傳查找結果
    var searchResult = columnValues.findIndex((element)=>element[0]==target);
    return searchResult;
  }
}

管理部署 or 重新部署

因為已經部署過 Read Mail了,有修改的話要管理部署 > 編輯 > 增加新版本
edit depoly

Reply Message 的資料庫也要跟著切換版本,看看是否有正確讀到最新版。有時候如果沒有自動同步到最新開發人員版本,可以先切成別的版本再切回來就會正常。

將 verification_code sheet 加上標題列

加上標題列如下:
add header row

因為增加了標題列,所以 Reply Message 的 generateVerificationCode 跟 tagVerificationCode 也需要稍作修改,避免寫入的範圍有誤差

修改 generateVerificationCode.gs 的 insertVerificationCode 如下:

function insertVerificationCode(uniqueVerificationCode) {
  var sheet = ReadMailAndInsertToGoogleSheet.connectToSheet('verification_code');
  var range = sheet.getRange(2, 1, uniqueVerificationCode.length, 1);
  range.setValues(uniqueVerificationCode);
}

修改 tagVerificationCode.gs 如下:

function tagVerificationCode(target, userId) {
  var sheet = ReadMailAndInsertToGoogleSheet.connectToSheet('verification_code');
  var searchResult = ReadMailAndInsertToGoogleSheet.searchColumnValue(sheet, 'code', target);
  if (searchResult !== -1) {
    var targetRange =  sheet.getRange((searchResult+2), 2, 1, 2);
    targetRange.setValues([[userId, new Date()]]);
  }
}

如此一來即可重複引用這個寫好的查找功能~

修改 Reply Message 加上身份驗證

接著讓我們對 Reply Message 的流程稍作修改,加上簡單的身份驗證流程吧

流程構思

  1. Reply Message 收到 Message Event
  2. 查找 userId 是否已驗證
    • 尚未驗證:檢驗輸入內容是否為我們產出的驗證碼,且尚未被綁定
      • 是:進行綁定作業,告知已完成身份認證
      • 否:回應請先進行身分認證綁定
    • 已驗證:檢驗輸入內容是否為獲取驗證碼
      • 是:回應信件內容的驗證碼
      • 否:回應無效輸入

新增 isUserIdVerified.gs

用來判斷這個 userId 是否已經存在我們的綁定紀錄裡

function isUserIdVerified(userId) {
  var sheet = ReadMailAndInsertToGoogleSheet.connectToSheet('verification_code');
  var searchResult = ReadMailAndInsertToGoogleSheet.searchColumnValue(sheet, 'user_id', userId);
  return (searchResult !== -1);
}

修改 replyMessage.gs

將 replyMessage.gs 修改以符合我們的新流程:

const CHANNEL_ACCESS_TOKEN = 'YOUR_CHANNEL_ACCESS_TOKEN';

function doPost(e) {
  var requestContent = JSON.parse(e.postData.contents);
  var event = requestContent.events[0];
  // 必須是 Message Event
  if (event && (event.type === 'message')) {
    var replyToken = event.replyToken;
    var userId = event.source && event.source.userId;
    var userMessage = event.message.text;
    var replyMessage = [];

    if(isUserIdVerified(userId)) {
      replyMessage = userIsVerifiedFlow(userMessage, userId);
    } else {
      replyMessage = userIsNotVerifiedFlow(userMessage, userId);
    }
    
    doReplyMessage(replyMessage, replyToken);
  }
      
  return ContentService.createTextOutput('success');
}

function userIsVerifiedFlow(userMessage, userId){
  return (userMessage === '獲取驗證碼') ? getValidationCodeMessage(userId) : getReplyMessage('無效的輸入');
}

function userIsNotVerifiedFlow(userMessage, userId){
  var bindResult = tagVerificationCode(userMessage, userId);
  var replyMessage = bindResult ? '綁定成功!請點擊選單或輸入獲取驗證碼。' : '請先進行身分認證綁定。'
  return getReplyMessage(replyMessage);
}

function getValidationCodeMessage(userId) {
  var validationCode = ReadMailAndInsertToGoogleSheet.app(userId);
  return [{
    'type': 'text',
    'text': validationCode
  }];
}

function getReplyMessage(message) {
  return [{
        'type': 'text',
        'text': message
  }];
}

function doReplyMessage(replyMessage, replyToken) {
  var payload = {
    replyToken: replyToken,
    messages: replyMessage
  };

  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN
    },
    'method': 'post',
    'payload': JSON.stringify(payload)
  });
}

修改 tagVerificationCode.gs

tagVerificationCode 也要稍作修改,增加檢查驗證碼是否已被綁定過(user_id 不為空),以符合我們的新流程:

function tagVerificationCode(target, userId) {
  var sheet = ReadMailAndInsertToGoogleSheet.connectToSheet('verification_code');
  var searchResult = ReadMailAndInsertToGoogleSheet.searchColumnValue(sheet, 'code', target);
  if (searchResult !== -1) {
    var targetRowIndex = searchResult+2;
    var userIdValue = sheet.getRange(targetRowIndex, 2, 1, 1).getValue();
    if (userIdValue.length === 0) {
      var targetRange =  sheet.getRange((searchResult+2), 2, 1, 2);
      targetRange.setValues([[userId, new Date()]]);
      return true;
    }
  }
  return false;
}

修改完後一樣要管理部署 > 編輯 > 建立新版本 如下:
depoly result

接著把網頁應用程式的網址複製,更新到驗證碼小幫手的 Messaging Api Webhook 網址
忘記怎麼設置的話請看 部署 Google App Script 專案(2) & Line Bot 簡單回應訊息

設定好後就可以用驗證碼小幫手測試看看結果囉~
結果如下圖:
Line bot result

Google Sheet 的結果如下:
Google Sheet Result 01
Google Sheet Result 02

以上~這樣就完成了簡單的身份驗證流程。

當然還有很多可以完善的地方,例如雖然可以交由程式自動產生驗證碼跟綁定,但卻要我們手動發送驗證碼給使用者,使用者也只能手動輸入,實在是太不貼心~但這就是我們可以學習進步的地方!明天要做什麼還尚待決定~差不多也該進入 Liff 應用的篇幅了,那麼就待明天再揭曉主題吧~


上一篇
幫 Line Bot 加上身份驗證(2)
下一篇
應用 LINE Front-end Framework 輕鬆建立互動 (1)
系列文
從無到有打造驗證碼共享的 Line 機器人30

尚未有邦友留言

立即登入留言