ポケモンBoTとは
LINEでポケモンの名前を言うと、博士が教えてくれます。
その他にも、1文字だけ言うとその文字で始まるポケモンを教えてくれたり、ハッシュタグ風に言うとポケモンしりとりをしてくれます。
システム概要
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でやること
- LINE Messaging APIでチャンネルを作成します
- チャンネルトークンを発行します。(GASのスクリプトで利用します)
- WebhookにGASでデプロイしたときに発行されたURLを指定します
最後に
LINE Developersに作成したチャネルにあるQRをLINEアプリで読み込み友達登録、
トーク画面でポケモンをメッセージ送信します。