🎃

Google Apps Script(GAS)でTwitter API v2の非公開統計情報を取得する

2022/07/20に公開

こんにちは。
リケイのオコジョです。

ocojo

はじめに

Google Apps ScriptでTwitter API v2の非公開統計情報を取得する方法をまとめます。
「非公開統計情報」 と書くとなんだかイケナイ雰囲気がありますが、Twitterアナリティクスのインプレッションやエンゲージメントなど、non_public_metricsを取得するというお話です。

twitter my nonpublic metrics
「うわっ…私のインプレッション、低すぎ…?」

Twitter API v2について

従来、non_public_metricsの取得はOAuth 1.0aというハードルを越える必要がありました。
Twitter API v2ではOAuth 2.0 User Context authenticationという、それなりに一般的な方式でもnon_public_metricsを取得することができるようになりました。

今回はGoogle Apps Scriptから試してみましょう。

Twitterの設定

まずはTwitter側の設定をDeveloper Dashboardから行います。
認証の設定からOAuth2.0ONにし、Type of AppとしてWeb Appを選択することでConfidential ClientとしてAPIを利用する設定を行います。

Twitter Developer Dashboard

Callback URIは以下のようにスクリプトIDを含ませたURIを利用します。
スクリプトIDはGoogle Apps Scriptのプロジェクトの設定画面から確認できます。

https://script.google.com/macros/d/<スクリプトID>/usercallback

後ほどGoogle Apps Scriptのスクリプトプロパティに登録するOAuth 2.0 Client ID and Client Secretを取得しておきます。

Twitter Developer Dashboard

Google Apps Scriptの設定

Google Apps Scriptの初期設定や基本的な使い方は以下の記事にまとめています。

https://qiita.com/rikei_ocojo/items/43579f24a7c15f4f258a

今回、TwitterのAPIを利用するため、libraryとしてOAuth2と、Google Apps Script自体のScopeとしてexternal_requestを設定する必要があります。

appscript.json
{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
    "libraries": [
      {
        "userSymbol": "OAuth2",
        "version": "41",
        "libraryId": "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF"
      }
    ]
  },
  "oauthScopes": [
    "https://www.googleapis.com/auth/script.external_request"
  ],
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}

スクリプトプロパティに先ほど取得したCLIENT_IDCLIENT_SECRETを設定しておきます。
いつの間にかスクリプトプロパティが新UIからも設定できるようになりましたね。

Twitter Developer Dashboard

Google Apps ScriptからTwitter API v2を利用する

まずはOAuth 2.0 Authorization Code Flow with PKCEでアクセストークンを取得します。

function getService() {
  pkceChallengeVerifier();
  const userProps = PropertiesService.getUserProperties();
  const scriptProps = PropertiesService.getScriptProperties();
  const clientId = scriptProps.getProperty('CLIENT_ID');
  const clientSecret = scriptProps.getProperty('CLIENT_SECRET');

  return OAuth2.createService('twitter')
    .setAuthorizationBaseUrl('https://twitter.com/i/oauth2/authorize')
    .setTokenUrl('https://api.twitter.com/2/oauth2/token?code_verifier=' + userProps.getProperty("code_verifier"))
    .setClientId(clientId)
    .setClientSecret(clientSecret)
    .setCallbackFunction('authCallback')
    .setPropertyStore(userProps)
    .setScope('users.read tweet.read offline.access')
    .setParam('response_type', 'code')
    .setParam('code_challenge_method', 'S256')
    .setParam('code_challenge', userProps.getProperty("code_challenge"))
    .setTokenHeaders({
      'Authorization': 'Basic ' + Utilities.base64Encode(clientId + ':' + clientSecret),
      'Content-Type': 'application/x-www-form-urlencoded'
    })
}

function authCallback(request) {
  const service = getService();
  const authorized = service.handleCallback(request);
  if (authorized) {
    return HtmlService.createHtmlOutput('Success!');
  } else {
    return HtmlService.createHtmlOutput('Denied.');
  }
}

function pkceChallengeVerifier() {
  var userProps = PropertiesService.getUserProperties();
  if (!userProps.getProperty("code_verifier")) {
    var verifier = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";

    for (var i = 0; i < 128; i++) {
      verifier += possible.charAt(Math.floor(Math.random() * possible.length));
    }

    var sha256Hash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, verifier)

    var challenge = Utilities.base64Encode(sha256Hash)
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=+$/, '')
    userProps.setProperty("code_verifier", verifier)
    userProps.setProperty("code_challenge", challenge)
  }
}

OAuth2.createService('twitter')で以下のような認可リクエストを作成しています。

https://twitter.com/i/oauth2/authorize?client_id=<CLIENT ID>&response_type=code&redirect_uri=<https://script.google.com/macros/d/<スクリプトID>/usercallback>&state=<STATE>&scope=<SCOPE>&code_challenge_method=S256&code_challenge=<CODE CHALLENGE>

各パラメータについては以下の記事が詳しいです。

https://zenn.dev/kg0r0/articles/8b1cfe654a1cee

  • state
    .setParamstateを指定していませんが、OAuth2ライブラリが自動で生成/検証してくれるようです。
  • scope
    ドキュメントにある通りGET /2/tweets/:idにはtweet.read users.readが必要です。
  • code_verifiercode_challenge
    PKCE(Proof Key for Code Exchange)の仕様はrfc7636です。

Google Apps ScriptでTwitter API v2の非公開統計情報を取得する

.hasAccess()でアクセストークンの状態を確認できます。
Authorizationヘッダーにアクセストークンをのせ/2/tweets/:idに対し?tweet.fields=non_public_metricsのクエリパラメータを付与しGETリクエストします。

function main() {
  const service = getService();
  if (service.hasAccess()) {
    const url = 'https://api.twitter.com/2/tweets/<TWEET ID>?tweet.fields=public_metrics,created_at,non_public_metrics';
    const response = UrlFetchApp.fetch(url, {
      headers: {
        Authorization: 'Bearer ' + service.getAccessToken()
      },
      muteHttpExceptions: true
    });
    const result = JSON.parse(response.getContentText());
    Logger.log(JSON.stringify(result, null, 2));
  } else {
    const authorizationUrl = service.getAuthorizationUrl();
    Logger.log('Open the following URL and re-run the script: %s', authorizationUrl);
  }
}

結果は以下のような感じです。

{
  "data": {
    "non_public_metrics": {
      "impression_count": 157,
      "url_link_clicks": 32,
      "user_profile_clicks": 1
    }
  }
}

「うわっ…私のインプレッション、低すぎ…?」
はい。ぜひフォローしてください。

https://twitter.com/mounmou_design

まとめ

Google Apps ScriptでTwitter API v2の非公開統計情報を取得する方法をまとめました。
定期実行し、Google Sheetsに書き出したりなど活用シーンが広がりそうですね。

参考

https://github.com/googleworkspace/apps-script-oauth2

Discussion