LoginSignup
1
2

More than 3 years have passed since last update.

【GAS】Steamの積みゲーをTrelloで視える化した話

Posted at

イントロダクション

この記事の概要

SteamのライブラリをTrelloのボードにまとめて、積みゲーの可視化を図ったときの話。

背景

セールのたびに調子に乗ってゲームを買い続けた結果、ライブラリにはゲームがあふれ、どれから手をつけるべきかわからなくなる。ちょっとずつ遊んで積んでいくうちにどれが遊んだゲームなのかわからなくなる。ゲーマーたるもの買ったゲームは遊ぶべき。コレクターになってはいけない。

ざっくりアーキテクチャ

architecture.png

0. 前提

事前に下記を用意する。
・Steam Web APIキー1
・Trello Web APIキー2
・Trelloトークン2

また、Trelloのボードも事前に準備しておく。リストは下記のとおり、五種類用意した。
APIを叩くときはリストID3を指定するため、リスト名は好きに決めて良い。

・Stacking(ライブラリにあるけどプレイ時間ゼロ)
・Pending(遊んだことあるけど直近2週間のプレイ時間ゼロ)
・Playing(直近2週間のプレイ実績あり)
・Played(全トロフィー獲得済み)
・Wanna Play(ウィッシュリスト)

1. GASコード全容

application.gs
// プロパティキー定数
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

キャプチャ.PNG

課題

GASで自動化したことで、ありがちな「視える化したけどメンテナンスされなくなって陳腐化する」事態は避けられた。

ただ、
・いったんカードを全てアーカイブしてから再登録する現状の仕様だと、Steam以外のプラットフォームで積んでるゲームソフトは手動で登録するみたいなことはできない
・上記と同じ理由で、自動登録されたカードを手動更新することはできない
・アーカイブしたカードが溜まりっぱなし4
などの課題は残る。

Trello側で登録/更新されたカードの差分がとれれば復元できるんだけどなあ。

syake-salmon/gamers-library-on-trello - GitHub


  1. Steamのライブラリ情報などをAPIを通して取得するときに使う。https://steamcommunity.com/dev/apikeyから発行する。 

  2. TrelloのボードをAPIを通して更新するときに使う。https://trello.com/app-keyから発行する。 

  3. https://api.trello.com/1/boards/{ボードID}/lists?key={APIキー}&token={トークン}にブラウザからアクセスすればリストIDがわかる。 

  4. APIはあるので消せばいい。ただの手抜き。 

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2