ソーシャルゲームに課金した額を調査する

今までソーシャルゲームにいくら課金したか覚えていますか？
1カ月くらいなら覚えておくことができますが、今まで累計でいくら課金したかを覚えておくことは無理だと思います。
そこで今回は累計課金額を調査してみました。

## 課金情報の収集方法について

課金した情報はどこから収集できるでしょうか。
アプリの中に課金額の表示があるものもありますが、アプリ内から見れたところですべて足していくのは面倒ですしそもそもない場合はどうしようもありません。
今回は課金した際に届くメールから情報を収集することにしました。
iPhoneの場合、AppStoreで課金すると必ずAppleからの領収書がメールで届きます。
それをパースしてやれば目的を達成することができそうです。
(Androidの場合は申し訳ありませんがよくわかりません。)

## メールをテキストにする

メールをパースしてプログラムから扱うためにまずはメールのデータをローカルにエクスポートする必要があります。
エクスポート方法については色々あると思いますが、私は登録にyahooメールを使用しているので以下のサイトを参考にしました。

[Yahoo!メールをテキストファイル形式でバックアップする方法(Thunderbird利用)](https://am-yu.net/2014/02/23/yahoomail-txt-backup/)

## メールをパースする

`ImportExportTools` を使用してエクスポートしたメールは結構カオスな感じになってます。
一部を抜粋すると以下のようになっていました。(一部データを改変しています。)

```text
JPN 	合計
¥9,800
日付
2018年1月1日
ご注文番号

XXXXXXXXXXXXX

App Store 	種類 	購入先 	価格
拡張少女系トライナリー, ジュエル 900個 	拡張少女系トライナリー, ジュエル 900個
レビューを書く | 問題を報告する 	App 内課金 	XXXX 	¥9,800
合計 		¥9,800
	お支払いに関してご不明な点がありましたら、iTunes サポートをご確認ください。

iTunes、iBooks、および App Store での購入時に使用するパスワード設定を管理する方法を見る。
```

上記の例は `拡張少女系トライナリー` に `9800` 円課金したときのものですが、ここから情報を取り出すのはそこそこ大変です。
あまり形が整ってないうえに同時に課金した場合や時期によって形がそこそこ変わるからです。
今回は以下の戦略でパースすることにしました。

1. メール全体からアプリ名の文字列が出現する`Index`を取得し、`StartIndex`とする。
2. `StartIndex`から見て次に`¥`(バックスラッシュではなく円記号)が出現する`Index`を取得し、`YenIndex`とする。
3. `YenIndex`から見て次に`\n`が出現する`Index`を取得し、`EndIndex`とする。
4. `YenIndex`から`EndIndex`までに課金額が入っているので必要のない文字を除去した後`int`値に変換する。
5. 1.に戻る。ただし、最初から探すのではなく`EndIndex`の次から探す。

上の文章を見てもよくわからないと思いますので実際に書いたコードを以下に掲載します。

```csharp
// メールをエクスポートしたフォルダ
var path = @"XXXXXXX\受信トレイ_20180522-2132\メッセージ";
var totalYen = 0;

// Appleからの領収書だけ抜き出す
foreach (var file in Directory.EnumerateFiles(path).Where(f => f.Contains("Apple") && f.Contains("領収書")))
{
    var text = File.ReadAllText(file);
    var index = 0;
    while (true)
    {
        index = text.IndexOf("トライナリー", index);
        if (index < 0)
        {
            break;
        }
        var startIndex = index;

        index = text.IndexOf('¥', startIndex);
        if (index < 0)
        {
            // タイトル名の後に'¥'が来ないのはおかしい(予想外の形式)ので止める
            throw new Exception();
        }
        var yenIndex = index;

        index = text.IndexOf('\n', yenIndex);
        if (index < 0)
        {
            // おかしい
            throw new Exception();
        }

        var endIndex = index;

        var yenStr = text.Substring(yenIndex + 1, endIndex - yenIndex).Trim().Replace(",", "");
        // intになってるはずなので例外チェックしない
        var yen = int.Parse(yenStr);

        totalYen += yen;
    }
}

Console.WriteLine(totalYen);
Console.ReadLine();
```

これで行けると思ったのですが、明らかに課金した額以上が表示されてしまいました。
エクスポートしたメールをよく見てみるとなぜかメールの前半部分と後半部分で同じ内容が入っていることがわかりました。
なぜこうなっているのかはよくわからないのですが、仕方がないのでどうにかしてトリミングできないか考えたところ
中間付近に`プライバシーポリシー`という単語が入っていたのでその部分まででパースを終了するように改良しました。
最終的には以下のコードになりました。

```csharp
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace ConsoleApp12
{
    class Program
    {
        static void Main(string[] args)
        {
            Print("ららマジ", false);
            Print("トライナリー", false);
            Console.ReadLine();
        }

        static void Print(string appName, bool debug)
        {
            var path = @"XXXXXXX\受信トレイ_20180522-2132\メッセージ";
            var totalYen = 0;
            foreach (var file in Directory.EnumerateFiles(path).Where(f => f.Contains("Apple") && f.Contains("領収書")))
            {
                // デバッグ用
                var isFirst = true;

                var text = File.ReadAllText(file);
                var banpei = text.IndexOf("プライバシーポリシー");

                var index = 0;
                while (true)
                {
                    index = text.IndexOf(appName, index);
                    if (index < 0)
                    {
                        break;
                    }
                    if (index > banpei)
                    {
                        break;
                    }

                    var startIndex = index;

                    if (isFirst)
                    {
                        if (debug)
                        {
                            Console.WriteLine(file);
                        }
                        isFirst = false;
                    }

                    index = text.IndexOf('¥', startIndex);
                    if (index < 0)
                    {
                        // タイトル名の後に'¥'が来ないのはおかしい(予想外の形式)ので止める
                        throw new Exception();
                    }
                    var yenIndex = index;

                    index = text.IndexOf('\n', yenIndex);
                    if (index < 0)
                    {
                        // おかしい
                        throw new Exception();
                    }

                    var endIndex = index;

                    var yenStr = text.Substring(yenIndex + 1, endIndex - yenIndex).Trim().Replace(",", "");
                    // intになってるはずなので例外チェックしない
                    var yen = int.Parse(yenStr);

                    if (debug)
                    {
                        Console.WriteLine(Regex.Replace(text.Substring(startIndex, endIndex - startIndex), @"\s", ""));
                    }

                    totalYen += yen;
                }

                if (!isFirst && debug)
                {
                    Console.WriteLine();
                }
            }


            Console.WriteLine($"{appName}:¥{totalYen}");
        }
    }
}
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/42193/65406086-d46b-3d1d-7058-632a3f4555e1.png)

前に手動で調べたときから比べて大体予想通りの増え方をしているのでたぶん成功していると思います。

## 最後に

課金額を調べるのがなかなか面倒だったのでこれからは半自動で計算できるので楽になります。
メールからは課金した日もとれるのでそれを使用してグラフ化するというのも面白いと思います。
もっと楽な方法があれば教えてください。