こんにちは、Juntechです。
前回の記事ではTwitter APIをAPIクライアントから叩いてみました。

【ツイートまで】Twitter APIを使ってみる ~その2【利用編】

せっかくAPIを使えるようになったのに、
APIクライアントから手動で実行しているだけではお得感がないですよね。
ということで今回は、
GASことGoogle Apps Scriptを使ってAPIを叩いてみたいと思います!

早速コード

こちらです。

function getTimeline(){
  var url = "https://api.twitter.com/1.1/statuses/usevr_timeline.json?screen_name=takapon_jp";
  var method = 'GET'
  Logger.log(encodeURIComponent(url))
  execute(url,method)
}

function tweet(){
  var tweet = "GASからツイートしてみた!"
  var url = "https://api.twitter.com/1.1/statuses/update.json?status="+tweet;
  var method = 'POST';
  execute(url,method)
}

function execute(url,method,body) {
  // プロパティ取得
  var CONSUMER_KEY    = PropertiesService.getScriptProperties().getProperty('consumer_key');
  var CONSUMER_SECRET = PropertiesService.getScriptProperties().getProperty('consumer_secret');
  var ACCESS_TOKEN    = PropertiesService.getScriptProperties().getProperty('access_token');
  var TOKEN_SECRET    = PropertiesService.getScriptProperties().getProperty('token_secret');
  // 認証情報作成
  var date = new Date();
  var timestamp = date.getTime().toString().substr(0,10);
  var nonce = timestamp; // 実行毎に一意であれば何でもいい
  var authorizationParameters = [
    'oauth_consumer_key=' +  CONSUMER_KEY,
    'oauth_nonce=' + nonce,
    'oauth_signature_method=HMAC-SHA1',
    'oauth_timestamp=' + timestamp,
    'oauth_token=' + ACCESS_TOKEN,
    'oauth_version=1.0'
  ];
  var encodedUrl = encodeURI(url)
  var baseUrl = encodedUrl
  var queryParams = new Array();
  if(encodedUrl.match(/\?/)){
    baseUrl = encodedUrl.split('?')[0]
    queryParams = encodedUrl.split('?')[1].split('&')
    queryParams.forEach(function(param){
      authorizationParameters.push(param);
    })
    authorizationParameters = authorizationParameters.sort()
    Logger.log(queryParams)
  }
  // Signature作成
  var signature_base_string = method + '&' + encodeURIComponent(baseUrl) + '&' + encodeURIComponent(authorizationParameters.join('&'));
  var signature_key = encodeURI(CONSUMER_SECRET) + '&' + encodeURI(TOKEN_SECRET);
  var signature = encodeURIComponent(Utilities.base64Encode(Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1, signature_base_string, signature_key)));
  var authorization = 'OAuth ' + authorizationParameters.join(',') + ',oauth_signature=' + signature;
  // APIパラメータ作成
  var parameters = {
    method : method,
    headers : {"Authorization": authorization},
    muteHttpExceptions : true
  };
  // API実行
  var response = UrlFetchApp.fetch(encodedUrl,parameters);
  return response
}

前回同様、
自分のタイムラインを取得するやつと、ツイートするやつを作ってみました。
見ての通り、まずはOAuth1a版で作成しています。
気分が乗ったらOAuth2版も作ろうと思います。

executeの解説

スクリプトプロパティ

まずはこの部分。

  // プロパティ取得
  var CONSUMER_KEY    = PropertiesService.getScriptProperties().getProperty('consumer_key');
  var CONSUMER_SECRET = PropertiesService.getScriptProperties().getProperty('consumer_secret');
  var ACCESS_TOKEN    = PropertiesService.getScriptProperties().getProperty('access_token');
  var TOKEN_SECRET    = PropertiesService.getScriptProperties().getProperty('token_secret');

GASには、スクリプトプロパティといって、環境変数みたいなものをセットできる機能があります。
これを使って、ソースコードには認証キーなどを持たせない作りにしています。
スクリプトプロパティは、ファイル -> プロジェクトのプロパティ から設定できます。

認証情報作成

ここが難関ですね。

  // 認証情報作成
  var date = new Date();
  var timestamp = date.getTime().toString().substr(0,10);
  var nonce = timestamp; // 実行毎に一意であれば何でもいい
  var authorizationParameters = [
    'oauth_consumer_key=' +  CONSUMER_KEY,
    'oauth_nonce=' + nonce,
    'oauth_signature_method=HMAC-SHA1',
    'oauth_timestamp=' + timestamp,
    'oauth_token=' + ACCESS_TOKEN,
    'oauth_version=1.0'
  ];
  var encodedUrl = encodeURI(url)
  var baseUrl = encodedUrl
  var queryParams = new Array();
  if(encodedUrl.match(/\?/)){
    baseUrl = encodedUrl.split('?')[0]
    queryParams = encodedUrl.split('?')[1].split('&')
    queryParams.forEach(function(param){
      authorizationParameters.push(param);
    })
    authorizationParameters = authorizationParameters.sort()
    Logger.log(queryParams)
  }
  // Signature作成
  var signature_base_string = method + '&' + encodeURIComponent(baseUrl) + '&' + encodeURIComponent(authorizationParameters.join('&'));
  var signature_key = encodeURI(CONSUMER_SECRET) + '&' + encodeURI(TOKEN_SECRET);
  var signature = encodeURIComponent(Utilities.base64Encode(Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1, signature_base_string, signature_key)));
  var authorization = 'OAuth ' + authorizationParameters.join(',') + ',oauth_signature=' + signature;

OAuth1では、下記の情報をヘッダにつけて送ります。

項目 中身
oauth_consumer_key Twitterアプリの認証キー
oauth_nonce リクエストごとに一意となる文字列。タイムスタンプとかでおk
oauth_signature_method 署名の作成形式。OAuth1はだいたいHMAC-SHA1
oauth_timestamp タイムスタンプ
oauth_token Twitterで取得したアクセストークン
oauth_version 1.0
oauth_signature 上6つとシークレットキーとかURLとか、各種パラメータをゴニョって作る署名

 
oauth_signatureのゴニョりっぷりについてはこちらを参考にしました。
https://developer.yahoo.co.jp/other/oauth/signinrequest.html

地味に厄介だったのがクエリパラメータです。
signatureを作成する際、
クエリパラメータとoauth...をアルファベット順でソートしてつなげる必要があるため、
リストに詰めてソートをかけています。

API実行

ここは、GASでAPI叩く時のお決まりみたいなところです。

  // APIパラメータ作成
  var parameters = {
    method : method,
    headers : {"Authorization": authorization},
    muteHttpExceptions : true
  };
  // API実行
  var response = UrlFetchApp.fetch(encodedUrl,parameters);
  return response

muteHttpExceptionsは、
API実行時にエラーが発生しても無視してレスポンスを返してくれるというものです。

叩いてみる

それでは叩いてみましょう!
まずはgetTimelineから。
取得結果をログ出力しています。

こんな感じ!

次はpostTweet

ログには何も出てきませんが…


ちゃんとツイートできています!

GASでは時間指定で起動するようにトリガーを設定できるので、
これとスプレッドシートを合わせることで、
ツイートを溜めておいて時間起動でツイートすることも可能になります!

ちなみに連続で実行してみましたが、
ツイート内容が同一だと無視されるようです。
こんなエラーが返されます。

{"errors":[{"code":187,"message":"Status is a duplicate."}]}

今回はここまで。
これからはこの技術を使って、
実用的な機能をたくさん装着していきます!