LINE Message APIとGoogle apps scriptを使ってアイデア帳を作る

プログラミング
スポンサーリンク

この記事で書かかないこと

LINE developerの登録や、アクセストークンの発行等々は色々なブログで共有されているので割愛。

今回はコードを中心とした解説をします。

※この記事は作成途中です。また更新します。(2018/11/11更新)

大まかな流れ

まず、LINE(bot)にメッセージが送られた時の大まかな流れの説明。
LINEにメッセージが送信されるとwebhook記載のURLにPOST送信でデータが送信されます。
それを最初に受け取る部分、それがdoPOST関数です。
そのため要はdoPOST関数に必要な処理さえ書き込めばそれで事足りるのです。

よく「おうむ返し」するものは作られていますよね。
これは、do POSTで送信されてきたJSONのmessage.typeが「text」であった時のtext部分を一度変数にでも入れて、replyしているだけです。

JSONのtypeについて

ここで、textでない場合はどうなるの?ってすぐに思われた人はいい感じです。
送られてきたJSONのtypeは数種類あります。
LINE Developerのメッセージイベントの確認はこちら

text:皆が良く打つメッセージ(文字列)


JSON:text
{
  "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
  "type": "message",
  "timestamp": 1462629479859,
  "source": {
    "type": "user",
    "userId": "U4af4980629..."
  },
  "message": {
    "id": "325708",
    "type": "text",
    "text": "Hello, world!"
  }
}

sticker:スタンプ


JSON:sticker
{
  "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
  "type": "message",
  "timestamp": 1462629479859,
  "source": {
    "type": "user",
    "userId": "U4af4980629..."
  },
  "message": {
    "id": "325708",
    "type": "sticker",
    "packageId": "1",
    "stickerId": "1"
  }
}

image:画像


JSON:image
{
  "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
  "type": "message",
  "timestamp": 1462629479859,
  "source": {
    "type": "user",
    "userId": "U4af4980629..."
  },
  "message": {
    "id": "325708",
    "type": "image"
  }
}

audio:音声


JSON:audio
{
  "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
  "type": "message",
  "timestamp": 1462629479859,
  "source": {
    "type": "user",
    "userId": "U4af4980629..."
  },
  "message": {
    "id": "325708",
    "type": "audio"
  }
}

file:ファイル


JSON:file
{
  "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
  "type": "message",
  "timestamp": 1462629479859,
  "source": {
    "type": "user",
    "userId": "U4af4980629..."
  },
  "message": {
    "id": "325708",
    "type": "file",
    "fileName": "file.txt",
    "fileSize": 2138
  }
}

video:動画


JSON:video
{
  "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
  "type": "message",
  "timestamp": 1462629479859,
  "source": {
    "type": "user",
    "userId": "U4af4980629..."
  },
  "message": {
    "id": "325708",
    "type": "video"
  }
}

location:位置情報


JSON:location
{
  "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
  "type": "message",
  "timestamp": 1462629479859,
  "source": {
    "type": "user",
    "userId": "U4af4980629..."
  },
  "message": {
    "id": "325708",
    "type": "location",
    "title": "my location",
    "address": "〒150-0002 東京都渋谷区渋谷2丁目21−1",
    "latitude": 35.65910807942215,
    "longitude": 139.70372892916203
  }
}

これで全て網羅できてると思うのですが、何か他にあれば教えて下さい(^-^)

用は送られてきたmessage.typeが上記の場合の処理を記載すればスタンプであろうが、位置情報であろうが適切にreplyすることができます。

アイデア帳コード解説

ざっと処理の流れ

1.LINE Message APIにメッセージを送るmessage.type→text
※Message.typeがtext出ない場合は”処理できないよ!”ってreplyする。
2.textがhelpの場合、使い方をreplyする。
3.textがspreadの場合、GoogleSpreadSheetURLをreplyする。
4.textがideaの場合、ideaをreplyする。
5.testが上記以外の場合、ユーザが入力したtextをシートに書き込む。

少し細かく処理の流れ

2は、message.textを一度変数に入れ、helpと”==”か?って処理をして”TRUE”が返却されれば使い方をreplyすれば良いですね。
3は、2同様GoogleSpreadSheetURLをreplyすれば良いですね。
4は3同様。
5は、上記以外でいけそうです。

問題は、3と4のGoogleSpreadSheetのURL取得(3.1)と、GoogleSpreadSheetへの書き込み(4.1)、GoogleSpreadSheetのideaの取得(4.2)といったところでしょうか。
3.1はGoogleSpreadSheetのClass ServiceのgetURLメソッドを使用すれば大丈夫ですね。
ScriptApp.getService().getURL()

4.1、GoogleSpreadSheetへの書き込みはSpreadSheet名(id)で対象のSpreadSheetを取得して、SpreadSheetのシート名を取得して、そのシートのセル(◯、×)ってすればなんとかなりそうですね。

4.2、GoogleSpreadSheetのideaの取得は4.1同様、対象のSpreadSheetを取得して、SpreadSheetのシート名を取得まで同じで、選択対象範囲を指定してやればなんとかできそうですね。

では、実装内容を見ていきましょう。

まず、全貌から。


line.js
// Messaging APIのアクセストークン
var channel_access_token = 'LINE Message APIアクセストークン';
// 応答メッセージAPIのURL
var line_endpoint = 'https://api.line.me/v2/bot/message/reply';
// ユーザープロフィールAPIのURL
var line_endpoint_profile = 'https://api.line.me/v2/bot/profile';

// Main処理
function doPost(e) {
  var json = JSON.parse(e.postData.contents);
  var reply_token = json.events[0].replyToken;
  // POSTのreplyTokenが'undefined'
  if (typeof reply_token === 'undefined') {
    return;
  }
  // POSTしたユーザのUserIDを取得する
  var user_id = json.events[0].source.userId;
  Logger.log(user_id);
  // POSTしたユーザのメッセージ内容の取得
  var user_message = json.events[0].message.text;
  Logger.log(user_message);
  // 変数宣言
  var reply_messages;
  var spreadSheet;
  // メッセージ内容分岐
  if (user_message === 'help') {
    reply_messages = ['スプレッドシートにアクセスしたい場合は「spread」と入力してください。\nアイデアを思い出したくなったら「アイデア」と入力してください。あなたの過去のアイデアをランダムにお伝えします。\n使い方がわからなくなったら「help」と入力してみてください。'];
  } else if (user_message === 'spread') {
    spreadSheet = getSpreadSheet(user_id);
    reply_messages = [spreadSheet.getUrl()];
  } else if (user_message === 'idea') {
    try {
      spreadSheet = getSpreadSheet(user_id);
      reply_messages = getRandomIdeas(spreadSheet);
    } catch (ex) {
      Logger.log(ex);
    }
  } else if (user_message === 'undefined') {
    reply_messages = ['ゴメンナサイ、文字以外の情報には対応していません。\n使い方が知りたいときは「ヘルプ」と入力してみてください。'];
  } else {
    addToSpreadSheet(user_id, user_message);
    reply_messages = ['アイデアが追加されました'];
}
  var messages = reply_messages.map(function (v) {
    return {'type': 'text', 'text': v};    
  });    
  UrlFetchApp.fetch(line_endpoint, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + channel_access_token
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': reply_token,
      'messages': messages,
    }),
  });
  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}

function getSpreadSheet(user_id) {  
  var sheet_id = PropertiesService.getScriptProperties().getProperty(user_id);  
  if (sheet_id == null) { 
    return createSpreadSheet(user_id);  
  } else {  
    try { 
      return SpreadsheetApp.openById(sheet_id); 
    } catch(e) {  
      return createSpreadSheet(user_id);  
    } 
  } 
} 
  
function getRandomIdeas(spreadSheet) {  
  var ideas = []; 
  var sheet = spreadSheet.getSheets()[0]; 
  var rowNum = sheet.getLastRow();  
  if (rowNum < 1) { 
    ideas.push('ゴメンナサイ、この機能を使うには、ある程度の数のアイデアが必要です。アイデアをいくつか入力してみてください。'); 
  } else {  
    ideas.push('あなたは以前、こんなアイデアを考えていました。');  
    var num = getRandom(rowNum);  
    ideas.push(getIdea(spreadSheet, num));  
    var num2  
    for (var i = 0; i < 100; i++) { 
      num2 = getRandom(rowNum); 
      if (num != num2) {  
        break;  
      } 
    } 
    ideas.push(getIdea(spreadSheet, num2)); 
  } 
  return ideas;   
} 
  
function addToSpreadSheet(user_id, message) { 
  var today = new Date(); 
  var spreadSheet = getSpreadSheet(user_id);  
  var sheet = spreadSheet.getSheets()[0]; 
  sheet.appendRow([today, message]);  
} 

function createSpreadSheet(user_id) {
  var spreadSheet = SpreadsheetApp.create("idea(" + getUserDisplayName(user_id) + ")");
  var sheet = spreadSheet.getSheets()[0];
  sheet.appendRow(['日時', 'メッセージ']);
  PropertiesService.getScriptProperties().setProperty(user_id, spreadSheet.getId());
  var file = DriveApp.getFileById(spreadSheet.getId());
  file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
  sheet.setColumnWidth(1, 130);
  sheet.setColumnWidth(2, 300);
  return spreadSheet;
}

function getUserDisplayName(user_id) {
  var res = UrlFetchApp.fetch(line_endpoint_profile + '/' + user_id, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + channel_access_token,
    },
    'method': 'get',
  });
  return JSON.parse(res).displayName;
}

function getRandom(num) {
  return Math.ceil(Math.random() * (num - 1));
}

function getIdea(spreadSheet, num) {
  var sheet = spreadSheet.getSheets()[0];
  var time = Utilities.formatDate(sheet.getRange((num + 1), 1).getValue(), 'Asia/Tokyo', 'yyyy/MM/dd hh:mm a');
  var message = sheet.getRange((num + 1), 2).getValue();
  return "(" + time + ")\n" + message;
}

function getSpreadSheet(user_id) {
  var sheet_id = PropertiesService.getScriptProperties().getProperty(user_id);
  if (sheet_id == null) {
    return createSpreadSheet(user_id);
  } else {
    try {
      return SpreadsheetApp.openById(sheet_id);
    } catch(e) {
      return createSpreadSheet(user_id);
    }
  }
}

コード解説詳細

各種APIについて


line.js
// Messaging APIのアクセストークン
var channel_access_token = 'LINE Message APIアクセストークン';・・・①
// 応答メッセージAPIのURL
var line_endpoint = 'https://api.line.me/v2/bot/message/reply';・・・②
// ユーザープロフィールAPIのURL
var line_endpoint_profile = 'https://api.line.me/v2/bot/profile';・・・③

①LINE Message APIアクセストークン

これはXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=っていうLINE Developerログインページ記載の長いトークンです。

②応答メッセージAPIのURL

https://api.line.me/v2/bot/message/reply
上記がLINE Developer Document記載のAPIのURLになります。これはこういうものです。

③ユーザープロフィールAPIのURL

https://api.line.me/v2/bot/profile
上記がLINE Developer Document記載のAPIのURLになります。これもこういうものです。

コードについての解説は少しづつしていきます。

コメント