Seaside Laboratory

Posts

怪盗ロワイヤル用ウィンクツール作成記 (JavaScript 編)

ページの内容を読まずに適当にクリックする方式は無理があったので、ちゃんと HTML 読んで解析する方式に変えることにした。

とは言っても、HTML 読むのはそう簡単な話ではない。Windows アプリケーションであれば WinSock を使って HTTP 通信を行うことになるが、IP アドレスの変換といった低レイヤー寄りの処理は面倒なので避けておきたいところ。こういうのは Perl や PHP が得意とする分野だが、通常の Windows 環境ではスクリプトを実行することはできないし、GUI の実装にも向いていない。

仮にこれらの問題が解消されたとしても、ゲーム内の HTML を読むには事前のログインが必要で、ログインするには SSL での通信や Cookie を使った認証などを行わなければならない。今まで何度か簡単な HTTP クライアントや CGI Proxy を作ったことがあるが、リクエストヘッダーの組み立てやステータスコードに応じた処理を書くだけでお腹いっぱいになる。

ということで、前置きが長くなってしまったが今回は JavaScript を使ってウィンクツールを作成することにした。Ajax の基幹技術として使われている XMLHttpRequest はブラウザ付属のオブジェクトだけあってかなり強力で、メソッドを呼び出すだけで面倒な通信処理を全てこなしてくれる。しかも HTML で UI 部分を作成することができるので GUI 面もバッチリ。通常はセキュリティの関係で他のドメインとは通信をすることができないが、IE だとローカル実行時にこの制約がなくなるので IE 専用とした。

ページ取得処理

まず、簡易的なページ取得用のクラスを作成。

function Browse()
{
    this._xhr = new XMLHttpRequest();
    this._userAgent = null;

    /**
     * 連想配列をクエリ文字列に変換
     *
     * @param Object params パラメータ連想配列
     * @return String クエリ文字列
     */
    this.getQueryStringByArray = function(params)
    {
        var pairs = new Array();

        for (var name in params)
        {
            pairs.push(escape(name) + '=' + escape(params[name]));
        }

        return pairs.join('&');
    };

    /**
     * ユーザーエージェントの変更
     *
     * @param String userAgent ユーザーエージェント
     * @return void
     */
    this.setUserAgent = function(userAgent)
    {
        this._userAgent = userAgent;
    };

    /**
     * リクエストの送信
     *
     * @param String method メソッド
     * @param String url URL
     * @param Object params パラメータ連想配列
     * @return String 送信結果
     */
    this.sendRequest = function(method, url, params)
    {
        var body = undefined;

        this._xhr.open(method, url, false);

        // ユーザーエージェントが指定されている場合はリクエストに含める
        if (this._userAgent !== null)
        {
            this._xhr.setRequestHeader('User-Agent', this._userAgent);
        }

        // POST の場合は送信パラメータをセット
        if (method == 'POST')
        {
            // パラメーターを送信するので Content-Type を指定する
            this._xhr.setRequestHeader('Content-Type' , 'application/x-www-form-urlencoded');

            // パラメーターの指定があったか
            if (params !== undefined)
            {
                // 配列を文字に変換
                body = this.getQueryStringByArray(params);
            }
        }

        this._xhr.send(body);

        return this._xhr.responseText;
    };

    /**
     * GET リクエスト
     *
     * @param String url URL
     * @return Object 送信結果
     */
    this.getPage = function(url)
    {
        return this.sendRequest('GET', url);
    };

    /**
     * POST リクエスト
     *
     * @param String url URL
     * @param Object params パラメータ連想配列
     * @return Object 送信結果
     */
    this.postPage = function(url, params)
    {
        return this.sendRequest('POST', url, params);
    };
}

Ajax として使うときは open 時に非同期モードを指定をして応答性を高めるのが一般的だが、今回の場合は逐次処理を行うので同期モードを指定した。

このクラスには最低限の機能が備わっているので、

var browse = new Browse();
browse.getPage('http://example.com/');

というコードを書くだけで簡単にページ内容を取得することができる。

自動ウィンク処理

先程作成した Browse クラスを使って自動ウィンク処理を作成する。まず、ログインを行うコード。

var browse = new Browse();
var url = 'https://ssl.sp.mbga.jp/_lg';
var params =
{
    '_from'   : 'lg',
    'login_cb': '/_lg',
    'new'     : '',
    'login_id': id,
    'login_pw': pw
};
browse.postPage(url, params);

URL やパラメーターはモバゲーのログインフォームを元に作成した。ログインに必要ないと思われるパラメーターも含まれているが、通常のログイン動作に近い方が不正を疑われにくいので忠実に真似しておいた。実際に使う場合は、フォーム入力部である id と pw に適切な値を入れておくこと。

POST してログインに成功すればブラウザ上にログインセッションが生成されるので、あとはページ巡回とウィンクをひたすら繰り返すだけ。ただ、単純に実装しただけだと目にも留まらぬ早さでウィンクが行われ、結果的に DoS (Denial of Service) 攻撃のような状態になってしまう。度が過ぎると「過剰アクセス規制中です」というメッセージとともに、アクセス規制を食らってしまうので実験はしないように。

規制を回避するには単純にウィンクスピードを緩めてやればいいわけだが、JavaScript には sleep のような実行休止関数が用意されていないので簡単に休ませることができない。タイマー系の処理は setInterval くらいしか残っていないので、これを使ってどうにかするしかない。

setInterval は一定間隔でコールバック関数を呼び出す関数なので、一塊だったウィンク処理を細切れにし、それらを少しずつ実行するという作戦をとることにした。以下はステートマシンとして実装したウィンク処理の一部。独自のメソッドやプロパティが多く出てくるが、コメントなどから雰囲気を掴んで欲しい。

// 状態に応じて処理を分岐
switch (this._state)
{
case 0: // 怪盗一覧
    // まだ取得ポイント数が閾値に達していない
    if (this._winkPoint < this._maxWinkPoint)
    {
        // 怪盗一覧にアクセス
        html = this._brows.getPage(this._kaitoListUrl);

        // 怪盗 URL 一覧を取得
        this._kaitoUrls = getKaitoUrls(html);

        // 次ページ URL を取得
        this._nextKaitoListUrl = getNextKaitoListUrl(html);

        // 状態を「怪盗ページ巡回」に変更
        this._state = 1;

        // 巡回した怪盗の数を初期化
        this._kaitoCount = 0;
    }
    else
    {
        // 終了
        this.stop();
    }
    break;

ステートマシンというと難しく聞こえるかもしれないが、単に状態に応じた処理を行い、次の状態へ遷移しているだけ。ウィンク処理を分割すると、どこまで実行したのかわからなくなってしまうので、状態を保持できるステートマシンは今回の用途に適している。

あと、クラスとしてウィンク処理を実装した場合、setInterval が要求するのはコールバック「関数」なのでメソッドをそのまま渡すことができない。クロージャーを経由することで呼び出しが出来るようなので、そうすることにした。

// ウィンク処理スタート
this.start = function()
{
    // クロージャー内からの参照用
    var obj = this;

    // クロージャーを定義
    var callback = function()
    {
        // ウィンク処理実行
        obj.run();
    }

    // 1000ms 間隔で呼び出す
    setInterval(callback, 1000);
}

つまづきそうなポイントはほとんど説明したので解説はこれで終わり。残りは自力で作成して大量のウィンクポイントをゲットしよう!

Windows 用やスマホ用のウィンクツールアプリが出回っているようだが、裏でパスワードを抜かれる可能性を考えると怖くて使えない…。