見出し画像

[LR2] ローカルのクリアランプでリコメンド更新するツールを作りました [BMS]

背景

最近では発狂難易度表以外にもStella/Satelliteといったものがあるので、発狂難易度表の譜面だけをやりこむという人は少ないと思います。とはいえ、リコメンドを使っている人はまだそれなりに居るのではないでしょうか。

リコメンドに自分のクリア状況を送信するためには、公式に提供されているブックマークレットを実行する必要があります。しかし、ブックマークレットはLR2IRで自分がプレイした譜面すべてを確認する動きになっているため、LR2IRに送信した譜面数が増えれば増えるほど更新に時間がかかるようになります。

かつてBeMusicSeekerには、ローカルのクリア状況を利用したリコメンドの更新機能が付いていましたが、ribbit.xyzが鯖落ちしてからは使えなくなっています。

そこで、ローカルにあるLR2のDBからクリア状況を抽出し、そのデータをリコメンドのサイトに送信することで更新することができるツールを作りました。PowerShell 7用のスクリプトなのでPowerShell 7を導入して使ってください。PowerShell 7については以下の記事で軽く触れています。

リコメンド更新スクリプト

以下の2つの定数は環境に応じて書き換えてください。

$SQLITE_PATH = "D:\BIN\SQLite\sqlite-tools-win-x64-3460100\sqlite3.exe"
$SCOREDB_PATH = "D:\LR2beta3\LR2files\Database\Score\NEETED.db"

当該スクリプト

「sqlite3.exe」については以下の記事に入手方法を記載しています。

$SEND_MODEはスクリプトのコメントアウトに記載している通り、0だと普通に送信するだけ、1だとNOPLAYをFAILEDとして送信、2だとFAILEDを送信せずNOPLAY扱いにします。
1で送信しておけば、今後は更新時に値が下がることはなくなります。
2で送信した場合が一番高い値になると思います。

使い方はシンプルで、設定用の定数3つを変えた後、実行するだけです。

2024-09-15: 「# 設定が有効か確認」の部分で設定値の不備を検出できていなかったので修正しました。本筋の動作に影響のある修正ではありません。

# これは「PowerShell 7」で動作確認したスクリプトです

# 設定用
$SQLITE_PATH = "D:\BIN\SQLite\sqlite-tools-win-x64-3460100\sqlite3.exe" # 環境変数PATHを設定しているなら"sqlite3"みたいな指定方法でもOK
$SCOREDB_PATH = "D:\LR2beta3\LR2files\Database\Score\NEETED.db"
$SEND_MODE = 0 # 通常 = 0、NOPLAYをFAILEDとして送信 = 1、FAILEDを送信せずNOPLAY扱いにする = 2

# # 仕様
# ## 公式に提供されているスクリプト(ブックマークレット)
# - LR2IRのマイページからクリア状況をスクレイピングすることでクリア状況を集めている
# - LR2IRに送信されているクリア状況全てを集めて送信している
# ## 当スクリプト
# - スコアDBからクリア状況を集めている
# - 「発狂BMS 難度推定表」に含まれる譜面のみクリア状況を集めている(送信データにはbmsidやcourseidを含ませる必要があるが、song.dbには通常存在しない情報なので、クリア状況全ての送信は難しい)
# - 公式スクリプトではパフェランプは"6"扱いとなっているが、当スクリプトではパフェでも"5"(FC)で送信している
# - LR2IR未送信のクリア状況も送信される
# 以上の差異から、公式ブックマークレットを実行した際と実力の値が異なる可能性があります。
# (FAILEDを消した状態のブックマークレットと、当スクリプトのFAILED未送信機能の実力値を個人的に比較した際は同一だったのでたぶんちゃんと使えてるはず)

# 設定が有効か確認
try {
  @($SQLITE_PATH, $SCOREDB_PATH) | ForEach-Object { Get-Command $_ -ErrorAction Stop } | Out-Null
  if (!($SEND_MODE -ge 0 -and $SEND_MODE -le 2)) { throw }
}
catch {
  Write-Host "[Error] 設定値がおかしいです" -BackgroundColor Red
  Write-Host "スクリプトが完了しました。Enterキーを押すとウィンドウを閉じます。"; Read-Host
  exit
}

# SQLのIN句で使うhashのリストを得る(発狂BMS 難度推定表の対象譜面のmd5,bmid,courseidを集める)
try {
  Write-Host "[Info] 発狂BMS 難度推定表のデータを取得します"
  $response = Invoke-WebRequest 'http://walkure.net/hakkyou/data/bms.json'
}
catch {
  Write-Host "[Error] 「発狂BMS 難度推定表」のデータ取得に失敗しました" -BackgroundColor Red
  Write-Host "スクリプトが完了しました。Enterキーを押すとウィンドウを閉じます。"; Read-Host
  exit
}
$json = $response.Content | ConvertFrom-Json -AsHashtable
$hashtb = @{}
$json.keys | ForEach-Object { $hashtb.Add($json.$_.md5hash, $_) }
$keysString = ($hashtb.Keys | ForEach-Object { "'$_'" }) -join ","

# sqliteから各種データを取得
Write-Host "[Info] スコアDBからクリア状況を取得します"
[console]::OutputEncoding = [System.Text.Encoding]::UTF8 # sqliteからの出力をUTF8して解釈するための設定
$player = "SELECT name, irid FROM player;" | & $SQLITE_PATH -csv -header $SCOREDB_PATH | ConvertFrom-Csv
$clear = "SELECT hash, clear FROM score WHERE hash IN (${keysString});" | & $SQLITE_PATH -csv -header $SCOREDB_PATH | ConvertFrom-Csv

# メモ: DBのclearは、0=通常プレイしてない, 1=FAILED, 2=EASY, 3=CLEAR, 4=HARD, 5=FC
switch ( $SEND_MODE ) {
  # 通常
  0 {
    $senddataList = $clear | ForEach-Object {
      # 0以外の場合(通常プレイ済みの場合)
      if ($_.clear -ne 0) {
        $hashtb[$_.hash] + "-" + $_.clear
      }
    }
  }
  # NOPLAYをFAILED扱い
  1 {
    $senddataList = $clear | ForEach-Object {
      # 0以外の場合(通常プレイ済みの場合)
      if ($_.clear -ne 0) {
        $hashtb[$_.hash] + "-" + $_.clear
        $hashtb.Remove($_.hash) # 通常プレイ済みの譜面を$hashtbから取り除く
      }
    }
    # 通常プレイ済みの譜面を取り除いた$hashtbにはNOPLAY(or通常プレイしていない)ものが残っているので、これを1=FAILED扱いで送信データに含める
    $senddataList += $hashtb.Values | ForEach-Object { $_ + "-" + 1 }
  }
  # FAILEDを送信せずNOPLAY扱いにする
  2 {
    $senddataList = $clear | ForEach-Object {
      # EASY(=2)以上のみ送信対象とする
      if ($_.clear -ge 2) {
        $hashtb[$_.hash] + "-" + $_.clear
      }
    }
  }
}

$senddata = $senddataList -join ","

# 名前をスコアDBの情報ではなく独自に指定したい場合は以下で設定
# $player.name = "マンハッタンカフェ"

# データ送信前に現在の実力値を取得
try {
  $response = Invoke-WebRequest -Uri "http://walkure.net/hakkyou/recommended_json.cgi?id=$($player.irid)"
  $json = $response.Content | ConvertFrom-Json
  $lastModified = Get-Date -UnixTimeSeconds $json.last_modified | ForEach-Object { $_.ToString() }
  $hoshi = $json.hoshi
  Write-Host "[Info] 前回: ★${hoshi} (${lastModified})"
}
catch {
  Write-Host "[Error] 実力値の取得に失敗しました" -BackgroundColor Red
  Write-Host "スクリプトが完了しました。Enterキーを押すとウィンドウを閉じます。"; Read-Host
  exit
}

# データ送信し、更新後の実力値も取得
try {
  $response = Invoke-WebRequest -Uri "http://walkure.net/hakkyou/mle.cgi" -Method Post -Body @{name = $player.name; id = $player.irid; data = $senddata; }
  $response = Invoke-WebRequest -Uri "http://walkure.net/hakkyou/recommended_json.cgi?id=$($player.irid)"
  $json = $response.Content | ConvertFrom-Json
  $lastModified = Get-Date -UnixTimeSeconds $json.last_modified | ForEach-Object { $_.ToString() }
  $hoshi = $json.hoshi
  Write-Host "[Info] 今回: ★${hoshi} (${lastModified})"
  Write-Host "[Info] クリア状況の送信に成功しました。リコメンドページを確認してください。"
  Write-Host "http://walkure.net/hakkyou/recommended_mypage.html?playerid=$($player.irid)"
  Start-Process "http://walkure.net/hakkyou/recommended_mypage.html?playerid=$($player.irid)"
}
catch {
  Write-Host "[Error] クリア状況の送信に失敗しました" -BackgroundColor Red
  Write-Host "スクリプトが完了しました。Enterキーを押すとウィンドウを閉じます。"; Read-Host
  exit
}

Write-Host "スクリプトが完了しました。Enterキーを押すとウィンドウを閉じます。"; Read-Host
exit

この記事が気に入ったらサポートをしてみませんか?