イントロダクション
この記事の概要
SteamのライブラリをTrelloのボードにまとめて、積みゲーの可視化を図ったときの話。
背景
セールのたびに調子に乗ってゲームを買い続けた結果、ライブラリにはゲームがあふれ、どれから手をつけるべきかわからなくなる。ちょっとずつ遊んで積んでいくうちにどれが遊んだゲームなのかわからなくなる。ゲーマーたるもの買ったゲームは遊ぶべき。コレクターになってはいけない。
ざっくりアーキテクチャ
0. 前提
事前に下記を用意する。
・Steam Web APIキー1
・Trello Web APIキー2
・Trelloトークン2
また、Trelloのボードも事前に準備しておく。リストは下記のとおり、五種類用意した。
APIを叩くときはリストID3を指定するため、リスト名は好きに決めて良い。
・Stacking(ライブラリにあるけどプレイ時間ゼロ)
・Pending(遊んだことあるけど直近2週間のプレイ時間ゼロ)
・Playing(直近2週間のプレイ実績あり)
・Played(全トロフィー獲得済み)
・Wanna Play(ウィッシュリスト)
1. GASコード全容
// プロパティキー定数
var PROPERTY_KEY_STEAM_API_KEY = 'STEAM_API_KEY';
var PROPERTY_KEY_STEAM_ID = 'STEAM_ID';
var PROPERTY_KEY_TRELLO_API_KEY = 'TRELLO_API_KEY';
var PROPERTY_KEY_TRELLO_TOKEN = 'TRELLO_TOKEN';
var PROPERTY_KEY_SLACK_WEBHOOK_ENDPOINT = 'SLACK_WEBHOOK_ENDPOINT';
// エンドポイントフォーマット定数
var FORMAT_STEAM_ENDPOINT_GET_OWNED_GAMES = 'http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?include_appinfo=true&include_played_free_games=true&key={0}&steamid={1}';
var FORMAT_STEAM_ENDPOINT_GET_PLAYER_ACHIEVEMENTS = 'http://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v0001/?key={0}&steamid={1}&appid={2}';
var FORMAT_STEAM_ENDPOINT_GET_PLAYER_WISHLIST = 'https://store.steampowered.com/wishlist/profiles/{0}/wishlistdata';
var FORMAT_STEAM_IMAGE_LOGO_URL = 'http://media.steampowered.com/steamcommunity/public/images/apps/{0}/{1}.jpg';
var FORMAT_TRELLO_ENDPOINT_ARCHIVE_ALL_CARD_ON_LIST = 'https://api.trello.com/1/lists/{0}/archiveAllCards?key={1}&token={2}';
var FORMAT_TRELLO_ENDPOINT_CREATE_NEW_CARD = 'https://api.trello.com/1/cards?key={0}&token={1}&idList={2}&desc={3}&name={4}&urlSource={5}';
// TrelloリストID定数
var LIST_ID_STACKING = '604ed2027599ae2d423ec526';
var LIST_ID_PENDING = '604ebe8213443e361da34fc1';
var LIST_ID_PLAYING = '604ea6be71f9ad1d1ab2a75f';
var LIST_ID_PLAYED = '604ea6c355a39c738ccda7b3';
var LIST_ID_WANNA_PLAY = '604ea6cab083aa0be93b88ce';
// プロパティ
var properties = PropertiesService.getScriptProperties();
// メイン関数
// 日次でトリガ起動される
function _daily() {
var steamKey = properties.getProperty(PROPERTY_KEY_STEAM_API_KEY);
var steamId = properties.getProperty(PROPERTY_KEY_STEAM_ID);
var trelloKey = properties.getProperty(PROPERTY_KEY_TRELLO_API_KEY);
var trelloToken = properties.getProperty(PROPERTY_KEY_TRELLO_TOKEN);
// Steamライブラリにあるゲーム一覧を取得する
var games = getOwnedGames(steamKey, steamId);
// トロフィー獲得状況を取得し、全トロフィー獲得済みフラグを立てる
games = markCompletedGames(steamKey, steamId, games);
// ウィッシュリストにあるゲーム一覧を取得する
var wishList = getWishList(steamId);
// Trelloボードの既存カードをすべてアーカイブする
archiveAllCards(trelloKey, trelloToken);
// ゲーム一覧からカードをつくり、Trelloへ登録する
createNewCards(trelloKey, trelloToken, games);
createNewCardsOnWannaPlay(trelloKey, trelloToken, wishList);
}
// Steamライブラリからゲーム一覧を取得する関数
function getOwnedGames(key, id) {
var url = FORMAT_STEAM_ENDPOINT_GET_OWNED_GAMES.format(key, id);
var options = {
method: 'get'
};
var response = callExternalAPI(url, options);
var rawGames = JSON.parse(response.getContentText()).response.games;
var games = [];
for (var i = 0; i < rawGames.length; i++) {
var game = new Game(rawGames[i].appid, rawGames[i].name, rawGames[i].playtime_forever, rawGames[i].img_logo_url);
// playtime_2weeksフィールドを持つゲームは直近2週間のプレイ実績があるので、「最近遊んだゲーム」としてフラグを立てる
if (typeof rawGames[i].playtime_2weeks != 'undefined') {
game.isRecentlyPlayed = true;
}
games.push(game);
}
return games;
}
// トロフィー獲得状況を取得し、全トロフィー獲得済みフラグを立てる関数
function markCompletedGames(key, id, games) {
var options = {
method: 'get',
// ゲームによってはトロフィー獲得状況を問い合わせたときにエラーになる?
// 処理は中断したくないのでtrueにしておく
muteHttpExceptions: true
};
for (var i = 0; i < games.length; i++) {
var url = FORMAT_STEAM_ENDPOINT_GET_PLAYER_ACHIEVEMENTS.format(key, id, games[i].appId);
var response = callExternalAPI(url, options);
var achievements = JSON.parse(response.getContentText()).playerstats.achievements;
// ゲームによってはachievementsが未定義の場合がある?
if (achievements) {
for (var j = 0; j < achievements.length; j++) {
if (achievements[j].achieved == 1) { // 全トロフィーが獲得済み(1)ならフラグを立てる
games[i].isCompleted = true;
continue;
} else { // 未獲得のトロフィーがある時点でbreakする
games[i].isCompleted = false;
break;
}
}
}
}
return games;
}
// ウィッシュリストにあるゲーム一覧を取得する関数
function getWishList(id) {
var url = FORMAT_STEAM_ENDPOINT_GET_PLAYER_WISHLIST.format(id);
var options = {
method: 'get'
};
var response = callExternalAPI(url, options);
var rawGames = JSON.parse(response.getContentText());
var keys = Object.keys(rawGames);
var games = [];
for (var i = 0; i < keys.length; i++) {
// ウィッシュリストのゲーム一覧は、ライブラリのゲーム一覧とJSONの構造が異なる
// appidがキーになっており、その下に情報がぶら下がるイメージ。
// ロゴURLもなぜか異なるのでいったん仮値で置く
var game = new Game(keys[i], rawGames[keys[i]].name, 0, "DUMMY");
// 仮値で置いていたロゴURLを設定する
game.logoUrl = rawGames[keys[i]].capsule;
games.push(game);
}
return games;
}
// Trelloボードの既存カードをすべてアーカイブする関数
function archiveAllCards(key, token) {
var options = {
method: 'post'
};
// Stackingリストのカードをすべてアーカイブ
var url = FORMAT_TRELLO_ENDPOINT_ARCHIVE_ALL_CARD_ON_LIST.format(LIST_ID_STACKING, key, token);
callExternalAPI(url, options);
// Pendingリストのカードをすべてアーカイブ
var url = FORMAT_TRELLO_ENDPOINT_ARCHIVE_ALL_CARD_ON_LIST.format(LIST_ID_PENDING, key, token);
callExternalAPI(url, options);
// Playingリストのカードをすべてアーカイブ
var url = FORMAT_TRELLO_ENDPOINT_ARCHIVE_ALL_CARD_ON_LIST.format(LIST_ID_PLAYING, key, token);
callExternalAPI(url, options);
// Playedリストのカードをすべてアーカイブ
var url = FORMAT_TRELLO_ENDPOINT_ARCHIVE_ALL_CARD_ON_LIST.format(LIST_ID_PLAYED, key, token);
callExternalAPI(url, options);
// Wanna Playリストのカードをすべてアーカイブ
var url = FORMAT_TRELLO_ENDPOINT_ARCHIVE_ALL_CARD_ON_LIST.format(LIST_ID_WANNA_PLAY, key, token);
callExternalAPI(url, options);
}
// ゲーム一覧からカードをつくり、Trelloへ登録する関数
function createNewCards(key, token, games) {
var options = {
method: 'post'
};
for (var i = 0; i < games.length; i++) {
// プレイ時間ゼロのゲームをStackingリストへ登録する
if (games[i].playtime == 0) {
var url = FORMAT_TRELLO_ENDPOINT_CREATE_NEW_CARD.format(key, token, LIST_ID_STACKING, games[i].appId, encodeURIComponent(games[i].name), games[i].logoUrl);
callExternalAPI(url, options);
continue;
}
// 直近2週間でプレイ実績のあるゲームをPlayingリストへ登録する
if (games[i].isRecentlyPlayed) {
var url = FORMAT_TRELLO_ENDPOINT_CREATE_NEW_CARD.format(key, token, LIST_ID_PLAYING, games[i].appId, encodeURIComponent(games[i].name), games[i].logoUrl);
callExternalAPI(url, options);
continue;
}
// 全トロフィー獲得済みのゲームをPlayedリストへ登録する
if (games[i].isCompleted) {
var url = FORMAT_TRELLO_ENDPOINT_CREATE_NEW_CARD.format(key, token, LIST_ID_PLAYED, games[i].appId, encodeURIComponent(games[i].name), games[i].logoUrl);
callExternalAPI(url, options);
continue;
}
// 上記のいずれにも該当しないゲームをPendingリストへ登録する
var url = FORMAT_TRELLO_ENDPOINT_CREATE_NEW_CARD.format(key, token, LIST_ID_PENDING, games[i].appId, encodeURIComponent(games[i].name), games[i].logoUrl);
callExternalAPI(url, options);
}
}
// ゲーム一覧からカードをつくり、Trelloへ登録する関数(ウィッシュリスト用)
function createNewCardsOnWannaPlay(key, token, games) {
var options = {
method: 'post'
};
for (var i = 0; i < games.length; i++) {
var url = FORMAT_TRELLO_ENDPOINT_CREATE_NEW_CARD.format(key, token, LIST_ID_WANNA_PLAY, games[i].appId, encodeURIComponent(games[i].name), games[i].logoUrl);
callExternalAPI(url, options);
}
}
// API呼び出し用汎用関数
function callExternalAPI(endpoint, options) {
var response = UrlFetchApp.fetch(endpoint, options);
return response;
}
// ゲームを表すエンティティクラス
var Game = (function () {
function Game(appId, name, playtime, logoHash) {
// Steamが付与するAppId
this.appId = appId;
// ゲームタイトル
this.name = name;
// プレイ時間トータル
this.playtime = playtime;
// ロゴ画像URL
this.logoUrl = FORMAT_STEAM_IMAGE_LOGO_URL.format(this.appId, logoHash);
// 全トロフィー獲得済みフラグ
this.isCompleted = false;
// 直近2週間のプレイ実績ありフラグ
this.isRecentlyPlayed = false;
}
;
return Game;
}());
2. 結果
うむ!積みゲーばっかりだな!
https://trello.com/b/90dnRQH5/gamers-sai-no-kawara
課題
GASで自動化したことで、ありがちな「視える化したけどメンテナンスされなくなって陳腐化する」事態は避けられた。
ただ、
・いったんカードを全てアーカイブしてから再登録する現状の仕様だと、Steam以外のプラットフォームで積んでるゲームソフトは手動で登録するみたいなことはできない
・上記と同じ理由で、自動登録されたカードを手動更新することはできない
・アーカイブしたカードが溜まりっぱなし4
などの課題は残る。
Trello側で登録/更新されたカードの差分がとれれば復元できるんだけどなあ。
-
Steamのライブラリ情報などをAPIを通して取得するときに使う。https://steamcommunity.com/dev/apikeyから発行する。 ↩
-
TrelloのボードをAPIを通して更新するときに使う。https://trello.com/app-keyから発行する。 ↩ ↩2
-
https://api.trello.com/1/boards/{ボードID}/lists?key={APIキー}&token={トークン}にブラウザからアクセスすればリストIDがわかる。 ↩
-
APIはあるので消せばいい。ただの手抜き。 ↩