LWP::UserAgentでイカリング(ニンテンドーネットワーク)の認証を突破する。

イカリングまでの認証をWWW::Mechanizeでやっていたのですが、ページ構成変わった時に対応できないよなーと思ったので、LWP::UserAgentで認証が突破できるようにしました。
参考にしたのは以下のサイトです。
<mini> Miiverse が楽しすぎて… – モノトーンの伝説日記
セッション管理をcookieでやってるようですが、ヘッダにトークンを指定してアクセスとかそういうのはできないものでしょうか。MiiverseもそうだけどAPI公開してくれると楽しいのになぁ。

手順としては以下のことをやっています。

  1. ログイン用のURLにアクセスして認証用のURLを取得
  2. 認証用のURLに認証に必要なパラメータを付与。アクセス。
  3. 返ってきたコールバックURLにアクセス

セッション管理をcookieでやっているので、cookie_jarを設定してクッキーをぱくぱくもぐもぐできるようにしています。クッキー容器っていう名称いいですね。

#!/usr/bin/perl

use warnings;
use strict;
 
use LWP::UserAgent;

my $ua = LWP::UserAgent->new;
$ua->cookie_jar({file =>"cookie.txt", autosave=>1});

my $res = $ua->post("https://splatoon.nintendo.net/users/auth/nintendo");
# 認証用のURLとパラメータを取得
my $location = $res->header("location");

# 認証に必要なパラメータの組み立て
my $dummy_url = URI->new;
$dummy_url->query_form(
"nintendo_authenticate" => "",
"nintendo_authorize" => "",
"scope" => "",
"lang" => "ja-JP" ,
"username" => "ニンテンドーネットワークのid",
"password" => "ニンテンドーネットワークのパスワード"
);

# 認証用のURLにパラメータをくっつける
my $url = URI->new($location.$dummy_url->query);
# 認証用URLにアクセスしてコールバックURLを取得
my $res_auth = $ua->post($url);
my $location_auth = $res_auth->header("location");

# コールバックURLにアクセス
$ua->get($location_auth);

# 目当てのページにアクセス
my $res_login = $ua->get("https://splatoon.nintendo.net/ranking");
print $res_login->content;

Mojoliciousのプレースホルダでドットを含むパスをキャプチャしたくない時の話。

全然別のタイトルで記事書いたのですが、内容が間違いまくってたのと愚痴っぽくてひどかったので消しました。30分位で消したから多分誰も見てないんじゃないでしょうか。

Mojoliciousの通常のプレースホルダはドットとスラッシュをキャプチャせず、含んでいた場合はルーティングに失敗します。が、その挙動を期待していたのに、ルーティングに失敗せずに200OKを返してしまうパターンがあってはまりました。
具体的に言うと、/standard/:nameのようなルートを設定していた場合、/standard/hello.htmlとか、hello.jsonのようなパスを設定されると、普通に200OKが返ってしまいます。
タチが悪いことに、param(‘name’)の値はhelloを返すので、paramの値で分岐するようなコードにしてると、意図しないルートをガード出来てないことになかなか気づきません。
具体例は以下。Perlはv5.16.3、Mojoliciousはv7.05です。

#!/usr/bin/perl

use strict;
use warnings;
use utf8;

use Mojolicious::Lite;

# リラックスプレースホルダ
get '/relax/#name' => sub{
        my $self = shift;
        return $self->render(json =>{param => $self->param('name'), format => $self->stash('format')});
} => 'r';
# /relax/hello.json:
# {"param":"hello.json","format":null}

# 普通のプレースホルダ
get '/standard/:name' => sub{
        my $self = shift;
        return $self->render(json =>{param => $self->param('name'), format => $self->stash('format')});
} => 's';
# /standard/hello.json: 
# {"format":"json","param":"hello"}

# 普通のプレースホルダ+format無効化
get '/standard_disable_format/:name' => [format => 0] => sub{
        my $self = shift;
        return $self->render(json =>{param => $self->param('name'), format => $self->stash('format')});
} => 'sd';
# /standard_disable_format/hello.json:
# status:404

普通のプレースホルダでドットを含んだ値を絶対キャプチャしたくない場合は、[format => 0]を指定しようと思いました。
MojoliciousとかMojolicious::Lite側の仕様は以下。
Mojolicious::Guides::Routing – Routing requests
Mojolicious::Lite – search.cpan.org

splapiはいろいろあってこのルートはガードする必要があるので、v1.12で修正しています。

DateTime::Format::DateManipインストール時にテストでこける。

cpanでDateTime::Format::DateManipをインストールするとテストでコケる。

Running Build test
t/00load.t ......... ok
t/01conversions.t .. WARNING: the TZ Date::Manip config variable is deprecated
         and will be removed in March 2016.  Please use
         the SetDate or ForceDate config variables instead.
Use of uninitialized value $mod in concatenation (.) or string at /usr/local/share/perl5/Date/Manip/TZ.pm line 181.
Use of uninitialized value $mod in concatenation (.) or string at /usr/local/share/perl5/Date/Manip/TZ.pm line 182.
Use of uninitialized value $mod in concatenation (.) or string at /usr/local/share/perl5/Date/Manip/TZ.pm line 183.
ERROR: [config_var] invalid zone in SetDate:
t/01conversions.t .. 1/6
#   Failed test 'Parse Date 'March 23, 2003''
#   at t/01conversions.t line 67.
#          got: '2003-03-23T00:00:00.000000000 JST
# '
#     expected: '2003-03-23T00:00:00.000000000 EST
# '
t/01conversions.t .. 2/6
#   Failed test 'Format Date '2003-03-23T00:00:00''
#   at t/01conversions.t line 73.
#          got: '2003032317:00:00'
#     expected: '2003032303:00:00'

#   Failed test 'Format Date '2003-03-23T12:00:00''
#   at t/01conversions.t line 73.
#          got: '2003032405:00:00'
#     expected: '2003032315:00:00'
# Looks like you failed 3 tests of 6.
t/01conversions.t .. Dubious, test returned 3 (wstat 768, 0x300)
Failed 3/6 subtests

対策

テスト時のタイムゾーンがAsia/Tokyoになってるのが原因なので、環境変数TZ=US/Easternをセットしてテストを実施する。
これは…テストの作り方がまずい気がするんだけどどうなんだろう…。

イカリングからブキ情報をスクレイピングする。

イカリングからブキ情報がスクレイピングできることに昨日初めて気づいたので、スクレイピングしてみました。
毎度ですがWWW::MechanizeとWeb::Scraperを使用します。
どうでもいいけど、一回NokogiriとかYasuri使おうと思ったのに結局RubyでWebスクレイピングしないで今年終わりました。
イカAPIのweaponsエンドポイントはver1.10で追加済みです。
#実はmapsとかgachi/rulesとかも追加してます。
それと、apiary.ioがgithubと連携できるのを昨日始めて知ったのですが、便利すぎてびっくりしました。絶対ローカルで編集してpushしたほうが楽ですね。

my $mech = WWW::Mechanize->new();

#ログイン
$mech->get('https://splatoon.nintendo.net/');
$mech->follow_link( url_regex => qr/auth/i );
$mech->submit_form(
    fields => {
        username => xxx,
        password => xxx
    }
);

#scraper本体
my $scraper = scraper {
    process '#user_intention_weapon option', 'weapons_list[]' => "TEXT";
};

my $spla = $scraper->scrape( $mech->content );
my $weapons_list = $spla->{weapons_list};

my %cnt;
for my $elem(@{$weapons_list}){ #user_intention_weaponが2つあるので多重カウントされる
    $cnt{$elem}++;
}

foreach my $key (sort(keys(%cnt))){
    print $key;
}

来年はネット繋げたいです。それでは良いお年を!

スプラトゥーンのステージ情報を取得できるAPIを作ったけど公開停止した。

イカリングのステージ情報をスクレーピング→MongoDBに保存→Web APIでステージ情報を取得できるようにしました。
スプラトゥーンのステージ情報がとれるやつ(非公式)
もともとは自分用に勝敗記録アプリを作ろうとしてて、ステージ情報も必要になるので、ステージ情報用の機能を別APIとして独立させようと思った次第です。Microserviceというやつですね(流行ってるから言ってみたかっただけ。)
ただし検索機能まで休日中に実装間に合いませんでした。平日がクソ忙しいので一体いつになったら実装できるのやらです。スプラトゥーンやる時間削って実装しました(本末転倒)。過去の情報にどのくらい価値が有るのかはわかりませんが、眺めてみたら面白いんじゃないかなーって思います。
今のところDBの内容を全部返却するようになっています。これ検索できないと意味無くなりそうなので早めに実装したいですね。
もう同様のサービス絶対ありそうだけど自分で作ることに意義があると思ってるので…(ふるえ
検索機能まで作り終わったらソースをgithubに公開します。
→(2015/10/12)公開しました。retrorocket/splapi
デスノートが始まっちゃうので今日はこのへんで。

2017/01/30

サービスの公開を停止しました。DNSのAレコードごとAPIを削除しました。
ご利用ありがとうございました。金輪際子供向けゲームのツールは公開したくありません。良いこと一つもなかったです。
代替APIとしてstat.inkの作者さまがAPIを公開してくださっているので、そちらのご利用をご検討いただければと思います。
splapi.fetus.jp
(2017/02/07) 諸事情によりサービス公開停止理由を記載した記事を削除しました。何かございましたらメールかコメント欄にお願いいたします。

2016/09/26

2015-12-24T15:00:00のレギュラーマッチのステージ情報が誤っていたため修正しました。(シオノメ→アロワナ)
メールでのご指摘ありがとうございました。

2016/09/12

httpsで接続できるようにしました。http/2に対応させています。証明書はLet’s Encryptで取得しています。
Let's Encrypt – Free SSL/TLS Certificates

2016/09/05

想定外のパスでAPIが実行できてしまう不具合を修正しました。(/regular/now.html等、任意の拡張子付きの形式で呼び出せる)
mojoliciousのformatsがデフォルトでONになってるの知りませんでした。

2016/04/19

/now, /next/, /next_all, /prev, /prev_all エンドポイントに全自分待望のstat.inkモードを追加しました。
stat_inkパラメータでonを指定すると、ステージ名とルール名がstat.inkのAPIに対応したもので返ってきます。
(origin_nameに日本語名、stat_ink_nameにstat.ink用の値が格納されます)
いちいちハッシュで変換かますのめんどくさかったので作ってよかったです。ちなみに実装のほうがメチャクソハードコーディングでブサイクなのですが、もうステージとルール追加ないだろうしいいやと思いました。
実行結果は例えばこんなかんじ。
ちなみにスクリプトにバグがあって、stat_inkフラグがずっとonになり続けてる状態が5分くらいありました。すみません。

2016/02/23

イカリングの情報を英語版で拾ってしまう問題と、DBの内容を日本語に修正しました。あと利用ルール守ってない人多すぎるのでほんと勘弁して下さい。

2016/01/27

利用者数も増えてきたようですので、API利用ルールを記載しました。問題が起こるとUAやIPで使用不可にしたりAPIキーを導入せざるを得なくなってしまうので、お手数ですが目を通していただけるとありがたいです。よっぽどのことがない限りはサーバ側でどうにかすべきだと思っているので、アクセスさばけないとかになりそうでもサーバ側でどうにかする予定です。
ちなみに今のところ210.172.144.35(ロリポップのどこか)からのアクセスはイエローカード気味です(UAもないしリファラもないしリンクされてる気配もないのでこっちでリンク元を調べられない&アクセス頻度的に個人利用じゃなさそうなので)

2016/01/06

2015/12/31-2016/01/01にかけて、日付に誤りのあるデータ(2015/12/31開始→2015/01/01終了等)が登録されていたためDB上の情報を修正しました。
スクリプトの修正自体は固定回線復旧後実施します。たぶん正月のnowとnext_allバグってたんじゃないかと思います。すみません。
#バグ報告ありがとうございました。助かりました。
#DBの修正とブログ記事の追記はテザリングでどうにかしました。月初めでよかったです。

2015/12/31

ブキがスクレイピングで取れるのをさっき知ったのでAPIで取得できるようにしました。
(更新用スクリプトが正常に動けば)新ブキ追加後の5分後にリストが更新されます。
たとえばこんなかんじ。
http://splapi.retrorocket.biz/weapons

2015/12/30

自分が必要になったので現在ゲーム上に存在するガチマッチのルールと、全マップのリストを返却するエンドポイントを作成しました。
(更新用スクリプトが正常に動けば)新ルール・マップ追加後の5分後にリストが更新されます。
たとえばこんなかんじ。
http://splapi.retrorocket.biz/maps
http://splapi.retrorocket.biz/gachi/rules

2015/11/18

(イカリングのデータ内容に誤りがあったことによる)11/13のデータの誤りを修正しました。

2015/10/15

map, ruleでOR検索ができるようにしました。カンマ(,)でパラメータを区切ってください。空白とかはいれないでください。
例えばこんな感じ。
splapi.retrorocket.biz/gachi?map=Bバスパーク,ヒラメが丘団地&rule=ガチヤグラ,ガチホコ

2015/09/13

cronのスクレーピングの実施時間見直しました。

2015/09/12

フェスのステージ情報を取得できるようにしました。ドキュメントにはまだ書いてません。
/next, /now, /next_allと組み合わせて使用できます。
?teamでチーム情報を絞り込めます。
たとえばこんなかんじ。
http://splapi.retrorocket.biz/fes?team=ボケ
検索に引っかからなかった場合、空の配列を返却します。
あと、書いてなかったけど、regularとgachiで、toパラメータで検索範囲を絞り込めます。

2015/09/03

特定時間を起点にするステージ情報を取得できるようにしました。
/next, /now, /next_allと組み合わせて使用できます。
たとえばこんなかんじ。
splapi.retrorocket.biz/regular/now?date=2015-09-01T14:00
splapi.retrorocket.biz/regular/next_all?date=2015-09-01T14:00
検索に引っかからなかった場合、空の配列を返却します。

2015/09/01

特定のマップ検索とガチマッチのルール検索ができるようにしました。
/, /next, /now, /next_allと組み合わせて使用できます。
たとえばこんなかんじ。
splapi.retrorocket.biz/regular?map=ネギトロ炭鉱
splapi.retrorocket.biz/gachi/next_all?rule=ガチヤグラ
検索に引っかからなかった場合、空の配列を返却します。

2015/08/31

とりあえず開催中のステージ情報と、次の1件と、次に開催されるすべてのステージ情報が取れるようにしました。
たとえば開催中はこんな感じ。
splapi.retrorocket.biz/regular/now