XboxInfoTwit

ダウンロード ver.2.2.0.0 2010/03/06

Xbox Liveのステータスを定期的に取得して、現在プレイ中のゲーム情報や獲得した実績をTwitterに投稿します。同種のオンラインサービス、ソフトも幾つか存在しますが、比較して「Twitterのパスワードを他者に預けないで済む」「投稿文、投稿条件のカスタマイズが自由自在」「ほぼリアルタイムに取りこぼしなく更新される」「ゲームタイトル等が日本語」というメリットが挙げられます。反面デメリットは「PCを起動している時にしか動作しない」ということになります。なお、このアプリケーションの実行には.NET Framework 3.5 SP1のインストールが必要です。

実行例として、私のXbox360用Twitterアカウントをご覧ください。また、@neuecc/XboxInfoTwitUsers, @neuecc/xboxinfotwitusers2に利用者のリストを作成していますので、カスタマイズの例などとしてご参照ください。なお、リストへの追加はTwitterのPublic Timelineから機械的に取得し追加しています。また、投稿のハッシュタグとして#XboxInfoTwitが用いられているようです。ハッシュタグの追加は任意ですので、加えたい場合は投稿文のカスタマイズでどうぞ。

既知の不具合としては、xbox.comの認証メールアドレスが「msn.com」の場合に利用不可能のようですので、その場合は申し訳ありませんが、Liveに関連付けるメールアドレスを変更するという形で対処してください。

ご意見ご感想バグ報告などは、下記のBlog記事の最新のものへコメントするか、私のTwitterの方に@かメッセージを送っていただけると嬉しいです。

XboxInfoTwit - ver.2.2.0.0

今回の更新は、HTMLをXMLに変換するライブラリをTidy.NetからSGMLReaderに変更しました。数日前にSGMLReaderでLinq to Html最高なんてエントリーを上げていたので、早速実戦投入というわけです。内部コードが割と変わったため、ver2.1系列から2.2へとアップ。利用者的にはぶっちゃけどうでもいい話です。すみません。

ユーザーに関係ある変更点は、先日発売されたばかりのBioShock2で実績が取得出来てなかったので、それを直しました。私はBioShock2でしか確認していないのですが、「カルドセプト」や「のーふぇいと!」も実績が取得出来ないという報告が上がっていたので、今回の修正によって取得出来るようになった、かもしれません。分かりません。カルドセプトやのーふぇいと!を持っている方は実績取れたよー、と教えていただけると助かります。一応Twitter検索で追っかけてはいるんですけど、最近投稿量が多くて(認証者数は1400行きました、ありがとうございます)全然目を通せていなかったりして。

追記:「カルドセプト」、「のーふぇいと!」ともに実績取得出来ているようです。確認していただいた方、ありがとうございました。

ver.2.1.0.1

Jewel Questで「未知のエラー」が発生する件を修正しました。言い訳がましいですが、これXbox.comのバグですよ! Netflixの時もなんじゃこりゃ、と思ったんですが、今回は”Insert translated text here”です。明らかにオイオイオイオイしっかりやれよ、って感じにアレなメッセージが浮かび上がってます。

こんなのがステータス画面のソースを開くと確認出来ます。いやまあ、だから何だって話ではあるのですけど。イレギュラーなことやってるのはコッチですからね……。

さて、ところで今回の不具合は1月上旬に報告を貰ったのに対処したのが1月ギリギリってどういうことよ、すみません本当にゴメンナサイ。不具合情報の報告は大変ありがたいのですけど、ちゃんとそのありがたさに応えなきゃダメですね、私。特に今回は確認も修正も全く難しくないところなので、しっかりしろよ、というお話でして。今後はしっかり対応していきます。

ただ、既知の不具合である、一部の人がログイン段階でコケるという件は全く手付かずです。いやー、自分のとこに環境ないとさっぱり分からん。あ、あとカルドセプトでステータスが反映されない件も放置中です、すみません。気が向いたら、というかソフト入手したらそのうち……。

ver.2.1.0.0

未知のエラーが発生する原因の一つを解消しました。私の確認出来た範囲では、フレンドの中にNetflixを使っている人がいると100%エラーが発生するようでした。原因はプレイ中状況が<Translated text>で、この<がエスケープされてないせいでタグとして認識してたせいでパースに失敗してるせいでした。適当に検索したところTranslated textはちょっと特殊な状況表示?で他のゲームでも出現するようですね、何だろう、翻訳しようと思ったけどまだ出来てませんって感じでしょうか(笑)

で、えーと、これはXbox.comが悪いですよ、ほんと、Microsoftはもっとしっかりサイト作って欲しいなあ。いつぞやかの実績暴走の件だってそもそも……。と、言ってみたところでユーザーからはプログラムがタコなせいにしか見えないわけですし、Xbox.comはXbox.comで、イレギュラーなアクセスをしてる輩のことなんて別に考える必要はないわけで、やっぱり悪いのはプログラムですね、あはは。

さて、今回は<Translated text>を丸ごと置換するという頭悪すぎな方法での応急処置をしたのですが、今後も平然と<がエスケープ抜きで登場するようなケースは、ありそうですね……。というわけで、何とかすべきところではあるのですが、汎用的な置換表現を作るのはほぼ不可能だし、全てに対応しようにも如何せん何処に出現するかも不定すぎて無理げ。別の問題が出た時にまた考えることにします。

機能追加が一つ。指定文字列が含まれる場合には投稿しない、という機能を足しました。例えば「Xbox 360 ダッシュボード|Halo Waypoint」にすれば、ダッシュボードとHalo Waypoint再生時は投稿しないようになります。なお、大文字と小文字やスペースの有無を完全に区別しますので、利用するときは一度Twitterに投稿されたものをコピペすると良いと思います。なお、ver.1にあった「ダッシュボードは無視」機能に似ていますが、ver.2のものは起動時投稿設定にも適用されるため、100%、ver.1と同じというわけではありません。うーん、ver.1の起動時設定のみ特別扱いってのがどうかなー、と思っていたので今回の仕様に変更されたわけですが、どうなんでしょうねえ。

ver.2.0.1.0

一部タイトル、例えばアジア版GoW2で実績が取得出来ないという不具合を修正しました。あと、今現在、私の方でもたまに「未知のエラー」が出るのは確認出来ているのですが、ちょっと原因が掴めていない状態なので修正にはもう少し時間がかかりそうです。それと、そもそもXbox.comへのログインに失敗するというのは全く分かってませんので、もう少しどころじゃなく時間がかかりそうです。

そういえば説明を忘れていたのですが、ver.2からver.1にあった「投稿の際ダッシュボードは無視」機能は削ってしまいました。これは、どうやっても綺麗に多言語対応と混ぜることが出来なかったので……。日本語だけに限定すれば決めうちで簡単なのですけどね。利便性的には多言語対応なんかよりもこっちのほうが遙かに上だろ!と突っ込みたい気持ちはとても分かりますが、そんなこんなな事情なので復活させることは恐らくありません。

もう一つ、ver.2からLiveのステータスが離席中になった際もオフライン扱いにしちゃっています。ver.1では中途半端な無視の仕方をしていて、潜在的なバグの危険性があったので、すっぱりとオフラインということにしてしまいました。本体を10分放置しているとスクリーンセーバーが動いて、Liveのステータスも自動的に離席中になるようなのですが、もし本体放置で離席中になるのを拒否したい場合はスクリーンセーバーをオフにすればLiveステータスもずっとオンラインのままになります。スクリーンセーバーの切り替えは本体設定から「システム設定→本体の設定→画面→スクリーンセーバー」で入れます。

ver.2.0.0.0

XboxInfoTwitの認証数が岡本641本吉起を超えた記念、というわけでもないのですが大幅に変更しました。例によって全然テストしてないので動かないとか色々あるかもなので、生暖かい目で見守ってください。というか、ボソッとTwitterでxxで動かねえ、とでも言って貰えると非常に助かります。

今回の更新の主な内容は、クローラーを刷新しIEを使用しなくなった。です。それによって「メモリ消費量激減」(というか前が多すぎた、というか完全にメモリリークしてた) 「スクリプトエラー消滅」「IEでのログイン状態に左右されない」「ページ遷移のクリック音UZEEと無縁」などなど、まあ、これで安心して使えるかと思います。環境依存的に動かなかった人も動くようになった、はず、きっと。そんなこんなで、今回から中身が全く別物になっているので、環境依存、もしくはバグによる動かないケースが(また)増えそうなので、その辺は見つかり次第早めに対処したいと思います。当面は不安定かもしれませんがご了承ください。

挙動の変更としては、実績解除の投稿が必ず行われるようにしました。今までは実績解除後、投稿されるまでの間にXbox360の電源を落としたり別のゲームに変えてしまったりすると解除の投稿を行わなかったのですが、今回からは、電源を落としても別のゲームに変えても実績解除の投稿を行います。ちなみにまだ一回も実績解除を試してないので(デバッグ用にデータをごそごそ弄って解除したフリ、ぐらいはやりましたが)本当に上手く動いてくれるのかは謎です。

あと、エラーメッセージが親切になりました(今まで一律に通信エラーで理解不能だったので) でもタイミング次第では平然と「未知のエラー」とかいう素っ気ない応答しか出しません。酷い。この辺は追々直していこうかな、とは思ってるのですが。

機能追加その一、別言語からの取得が可能に!今まではja-JPだけでしたが、英語ならen-USを、台湾語(中国語?)ならzh-TWを指定することによって、他の言語のデータが投稿されます。別にja-JPしか使わないとは思いますが、将来的にアプリケーション自体を多言語対応にして海外版もリリースしたいなあ、と思っているので(そのタイミングでコードも公開しようと考えてます)そのための下準備の一つです。

機能追加その二、ハッシュタグの自動付加。新しくプレイしたタイトルはタイトル名が記録されて、設定画面のハッシュタグタブの一覧に自動的に追加されます(任意での追加は不可能です)。ここでリストに、例えば「モダン・ウォーフェア2」だったら「MW2」と入力すれば、モダン・ウォーフェア2をプレイ時の投稿全ての末尾に「 #MW2」が付加されます。#に関しては付けなくても自動的に付けます(なのでハッシュタグとしてではなく、フッタとしての利用は現状不可能です)

機能追加その三、バルーンによる投稿通知。私的にはどうでもいいと思ってごほごほ。

2.0.0.1

例によって不具合発覚。20-30分ぐらい使ってると未知のエラーで死ぬようです。あまりにもの未知のエラー祭りは酷すぎた、のでとりあえず様子見で暫定的に対処してみました。うまくいってるかは不明。たかだか10数分間連続利用のテストすらしていないという!すみませんすみません。とりあえず今日は発売日に買ったけど全然プレイしてない(実績数がそれを物語ってる)Fallout3(OBLIVIONは超はまったのにFallout3はさっぱり琴線に触れず)をじっくりプレイしながらテストします、はい。

ver.1.3.0.8

暴走してしまいました。大変申し訳ありませんでした。Xbox.comが半メンテナンスで、壊れたデータを放出していたのですが(例えばFallout3の最大実績が620になったり)、それを取得して解析していた結果、実績を超連続投稿するということが発生しました。今回のものは暴走抑止用の暫定対策版となっています。しかし確実に防げる保証はないので、利用はXbox.comが安定してからにしてください。また、お願いなのですがXboxInfoTwitが不調な場合は、またXboxInfoTwitのクソが不安定だぜ、と思うのは当然なのですけど、少しだけXbox.comのほうも疑ってあげてください。そして、Xbox.comが怪しかったら、その日は利用を控えるという形でお願いします。これからは、データが怪しい場合は弾くような処理も増やしていこうとは思いますが、それでも全ての怪しいケースを弾けるわけではないので。

暴走抑止のほか、とりあえず実績連続投稿の最大数は10に設定しました。5だと、場合によっては少ないケースも出てきそうなのでとりあえず10で。それと、今回のアップデートは全くテストしていないので、そもそも正常動作するかも分かりません。その辺は、Xbox.comが落ち着いたら見ていきたいと思っています。

あと、責任はとても重く感じています……。公開停止しようとも思ったのですが、誰もがこのサイトを見に来ているわけでは当然ないので、まずは修正して新しく起動する人が、自動アップデートで最低限の回避をするのが第一だと思いました。今後の公開停止ですが、既に相当数の利用者がいる状態なので、公開停止にしてメンテ放置するよりは、問題が起こった際にちゃんと面倒を見る方が重要だと考え、当分は公開を続けることにします。

ver.1.3.0.9

連続ですがまた更新。1.3.0.8では実績解除自体が100%投稿できない状態になってました。風呂に入って頭冷やしてたら思い違いに気づいてああああああ、となりました、はは。それと、暴走の原因らしきものが見えたので(原因自体はXbox.comのデータ壊れなのですが、どの部分がどういう風に壊れていたのか、というのが私自体が遭遇してないので想像でしかないのですよー) とりあえずそれへの対策を重点的に追加してみました。原因が見込み違いだったり、他の原因だった場合は、まあ、しょうがない。そういえば今日ダッシュボード機能追加なんですね。毎回、機能追加前はXbox.comも合わせてドタバタしますが、しかし今回ほど酷いこともなかった。ちなみにもう一つの実績解除ツールも暴走していたので、今回のは本当に本当に不測の事態というかXbox.comのデータの壊れ方が誰にとっても想定外でした。天下のMSなのだから、メンテ時でもしっかりやってくれ、というのは贅沢ですかね。

ver.1.3.0.7

CoD4やMW2、L4Dなんかで顕著に見られるようですが、オンライン対戦時の他プレイヤーが「参加可能」時にデータの取得に失敗して、ゲーム名に参加可能が付いていたり実績が0/0になっていたりするような件を修正しました。実のところ、これver1.3.0.1の時に修正したはずだったんですが、いつのまにやらその時対策したはずのコードが元に戻ってました。あらららら……。

といったように、非常にいい加減な開発姿勢なので今回のリリースでも更にバグ埋め込んだりする可能性大です。バグ見つけたら怒ってやってください。Twitter検索でキーワード「XboxInfoTwit」を始終チェックしてますので、もし不具合があったらTwitterでの投稿時に「XboxInfoTwit」と文中に混ぜておけば、例えば「XboxInfoTwitクソ、***で動かねえ」とか言ってくれれば私の方で巡回して気づくと思いますので、気楽に苦情文句要望バグ報告してやってください。

そういえばというわけでもないのですけど、利用ユーザー数が500超えました。いやー、ビックリですね。当初は2桁台に行けばいいなあ、とか言ってたぐらいだったり、実際致命的な不具合があったのに3ヶ月放置してたり(誰も使わないので気づかなかった!)などだったはずが。嬉しいです。が、現在のコード品質は相当アレなので、なるべく早く、せめて今年中には全面的に書き換えたver2.0を出せるといいなあ、なんて思っています。

ちなみに、amazonアサマシゲイツポイントの購入者数はゼロに近かったりします(買ってくれた人は本当に本当にありがとうございます)。いやまあ、別にネタなのでいいんですけどね。はは。

C#でTwitterのStreaming APIを使ってリスト自動追加

XboxInfoTwitの認証数は現在450を越えて、近いうちに500には届きそうです。現在の実装はIEを裏で動かすという、しょーもないものになっていて、それに起因する不具合や、どうしょうもない点が幾つかあるため、クローラー部分は全面的に書き変えようと思っています。あと、エラーメッセージがド不親切とか至らない点だらけでした、すみません。そんな次期バージョンの作業は全然捗ってないのですが、せめて年内ぐらいには何とかしたいです。

@のお話

ゲーム名に@が含まれるものをポストする(例えばTHE IDOLM@STER)と、STERさんに@が飛んで迷惑。というお話を見たので検証してみました。@は行頭かスペース + @ + 数字/アルファベットのものがあると飛びます。つまり、@の前にアルファベットがあれば@は飛びません。なので、別にIDOLM@STERだからってSTERさんに@が飛びまくる、なんてことはありません。正規表現で表すと「(?<=^| )@[a-zA-Z0-9_]+」になります。ついでに、ハッシュタグのほうも軽く検証してみました。基本的には@と同じですが、英単語以外にもリンクが張られるようなので、正規表現は「(?<=^| )#[^ ]+」になるようです。

List

Twitterにリストが実装されました。そこで、XboxInfoTwitユーザーのリストを作ってみることにしました。手動で探して登録も大変なので、プログラムでクロールして追加していきましょう。パブリックイタイムラインからXboxInfoTwit利用者(Source=XboxInfoTwit)の人を片っ端からリスト登録するという方針で行きます。以下、C#でのTwitterストリーミングAPIの使用法と実際のコードになります。同じようなことをやりたい人は、適当に書き替えてどうぞ使ってください。突っ込みどころ多数なのでむしろ突っ込んで欲しい……。

2010/4/29 追記:このコードはストリームAPIの利用法にしては冗長すぎるので、書き直しました → neue cc - C#とLinq to JsonとTwitterのChirpUserStreamsとReactive Extensions ストリームAPI取得コードを参考にする場合は、新しいほうを見てください。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml.Linq;
using System.Collections.Specialized;
using System.Threading;
using System.Xml;
 
static class Program
{
    // ザ・決めうち文字列s
    const string UserName = "neuecc"; // 自分のアカウントのユーザー名を
    const string Password = "password"; // 同じくパスワードを
    const string ListName = "xboxinfotwitusers"; // リストを、入力ですです
    const string StreamApi = "http://stream.twitter.com/1/statuses/sample.xml";
    const string ListMembersApiFormat = "http://twitter.com/{0}/{1}/members.xml";
 
    /// <summary>指定リストにメンバーを追加する</summary>
    static void AddMemberToList(string userName, string listName, int id)
    {
        var url = string.Format(ListMembersApiFormat, userName, listName);
        var wc = new WebClient { Credentials = new NetworkCredential(UserName, Password) };
        wc.UploadValues(url, new NameValueCollection() { { "id", id.ToString() } });
    }
 
    /// <summary>指定リストのメンバーIDを全て取得する</summary>
    static IEnumerable<int> EnumerateListMemberID(string userName, string listName)
    {
        var format = string.Format(ListMembersApiFormat, userName, listName) + "?cursor={0}";
        var cursor = -1L;
        var xmlReaderSettings = new XmlReaderSettings
        {
            XmlResolver = new XmlUrlResolver { Credentials = new NetworkCredential(UserName, Password) }
        };
        while (true)
        {
            using (var xr = XmlReader.Create(string.Format(format, cursor), xmlReaderSettings))
            {
                var xEle = XElement.Load(xr);
                foreach (var item in xEle.Descendants("user").Select(x => (int)x.Element("id")))
                {
                    yield return item;
                }
                cursor = long.Parse(xEle.Element("next_cursor").Value);
                if (cursor == 0) yield break;
            }
        }
    }
 
    /// <summary>ストリームAPIのパブリックタイムラインから無限に取得</summary>
    static IEnumerable<XElement> EnumeratePublicTimeline(StreamReader reader)
    {
        while (true)
        {
            var xmlString = reader.EnumerateLines()
                .TakeWhile(s => s != "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
                .Join();
            if (xmlString == "") continue;
            yield return XElement.Parse(xmlString);
        }
    }
 
    /// <summary>サーバーが死んでないか確認</summary>
    static bool IsServerStatusOK()
    {
        var req = WebRequest.Create("http://twitter.com/help/test.xml");
        HttpWebResponse res = null;
        try
        {
            res = (HttpWebResponse)req.GetResponse();
            if (res.StatusCode == HttpStatusCode.OK) return true;
        }
        catch (WebException e) { Console.WriteLine(e); }
        finally { if (res != null) res.Close(); } // どうでもいいと思っていたり
 
        return false;
    }
 
    static void Main(string[] args)
    {
        ServicePointManager.Expect100Continue = false; // おまじない(笑)
        var count = 0; // モニタリング用のカウント変数(動作的には別に使わない)
 
        var following = new HashSet<int>(EnumerateListMemberID(UserName, ListName));
        var webRequest = (HttpWebRequest)HttpWebRequest.Create(StreamApi);
        webRequest.KeepAlive = true;
        webRequest.Credentials = new NetworkCredential(UserName, Password);
 
    LOOP:
        using (var res = webRequest.GetResponse())
        using (var stream = res.GetResponseStream())
        using (var reader = new StreamReader(stream))
        {
            try
            {
                // 例外が発生しなければ、無限リピートになっているのでこの部分を永久に続けます
                EnumeratePublicTimeline(reader)
                    .Do(_ => { if (++count % 100 == 0) Console.WriteLine("{0} : {1}", DateTime.Now, count); }) // 確認表示用
                    .Where(x => x.Name == "status")
                    .Select(x => new
                    {
                        Source = x.Element("source").Value,
                        ID = (int)x.Element("user").Element("id"),
                        Name = x.Element("user").Element("screen_name").Value
                    })
                    .Where(a => a.Source.Contains("XboxInfoTwit"))
                    .Do(a => Console.WriteLine("Found:{0}", a.Name)) // ここでも確認表示用
                    .Where(a => following.Add(a.ID))
                    .ForEach(a =>
                    {
                        AddMemberToList(UserName, ListName, a.ID);
                        Console.WriteLine("{0} : {1} : {2}", a.Name, DateTime.Now, count); // 確認表示用
                    });
            }
            catch (IOException e)
            {
                Console.WriteLine(e); // 接続が閉じられてたりするのでー。
                while (!IsServerStatusOK())
                {
                    Thread.Sleep(TimeSpan.FromMinutes(5)); // サーバー死んでたら5分間お休み
                }
            }
            finally
            {
                webRequest.Abort(); // これ呼ぶ前にCloseするとハング
            }
        }
        goto LOOP; // goto! goto!
    }
 
    // Extension Methods
 
    public static IEnumerable<string> EnumerateLines(this StreamReader streamReader)
    {
        while (!streamReader.EndOfStream)
        {
            yield return streamReader.ReadLine();
        }
    }
 
    public static string Join<T>(this IEnumerable<T> source)
    {
        return source.Aggregate(new StringBuilder(), (sb, s) => sb.Append(s)).ToString();
    }
 
    public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (var item in source)
        {
            action(item);
        }
    }
 
    public static IEnumerable<T> Do<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (var item in source)
        {
            action(item);
            yield return item;
        }
    }
}

ストリーミングAPIとは無関係のリスト関連の処理や、投げやりなtry-catch-gotoがあって、ちょっとゴチャゴチャしてますが、基本的にはusing三段重ねの部分だけです。無限にXMLが継ぎ足されてくるので、接続を切らさずひたすらReadLine。XMLの切れ目は、XML宣言部を使うことにしましたが、今一つスマートではないです。文字列にしてParseってのもあまり良い感じじゃなく。あ、あと例外処理は全然出来てませんので何かあると平然と死にます。

コードは、書き捨て感全開。例によって何でもLinq、何でもIEnumerable。コレクションになりそうな気配があると、すぐにじゃあyieldね、と考える癖がついてしまっていて。細かいことは後段に任せればいーんだよ、というのが楽ちんでして。リストメンバー全件取得の部分なんかは、わりとスマートに書けてるかと思うのですがどうでしょう。

なお、このストリーミングAPIは全件を漏れなく取得出来るわけではないので、それなり、というかかなり漏れが出ます。なのでXboxInfoTwit使ってるのに登録されねーぞ、という場合は、しょーがない。です。そのうち登録されると思います。あと、このプログラムはサーバー上で24時間動かしているわけじゃなく、私のローカルPC上で動かしているだけなので、私の気まぐれで動かしてたり動かしてなかったりします。私が寝てる間はPCがウルサイので動いてませんし、私が家に居ない時は省エネのために動いてません。なので、むしろ登録されるほうが珍しいです。レアです。効率的には20000件に1人登録出来るか出来ないか、って感じでした。一時間に一人見つかるかどうかも怪しいぐらいの頻度。とてもレア。ぶっちゃけgoogle経由で引っ張ってくるとかしたほうが遙かに効率良さそうですが、まあ、Streaming API使ってみたかったというだけなので。

そういえばですが、逆にリストに登録されてUZEEEE、という場合は、現状はリスト機能がベータのせいなのか拒否は出来ないようです。すみません。UZEEEE、と思っても我慢してください。どうしても嫌な場合は私の方にメッセージをくれれば、リストからの撤去と、プログラムから以後の追加をしないようなコードを入れたいと思っています。

3桁到達

開発者用のOAuth管理ページで認証ユーザー数が見れるのですが(「誰が」までは分からないので安心してください)、いつのまにやらユーザー数が100を超えていました。三桁!奇跡的ですね。はてな同期云々とかフォトライフ云々は壊滅的な状態なので。世の中そんなものです。ついでにこのサイトのアクセス数も壊滅的だったりはします。成り行きでプログラミング系サイトに転換してから半年、以上は経つ感じですが、伸びもせず縮みもせず、ずっと低調をキープ。サイトの内容がガラッと変わったのにアクセス平均が変わらないってのも面白いですけど。検索サイトからのアクセスがほとんどなので、検索キーワードが入れ替わって、でも流入人口は変わらずという。あー、まあ、少し増えた、かな。つまりかわりに常連的な人口が減った、と。おお、虚しい!悲しい!RSSリーダー登録数も伸びないしね。しょぼーん。

ゲーム系サイトへは、XNAで返り咲きしたいとこっそり思ってます。XNAは使用言語がC#だからね。C#好きーなのです、私。積みタスクを全部消化したらXNAやりたいんですが中々どうして……。 そういえばインディーズゲーム(XBIG)を完全スルー状態なのはぶっちけ(略)

fromにクライアント名が出るようになってからは、googleのサイト検索で利用具合が見つかるので、毎日24時間以内の結果を眺めていたりします。見ていて思うというか教訓は、デフォルト設定大事ってことでしょうかね。カスタマイズせずそのまま、カスタマイズする場合も、デフォルトを残しつつ細部を変える、という感じなので、デフォルトの投稿文はちゃんとしたものを用意しなきゃダメなんですね。当たり前といえば当たり前なのですけど、この辺はてなついったー同期ツールは大失敗していて、どうせカスタマイズするだろうと踏んで、書式のサンプルとばかりにゴテゴテのものをデフォルトにしてしまったので……。反省。

あと、デフォルトでは「プレイ中タイトルの状況が変わった時の投稿」はオフにしているのですが、意外とこれをオンにする人が多かったのも驚き。これオンにすると物凄い勢いで投稿されるんですよ。更新間隔を5分にすると、例えばGoW2のHordeすると、WAVE44,45,46…と、全部のWAVE投稿するんじゃないか、最新50個の投稿が全部XboxInfoTwit経由になってますが大丈夫?みたいなことになる。こういう滅茶苦茶なことが出来るのはローカルで動くツールならでは、なのですが(ウェブサービス系じゃあ、ちいと無理ですね、秋のTwitter対応が仮に実績やプレイ状況の投稿に対応するとしても、ここまでの連投は無理かと)フォロワーの目からどうなのか、というと、まあ、分からにゃい。いや、本人の満足が一番だと思いますよ。一日のプレイ後に投稿を眺めると、状況の変化がよく見えて結構楽しかったりはします。お薦めはしませんけどお薦め。別アカでやるなら何も問題なくお薦め。

ver 1.3.0.5 バグ修正とか動かない人用とか色々

動かない-、という人にログを出して貰ったお陰で、幾つか問題を潰せました。本当にありがとうございます。自分の環境で再現出来るエラーを潰せるのは当たり前。再現しないエラーを潰せないのは三流。というわけで三流な私はさっぱり分かりませんでした。分かってみれば、ああ、確かに問題だなーって感じなんですけどねえ。

変更内容は「オフライン→オンライン時投稿のチェックを外していた場合、同期に失敗する不具合を修正」だそうです。えー、こんな初歩ミス埋め込んでたのー、っていうと、そうです、はい、埋め込んでました、はい。げふんげふん。これは酷い。そう、「実績解除」だけ利用できればいいや、って人が利用出来なかったのです、なんだってー。あともう一つ、「GamerTagの入力を大文字小文字を区別しないように変更」です。今まで区別していたので、例えばnEUEcCとか入力すると、同期出来てませんでした。これはいかんですね。いかんので、区別しないようにしました。

ていうか、自分の環境で再現しない問題、じゃなくてただたんに例外ケースの見積もりが甘すぎなだけですなあ。もうちっと気を引き締めて書かないとダメですね。

追記

ver 1.3.0.6になりました。1.3.0.4以降は「+記号」が使えなかったっぽいので、それを直しました。ダメダメすぎて涙。

ver 1.3.0.4 動かなくなったのでちょっと書き換えた

昨日の朝だか昼だかから動かなくなってたっぽいので、それを直しました。何で動かなかったのかよく分かってないんですが、ダメになった箇所だけは分かったので別のアプローチで、ということで。応急処置もいいとこで、まー情けないのですが、動けばいっか、ってことで。眠いし。ダメですかダメですねすみません。

今後だと、一応ダッシュボードのアップデートが8月に控えているらしいので、それでも引っかかってダメになるかもしれませんが、ダメっぽくなったのを捕捉次第、速やかに直したいとは思っているのでダラダラとお待ちください。

ver 1.3.0.3 / ちょっとした拡張メソッド群

Gears of War 2やCall of Duty : World at Warでステータスが取得出来なかった件を修正しました。GoW2は修正を確認出来ましたがCoD:WaWは持ってないので分からない。多分直ってると思うけど。あと、この修正の影響で他がダメになる可能性がなくもないです。ダメっぽいのを発見したら即直す、という方向で暴走さえしなければある程度はいっかー、的にゆるふわ気分でいるのですがダメでしょうかダメですね。

ところで、int.Parseが多くなって結構面倒くさいので文字列に拡張メソッド。

public static int ToInt(this string value)
{
    return int.Parse(value);
}
 
public static int ToIntOrDefault(this string value)
{
    int result;
    int.TryParse(value, out result);
    return result;
}
 
public static string Remove(this string input, string pattern)
{
    return Regex.Replace(input, pattern, "");
}
 
public static Match Match(this string input, string pattern)
{
    return Regex.Match(input, pattern);
}
 
public static string Replace(this string input, string pattern, MatchEvaluator evaluator)
{
    return Regex.Replace(input, pattern, evaluator);
}

RemoveとReplaceは既存のメソッド名のものに足してるので、別の名前のほうが良いかしらん。あと、Replaceは引数が被るので、ただの文字列置換は用意できなかった。まあ、_ => “hogehoge” といった具合に「_ => 」が増えるだけならそう手間でもないような十二分に手間のような。

totalGamerScore = document
    .Descendants("div")
    .First(e => e.GetAttribute("className") == "XbcProfileSubHead")
    .Descendants("strong")
    .Last()
    .InnerText
    .Match(@":\s*(\d+)/")
    .Groups[1]
    .Value
    .ToInt();

こんな風に書けます。これでスクレイピングも快適ー。ドット繋げまくれてバンザイしちゃう。ただまあ、デバッグはし辛いですね。ログ吐くメソッドを仕込んだりもいいんですが、もっと単純に、ラムダ式挟めばデバッガで止められるので

public static class Ext
{
    public static TResult Tap<T, TResult>(this T self, Func<T, TResult> func)
    {
        return func(self);
    }
 
    public static T Tap<T>(this T self, Action<T> action)
    {
        action(self);
        return self;
    }
}

RubyのTapみたいな、ということで。

ダミー置いて、その場で止められるようになる。それで作って、出来あがったらTapの行をCtrl+Xでゴミ箱ぽい。IEnumerableの場合は、普通にダミーのSelect置けばいいだけでしたね! 今、そのことに気付いた。うーん、あと.Groups.Cast<Group>().Skip(1).Select(g => g.Value) も、頻繁にあるあるなので、Matchに拡張メソッド埋めときますか。

ver 1.3.0.2 色々バグ修正

バグフィックス祭り! まずバグ報告があってそれを直して、つまりは普段と少し違う状態の時の処理が全く入ってなかったので、洗い出して処理を入れました。別に全く考えていなかったわけじゃないのですが、想定からの漏れが幾つか、というかたっぷりありまして……。具体的には「申請待ちのフレンドがいるとエラーになる」「ステータスが「退席中」の際にデータが正しく取得出来ない」「ステータスが「取り込み中」の際にエラーになる」です。不具合くせえええ、と思ったら遠慮無く言っていただけるととっても助かりますので、お願いします。

ver 1.3.0.1(XboxInfoTwit) / exception(アーケード)

全てのゲームタイトルで、オンラインマッチ時にステータスが途中参加可能な状態で実績取得が出来ず、タイトル名がおかしくなる件を修正しました。Xbox.comから文字列決めうちで切り出しているので、どんな文言が来るのかのパターンに漏れがありましたというか知らんかった……。(ちなみに現在、待機中でも同様の問題があったりするので、直します、次回に、他にもないかちゃんと調べてから)。こうしてバグ発覚な上に(しかもこの問題は以前からあったっぽいです、全然気づいてなかった)、自信満々に互換性向上!とか言っておきながら、動かなかったって人がいるみたいでごめんなさい。しかも以前のバージョンでは動いていたとなると、もう本当にごめんなさい……。

//

ところで話かわって、exceptionのアーケード版がロケテされてるらしい、新宿南口ワールドで7/9~15だそうだ。というわけで行ってきましたは二日目。実は初日にも行ったんですが、お上りさんなので間違えて東口のタイトーステーションに行って、置いてないなあ、と思ってた。いや、「タイトーステーション 新宿南口ゲームワールド店」自体には何度か行ったことはあるんですよ!でも、タイトーって印象がないというか真面目にセガだと思ってた(笑) 別に西口のセガのと思い違いしてたわけでもなく素で。それで、東口のほうにはインベーダー(=タイトー)の印象があったので、ほぅほぅ、あんなところも南口に含めるのか、とか思ってたとか思ってたとか。悲しい。ゲーセンの店名って意識したことないのよね、あまり行かない人なので。

アーケード版のゲーム内容ですが、普通にexceptionでした。ステージは若干アレンジされていたり、オリジナルステージがあったりなどですが、内容は全くそのままPC版のexception。処理落ちなどもなく、ヌルヌルと動いておりました。ただし破片量はノーマルか、それより少ないか、といった感じで若干の寂しさは否めない。

気になる操作方法はジョイスティックとボタン3つ。全方位STGでスティック一本ということで、ボタン一つが方向 転換用の固定ボタン(PC版のディレクション)に割り当てられていて、これがぶっちゃけ操作しづらい。残りのボタンはレーザーとレイ、二つを(一度離して)同時押しでカタパルト。このカタパルト動作が筋力貧弱な私には存外辛くて、終わり頃には腕が疲れちゃってた。カタパルト→カタパルト→カタパルトと連打するわけなので、ワンボタンで出したいなあ、と思いつつも、まあボタンあまってないししょうがないね。

そんなこんなだから、というわけじゃないのですが、あえなくPC版3面ボス(アーケードでは5面、だったかな?)で死亡してスコアは5位でした。ランキング一位は、PC版作者のi-saint氏のよう。i-saint氏が初見で?クリアしたようなので、なら私も!とか意気込んでいたのですがズタボロ。いてれーたんに普通にいいように弄ばれるという微笑ましさを見せつけました。

ステージ構成はPC 版->アーケードオリジナル->PC版……と交互のようです。PC版は若干短く簡素にアレンジ。アーケードオリジナルのほうは破片の少なさを補うためにギミック中心の構造になっていて新鮮で面白い。ボスも同様に飛び道具系、というか変形する。アーケードオリジナルボス2体目の倒し方が分からなかくてライフが半分減るまで無意味なことやってたり。新鮮なのはよいことです。

全体的には、昔のアーケード→ファミコン移植のような印象。おお、頑張ってる!という。破片量はしょうがないとしても、操作し辛いのはどうしたものかなー。今回私は3ボタン使う上級者向け操作を選んだので、2ボタン操作の初心者向け操作だと印象違うかもしれません。そういえば、エフェクトも弱めなのでカタパルトの爽快感が薄かったような気がする。回りを破片で囲まれてピンチをカタパルトで一気に突破!的なのが、画面が真っ白になって抜け出せたのか何なのかわけわからないというか自機が本当に白に埋まって見失う、のはどうなのだろう、慣れかな?ジオメトリとかも見失うって言われるわけだし。

とはいえ普通にexceptionだったので普通に面白いと思います。オリジナル面も良い感じだし。PC版に比べてどうよ?と言われると完全にノーコメント(すみません)ですけど、ジョイスティックでやるのも新鮮、ということで遊びに行ける人は遊びに行くと良いやも。とりあえず私は途中で死亡したのが悔しいので、明日も行きます、というかロケテ中にはクリアしたい。(それにしても音楽も効果音も全く聞こえなかった、周囲の音うるさすぎ、私はひなびた田舎のゲーセンが好きですよ(笑))

あと、せっかくなので明後日、7/12の日曜日の22:00頃からUstreamでexceptionとexception conflictを実況プレイしようと思うので良ければ見てやってください。

ver 1.3.0.0 ロジック変更/OAuth対応

公開から半年以上経って、何故かここ数週間で利用者が増えてきたようで同時に動かない報告も目にするようになって嬉しくも悲しい。なので、今回ロジック部分を完全に書き換えました。これで色々な環境でも動作するようになった、はず、です。以前に試して動かなかったという方は、再度試していただけると嬉しいです。ついでに取得処理も軽量化しました。真面目に10倍ぐらいは速くなってます(今までが酷過ぎただけなんだけどね!)。そんなこんなで手を入れた部分が多いので、(ただでさえ不安定との定評があったのに)安定性はむしろ下がった、かも……。

それと、認証がパスワード方式からOAuth方式に変更になりました。ユーザーにとってOAuth対応によるメリットは、クライアントアプリではぶっちゃけ別にないどころかむしろ色々な不都合のほうが多いのですが私がやりたかった、ということで勘弁してください。投稿にfrom XboxInfoTwitってクライアント名が表示されるようになって嬉しいのです、にひひ。自動アップデート経由の方は、認証を一度済ませなければならないので手間です、すみません。

あ、で、ver1.2からそうなのですが、現在はAPIは一切利用していませんので遅延はありません。Xbox.comのステータス反映速度(1分以下で追随しているようです)で処理していますので、原理的には他の同種のアプリと同じです。が、多機能風味なため、巡回ページ数が多いのでチンタラ通信してたりする時があって、そのせいで反映が遅いという印象を持たれるかもしれません。今回の軽量化で若干印象は変わると思いますがそんなに変わらないかもしれません。その他、投稿の法則やFAQは同梱の説明書きを見てください。

↑通信内容はこんな感じです(Fiddlerで観測)。30秒かかりました。結構長いけれど、Xbox.comが重いサイトだからねえ……。まずフレンドリストの取得、そこからフレンドのフレンドに飛んで自分のステータスを取得(取得できない場合は取得出来るまで次のフレンドに飛ぶ)、トップページに飛んでプレイ中ゲームのURLを取得、プレイ中ゲームのページから実績を取得。という流れです。これがステータス取得の最短パスじゃないかと思っています。自分のステータスがFriendsOfFriend経由でしか見れないというのが意味不明で困るところ。

そういえばでどうでもいいんですが、タスクバー右クリックに「ゲイツポイントを買う」が追加されてます。アフィです、アフィ。んーと、ノーコメント。

Software