友達の寿司打ツイートからスコアを集計したい！

# はじめに
僕の友人は、毎朝寿司打というタイピングゲームに取り組み日々タイピングスキルを磨いております。


そんな友人と話しているとき、「寿司打のデータ集計したらわかりやすいけど、自分でやるのはめんどくさい」という話が出てきました。

彼は、毎朝寿司打に取り組み、そのスコアをTwitterでツイートしています。

そこで、今までのツイートからスコアをうまく切り出すことができたらデータが集計出来、かつ自分の勉強にもなるということで今回取り組むことにしました。


# 寿司打とは？
[寿司打 - タイピングゲーム](http://typingx0.net/sushida/)

> 『寿司打』はローマ字入力用のタイピング練習ゲームです。
回転寿司のお皿が流れてしまう前に画面の文字をタイプして、どれだけモトを取れるか（= たくさん食べられるか）を競います。
無料＆ブラウザで遊べますので、ちょっとした空き時間の練習や暇つぶしにどうぞ。複数の入力に対応しており、表示されているキー以外にもお好みの打ち方でタイピングいただけます。

自分もやったことがありますが、寿司打はワードチョイスも面白くとても楽しくタイピングの練習が出来るのでおすすめです。


# 使用言語、ライブラリ等
今回はツイートから情報を取得するため、TwitterAPIを使用します。
TwitterOAuthライブラリでTwitterAPIを使用し、Chart.jsを用いて簡易的なグラフを描画します。

- PHP 7.3.18
- JavaScript
- Chart.js 2.7.2
- TwitterOAuth
- TwitterAPI
- composer 2.1.3

TwitterAPIを使用するには、利用申請をする必要があります。今回は申請方法や各Keyの取得方法は省きます（ネットに沢山情報があるのでそちらを参考に）。
なんなら、ここが一番の壁です。ここさえやってしまえば実装自体は簡単です。


# 実際のツイートから方針を決める
実際のツイートは以下の通りです。
前日のリプライに対して、その日の寿司打ツイートを行っているという状態です。
![sushida1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381376/a9a3c88a-8635-68f4-c532-3d09a8c3bc46.png)

寿司打のツイートでは、以下の情報が記載されています。

- コース名
- スコア
- 入力速度
- ミスタイプ数
- 寿司打URL

今回はお試しということで、とりあえず１つのコースでスコアのみを集計することにします。


また、調べてみたところ以下のことがわかりました。

- スコアがマイナスの場合（損）とプラスの場合（得）で、表示テキストが異なる
- スコアの桁数が4桁から「,」で区切られる

![suhida2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381376/c0b856cf-bf6d-1927-bbe8-0bb1fef9dc11.png)

これらを考慮して、データを取得していこうと思います。




# 実装
## TwitterOAuthライブラリのインストール
composerを使用してインストールします。確か、compsoerが古いとバージョンアップしてとメッセージが出てきた気がするので、その際はバージョンアップします。

```php
//TwitterOAuthのインストール
composer require abraham/twitteroauth　

//必要であれば、最新バージョンへアップデート
composer self-update
```

## TwitterAPIキーとトークンの取得
ツイート情報などの取得には、APIキーやトークンが必要です。必要な情報は以下の４つです。

- APIKey
- APISecretKey
- AccessToken
- AccessSecretToken

取得したら、どこかに控えておきましょう。

また、対象ツイートを取得するにはその対象アカウントが公開アカウントである。
もしくは、プライベートアカウントである場合、Twitterデベロッパーアカウントでフォローしている必要があります（多分）


## ツイート取得用処理の実装
以下に全体のコードを記載します。
各処理については、コメントを参考に。

```php
//autoload.phpの読み込み
require __DIR__ . '/vendor/autoload.php';

use Abraham\TwitterOAuth\TwitterOAuth;

// Twitter APIを利用するための認証情報
$api_key = '取得したAPIKeyをセット';
$api_secret_key = '取得したAPISecretKeyをセット';
$access_token = '取得したAccessTokenをセット';
$access_secret_token = '取得したAccessSecretTokenをセット';

//TwitterOAuthクラスのインスタンスを生成
$twitter = new TwitterOAuth($api_key, $api_secret_key, $access_token, $access_secret_token);

$statuses = $twitter->get(
    //特定のユーザーツイートを取得する
    'statuses/user_timeline',
    // 取得するツイートの条件を配列で指定
    array(
        // ユーザー名（@は不要）
        'screen_name'       => '対象のユーザー名',
        // ツイート件数
        'count'             => '200',
        // リプライを除外するかを、true（除外する）、false（除外しない）で指定　今回はリプライの情報も取得したいためfalse
        'exclude_replies'   => 'false',
        // リツイートを含めるかを、true（含める）、false（含めない）で指定
        'include_rts'       => 'false'
    )
);

//ツイートから取得したデータを格納する配列
$sushida_data = [
    'scores' => [],
    'date' => [],
];


//今回取得したい寿司打のモード名
$sushida_mode = '高級10,000円コース【練習】';

// ツイートをループ処理で取り出していく
foreach ($statuses as $tweet) {
    $text = $tweet->text; //ツイート本文
    $date = $tweet->created_at; //ツイート日時

    //「高級10,000円コース【練習】」が文字列内にあるかどうか判定　
    if (strpos($text, $sushida_mode) !== false) {
        //日付データを日本時間にする
        $t = new DateTime($date);
        $t->setTimeZone(new DateTimeZone('Asia/Tokyo'));
        $date = $t->format('Y/m/d');

        $is_profit = is_profit($text); //利益が出たかどうか（スコアがマイナスかプラスか）
        $score = get_score($text, $is_profit);

        //スコアと日付データを配列の先頭に追加（古いツイートを配列の先頭に追加していきたいため）
        array_unshift($sushida_data['date'], $date);
        array_unshift($sushida_data['scores'], $score);
        
    }
}

//json形式に変換（chart.jsでグラフ描画を行うため）
$json_sushida = json_encode($sushida_data);


//スコアを取得する
function get_score($text, $is_profit) {
    //正規表現で、〇〇円分の箇所を正規表現で取得する　〇〇円だと、コースの価格を取得してしまうため
    preg_match("/[0-9]*[,]?[0-9]*円分/", $text, $score);

    //取得したスコア内に「,」がある場合はそれをとり除く
    if (strpos($score[0], ',')) {
        $score[0] = str_replace(',', '', $score[0]);
    }

    //「円分」をとり除く　ついでに数値型に変換もしておく
    $score[0] = intval(str_replace('円分', '', $score[0]));

    //「損」であった場合、マイナスにする
    if (!$is_profit) {
        $score[0] = -$score[0];
    }

    return $score[0];
}

//得しているか損しているかを判定し、真偽値を返す
function is_profit($text) {
    if (strpos($text, 'お得でした！')) {
        return true;
    } else {
        return false;
    }
}

```

## 取得データをもとにグラフを描画する
グラフの描画は、Chart.jsを使用します。取得データをjson形式にしたものをただグラフで描画しているだけです。ネットに沢山参考例があるためそちらを見るとよいかと思います。

```php
<?php require_once('getTweet.php') ?>

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="utf-8">
      <title>寿司打スコア集計</title>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
</head>
<body>
    <canvas id="myLineChart"></canvas>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>

<script>
    // スコアと日付データを格納
    var data = <?php echo $json_sushida; ?>;

    var ctx = document.getElementById("myLineChart").getContext('2d');
    var myChart = new Chart(ctx, {
        type: "bar",    // ★必須　グラフの種類
        data: {
            labels:  data['date'],  // Ｘ軸のラベル
            datasets: [
                {
                    label: "点数",                            // 系列名
                    data: data['scores'],
                }
            ],
        },
        options: {
            title: {                           //タイトル設定
                display: true,                 //表示設定
                fontSize: 18,                  //フォントサイズ
                text: '寿司打スコア集計グラフ'     //ラベル
            },
            //軸設定
            scales: {
                yAxes: [{                      //y軸設定
                    display: true,             //表示設定
                    scaleLabel: {              //軸ラベル設定
                        display: true,          //表示設定
                        labelString: 'スコア',  //ラベル
                        fontSize: 8             //フォントサイズ
                    },
                    ticks: {                      //最大値最小値設定
                        fontSize: 8,             //フォントサイズ
                        stepSize: 500               //軸間隔
                    },
                }],
                xAxes: [{                         //x軸設定
                    display: true,                //表示設定
                    // barPercentage: 0.8,           //棒グラフ幅
                    // categoryPercentage: 0.8,      //棒グラフ幅
                    scaleLabel: {                 //軸ラベル設定
                        display: true,             //表示設定
                        labelString: '日付',  //ラベル
                        fontSize: 8               //フォントサイズ
                    },
                    ticks: {
                        fontSize: 8             //フォントサイズ
                    },
                }],
            }
        }
    });
</script>
</body>

</html>
```

# 集計結果
集計したデータをもとに描画したグラフが以下のものです。

![screencapture-localhost-twitterapp-sushida-php-2021-07-04-15_44_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381376/4fb5ad2f-429e-bbd4-3876-47e73039f933.png)

今回は、お試しということでこのようなシンプルなグラフとなっていますが、入力速度やミスタイプ数などを集計するといいんじゃないかと思います。

現状の問題点としては、今回取得ツイート件数を200件としましたが、寿司打のツイートを200件取得するのではなく最新200件のツイートを取得して、そこから寿司打の対象モードのツイートを取得しています。

そのため、直近のツイートで関係ないツイートが増えるほど集計できるツイートが減ってしまいます。


# まとめ
今回は、TwitterAPIを用いて友人の寿司打ツイートからスコアを取得して集計してみました。
ある程度形式の決まっているツイートであれば簡単にデータを集計できるんだなと思いました。

楽しみながら、勉強にもなって楽しかったです。

以上。


# 参考
[PHP: preg_match() / JavaScript: match() 正規表現チェッカー ver3.1](http://okumocchi.jp/php/re.php)
[[PHP]DateTimeでUTCを日本時間に変換する方法](http://dqn.sakusakutto.jp/2013/10/php_datetime_utc.html)
[Chart.js 日本語ドキュメント](https://misc.0o0o.org/chartjs-doc-ja/)