splatoon2は、公式アプリを使うことで最近の試合の結果を見ることができます。
splatnet2statinkは、ゴネゴネしてそのデータを取得し、stat.inkにアップロードしてくれるツールです。
常に動かすには少し面倒な仕様だったので、Dockerイメージにして使いやすくしました。
使い方が知りたい人は、GitHubのリポジトリのREADMEを見てください。
問題点
Python製
よくわからないけど経験上気づいたら壊れていがちです。
あとあまり環境を汚したくないです。
起動方法
config.txt (初回に生成される) と同じディレクトリで起動しなきゃいけないです。
Python製というのもあいまって、ポータビリティが悪いです。
解決策
DockerイメージにしてDocker Hubにあげちゃいます。
気づいたら壊れていがち → 壊れない!
環境を汚したくない → 汚れない!
config.txtと同じディレクトリで起動しなきゃいけない → 後述
ポータビリティが悪い → Docker最強!
ということで解決しました。
nu50218/splatnet2statink - Docker Hub
config.txtの処理
config.txtは初回に生成されます。
volumeでホストにconfig.txtをおいてもいいんですが、他のPCに移すときにコピーしなきゃいけないのでちょっと面倒です。
個人的には設定ファイルよりも環境変数で設定したい気持ちがあります。
なので、以下のようなGoのコードを書いて、docker runしたときに間に挟まって起動時に環境変数からconfig.txtを生成してくれるようにしました。
https://github.com/nu50218/splatnet2statink-docker/blob/main/main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
| package main
import (
"encoding/json"
"log"
"os"
"os/exec"
"github.com/pkg/errors"
)
// {"api_key": "", "cookie": "", "user_lang": "", "session_token": ""}
type ConfigData struct {
APIKey string `json:"api_key"`
Cookie string `json:"cookie"`
UserLang string `json:"user_lang"`
SessionToken string `json:"session_token"`
}
// generate config.txt from environment variable
func prepare() error {
if os.Getenv("USE_ENVIRONMENT_VARIABLE") != "1" {
return nil
}
config := &ConfigData{
APIKey: os.Getenv("API_KEY"),
Cookie: os.Getenv("COOKIE"),
UserLang: os.Getenv("USER_LANG"),
SessionToken: os.Getenv("SESSION_TOKEN"),
}
f, err := os.Create("config.txt")
if err != nil {
return errors.WithStack(err)
}
if err := json.NewEncoder(f).Encode(config); err != nil {
return errors.WithStack(err)
}
return nil
}
func run() int {
cmd := exec.Command("python", append([]string{"splatnet2statink.py"}, os.Args[1:]...)...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
_ = cmd.Run()
return cmd.ProcessState.ExitCode()
}
func main() {
if err := prepare(); err != nil {
log.Println(err)
os.Exit(1)
}
os.Exit(run())
}
|
標準入出力、エラー出力はそのままつないで、exit codeも同じものを返すようにして擬態させました。
これでたとえばdocker-compose.ymlを使うと、
1
2
3
4
5
6
7
8
9
10
| splatnet2statink:
image: nu50218/splatnet2statink:latest
environment:
- USE_ENVIRONMENT_VARIABLE=1
- API_KEY=えーぴーあい・かぎ
- COOKIE=くっきー
- SESSION_TOKEN=せっしょん・とーくん
- USER_LANG=ゆーざー・げんご
restart: always
command: -s -r -M
|
のように設定するだけで勝手にconfig.txtが内部的に生成されて、splatnet2statink.pyが起動されてくれるようになりました!
ということで
config.txtと同じディレクトリで起動しなきゃいけない
も解決です。
余談: なんか壊れてたのでPR出した
自作イメージを使ってさっそく常時アップロードをはじめたところ、なんか壊れました。
1
2
3
4
5
6
7
8
9
10
| Traceback (most recent call last):
File "/splatnet2statink/splatnet2statink.py", line 1282, in <module>
populate_battles(is_s, is_t, is_r, debug)
File "/splatnet2statink/splatnet2statink.py", line 375, in populate_battles
post_battle(0, [result], s_flag, t_flag, -1, True if i == 0 else False, debug, False)
File "/splatnet2statink/splatnet2statink.py", line 1092, in post_battle
payload = set_scoreboard(payload, bn, mystats, s_flag)
File "/splatnet2statink/splatnet2statink.py", line 658, in set_scoreboard
sorted_ally_scoreboard = sorted(ally_scoreboard, key=itemgetter(0, 1, 3, 4, 2, 11), reverse=True)
TypeError: '<' not supported between instances of 'NoneType' and 'str'
|
まあエラーメッセージを読めば分かる通り、Noneと文字列を比較しようとして、比較関数がないよ!って怒られています。
Pythonワカラナイなりにprintデバッグをして頑張った結果原因が判明しました。
ソートのキーにユーザー名を使っているくせに、匿名アップロードオプション(他のユーザーの名前を隠して投稿する機能)で匿名をNoneとして扱っていたのが原因でした。
コマンドラインツールでどんどんオプション追加していくとバグるやつ、わかるなぁと思いながら修正してPRを出し、マージされました。
https://github.com/frozenpandaman/splatnet2statink/pull/119
今回の場合は、いろいろなオプションを一つの関数で頑張ってやっているのがよくなくて、単一責任の原則を無視して関数にオプションを追加していった結果バグが生まれたのかなぁとコードを読んでいて思いました。
コード量増えるけど適度にupload()
とupload_anonymously()
みたいに関数分割するの大事そうです。