LoginSignup
1
1

More than 1 year has passed since last update.

LINE Messaging APIとGASで作るポケモンBoT

Last updated at Posted at 2022-03-27

ポケモンBoTとは

LINEでポケモンの名前を言うと、博士が教えてくれます。
その他にも、1文字だけ言うとその文字で始まるポケモンを教えてくれたり、ハッシュタグ風に言うとポケモンしりとりをしてくれます。

システム概要

LINE Messaging APIとGASを使ってます。
pokemon-bot (1).jpg

GASでやること

GASではLINE Messaging APIから情報を受け取り、ポケモンデータを生成してレスポンスします。
ポケモンのマスタデータは pokemon_data、画像データは https://pokeapi.co/ を利用させていただいております。
タイプデータはこちらをご利用ください。

pokemon_type.json
{"ノーマル":
  {"こうげき":{"○":[],"△":["いわ","はがね"],"×":["ゴースト"]},
   "ぼうぎょ":{"○":["かくとう"],"△":[],"×":["ゴースト"]}
  }
,"ほのお":
  {"こうげき":{"○":["くさ","こおり","むし","はがね"],"△":["ほのお","みず","いわ","ドラゴン"],"×":["ゴースト"]},
   "ぼうぎょ":{"○":["みず","じめん","いわ"],"△":["ほのお","くさ","こおり","むし","はがね","フェアリー"],"×":[]}
  }
,"みず":
  {"こうげき":{"○":["ほのお","じめん","いわ"],"△":["みず","くさ","ドラゴン"],"×":[]},
   "ぼうぎょ":{"○":["でんき","くさ"],"△":["ほのお","みず","こおり","はがね"],"×":[]}
  }
,"でんき":
  {"こうげき":{"○":["みず","ひこう"],"△":["でんき","くさ","ドラゴン"],"×":["じめん"]},
   "ぼうぎょ":{"○":["じめん"],"△":["でんき","ひこう","はがね"],"×":[]}
  }
,"くさ":
  {"ぼうぎょ":{"○":["ほのお","こおり","どく","ひこう","むし"],"△":["みず","でんき","くさ","じめん"],"×":[]}}
,"こおり":
  {"ぼうぎょ":{"○":["ほのお","かくとう","いわ","はがね"],"△":["こおり"],"×":[]}}
,"かくとう":
  {"ぼうぎょ":{"○":["ひこう","エスパー","フェアリー"],"△":["むし","いわ","あく"],"×":[]}}
,"どく":
  {"ぼうぎょ":{"○":["じめん","エスパー"],"△":["くさ","かくとう","どく","むし","フェアリー"],"×":[]}}
,"じめん":
  {"ぼうぎょ":{"○":["みず","くさ","こおり"],"△":["どく","いわ"],"×":["でんき"]}}
,"ひこう":
  {"こうげき":{"○":["くさ","かくとう","むし"],"△":["でんき","いわ","はがね"],"×":[]},
   "ぼうぎょ":{"○":["でんき","こおり","いわ"],"△":["くさ","かくとう","むし"],"×":["じめん"]}
  }
,"エスパー":{"ぼうぎょ":{"○":["むし","ゴースト","あく"],"△":["かくとう","エスパー"],"×":[]}}
,"むし":{"ぼうぎょ":{"○":["ほのお","ひこう","いわ"],"△":["くさ","かくとう","じめん"],"×":[]}}
,"いわ":{"ぼうぎょ":{"○":["みず","くさ","かくとう","じめん","はがね"],"△":["ノーマル","ほのお","どく","ひこう"],"×":[]}}
,"ゴースト":{"ぼうぎょ":{"○":["ゴースト","あく"],"△":["どく","むし"],"×":["ノーマル","かくとう"]}}
,"ドラゴン":{"ぼうぎょ":{"○":["こおり","ドラゴン","フェアリー"],"△":["ほのお","みず","でんき","くさ"],"×":[]}}
,"あく":{"ぼうぎょ":{"○":["かくとう","むし","フェアリー"],"△":["ゴースト","あく"],"×":["エスパー"]}}
,"はがね":{"ぼうぎょ":{"○":["ほのお","かくとう","じめん"],"△":["ノーマル","くさ","こおり","ひこう","エスパー","むし","いわ","ドラゴン","はがね","フェアリー"],"×":["どく"]}}
,"フェアリー":{"ぼうぎょ":{"○":["どく","はがね"],"△":["かくとう","むし","あく"],"×":["ドラゴン"]}}
}

jsonデータを配置

マイドライブに pokemon_data.json, pokemon_type.json を置きます。

スクリプトを作成しデプロイ

勢いで書きました。

Apps Script
function doPost(e) {

  //リプライ用トークン(LINE
  var reply_token = "";

  var newLine = "<br/>";

  try{ 
    Logger.log('処理開始 PokemonBOT');
        
    //投稿されたコメント
    var content = "";

    try {
      //LINEからの通知処理かどうか
      reply_token= JSON.parse(e.postData.contents).events[0].replyToken;
      if (typeof reply_token === 'undefined') {
        Logger.log('reply_token === undefined');
        return;
      }
    } catch(e) {
      return;
    }

    try {  
      //Botに送信されたメッセージ
      content = JSON.parse(e.postData.contents).events[0].message.text;
      Logger.log(content);

      if (typeof content === 'undefined') {
        throw new Error();
      }
      
      //ログをクリア
      getLogFile().getBody().clear();      
      
      Logger.log('処理開始 PokemonBOT LINE');
      newLine = "\n";
      
    } catch(e) {
      replyMessage('メッセージが読み取れませんでした。\(^o^)/', 54, reply_token);
      Logger.log('LINEからのメッセージが読み取れなかったため処理中止します。');
      Logger.log('処理終了 PokeonBOT');
      return;
    }

    ////////////////////////////////////////////////////////////////
    //コメントが1文字だった場合は、その文字ではじまるポケモンをすべて返す
    if (content.length == 1) {
      var message = "";
      var pokemonDataList = getPokemonDataList(hiraToKana(content));
      
      //ポケモンデータ整形
      if (pokemonDataList.length > 0) {
        for(var i = 0; i < pokemonDataList.length; i++) {
          message += '[' + pokemonDataList[i].no + '] ' + pokemonDataList[i].name;
          if (pokemonDataList[i].form) {
            message += '(' + pokemonDataList[i].form + ')';
          }      
          message += newLine;
        }
        message = message.slice( 0, - newLine.length );
      } else {
        message += userName +  '' + content + '」で始まるポケモンはいませんでした\(^o^)/';
      }
      //画像はリストがあれば1個目のポケモンにして、なければコダック
      replyMessage(message, pokemonDataList.length ? pokemonDataList[0].no : 54 , reply_token);
      Logger.log(message);
      Logger.log('処理終了 PokemonBOT');
      return;
    }

    ////////////////////////////////////////////////////////////////
    //コメントが#はじまりだった場合は、ポケモンしりとりをする
    var firstStr = content.slice(0, 1); 
    if (firstStr == "#" || firstStr == "") {
      content = content.slice(1); 
    }
    
    //ポケモンデータ取得
    var pokemonData = getPokemonData(hiraToKana(content));

    if (!pokemonData) {
      replyMessage(userName + '' + content + '」のポケモンはまだ観測されていません\(^o^)/', 54, reply_token);
      Logger.log('処理終了 PokemonBOT (pokemonData not find.)');
      return;
    }

    ////////////////////////////////////////////////////////////////
    //ポケモンしりとりをする
    if (firstStr == "#" || firstStr == "") {
      var shiritoriList = getPokemonShiritori(hiraToKana(content), 5);
      //ポケモンデータ整形
      var message = hiraToKana(content) + " からはじめるポケモンしりとり" + newLine + "";
   
      for(var i = 0; i < shiritoriList.length; i++) {
        message += shiritoriList[i] + ', ';
      }
      message = message.slice( 0, -2 );
      if (shiritoriList.length == 1) {
        message += '...';
      }
      message += '';
      replyMessage(message, pokemonData.no, reply_token);
      return;
    }

    //進化前のポケモン取得
    var pokemonBeforeData = getPokemonBeforeData(pokemonData.no);

    //進化後のポケモン取得
    var pokemonAfterData = getPokemonAfterData(pokemonData.evolutions);
    
    //ポケモンデータ整形
    var message = "";
    message += '番号 :' + pokemonData.no + newLine;
    message += '名前 :' + pokemonData.name + newLine;
    message += 'タイプ:' + pokemonData.types + newLine;
    if (pokemonBeforeData) {
      message += '進化前:' + pokemonBeforeData.name + newLine;
    }
    if (pokemonAfterData) {
      message += '進化後:' + pokemonAfterData.name + newLine;
    }
    //ダメージ
    var damageObj = getDamage(pokemonData.types);
    if (damageObj) {
      Logger.log(damageObj);
      message += "-- ダメージ --" + newLine;
      if ("" in damageObj) {message += "◎:" + damageObj[""] + newLine;}
      if ("" in damageObj) {message += "○:" + damageObj[""] + newLine;}
      if ("" in damageObj) {message += "△:" + damageObj[""] + newLine;}
      if ("×" in damageObj && damageObj["×"].length > 0) {message += "×:" + damageObj["×"] + newLine;}      
    } else {
      Logger.log("damageObj なし");
    }
    
    Logger.log(message);
    
    if (message) {
      replyMessage(message, pokemonData.no, reply_token);
    }
    
    Logger.log('処理終了 PokemonBOT');
  } catch (e) {
    Logger.log(e);
    replyMessage('エラーが発生しました。管理者にお問い合わせください\(^o^)/', 54, reply_token);
  } finally {
    //ログ出力  
    getLogFile().getBody().appendParagraph(Logger.getLog());
    
    return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
  }
}

// ポケモンの名前からポケモンデータを取得
function getPokemonData(pokemonName) {
  Logger.log('function開始 getPokemonData()');
  var returnData = "";
  
  // Google Driveフォルダーからファイル一覧を取得
  var folder = DriveApp.getFolderById("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
  var files = folder.getFiles();
  while (files.hasNext()) {
    var file = files.next();

    if (file.getName() != 'pokemon_data.json') {
      continue;
    }
    
    // JSONファイルをUTF-8として内容を取得
    var cont = file.getBlob().getDataAsString("utf-8");
    var json = JSON.parse(cont);
    for (var i = 0; i < json.length; i++) {
      if (isNaN(pokemonName)) {
        if (pokemonName == json[i].name) {
          Logger.log('ポケモンデータゲット!');
          Logger.log(json[i]);
          returnData = json[i];
          break;
        }
      } else {
        if (pokemonName == json[i].no) {
          Logger.log('ポケモンデータゲット!');
          Logger.log(json[i]);
          returnData = json[i];
          break;
        }
      }
    }
  }
  Logger.log('function終了 getPokemonData()');
  return returnData;
}

// 進化前ポケモン
function getPokemonBeforeData(no) {
  Logger.log('function開始 getPokemonBeforeData()');
  var returnData = "";

  // Google Driveフォルダーからファイル一覧を取得
  var folder = DriveApp.getFolderById("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
  var files = folder.getFiles();
  while (files.hasNext()) {
    var file = files.next();

    if (file.getName() != 'pokemon_data.json') {
      continue;
    }
    
    // JSONファイルをUTF-8として内容を取得
    var cont = file.getBlob().getDataAsString("utf-8");
    var json = JSON.parse(cont);

    for (var i = 0; i < json.length; i++) {
      if (no == json[i].evolutions) {
        Logger.log('ポケモンデータゲット!');
        Logger.log(json[i]);
        returnData = json[i];
        break;
      }
    }
  }

  Logger.log('進化前ポケモンはありません');
  Logger.log('function終了 getPokemonBeforeData()');
  return returnData;
}

// 進化後ポケモン
function getPokemonAfterData(evolutions) {
  Logger.log('function開始 getPokemonAfterData()');
  var returnData = "";

  if (evolutions.length == 0) {
    Logger.log('進化後ポケモンはありません');
    return returnData;
  }
  
  // Google Driveフォルダーからファイル一覧を取得
  var folder = DriveApp.getFolderById("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
  var files = folder.getFiles();
  while (files.hasNext()) {
    var file = files.next();

    if (file.getName() != 'pokemon_data.json') {
      continue;
    }
    
    // JSONファイルをUTF-8として内容を取得
    var cont = file.getBlob().getDataAsString("utf-8");
    var json = JSON.parse(cont);

    for (var i = 0; i < json.length; i++) {
      if (evolutions == json[i].no) {
        Logger.log('ポケモンデータゲット!');
        Logger.log(json[i]);
        returnData = json[i];
        break;
      }
    }
  }

  Logger.log('進化後ポケモンが取得できませんでした');
  Logger.log('function終了 getPokemonAfterData()');
  return returnData;
}

// 指定された文字(1文字)から始まるポケモンのリストを返す
function getPokemonDataList(startStr) {
  Logger.log('function開始 getPokemonDataList()');
  var returnDataList = [];
  
  // Google Driveフォルダーからファイル一覧を取得
  var folder = DriveApp.getFolderById("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
  var files = folder.getFiles();
  while (files.hasNext()) {
    var file = files.next();

    if (file.getName() != 'pokemon_data.json') {
      continue;
    }
    
    // JSONファイルをUTF-8として内容を取得
    var cont = file.getBlob().getDataAsString("utf-8");
    var json = JSON.parse(cont);
    for (var i = 0; i < json.length; i++) {
      if (startsWith(json[i].name, startStr)) {
        Logger.log('ポケモンデータゲット! ' + i);
        returnDataList.push(json[i]);
      }
    }
  }
  Logger.log('function終了 getPokemonDataList()');
  return returnDataList;
}

// 指定したポケモンの名前でしりとりしたリストを取得
function getPokemonShiritori(name, count) { 
  Logger.log('function開始 getPokemonShiritori()');

  var pokemonList = getPokemonListRundom();

  var shiritoriList = [name];
  var _lastStr = shiritoriReplace(name).slice(-1) ;
  
  for (var i = 0; i < 5; i++) {
    for (var y = 0; y < pokemonList.length; y++) {
      var _name = pokemonList[y].name;
      
      // 最後の文字ではじまるポケモン
      if (startsWith(_name, _lastStr)) {
        // リストにないこと、ンで終わらないこと
        if (shiritoriList.indexOf(_name) == -1 && !endWith(_name, '')) {
          shiritoriList.push(_name);
          _lastStr = shiritoriReplace(_name).slice(-1) ;
          break;
        }
      }
    }
  }
  Logger.log(shiritoriList);
  return shiritoriList;
}

// ポケモンのリストをシャッフルした状態で取得
function getPokemonListRundom() { 
  Logger.log('function開始 getPokemonListRundom()');
  
  // Google Driveフォルダーからファイル一覧を取得
  var folder = DriveApp.getFolderById("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
  var files = folder.getFiles();
  while (files.hasNext()) {
    var file = files.next();

    if (file.getName() != 'pokemon_data.json') {
      continue;
    }
    
    // JSONファイルをUTF-8として内容を取得
    var cont = file.getBlob().getDataAsString("utf-8");
    var json = JSON.parse(cont);

    // シャッフル
    for(var i = json.length - 1; i > 0; i--){
      var r = Math.floor(Math.random() * (i + 1));
      var tmp = json[i];
      json[i] = json[r];
      json[r] = tmp;
    }
    return json;
  }  
}

// リプライ
function replyMessage(message, no, reply_token) {
  Logger.log('function開始 replyMessage()');

  //LINE developerで登録をした、自分のCHANNEL_ACCESS_TOKENを入れて下さい
  var CHANNEL_ACCESS_TOKEN = 'YOU_ACCESS_TOKEN';

  var url = 'https://api.line.me/v2/bot/message/reply';
  UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': reply_token,
      'messages': [{
        'type': 'text',
        'wrap': true, //textおりかえし
        'text': message
      }
                   ,{
                     "type": "image",
                     "originalContentUrl": 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/' + no + '.png',
                     "previewImageUrl": 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/' + no + '.png'
                   }
                  ],
    }),
  });
}

//ひらがなをカタカナに変換
function hiraToKana(str) {
    return str.replace(/[\u3041-\u3096]/g, function(match) {
        var chr = match.charCodeAt(0) + 0x60;
        return String.fromCharCode(chr);
    });
}

//前方一致
function startsWith(target, pattern) {
  return target.indexOf(pattern) === 0; 
}

//後方一致
function endWith(target, pattern) {
  if((target.lastIndexOf(pattern)+pattern.length===target.length)&&(pattern.length<=target.length)){
    return true;
  }
  return false; 
}

//しりとり用文字変換
function shiritoriReplace(name) {
  var _name = name;
  replaceFm = new Array('','','','','','','','','','');
  replaceTo = new Array('','','','','','','','','','');
  for (var key in replaceFm) {
    _name = _name.replace(new RegExp(replaceFm[key], 'g'),replaceTo[key]);
  }
  return _name;
}

function getJson(fileName) {
  // タイプjsonを取得
  // Google Driveにjsonをs配置したフォルダーのIDをセット
  var folder = DriveApp.getFolderById("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
  var files = folder.getFiles();
  while (files.hasNext()) {
    var file = files.next();
    
    if (file.getName() != fileName) {
      continue;
    }
    
    // JSONファイルをUTF-8として内容を取得
    var cont = file.getBlob().getDataAsString("utf-8");
    return JSON.parse(cont);
  }
  Logger.log(fileName + ' が取得できませんでした');
  return;
}

//指定したタイプのダメージを返す
//みずとひこうの場合 -> {△=[ほのお, みず, はがね, かくとう, むし], ×=[じめん], ○=[いわ], ◎=[でんき]}
function getDamage(typeArr) {
  Logger.log('function開始 getDamage()');
  try {
    var typeJson = getJson("pokemon_type.json");
    var defenseArr = [];
    for(var i = 0; i < typeArr.length; i++) {    
      if (typeArr[i] in typeJson) {
        defenseArr.push(typeJson[typeArr[i]]["ぼうぎょ"]);
      }
    }
    
    if (defenseArr.length == 0) {
      return;
    }
    if (defenseArr.length == 1) {
      return defenseArr[0];
    }
    
    var damageObj = {};
    var defenseObj1 = defenseArr[0];
    var defenseObj2 = defenseArr[1];
    
    //1に2をマージして damageObj にセット
    if (defenseObj1[""].length > 0){
      for(var i = 0; i < defenseObj1[""].length; i++) {    
        var type1 = defenseObj1[""][i];
        
        if (defenseObj2[""].indexOf(type1) >= 0){
          //両方に存在するので二重弱点
          if (damageObj[""]) {
            damageObj[""].push(type1);
          } else {
            damageObj[""] = [type1];
          }
          continue;
        }
        if (defenseObj2[""].indexOf(type1) >= 0){
          //○と△の場合は等倍ダメージになるので対象外
          continue;
        }
        if (defenseObj2["×"].indexOf(type1) >= 0){
          //○と×の場合は△扱い
          if (damageObj[""]) {
            damageObj[""].push(type1);
          } else {
            damageObj[""] = [type1];
          }
          continue;
        }
        //1にしかない場合
        if (damageObj[""]) {
          damageObj[""].push(type1);
        } else {
          damageObj[""] = [type1];
        }
      }
    }
    if (defenseObj1[""].length > 0){
      for(var i = 0; i < defenseObj1[""].length; i++) {    
        var type1 = defenseObj1[""][i];
        
        if (defenseObj2[""].indexOf(type1) >= 0){
          //両方に存在するので二重耐性
          if (damageObj["×"]) {
            damageObj["×"].push(type1);
          } else {
            damageObj["×"] = [type1];
          }
          continue;
        }
        if (defenseObj2[""].indexOf(type1) >= 0){
          //○と△の場合は等倍ダメージになるので対象外
          continue;
        }
        if (defenseObj2["×"].indexOf(type1) >= 0){
          //△と×の場合は×扱いとする(三重耐性だが
          if (damageObj["×"]) {
            damageObj["×"].push(type1);
          } else {
            damageObj["×"] = [type1];
          }
          continue;
        }
        //1にしかない場合
        if (damageObj[""]) {
          damageObj[""].push(type1);
        } else {
          damageObj[""] = [type1];
        }
      }
    }
    if (defenseObj1["×"].length > 0){
      for(var i = 0; i < defenseObj1["×"].length; i++) {    
        var type1 = defenseObj1["×"][i];
        
        if (defenseObj2[""].indexOf(type1) >= 0){
          //×と○の場合は△扱い
          if (damageObj[""]) {
            damageObj[""].push(type1);
          } else {
            damageObj[""] = [type1];
          }
          continue;
        }
        if (defenseObj2[""].indexOf(type1) >= 0){
          //△と×の場合は×扱いとする(三重耐性だが
          if (damageObj["×"]) {
            damageObj["×"].push(type1);
          } else {
            damageObj["×"] = [type1];
          }
          continue;
        }
        //1にしかない場合
        if (damageObj["×"]) {
          damageObj["×"].push(type1);
        } else {
          damageObj["×"] = [type1];
        }
      }
    }
    
    //1つ目のタイプの弱点タイプ
    var defenseObj1TypeArr = [];
    for(var damage in defenseObj1) {
      defenseObj1TypeArr = defenseObj1TypeArr.concat(defenseObj1[damage]);
    }
    Logger.log(defenseObj1TypeArr);
    
    //2にしかないタイプを damageObj にマージ
    for(var damage in defenseObj2) {
      for(var i = 0; i < defenseObj2[damage].length; i++) {    
        var type = defenseObj2[damage][i];
        //1にないタイプの場合追加
        if (defenseObj1TypeArr.indexOf(type) == -1){
          if (damageObj[damage]) {
            damageObj[damage].push(type);
          } else {
            damageObj[damage] = [type];
          }
        }
      }
    } 
    return damageObj;
  } catch(e){
    Logger.log('Error!! getDamage()');
  }
}

// log output
// ログ出力するGoogleドキュメントのIDをセット
var LOG_FILE_ID = 'YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY';
function getLogFile() {
  if (this.logFile === undefined) {
    this.logFile = DocumentApp.openById(this.LOG_FILE_ID);
  }
  return this.logFile;
}

LINE Messaging APIでやること

  1. LINE Messaging APIでチャンネルを作成します
  2. チャンネルトークンを発行します。(GASのスクリプトで利用します)
  3. WebhookにGASでデプロイしたときに発行されたURLを指定します

最後に

LINE Developersに作成したチャネルにあるQRをLINEアプリで読み込み友達登録、
トーク画面でポケモンをメッセージ送信します。

1
1
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
1
1