VRChatのフレンドがいるワールドをGASを使ってカレンダーに記録

# VRChatのフレンドがいるワールドをGASを使ってカレンダーに記録する
最近、VRChatのAPIを叩き始め、何か面白いことできないかと思って作ってみた。

GAS(Google Apps Script)には指定したURLにリクエストを出す機能(UrlFetchApp.fetch())がある。
これを用いればVRCのAPIが叩ける。


今回作ったのは、あるフレンドの現在いるワールドをGoogleのスプレッドシートとカレンダーに記録して行くスクリプト。
(本当は自分のいるワールドの記録をとりたかったのだけど、自分のフレンドしか現在いるワールドを取得できないため、自分のワールドを取得できない。)
使う場合は、フレンドの了承が得られてから。

## VRChatのAPI
VRChatのAPIで取得できる情報はいろいろある。結構使えなくなってるのも多いけど。
[ドキュメントはここ](https://vrchatapi.github.io/)
今回はフレンドの名前から情報を得る、"Get By Name"を使う。
エンドポイントは以下のURL
https://api.vrchat.cloud/api/1/users/[USERNAME]/name

また、ワールドの名前をIDから得るために、"Get World"を用いる
エンドポイントは以下のURL
https://api.vrchat.cloud/api/1/worlds/[ID]

非公式のものなので急に使えなくなる可能性がある。
また、負荷をかけないためにもリクエストは1分間に1度まで。

## GAS
Google Apps ScriptはGoogleの各種サービスをスクリプトで制御するもの。
JavaScriptベースなのでWeb業界の人なら新しく覚えることも少ない。
GASは時間ベースのトリガーを使って*分ごとに自動実行というのが行える。


## 準備
必要なもの
- Googleアカウント
以上

ドライブ上でGASとスプレッドシートを1個ずつ作る。
右クリックすれば作れる。
(GASは最初ないので、"その他->アプリを追加"から追加しておく)
適当にGASとスプレッドシートは名前を付けておく。
![876df5a9-525e-4b25-b0bd-d2aa32a0b014.jpg](https://qiita-image-store.s3.amazonaws.com/0/332214/884a0e90-9d73-1a83-900d-19ee57a2f10c.jpeg)


スプレッドシートからIDを記録しておく。
(スプレッドシートの編集画面でURLの/d/と/editに囲まれた文字列がID)
スプレッドシートの一行目に以下の画像のように適当にヘッダーを書いておく
![d09e7dbe-a74e-4282-951c-df07ea51a0eb.jpg](https://qiita-image-store.s3.amazonaws.com/0/332214/1970fb0e-057b-1335-9458-6471acabc7b0.jpeg)

Googleカレンダーでワールド記録用にカレンダーを一個作っておく。
そのカレンダーのIDを記録しておく。

## 実装
以下のスクリプトをGASに書き込む。  
自分の環境に合わせてVRChatのID、パスワード、フレンド名、スプレッドシートとカレンダーのIDを埋めておく。
 

~~~
function GetWorldID() {
  // アクセス先の情報
  var url = "https://api.vrchat.cloud/api/1/users/";
  var friendName = "";//追跡するフレンドの名前
  url = url + friendName + "/name"
  var userid = "";//VRCのユーザ名
  var password = "";  //VRCのパスワード
  var queryString = "?apiKey=JlE5Jldo5Jibnk5O5hTx6XVqsJu4WJ26";
  
  
  //スプレッドシートを得るための情報
  var SheetId = "";//スプレッドシートのID
  var SheetName = "シート1";
  var ss = SpreadsheetApp.openById(SheetId);
  var Sheet = ss.getSheetByName(SheetName);
  var dat = Sheet.getDataRange().getValues(); //シートデータを取得
  
  url = url + queryString;
  Logger.log(url);

  // GETメソッドのオプション
  var options = {
    "contentType": "application/json",
    "headers" : {"Authorization" : "Basic " + Utilities.base64Encode(userid + ':' + password, Utilities.Charset.UTF_8)},
    "muteHttpExceptions" : true
  }

  try{
    var response = UrlFetchApp.fetch(url, options);
    var content = response.getContentText("UTF-8");
    Logger.log(content);
    var json=JSON.parse(response);
 
    Logger.log("WorldIDは" + json["worldId"]);
    
    var WorldID = json["worldId"];

    //VRCHATをやっていないときは記録しない
    if(WorldID == "offline"){
      Logger.log("User id offline");
      return 0;
   }

    var now = new Date();
    
    Sheet.getRange(dat.length+1,2).setValue(WorldID);
    Sheet.getRange(dat.length+1,1).setValue(now);
    
   //set Trigger 2分後に起動
    var triggerDay = new Date();
    triggerDay.setMinutes(triggerDay.getMinutes() +2);
    ScriptApp.newTrigger("GetWorldName").timeBased().at(triggerDay).create();
   
    
    
  }catch(e){
    Logger.log(e.message);
  }
}


function GetWorldName() {
  deleteTrigger();
  var url = "https://api.vrchat.cloud/api/1/worlds/";
  var userid = "";//VRCのユーザ名
  var password = "";//VRCのパスワード
  var queryString = "?apiKey=JlE5Jldo5Jibnk5O5hTx6XVqsJu4WJ26";
  
  var SheetId = "";//スプレッドシートのID
  var SheetName = "シート1";
  var ss = SpreadsheetApp.openById(SheetId);
  var Sheet = ss.getSheetByName(SheetName);
  var dat = Sheet.getDataRange().getValues(); 
  
  var WorldID = Sheet.getRange(dat.length, 2).getValue();
  var TheTime = Sheet.getRange(dat.length, 1).getValue();
  var TheTime10 = Sheet.getRange(dat.length, 1).getValue();

  var options = {
    "contentType": "application/json",
    "headers" : {"Authorization" : "Basic " + Utilities.base64Encode(userid + ':' + password, Utilities.Charset.UTF_8)},
    "muteHttpExceptions" : true
  }

  try{
    url = url + WorldID + queryString;
    var response = UrlFetchApp.fetch(url, options);
    var content = response.getContentText("UTF-8");
    Logger.log(content);
    
    var json=JSON.parse(response); 
    Logger.log("WorldNameは" + json["name"]);
    var WorldName = json["name"];
        
    Sheet.getRange(dat.length,3).setValue(WorldName);
    
    RecordCalender(WorldName, TheTime, TheTime10);
    
  }catch(e){
    Logger.log(e.message);
  }
}

function RecordCalender(WorldName, TheTime, TheTime10){
  var calID =  "";//カレンダーのID
  var cal = CalendarApp.getCalendarById(calID);
  var now = TheTime;
  var now10 = TheTime10;
  now10.setMinutes(now10.getMinutes() + 10);
  cal.createEvent(WorldName, now, now10);  
}

function deleteTrigger() {
  var triggers = ScriptApp.getProjectTriggers();
  for(var i=0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() == "GetWorldName") {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
}
~~~

スクリプトの処理の流れとしては、
1. VRCAPIを使ってフレンドの現在いるワールドのIDを取得しスプレッドシートに追加。2分後に2.を起動するトリガーを設定。
2.1 この処理を起動したトリガーを削除。ワールドIDからワールドの名前をVRCのAPIを使って取得し、スプレッドシートに追加。
2.2. カレンダーに記録する。

## トリガー設定
トリガー設定前に、必ずGASエディタ画面で1回は実行しておくこと(エディタ画面でctrl + r)。
ワールドIDを取得する関数を10分ごとに呼び出すようにトリガーを追加設定しておく。
トリガーはGASエディタ画面の編集->現在のプロジェクトのトリガーから行える。
![dc9da21a-1ea1-4c2b-8686-91540dfb3872.jpg](https://qiita-image-store.s3.amazonaws.com/0/332214/c1eebdc5-d2ca-ef03-8b08-8723816f0ce8.jpeg)

## 結果
記録されたデータは、ワールド名をタイトルにイベントとしてGoogleカレンダーに記録される。（見づらい…）。
Googleカレンダーなので全世界に公開も可能。
![4802e6e7-2a57-4289-8d4a-723ba9ff571e.jpg](https://qiita-image-store.s3.amazonaws.com/0/332214/0ca8d6c8-da7b-9d28-31b1-7393f5e3d324.jpeg)

## はまったとこ
ユーザ名に日本語名が入っていると、リクエストの際にBASIC認証で、UTF-8を指定しておかないといけない。
自分は自分のフレンドにはなれない。
VRCAPIが1分間に1回の制限、GASの実行時間が1日に1時間までの制限があるのであつかいづらい。

## 今後やりたいこと
### 今回のスクリプトの改善
カレンダーで見やすいイベント(ワールド名)追加。
- 同じワールドなら表示をまとめる。
10分感覚で情報を取得ているが、もっと狭い感覚で情報を取得する。
WorldIDから名前を取得するときにVRCのAPIを叩かずにスプレッドシートから取得するキャッシュ機能
- どのワールドに長くいたとかの分析機能

### APIを用いた別のこと
フレンド登録日をカレンダーに記録するGASの作成。