GAS（Google Apps Script）からQiita APIにアクセスして、Google Spreadsheetに取得した情報書き込む


タイトルのまんまのことをやります。
まず、動作イメージです。
</br>

※ GAS（Google Apps Script）ってどうやって始めればいい？ って感じの場合：

* 「[GAS（Google Apps Script）事始め](http://humangas.hatenablog.jp/entry/2014/02/11/191541)」 に自分が思う最低限の情報を纏めてみました。


***
# 動作イメージ

1. 事前準備：QiitaアカウントをSpreadsheetに定義しておく
1. GAS：Qiitaアカウント名を取得する
1. GAS：取得したアカウント名をキーにして、Qiita APIからアカウント情報を取得する
1. GAS：取得したアカウント情報をSpreadsheetに書き出す

このGASをトリガー（スケジューラ）に、1日1回任意の時間に動かすとかの設定をしておきます。
</br>

![スクリーンショット_2014-02-10_13_28_27.png](https://qiita-image-store.s3.amazonaws.com/0/26565/c3a7ba4f-d227-2b96-35bd-1fad2a4dd0b1.png "スクリーンショット_2014-02-10_13_28_27.png")


# Point
この実装で必要なGASのポイントは主に以下2点です。

* Spreadsheetの読み込み・書き出し
* Qiita APIに、GASの`UrlFetchApp.fetch(url, urlFetchOption)`でアクセス
  * 今回の実装ではQiita認証はしてません（ちなみに、1時間に150リクエストまでしか送れません）。
  * GAS：UrlFetchApp のAPIリファレンスは[コチラ](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetch(String,Object))


## 参考情報：Qiita APIの仕様
[ココ](http://qiita.com/docs)に仕様（制限も）が書いてあります。
本当は、Contributionの数も取得したかったのですが、取れないようです。。。

API利用制限部分だけ抜粋しときます。
</br>

> APIの利用制限
> 
> 上限: 150リクエスト/1時間
> 認証リクエストでは認証ユーザーごと，認証していないリクエストではIPごとに制限される．
> 以下のようにレスポンスヘッダにリクエスト上限数と残りリクエスト可能回数が入っている．
> 
> $ curl -I https://qiita.com/api/v1/users/whoever.json
> HTTP/1.1 200 OK
> X-RateLimit-Limit: 150
> X-RateLimit-Remaining: 140
> またこれらの情報はGET /api/v1/rate_limitでも取得できる．
> 
> json
> {
>   "remaining": 140,
>   "limit": 150
> }



# ソースコード
トリガー（スケジューラ）に仕込んでおくメソッドは、`qiitaInfoWrite2SpreadSheet`です。
エラーハンドリング等、一部適当な実装もありますがこのソースで普通に動きます。

## 処理の流れはこんな感じ
1. メイン関数起動（スケジューラに仕込むやつ）：`qiitaInfoWrite2SpreadSheet`
1. スプレッドシートからQiitaアカウントをObject（Map）で取得：`getQiitaAccountList`
1. Qiitaアカウント名をループさせて、そのアカウントの情報をQiita APIで取得：`getUserQiitaInfo`
  * → 取得した情報を引数に渡したQiitaアカウントObject（Map）に追加で情報を積めて返す
1. Qiita APIを`UrlFetchApp.fetch(url, urlFetchOption)`で叩いて、取得したbody部をJSONパースして返す：`requestQiita`

</br>

```javascript:GetQiitaAccountInfo
// @see Qiita API: http://qiita.com/docs

var ENDPOINT = "https://qiita.com/api/v1";

var ACCOUNT_LIST_ = {
  SHEET_NAME : "アカウント一覧",
  ROW_START : 3,  
  COL_ACCOUNT : 2
};


/**
 * Qiita情報をスプレッドシートに書く。
 */
function qiitaInfoWrite2SpreadSheet(){
  
  var accountList = getQiitaAccountList();
  
  var values = [];
  var cnt = 1;
  
  for each(var val in accountList){
    var uinflist = [];
    var userinfo = getUserQiitaInfo(val);
    
    // 列の順番でpushすること
    uinflist.push(cnt);
    uinflist.push(userinfo.account);
    uinflist.push(userinfo.status);
    uinflist.push(userinfo.items);
    uinflist.push(userinfo.follow_tags);
    uinflist.push(userinfo.following_users);
    uinflist.push(userinfo.followers);
    uinflist.push(userinfo.organization);
    uinflist.push(userinfo.url);    
    
    values.push(uinflist);
    cnt++;
  }  
  
  // まとめてドーン！
  var sheet = SpreadsheetApp.getActive().getSheetByName(ACCOUNT_LIST_.SHEET_NAME);
  var srange = sheet.getRange(ACCOUNT_LIST_.ROW_START, 1, values.length, uinflist.length);

  srange.setValues(values);
}


/**
 * アカウントリストを返す。
 */
function getQiitaAccountList(){
 
  var spApp = SpreadsheetApp.getActive();
  var sheet = spApp.getSheetByName(ACCOUNT_LIST_.SHEET_NAME);
  var values = sheet.getDataRange().getValues();
  
  var accountListMap = {};
  
  for(var i = ACCOUNT_LIST_.ROW_START-1; i < values.length; i++){    
    var val = values[i];
    
    var account = val[ACCOUNT_LIST_.COL_ACCOUNT-1];            
    if(!account){
      continue;
    }
    
    if(accountListMap[account]){
      // 気づいてね。取り敢えず手で直して。
      accountListMap[account].status = 'err:duplicate'
      continue;
    }
        
    accountListMap[account] = {
      account : account,  
      status : ''
    };    
  }
    
  return accountListMap;
}  


/**
 * 指定したQiitaアカウントの情報を返す。
 */
function getUserQiitaInfo(accountMap) {
  
  // 特定ユーザーの情報取得
  // GET /api/v1/users/:url_name
  var result = requestQiita('/users/' + accountMap.account , 'GET');
  
  if(result.parseError){
    accountMap.isError = true;
    accountMap.status += 'parseError'
    return accountMap;
  }
  
  if (result.body.error){
    accountMap.isError = true;
    accountMap.status += result.body.error;
    return accountMap;
  }
  
  // 特定ユーザーのフォローしているタグ取得
  //GET /api/v1/users/:url_name/following_tags
  var resultf = requestQiita('/users/' + accountMap.account + '/following_tags', 'GET');  
  var rbody = resultf.body;
  
  var follow_tags = '';
  for each(var val in rbody){
    var follow_tags = follow_tags + ',' + val.name;
  }  
  follow_tags = follow_tags.replace(/^,/,'');  
  
  accountMap.status += ',normal'; //適当
  accountMap.items  = result.body.items;
  accountMap.followers = result.body.followers;
  accountMap.following_users = result.body.following_users;
  accountMap.url = result.body.url;
  accountMap.organization = result.body.organization;
  accountMap.follow_tags = follow_tags;
  
  return accountMap;
}


/**
 * Qiitaにリクエストを送信して結果を返す。
 */
function requestQiita(path, method) {
  
  var url = ENDPOINT + path;
    
  var urlFetchOption = {
    'method' : (method || 'get'),    
    'contentType' : 'application/json; charset=utf-8',
    'muteHttpExceptions' : true
  };
    
  var response = UrlFetchApp.fetch(url, urlFetchOption);
  try {
    return {
      responseCode : response.getResponseCode(),
      rateLimit : {
        limit : response.getHeaders()['X-RateLimit-Limit'],
        remaining : response.getHeaders()['X-RateLimit-Remaining'],
      },
      parseError : false,
      body : JSON.parse(response.getContentText()),
      bodyText : response.getContentText()
    };
  } catch(e) {
    return {
      responseCode : response.getResponseCode(),
      rateLimit : {
        limit : response.getHeaders()['X-RateLimit-Limit'],
        remaining : response.getHeaders()['X-RateLimit-Remaining'],
      },      
      // 何らかのエラーが発生した場合、parseError=trueにする
      parseError : true,
      // JSON.parse(response.getContent())で落ちる時があるので、そん時はbody=null返す
      // TODO:ただ、今は呼出元でnull返しの対処はしてない。。。
      body : null,
      bodyText : response.getContentText()
    };
  }
  
}
```


# このソースで取得できる情報は？
このまんまのソースであれば、以下の赤枠のところが取得できます。
</br>

![スクリーンショット_2014-02-10_13_54_47.png](https://qiita-image-store.s3.amazonaws.com/0/26565/90636f13-dab1-78d0-afb8-910555a9e85e.png "スクリーンショット_2014-02-10_13_54_47.png")



***
# 最後に：なぜ、やりたかったのか？
社内のエンジニアの技術交流を活性化させるための一つの施策としてQiitaを利用させてもらってます。

ただ、なかなかアカウント登録が進まない ＆ 登録しても投稿が一部でしか増えないので、まだやってない人にやってる感じを見せるのと、沈んでるところを簡単に見える化して、盛り上げ喚起しやすくするためにやりました。

勝負はコレからです。