最近、NeosVRやVRChatなどのVRゲームをする時間が増えてきた。 ここ数年、ライフログとして自動的に記録できるアクティビティはGoogleカレンダーに記録するようにしている。

……というわけでSteamで何時から何時まで何のゲームを遊んでいたのかが自動的に記録されるようにしてみた。 APIを叩いてGoogleカレンダーに転記するだけのプログラムはGASで書くのが楽。

しくみ

 SteamではWeb APIが公開されている。

この中にGetPlayerSummariesというエンドポイントがある。

これはユーザーの情報を取得するエンドポイントだが、ゲームを遊んでいる間だけ

  • gameid
  • gameextrainfo

というフィールドが生えてくる。 このAPIを定期的に叩き、gameidとgameextrainfoが生えている間はゲームのプレイ中としてカレンダーのイベントを作成・延長していく。

※ちなみに何時から何時までゲームをした、という情報を取れるエンドポイントは見当たらなかった

実装

Steam API Keyの発行

 探せばいっぱい記事があるので省略。

GAS (Google Apps Script) を書く

 適当なGoogleスプレッドシートを作成し、スクリプトエディタを開いてGASを書く。 Googleスプレッドシートは、直近で記録したGoogleカレンダーのイベントIDとゲーム情報を一時保存するために利用する。

const CALENDAR_ID = "YOUR_CALENDAR_ID";
const STEAM_ID = "YOUR_STEAM_ID";
const STEAM_API_KEY = "YOUR_STEAM_API_KEY";
function run() {
const api = new SteamApi(STEAM_API_KEY);
const json = api.getPlayerSummary(STEAM_ID);
const gameid = json["gameid"];
const gameextrainfo = json["gameextrainfo"];
main(gameid, gameextrainfo);
}
function main(gameid, title) {
const cacheRange = SpreadsheetApp.getActiveSheet().getRange(2,1,1,5);
const cache = new SheetRangeCacheService(cacheRange, new GameEventConverter());
if (!gameid || !title) {
Logger.log("clear");
cache.clear();
return;
}
const calendarService = new CalendarService(CALENDAR_ID);
const current = cache.restore();
if (current && current.gameid == gameid) {
Logger.log("update");
current.endTime = new Date();
calendarService.updateEventEndTime(current.calendarEventId, current.endTime);
cache.store(current);
} else {
Logger.log("new");
const startTime = new Date();
const endTime = new Date();
const calendarEventId = calendarService.createEvent(title, startTime, endTime);
const data = new GameEvent(gameid, title, calendarEventId, startTime, endTime)
cache.store(data);
}
}
class SteamApi {
constructor(apiKey) {
this.apiKey = apiKey;
}
getPlayerSummary(steamId) {
const url = `http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?key=${this.apiKey}&steamids=${steamId}&format=json`;
const options = {
"muteHttpExceptions": true
}
const res = UrlFetchApp.fetch(url, options);
if (res.getResponseCode() === 200) {
const content = res.getContentText();
const json = JSON.parse(content);
return json.response.players[0];
} else {
console.error(`${res.getResponseCode()}: ${res.getContentText()}`);
return {};
}
}
}
class GameEvent {
constructor(gameid, title, calendarEventId, startTime, endTime) {
this.gameid = gameid;
this.title = title;
this.calendarEventId = calendarEventId;
this.startTime = startTime;
this.endTime = endTime;
}
}
class GameEventConverter {
toArray(gameEvent) {
return [
gameEvent.gameid,
gameEvent.title,
gameEvent.calendarEventId,
gameEvent.startTime,
gameEvent.endTime
];
}
fromArray(arr) {
return new GameEvent(arr[0], arr[1], arr[2], arr[3], arr[4]);
}
}
class SheetRangeCacheService {
constructor(range, converter) {
this.range = range;
this.converter = converter;
}
store(obj) {
const data = this.converter.toArray(obj);
this.range.setValues([data]);
}
restore() {
const data = this.range.getValues()[0];
return this.converter.fromArray(data);
}
clear() {
this.range.clearContent();
}
}
class CalendarService {
constructor(calendarId) {
this.calendar = CalendarApp.getCalendarById(calendarId);
}
createEvent(title, startTime, endTime) {
const res = this.calendar.createEvent(title, startTime, endTime);
const eventId = res.getId();
return eventId;
}
updateEventEndTime(eventId, endTime) {
const event = this.calendar.getEventById(eventId);
const startTime = event.getStartTime();
event.setTime(startTime, endTime);
}
}
function test_api() {
const api = new SteamApi(STEAM_API_KEY);
const json = api.getPlayerSummary(STEAM_ID);
Logger.info(json);
}
function test_NeosVR() {
main("111", "NeosVR");
}
function test_VRChat() {
main("222", "VRChat");
}
function test_clear() {
main();
}
view raw test.gs hosted with ❤ by GitHub

実行トリガーの設定

run関数を定期実行。

実行結果

 色は薄いですがこんな感じ。

実行イメージ