Node.js
Hubot
Slack
pokemon
PokemonGo

あの公園にポケモンがどれ位いるかリアルタイムで教えてくれるHubot

先日ポケモンの出現を通知する投稿を拝見し(オフィス周辺にポケモンが現れたら Slack チャンネルに通知するようにした)、「面白いことやる人がいるなあ。。」と思ったのですが、自分でも試してみたくなりました。

ただ、残念なことにライブラリ「dgoguerra/pokego-scan」がクローズされてしまったようです。残念。

ライブラリをよく読んでみると代替ライブラリとして使えそうなものが紹介してありました。今回は「 Armax/Pokemon-GO-node-api」を利用してみることにしました。

あの公園に行った気になる

Slackに設置したHubotに「go 〇〇公園」みたいに話しかけると事前に登録した自分の
Pokémon GOアカウントに接続して、その近辺にいるモンスターを教えてくれます。

試しに話題になった愛知県の「鶴舞公園」に行ってみることにします。

Slack 2016-07-27 22-29-24.png

HubotはデフォルトでGoogle Map検索が出来るので一応キーワードが正しく反映されるか確認するのも良いかもしれません。

設定は変更出来るのですが、今回は「位置」「座標」「ユーザー名」「モンスターの最大値」「アイテムの最大値」「ポケコイン数」「スターダストの数」を表示させてみました。

モンスターは地図上の200m以内に出現したものを表示させます。ここでは5秒毎に5回検索をかけいます。

これは(恐らく)ルアーで出現したものは含まれないと思います。

ちょっと「新宿御苑」を覗いてみます

Slack 2016-07-27 22-45-52.png

なんかピカチュウ祭りっぽいですね。

こんな感じで楽しむことができます。

設置の仕方

Armax/Pokemon-GO-node-apiのドキュメントに沿ってやってみました。

通常通りHubotをSlackに設定します。その後必要なライブラリを導入します。

terminal
npm install pokemon-go-node-api

それ以外のpokemon-go-node-apiを利用するのに必要なライブラリはnpm installして下さい。

(追記:2016/7/30 必要なさそうです)

package.json
  "dependencies": {
    "events": "^1.1.1",
    "geocoder": "^0.2.2",
    "gpsoauthnode": "^0.0.5",
    "istanbul": "^0.4.4",
    "protobufjs": "^5.0.1",
    "request": "^2.74.0",
    "s2geometry-node": "^1.3.0",
    "tape": "^4.6.0"
  }

その他必要なjsファイルはhubot scriptと同じ階層に設置して下さい。

package.json — pikachubot — :Users:pon_dad:pokemon:pikachubot 2016-07-27 22-56-06.png

Hubot scriptはドキュメントのjsを利用しました。

gogo.js
module.exports = function(robot) {
  robot.respond(/go (.*)/i, function(msg) {
    var PokemonGO = require('./poke.io.js');
    // using var so you can login with multiple users
    var a = new PokemonGO.Pokeio();
    var location = msg.match[1]

    //Set environment variables or replace placeholder text
    var location = {
        type: 'name',
        name: process.env.PGO_LOCATION || location
    };

    var username = process.env.PGO_USERNAME || 'アカウントのアドレス';
    var password = process.env.PGO_PASSWORD || 'アカウントのパスワード';
    var provider = process.env.PGO_PROVIDER || 'google';

    a.init(username, password, location, provider, function(err) {
        if (err) throw err;

        console.log('Current location: ' + a.playerInfo.locationName);
        console.log('lat/long/alt: : ' + a.playerInfo.latitude + ' ' + a.playerInfo.longitude + ' ' + a.playerInfo.altitude);
        msg.send('Current location: ' + a.playerInfo.locationName);
        msg.send('lat/long/alt: : ' + a.playerInfo.latitude + ' ' + a.playerInfo.longitude + ' ' + a.playerInfo.altitude);

        a.GetProfile(function(err, profile) {
            if (err) throw err;

            console.log('Username: ' + profile.username);
            console.log('Poke Storage: ' + profile.poke_storage);
            console.log('Item Storage: ' + profile.item_storage);

            msg.send('Username: ' + profile.username);
            msg.send('Poke Storage: ' + profile.poke_storage);
            msg.send('Item Storage: ' + profile.item_storage);

            var poke = 0;
            if (profile.currency[0].amount) {
                poke = profile.currency[0].amount;
            }

            console.log('Pokecoin: ' + poke);
            console.log('Stardust: ' + profile.currency[1].amount);
            msg.send('Pokecoin: ' + poke);
            msg.send('Stardust: ' + profile.currency[1].amount);

            var count = 0;
            var intervalID = setInterval(function(){
                a.Heartbeat(function(err,hb) {
                    if(err) {
                        console.log(err);
                    }

                    for (var i = hb.cells.length - 1; i >= 0; i--) {
                        if(hb.cells[i].NearbyPokemon[0]) {
                            //console.log(a.pokemonlist[0])
                            var pokemon = a.pokemonlist[parseInt(hb.cells[i].NearbyPokemon[0].PokedexNumber)-1];
                            console.log('There is a ' + pokemon.name + ' at ' + hb.cells[i].NearbyPokemon[0].DistanceMeters.toString() + ' meters');

                            msg.send(pokemon.name + ' at ' + hb.cells[i].NearbyPokemon[0].DistanceMeters.toString() + ' meters');

                        }
                    }

                });
                if (++count === 5) {
                     clearInterval(intervalID);
                 }
            }, 5000);

        });
    });

  });
};

アカウントのアドレスとパスワードはここでは直書きしてしまっていますが、環境変数に格納した方が良いと思われます。

ユーザー名とかステータスなどは必要なければ削除しても良いと思います。

気持ちだけ小旅行

気分だけ小旅行に。(5秒間検索をピックアップしてみました)

新宿御苑
[10:45]  Pikachu at 200 meters
[10:45]  Tangela at 200 meters
[10:45]  Doduo at 200 meters
[10:45]  Pikachu at 200 meters
[10:45]  Pikachu at 200 meters
江の島公園
[11:04]  Seadra at 200 meters
[11:04]  Krabby at 200 meters
[11:04]  Seadra at 200 meters
[11:04]  Krabby at 200 meters
[11:04]  Seadra at 200 meters
大阪城
[11:10]  Cubone at 200 meters
[11:10]  Paras at 200 meters
[11:10]  Clefairy at 200 meters
[11:10]  Cubone at 200 meters
[11:10]  Paras at 200 meters
CentralPark(アメリカ/セントラルパーク)
[11:12]  Rattata at 200 meters
[11:12]  Weedle at 200 meters
[11:12]  Pinsir at 200 meters
[11:12]  Rattata at 200 meters
[11:12]  Weedle at 200 meters
TrafalgarSquare(イギリス/トラファルガー広場)
[11:16]  Paras at 200 meters
[11:16]  Spearow at 200 meters
うちの近くの公園
[11:21]  Rattata at 200 meters
[11:21]  Magikarp at 200 meters
[11:21]  Pidgey at 200 meters

まとめ

このAPIどうもアンドロイド用のAPIを利用しているようです。(最初「Android端末に接続しました」と通知がきます。)アカウントの接続不良が起きた際はkillall nodeコマンドでHubotを一度リセットしてみると良いと思われます。

モンスター名を日本語に変換したかったのですが力及ばずでした...週末にちょっと挑戦してみたいと思います。

それでは皆様良いポケモンライフを。

追記:2016/7/30

日本語対応しました。ポケモンの名前を英語から日本語に翻訳するスクリプトこの投稿で作ったjsonファイルを利用します。

Hubotのファイルは最上階より読み込まれるので、ここでは直下にファイルを設置しました。(lib/pokemons-ja.json

Hubot scriptはこんな感じに書き換えました。

gogo.js
module.exports = function(robot) {
  robot.respond(/go (.*)/i, function(msg) {
    var PokemonGO = require('./poke.io.js');
    var a = new PokemonGO.Pokeio();
    var fs = require("fs");
    var json = JSON.parse(fs.readFileSync('./lib/pokemons-ja.json', 'utf8'));
    var your_location = msg.match[1]

    var location = {
        type: 'name',
        name: process.env.PGO_LOCATION || your_location
    };

    var username = process.env.PGO_USERNAME || 'アカウントのアドレス';
    var password = process.env.PGO_PASSWORD || 'アカウントのパスワード';
    var provider = process.env.PGO_PROVIDER || 'google';

    a.init(username, password, location, provider, function(err) {
        if (err) throw err;

        console.log('Current location: ' + a.playerInfo.locationName);
        console.log('lat/long/alt: : ' + a.playerInfo.latitude + ' ' + a.playerInfo.longitude + ' ' + a.playerInfo.altitude);
        msg.send(a.playerInfo.locationName + 'に来たよ');

        a.GetProfile(function(err, profile) {
            if (err) throw err;

            var count = 0;
            var intervalID = setInterval(function(){
                a.Heartbeat(function(err,hb) {
                    if(err) {
                        console.log(err);
                    }

                    for (var i = hb.cells.length - 1; i >= 0; i--) {
                        if(hb.cells[i].NearbyPokemon[0]) {
                            //console.log(a.pokemonlist[0])
                            var pokemon = a.pokemonlist[parseInt(hb.cells[i].NearbyPokemon[0].PokedexNumber)-1];
                            console.log('There is a ' + pokemon.name + ' at ' + hb.cells[i].NearbyPokemon[0].DistanceMeters.toString() + ' meters');
                            for(var j = 0 ; j<json.length ; j++ ){
                              if(json[j].en === pokemon.name){
                                msg.send(json[j].ja + ' がいるよ');
                                msg.send(pokemon.img);
                              }
                            };
                        }
                    }

                });
                if (++count === 5) {
                     clearInterval(intervalID);
                 }
            }, 5000);

        });
    });

  });
};

よくコードを見てみると、ライブラリのpokemons.jsonの中に画像リンクコードも設置してあるので、そちらのリンクも合わせて投稿してみます。

Slack 2016-07-30 08-57-17.png

こんな感じで表示されました。

余談ですが、ライブラリが一部更新されており少し前にクローンしたものだと動作しませんでした。(最新版をクローンすれば大丈夫でした)こちらもいつまで利用出来るかは分かりませんが、使えるうちに楽しみましょう。

それでは、皆様マナーを守って良きポケモンライフを。