GCPの無料枠サーバーを借りて、毎晩自分のスプラのデイリー戦績をツイートする仕組みを用意したい（戦績取得→ツイート）

# はじめに
[GCPの無料枠サーバーを借りて、毎晩自分のスプラのデイリー戦績をツイートする仕組みを用意したい（鯖立てるまで）](https://qiita.com/kft12/items/64ec0b6e17074fa7969e)
の後編です。

立てたサーバー内でわちゃわちゃして、スプラ3の戦績を取得→ツイートまでの流れです。

利用したもの、参考にさせていただいたサイトは前編と同様です。

# 利用したもの
* Windows10(ローカル)
* Google Cloud　VMインスタンス
* ubuntu(インスタンス)
* python
* s3s
* tweepy

# 参考にさせていただいたサイト
正直、こちらのサイトを見ていただいたほうがわかりやすいし早いかもしれないです。
皆さん大変助かりました、ありがとうございます。

[これから始めるGCP（GCE）　安全に無料枠を使い倒せ](https://qiita.com/Brutus/items/22dfd31a681b67837a74)
[【GCP】無料枠でGCEのVMインスタンス作成からSSH接続・ポート変更まで](https://mintaku-blog.net/gce-vminstance/)
[【Splatoon3】s3sを立てて戦績をstat.inkに自動アップする](https://zenn.dev/hibikine/articles/1febb4eb03b604)
[pythonでtwitter APIを取得する方法【全18枚の図解+例文あり】](https://python-man.club/python_twitter_api/)
[【Python】pythonでtweetしてみた](https://qiita.com/tamago_pinoko/items/221f5df4db06778bf83b)

# 準備をする
### 戦績
戦績の取得に際して、[s3s](https://github.com/frozenpandaman/s3s)というスクリプトを利用させていただきます。

こちらは元々、スプラトゥーンの戦績を管理できる[stat.ink](https://stat.ink/)というサービスに戦績をアップロードしてくれるものなのですが、オプションとしてローカルにjsonファイルを吐き出してくれる機能があったので、そちらを利用します。

python3のアップデート、およびGitの環境を用意します。
作業フォルダは参考にさせていただいたサイトにならい、`/home/test_user/src`とさせていただきました。

```console
sudo apt update
sudo apt install -y git python3 python3-pip
git clone https://github.com/frozenpandaman/s3s
cd s3s
pip3 install -r requirements.txt
```
s3sを利用する際には、以下の2つの設定が必要です。
* stat.inkのAPIキー
* ニンテンドーネットワークのAPIキー

どうやって取ってくるかはこちらのサイト
[【Splatoon3】s3sを立てて戦績をstat.inkに自動アップする](https://zenn.dev/hibikine/articles/1febb4eb03b604)
にて完璧に書いてくださっていたので、割愛します。
まんまこちらと同じことをしました。

### Twitter(tweepy)
Pythonからツイートをするのにあたり、tweepyを利用しました。
pip3でインストールしておきます。
```console
pip3 install tweepy
```

また、Twitterを利用するために下記の4項目が必要になります。
* API Key
* API Secret Key
* Access Token
* Access Token Secret

こちらを準備していきます。

※こちら自分で改めて動かしながら手順まとめようと思ったのですが、ツイッター側の制限により2回目以降は同様の作業ができなかったので思い出しながら書いてます・・。ご了承下さい。

##### ツイッター APIキーの取得
まずは[ツイッターのデベロッパーポータル](https://developer.twitter.com/en/portal/dashboard)にアクセスします。
アクセスしたら、「Create an app」を押下してください。
承認画面が出てきますが、気にせず「Apply」。

次に、どういうものにAPIを使うの？的な項目を入力していきます。
項目1：名前
項目2：国(Japanにしました)
項目3：利用目的(build customized solutions in-house にしました。社内での利用が目的、みたいな意味です)
項目4：政府とかが使う予定はあるか？(Noです)

最後のチェックボックスはメルマガの登録なので、OFFにしておきます。

その後開発ポリシーを確認して、Submitを押すことで登録が完了します。
この時、Twitterアカウントに電話番号を登録していないとエラーが発生するので登録しておきましょう。
エラーが出てからスマホを取り出して、登録するのでも大丈夫です。

登録が完了すると、Twitterアカウントに紐づけているメールアドレス宛に、認証メールが飛びます。
そのメールを開いて、「Confirm your email」を押下することでメールアドレスの認証も完了です。

認証後の画面に「API Key」「API Secret Key」があります。
しっかりと保存しておいてください。

##### アクセストークンの取得
このあたりが、参考にしていたサイトのいずれも微妙に現行版と違っていて進めるのが難しかった記憶があります・・。
ただ、もう一度やり直そうと思うと色々面倒くさいので、キャプチャの用意ができません、すみません・・。

手順の流れとしては一応、下記のような感じだったと思います。

###### 1.アプリケーションのセットアップを実施
ダッシュボード左側の「Project & Apps」→ 先程登録したアプリケーション名をクリック
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2603116/2385342a-5ab1-bdab-d0c1-2b49fe97ceb6.png)

画面中央あたりのUser authentication settingsのボタン「Set up(のはず)」をクリック

いくつかの項目の入力が必須となっています。
(記憶にある範囲で)説明していきます。

* App permissions：「今回利用するアプリケーションにどの程度の権限が必要か」
今回はツイート機能を利用したかったので、Read and Writeにした。
* Callback URI/Redirect URL：https://twitter.com/ で問題なし
* Website URL：同様にhttps://twitter.com/ で問題なし

これで登録します。

###### 2.アクセストークンの発行
先程と同様に、ダッシュボード左側の「Project & Apps」→ 先程登録したアプリケーション名をクリックしてアプリケーションの設定画面を開きます。

「Keys and tokens」タブに切り替え、「Access Token and Secret」の「Generate」を押下してください。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2603116/ebba451e-bd2c-db7e-2116-827d3eeec29a.png)
(このキャプチャだと、既に生成済なのでRegenerateになっています)

これで
「Access Token」「Access Token Secret」の取得が完了しました。

# 定期実行コードの作成
pythonでのコーディングは初だったので、命名規則などを調べながら頑張って書きました。

<details><summary>コード</summary>

```python
import json
import os
import shutil
import subprocess
import datetime
import tweepy
import sys

"""
定数宣言
"""
# ルール
AREA = 'AREA' # ガチエリア
LOFT = 'LOFT' # ガチヤグラ
GOAL = 'GOAL' # ガチホコ
CLAM = 'CLAM' # ガチアサリ
TURF = 'TURF_WAR' # ナワバリ
TRI = 'TRI_COLOR' # トリカラ

# 勝敗
WIN = 'WIN'
LOSE = 'LOSE'
DRAW = 'DRAW' # フェスのトリカラバトルにて2位だったときの表記っぽい

# 個人データ
KILL = 'KILL'
ASSIST = 'ASSIST'
DEATH = 'DEATH'
PAINT = 'PAINT'
SPECIAL = 'SPECIAL'

# Twitter
CONSUMER_KEY = 'XXXXXX'
CONSUMER_SECRET = 'XXXXXX'
ACCESS_TOKEN = 'XXXXXX'
ACCESS_TOKEN_SECRET = 'XXXXXX'

"""
システム実行時刻を設定
"""
t_delta = datetime.timedelta(hours=9)
JST = datetime.timezone(t_delta, 'JST')
now = datetime.datetime.now(JST)
today = now.strftime('%Y-%m-%d')
if len(sys.argv) == 2:
    today = sys.argv[1]

def get_foldername_list():
    files = os.listdir('./')
    folders = [f for f in files if os.path.isdir(os.path.join('./', f))]

    return folders

def get_export_foldername():
    folders = get_foldername_list()

    for foldername in folders:
        if foldername.startswith('export'):
            return foldername

def delete_old_export_folder():
    shutil.rmtree('./' + get_export_foldername())

def build_rule_result():
    rule_result = {
        AREA : {
            WIN : 0
            ,LOSE : 0
        }
        ,LOFT : {
            WIN : 0
            ,LOSE : 0
        }
        ,GOAL : {
            WIN : 0
            ,LOSE : 0
        }
        ,CLAM : {
            WIN : 0
            ,LOSE : 0
        }
        ,TURF : {
            WIN : 0
            ,LOSE : 0
        }
        ,TRI : {
            WIN : 0
            ,LOSE : 0
            ,DRAW : 0
        }
    }

    return rule_result

def build_my_result():
    my_result = {
        KILL : 0
        ,ASSIST : 0
        ,DEATH : 0
        ,PAINT : 0
        ,SPECIAL : 0
    }

    return my_result

def add_rule_result(rule_result, rule, judgement):
    # フェスにて、身内同士での対戦に負けた時はDEEMED_LOSEとされる？
    if rule == TURF and judgement == 'DEEMED_LOSE':
        rule_result[rule][LOSE] += 1
        return

    rule_result[rule][judgement] += 1

def get_my_data(player_list):

    for player in player_list:
        if player['isMyself']:
            return player

def add_my_result(my_result, my_data):
    data_result = my_data['result']

    if data_result is None:
        return

    my_result[KILL] += data_result['kill']
    my_result[DEATH] += data_result['death']
    my_result[ASSIST] += data_result['assist']
    my_result[SPECIAL] += data_result['special']
    my_result[PAINT] += my_data['paint']

def calculate_total_match(rule_result):
    all_match = 0
    all_match += rule_result[AREA][WIN]
    all_match += rule_result[AREA][LOSE]
    all_match += rule_result[LOFT][WIN]
    all_match += rule_result[LOFT][LOSE]
    all_match += rule_result[GOAL][WIN]
    all_match += rule_result[GOAL][LOSE]
    all_match += rule_result[CLAM][WIN]
    all_match += rule_result[CLAM][LOSE]
    all_match += rule_result[TURF][WIN]
    all_match += rule_result[TURF][LOSE]
    all_match += rule_result[TRI][WIN]
    all_match += rule_result[TRI][LOSE]
    all_match += rule_result[TRI][DRAW]

    total_match = {
        'ALL' : all_match
        ,WIN  : rule_result[AREA][WIN] + rule_result[LOFT][WIN] + rule_result[GOAL][WIN] + rule_result[CLAM][WIN] + rule_result[TURF][WIN] + rule_result[TRI][WIN]
        ,LOSE : rule_result[AREA][LOSE] + rule_result[LOFT][LOSE] + rule_result[GOAL][LOSE] + rule_result[CLAM][LOSE] + rule_result[TURF][LOSE] + rule_result[TRI][LOSE]
        ,AREA : rule_result[AREA][WIN] + rule_result[AREA][LOSE]
        ,LOFT : rule_result[LOFT][WIN] + rule_result[LOFT][LOSE]
        ,GOAL : rule_result[GOAL][WIN] + rule_result[GOAL][LOSE]
        ,CLAM : rule_result[CLAM][WIN] + rule_result[CLAM][LOSE]
        ,TURF : rule_result[TURF][WIN] + rule_result[TURF][LOSE]
        ,TRI : rule_result[TRI][WIN] + rule_result[TRI][LOSE] + rule_result[TRI][DRAW]
    }

    return total_match

def calculate_average(my_result, total_match):
    average = {
        KILL : round(my_result[KILL] / total_match, 1)
        ,DEATH : round(my_result[DEATH] / total_match, 1)
        ,ASSIST : round(my_result[ASSIST] / total_match, 1)
        ,SPECIAL : round(my_result[SPECIAL] / total_match, 1)
        ,PAINT : round(my_result[PAINT] / total_match, 1)
    }

    return average

def build_tweet_text(my_result, rule_result):
    tweet_text += today + '\r\n'
    total_match = calculate_total_match(rule_result)

    if total_match['ALL'] == 0:
        tweet_text = 'バトルしてない'

        return tweet_text

    average = calculate_average(my_result, total_match['ALL'])

    win_average = round(total_match[WIN] / total_match['ALL'] * 100, 1)

    tweet_text = 'XXXXX'

    return tweet_text


def main():
    global kill_total
    global assist_total
    global death_total
    global paint_total
    global special_total

    delete_old_export_folder()

    # s3sを用いて、戦績をjson形式で取得
    subprocess.run(['python3', 's3s.py', '-o'])

    foldername = get_export_foldername()

    json_open = open(foldername + '/results.json', 'r')
    json_load = json.load(json_open)

    rule_result = build_rule_result()
    my_result = build_my_result()

    for index, item in enumerate(json_load):
        result = item['data']['vsHistoryDetail']

        # マッチの日付が今日ではない場合、計測しない
        if not result['playedTime'].startswith(today):
            continue

        my_data = get_my_data(result['myTeam']['players'])

        add_my_result(my_result, my_data)
        add_rule_result(rule_result, result['vsRule']['rule'], result['judgement'])

    tweet_text = build_tweet_text(my_result, rule_result)

    # ツイートを投稿
    tweepy_client = tweepy.Client(
        consumer_key = CONSUMER_KEY
        ,consumer_secret = CONSUMER_SECRET
        ,access_token = ACCESS_TOKEN
        ,access_token_secret = ACCESS_TOKEN_SECRET
    )

    tweepy_client.create_tweet(
        text = tweet_text
    )

main()
```
</details>

処理の流れとしては下記の通りです。

1. 前回取得してきた戦績の入っている、'export_xxxxx'フォルダを削除する
(今回容量30GBのサーバを使っているため、放っておくと怖かったので)
2. s3sを「-o」オプション付きで呼び出し、戦績をエクスポートさせる
3. 取ってきたjsonファイルを読み込み、戦績等それぞれのデータをバラす
構造はそこそこにややこしかったですが、見てる感じだとかなりデータ量は充実していました。
ほかプレイヤーがつけてるギアの名前と、サブギアが何かとかまで全部取れそうです。
4. ツイート文言を組み立てる
5. ツイートする

# コードの定期実行処理を設定
ubuntuのservice/timer機能で、毎晩実行させるようにします。

`/etc/systemd/system/`ディレクトリに、下記のファイルを保存します。

``` tweetResult.service
[Unit]
Description = tweet Splatoon battle result

[Service]
Type = oneshot
Restart = no
ExecStart = /usr/bin/python3 /home/test_user/src/s3s/tweetResult.py
User = test_user
WorkingDirectory = /home/test_user/src/s3s

[Install]
WantedBy = multi-user.target
```

``` tweetResult.timer
[Unit]
Description = Timer for tweetResult

[Timer]
OnCalendar = *-*-* 22:30:00
# 毎日22時30分に実行される

[Install]
WantedBy = timers.target
```

置いておくだけでは定期実行してくれないので、きちんとサービスを起動します。

```py
#タイマーの実行
sudo systemctl start tweetResult.timer
#サーバが再起動されたときに、タイマーを自動的に実行するよう設定
sudo systemctl enable tweetResult.timer

#タイマーの一覧で、問題なく実行できているか確認
sudo systemctl list-timers
```

# まとめ
とりあえず数日経ちましたが、無事に戦績が見れるようになりました。
嬉しい。

サーバを借りてあーだこーだするような経験は初めてだったのですが、楽しかったです。
せっかく借りたので、また何か試したい事が増えたときはチャレンジしていこうと思います。