🎤

DAMの精密採点Aiの結果をGASで取得した話

に公開

どうも。カラオケはちょこちょこやっているYUSAMAです。兄がExcelに手でちまちま記録しているのを見てAPIで取れないのかなとやってみた結果を書いていきます。

偉大なる参考記事

https://qiita.com/KadoProG/items/0bfe392945968ec76204

今回作成したソース

ここにあります
https://github.com/yusama125718/DAMResultScript/blob/main/DAMAPI.js

本題

実際にデータを取得する

まずどこから取得するんじゃいという話ですが、参考記事にもある通りhttps://www.clubdam.com/app/damtomo/scoring/GetScoringAiListXML.do から取得します。ちなみにメソッドはGETです。

パラメータは以下が指定できるみたいです

key value
cdmCardNo CLUB DAM CARD ID(必須)
scoringAiId 採点AiのID(単体取得の場合必須っぽい?)
pageNo ページNo(リスト取得の場合は指定可能。未指定の場合1ページ目)
detailFlg detailを表示するか(1で表示)

GASなのでfetchで取得しました。ちなみにレスポンスはXMLで帰ってきます。JSONじゃないんだね。

XMLを処理する

レスポンスがXMLなのでうまく処理してやる必要があります。頑張りました。
今回はGASなのでXMLの処理にはXmlServiceを使いました。ドキュメント読めば大体わかります。

XMLは疎いのでネームスペース周りは少し苦労しました。結果的には無名のネームスペースを指定してやることでうまく取得できました。
実際の実装はこんな感じになっています。

// XMLに変換
const xmlDocs = XmlService.parse(responce.getContentText())
// 無名のネームスペースを取得し、採点結果のリストを取得
const ns = XmlService.getNamespace("", "https://www.clubdam.com/app/damtomo/scoring/GetScoringAiListXML")
const datas = xmlDocs.getRootElement().getChild("list", ns).getChildren("data", ns)

そうこうして、XMLを取得するのをリストは5件なのでfor文を回してうまい具合に取得します。
あと点数以外の情報はAttributeに入っているのでほしいデータをうまく引っ張り出します。ざっくりこんな感じ

for(let i = 0; i < datas.length; i++){
  const data = datas[i].getChild("scoring", ns)
  const songName = data.getAttribute('contentsName').getValue()
}

スプレッドシートに保存する

基本的な操作はググればいくらでも出てくるので適当に書きます。
基本的な作戦として新しいものから順に取得できるので表の上から1行づつ挿入する形で入れていきます。
曲名やアーティストなどは受け取った値をそのまま入れればいいので簡単なのですが問題は時刻と点数です。
時刻はYYYYMMDDHHmmssの形式で飛んできます。ちょっと処理しないと読みづらいので処理します。
ちなみにベースはChatGPT君に考えてもらいました。優秀。

// 日時を挿入
const timestamp = data.getAttribute('scoringDateTime').getValue()
let year = parseInt(timestamp.substring(0, 4), 10);
let month = parseInt(timestamp.substring(4, 6), 10) - 1; // JavaScript の月は 0 から始まる
let day = parseInt(timestamp.substring(6, 8), 10);
let hour = parseInt(timestamp.substring(8, 10), 10);
let minute = parseInt(timestamp.substring(10, 12), 10);
let second = parseInt(timestamp.substring(12, 14), 10);

let date = new Date(year, month, day, hour, minute, second);

// フォーマットを作成
let formattedDate = Utilities.formatDate(date, Session.getScriptTimeZone(), "yyyy/MM/dd HH:mm");
sheet.getRange(insertRow, 5).setValue(formattedDate)

要するに各要素ごとに分解してがっちゃんこしてます。

点数は小数点以下三桁までが小数点を含まない状態で渡されるので小数点を付け直して保存します。

// 得点を挿入
let pointTxt = data.getText()
let pointFew = pointTxt.slice(pointTxt.length - 3)
pointTxt = pointTxt.slice(0, pointTxt.length - 3) + "." + pointFew
sheet.getRange(insertRow, 4).setValue(pointTxt)

これで保存する値は大体いい感じでしょう。

表にないものをすべて取得したい

できれば一回関数をたたけば表にない記録をすべて取得してほしいですよね。
というわけで今回は記録のIDを既存データの最後の記録(要は表の一番上のデータ)のIDととってきたIDが一致するまで取得するというようにしました。
ちなみにこの実装だとデータの順番をいじられてしまうとバグる可能性があります。ダメじゃん。

let page = 1
while(true){
  // 取得処理

  // IDが同じデータの場合処理を終了する
  const id = data.getAttribute('scoringAiId').getValue()
  if (id == sheet.getRange(insertRow, 1).getValue()) return;

  // 表挿入処理

  // 次ページがなかったら終了
  let hasNext = xmlDocs.getRootElement().getChild("data", ns).getChild("page", ns).getAttribute('hasNext').getValue()
  if (hasNext == "0") return
  page++
}

ちなみにレコードの最後(仕様上最大200件までしか取得できない)になるとhasNextの値が0になるのでそれでも中断するようにしてます。

おわりに

作ってみたら案外簡単に作れました。これをシート内のボタンに割り当ててワンクリックで取得できるようにしてます。

Discussion