Google Apps ScriptからTwitter API v2を使ってツイートする【GAS】

2020年8月にそれまで制限の厳しかったTwitterのAPIが割と簡単に申請して利用できるようになったので、遅まきながらGoogle Apps ScriptからこのTwitter API v2を使ってツイートをしてみた。

しかし、他のREST APIと異なり、OAuth2.0認証ではなく古いOAuth1.0認証のままなので、ちょっとこれまでのコードとは異なる(特に使用するライブラリ)。ということで、ボットなどで使えるように下準備をしてみました。

※2023年1月時点、大きくTwitter APIが変更されており動作しなくなりました。また新しくOAuth2.0認証も登場したので、そちらでコードを書き直しました。以下のエントリーを参考にしてみてください。

Google Apps ScriptからTwitter APIをOAuth2.0認証で使う【GAS】

今回使用するスプレッドシート等

いつも使ってるOAuth2.0認証用ライブラリではなく、1.0用のライブラリを使用します。コードの書き方は殆ど同じになるように寄せてます。また、本ライブラリはV8ランタイムでもきちんと動作します。

APIリファレンスをよく見て色々な事に活用しましょう。11/15より正式にv2がv1より変わった事に伴ってAPIの追加などもされているので、注意です。

事前準備

Twitter API v2は利用申請が必要です。関連リンクを参考に申請を行い、利用できるようにしておく必要があります。利用できるようになったら、以下の手順でGAS側、Twitter側でそれぞれ準備を行います。

GAS側の事前準備

ライブラリの追加

以下の手順でOAuth1 for Apps Scriptライブラリを追加しましょう。

  1. スクリプトエディタを開きます。
  2. サイドバーよりより「ライブラリ」の+ボタンをクリック
  3. ライブラリを追加欄に「1CXDCY5sqT9ph64fFwSzVtXnbjpSfWdRymafDrtIZ7Z_hwysTY7IIhi7s」を追加します。
  4. 今回はバージョンは18を選択してみます。
  5. 保存ボタンを押して完了

これで、OAuth1.0認証にまつわる様々な関数を手軽に利用できるようになります。

図:ライブラリを追加した様子

コールバックURLを取得する

コールバックURLとは、認証を完了しAccess Tokenを取得したら戻るべきURLを指定するものです。これは、スクリプトIDをもとに作られているので、スクリプトIDを取得して組み立てます。

  1. スクリプトエディタのサイドバーより「プロジェクトの設定」を開く
  2. 情報の中にある「スクリプトID」を控えておく。
  3. https://script.google.com/macros/d/スクリプトID/usercallback として組み立てる。これがコールバックURLとなる。

図:スクリプトIDはファイル毎に異なるのです。

Twitter側の事前準備

アプリの作成

Twitter側ではアプリのプロジェクトを作成すると共に、API KeyとSecretを取得する必要があります。以下の手順で作成しましょう。

  1. Twitter DevelopersのDashboardにアクセスする
  2. 下のほうにある「Create App」をクリックする
  3. AppNameを指定します。今回は「autotweet_gas」と命名しました。これは後にコード内で利用します。同じ名前は利用出来ないので、誰かが取ってると使えません。
  4. API KeyとSecretが表示されてるので、コピーしておきます。
  5. App Settingsをクリックする
  6. App PermissionsのEditボタンをクリック
  7. Read and Writeに変更して、Saveをクリックする
  8. Authentication SettingsのEditボタンをクリックする
  9. Enable 3-legged OAuthのスイッチをオンにする
  10. Callback URLsに前述のGAS側のコールバックURLを入れる
  11. Website URLは適当に入れて、Saveをクリックする

注意点として、最初の1個目はstandardアプリとして作れますが、2個目以降はstand aloneアプリとなり、こちらではGASからはツイートは出来ません(403エラーになります

図:ここの設定の変更が重要です

利用制限

Twitter API v2のQuotaを調べて見ると、以下のような制限があります。

  • ツイートは15分、1アプリ当たり300件(ユーザ単位だと900件まで) - v1エンドポイントの場合
  • ツイートは15分、1アプリ当たり200件 - v2エンドポイントの場合
  • また、v2エンドポイントでの投稿の場合、3時間以内にユーザ・アプリ毎に300リクエストまでという追加規制があります。
  • いいねは、24時間当たり1000リクエストまで
  • ツイートの取得は1リクエスト100件まで(100件を超える部分は、next tokenでの取得が必要)
  • 画像のアップロードはv1でなければ現在まだアップロード出来ません。v2ではメディアアップロードのエンドポイントがまだ存在していません。v1のドキュメントはこちらにあります

他にも細かな項目ごとにリミットが掛けられているので、利用する場合には意識する必要があります。特に同じアプリを複数で共有する場合、あっという間にリミットに達してしまうので要注意。応答ヘッダーにその内容がx-rate-limitとして含まれているので、エラー処理を加えたい場合には考慮すると良いでしょう。超過すると429エラー(Too Many Request)が返ってきます。

また、API KeyはPermissionを変更すると使えなくなるので、再度生成する必要性があります。

同じようなキーワードでの検索の場合、リアルタイムでなければスプレッドシートにキャッシュしておいて、それを返すようにしておくと、リクエスト数を減らす事が可能(キャッシュはトリガー使用で1時間ごとに1回取得などにしておくと良いでしょう)。また、連続して大量のツイートを取得するようなケースでは、リミットに抵触しないように、Utilities.sleepでスリープ処理を入れるべきでしょう。

申請不要で使えるようになりました

2021年11月15日、Twitter API v2が正式なAPIとしてv1から移行され、同時にこれまで煩雑な申請が必要であったAPI利用申請ですが、「Essensial」というレベルであれば、申請不要で利用可能になりました。その上位の「Elevated」というレベルでは申請が必要です。既にAPI利用申請を出して取得済みの人は、自動的にElevatedレベルになります(Elevated+やAcademic Researchという研究用レベルも用意されています。)

  • Essential : 1アプリケーション、50万ツイート/月の取得まで
  • Elevated : 3アプリケーション、200万ツイート/月の取得まで

となっています。詳細はこちらのURLより

図:自動的にElevatedになってた

認証を行う処理を作成する

ここまでで事前準備が完了し、あとは認証を実行してAccess Tokenを取得すればツイートの実行準備はすべて完了になります。

GAS側コード

//メニューを構築する
function onOpen(e) {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('▶OAuth認証')
      .addItem('認証の実行', 'startoauth')
      .addItem('テストツイート', 'testtweet')
      .addSeparator()
      .addItem('ログアウト', 'reset')
      .addItem('スクリプトプロパティ', 'openCheck')
      .addToUi();
}

//認証用の各種変数
var apikey = 'ここにAPI KEYを入れる';
var apisecret='ここにAPI Secretを入れる';
var tokenurl = "https://api.twitter.com/oauth/access_token";
var reqtoken = "https://api.twitter.com/oauth/request_token";
var authurl = "https://api.twitter.com/oauth/authorize";
var endpoint = "https://api.twitter.com/1.1/statuses/update.json";  //ツイートをするエンドポイント
var endpoint2 = "https://api.twitter.com/2/tweets";  //v2のツイートするエンドポイント
var appname = "ここにアプリの名称を入れる";  //アプリの名称

//認証実行
function startoauth(){
  //UIを取得する
  var ui = SpreadsheetApp.getUi();
  
  //認証済みかチェックする
  var service = checkOAuth(appname);
  if (!service.hasAccess()) {
    //認証画面を出力
    var output = HtmlService.createHtmlOutputFromFile('template').setHeight(450).setWidth(500).setSandboxMode(HtmlService.SandboxMode.IFRAME);
    ui.showModalDialog(output, 'OAuth1.0認証');
  } else {
    //認証済みなので終了する
    ui.alert("すでに認証済みです。");
  }
}

//認証チェック用関数
function checkOAuth(serviceName) {
  return OAuth1.createService(serviceName)
    .setAccessTokenUrl(tokenurl)
    .setRequestTokenUrl(reqtoken)
    .setAuthorizationUrl(authurl)
    .setConsumerKey(apikey)
    .setConsumerSecret(apisecret)
    .setCallbackFunction('authCallback')
    .setPropertyStore(PropertiesService.getUserProperties());
}

//認証コールバック
function authCallback(request) {
  var service = checkOAuth(request.parameter.serviceName);
  var isAuthorized = service.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput('認証が正常に終了しました');
  } else {
    return HtmlService.createHtmlOutput('認証がキャンセルされました');
  }
}

//アクセストークンURLを含んだHTMLを返す関数
function authpage(){
  var service = checkOAuth(appname);
  var authorizationUrl = service.authorize();
  var html = "<center><b><a href='" + authorizationUrl + "' target='_blank' onclick='closeMe();'>アクセス承認</a></b></center>"
  return html;
}

//ログアウト
function reset() {
  OAuth1.createService(appname)
      .setPropertyStore(PropertiesService.getUserProperties())
      .reset();
  SpreadsheetApp.getUi().alert("ログアウトしました。")
}
  • 取得したAPI Key、API SecretおよびAppNameをそれぞれコード内に記述します。
  • 今回利用するTwitter APIのエンドポイントはhttps://api.twitter.com/1.1/statuses/update.jsonとなります。v2用のエンドポイントは、
    https://api.twitter.com/2/tweetsとなっています(2021/11/15に追加)
  • startoauthを実行して認証を実行すれば、スクリプトプロパティにAccess Tokenが格納されます。
  • 認証時に次項のTemplate.htmlが呼び出され、認証完了するとCallback URLに指定したURLにアクセスされて、authCallBackが実行されます。

HTML側コード

template.htmlというダイアログ用のファイルを用意します。ここでアクセス承認を実行し、ログインをすると、アクセストークンその他が取得可能になります。

<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script>
    //認証用URLを取得する
    google.script.run.withSuccessHandler(onSuccess).authpage();

    function onSuccess(data){
        document.getElementById("kinoko").innerHTML = data;
    }
</script>

<style type="text/css">
    /* --- ボックス --- */
    div.section {
        width: 480px; /* ボックスの幅 */
        background-color: #ffffff; /* ボックスの背景色 */
        border: 1px #c0c0c0 solid; /* ボックスの境界線 */
        font-size: 100%; /* ボックスの文字サイズ */
    }

    /* --- 見出し --- */
    div.section h3 {
        margin: 0; /* 見出しのマージン */
        padding: 6px 10px; /* 見出しのパディング(上下、左右) */
        background-color: #f5f5f5; /* 見出しの背景色 */
        border-bottom: 1px #c0c0c0 solid; /* 見出しの下境界線 */
        font-size: 120%; /* 見出しの文字サイズ */
    }

    /* --- ボックス内の段落 --- */
    div.section p {
        margin: 1em 10px; /* 段落のマージン(上下、左右) */
    }
</style>

<div class='section'>
    <h3 id='header'>OAuth認証の許可が必要です。</h3>
    <hr>
    <div id="info">
        <p>
        このスクリプトは、Twitter API V2にアクセスするために、特別なログイン処理を利用しています。<br>
        既に特別なログインに関する設定はなされており、承認がされるとプログラムを実行することが出来ます。この承認がなされない場合、プログラムの実行に制限が掛かり、
        処理が続行できません。<br><br>

        <div id="kinoko"></div>
    </div>
    <p>
    <script>
        function closeMe(){
            if(google && google.script && google.script.host){
                google.script.host.close();
            } else if(window && window.close){
                window.close();
            } 
        }
    </script>
</div>
  • 実際にこれらのコードで、startoauthを実行すると、スプレッドシート上で認証用のダイアログが出ます。
  • 認証でtwitterにログインします
  • 取得したAccess Tokenほかはスクリプトプロパティのappnameという項目にガッツリ値が格納されます。
  • reset関数はログアウトされて、再度認証ができるようになります。

ツイートを実行する

認証の実行

スプレッドシートを開き直して、メニューより「OAuth認証」を開くと、「認証の実行」があるので、クリックして実行します。ダイアログが出てくるので、実行するとTwitter認証画面が出ます。

連携アプリを認証を実行するとAccess Tokenが取得されてスクリプトプロパティに格納され、認証が完了します。

図:認証を実行してる画面

取得したTokenを確認

本サンプルスプレッドシートは、スクリプトプロパティの中身を見られるように、機能を追加済みです。メニューより「OAuth認証」を開くと、「スクリプトプロパティ」があるので、開くことで、認証結果のTokenなどを確認出来ます。

図:無事取得出来てるのを確認する

ツイートの実行

v1 APIの場合

同じく、メニューより「OAuth認証」を開くと、「テストツイート」があるので、開くと以下のGASのコードを実行し、GAS側からツイートを実行することが可能になります。これを改造して、例えばスプレッドシートに列挙したデータをトリガーなどを使って自動ツイートしてみたりするBotを作る、何かの処理を行った結果を、GASで受け付けて、テストツイートを実行するように仕込んだりで、色々と使いみちがあると思います。

エンドポイントURLはhttps://api.twitter.com/1.1/statuses/update.jsonとなっています。

//テストツイートする
function testtweet(){
  //トークン確認
  var service = checkOAuth(appname);

  //リクエストオプション
  var options = {
    "method": "post",
    "payload": {
      status: 'GASからツイートを実行してみた@v2 API'
    }
  }

  //リクエスト実行
  var response = JSON.parse(service.fetch(endpoint, options));

  //リクエスト結果
  console.log(response)
}
  • リクエストはPOSTで行い、service.fetchという形でライブラリの力を借りてリクエストを実行します。
  • @アカウント名をstatusに入れれば、リプになります。
  • この他ツイートだけでなくデータの取得などのメソッドもたくさん用意されてるので、この仕組があれば、Twitterを色々と操作することが可能になると思います。

図:GASから実行した結果

v2 APIの場合

正式にv2 APIがリリースされたことによって、v2用のエンドポイントURLが用意されました。URLは、https://api.twitter.com/2/tweetsとなり、このURLに対して、POSTメソッドで投げる必要性があります。リクエストパラメータが変更されていて、しかも公式ドキュメントが非常に読みにくい物になっています。

var endpoint2 = "https://api.twitter.com/2/tweets";

//テストツイートする
function testtweet2(){
  //トークン確認
  var service = checkOAuth(appname);

  //message本文
  var message = {
    //テキストメッセージ本文
    text: 'GASからツイートを実行してみた@v2 API'
  }

  //リクエストオプション
  var options = {
    "method": "post",
    "muteHttpExceptions" : true,
    'contentType': 'application/json',
    'payload': JSON.stringify(message)
  }

  //リクエスト実行
  var response = JSON.parse(service.fetch(endpoint2, options));

  //リクエスト結果
  console.log(response)
}
  • メッセージ本文はJSON化する必要があります。またその時のリクエストパラメータは本文はtextで指定が必要
  • リクエスト時にcontentTypeにてapplication/jsonの指定が必要です。
  • 画像などはリクエストパラメータにて、mediaで指定するようですが、こちらはまだ調査中。

ツイートを取得してみる

Twitterをキーワード検索して結果をスプレッドシートに書き出してみようと思います。クエリの作り方については、Building queries for Search Tweetsに記載があります。エンドポイントは、https://api.twitter.com/2/tweets/search/recent?query=となり、これにワードやオプションをつなげて実行します。ユーザ名検索の場合は@マークはつけません(こちらはAPIのバージョンが2となってる)

今回は「Google Apps Script」で検索して日本でのツイートを20件ずつ合計40件取得してみます。また、検索オプションで指定する取得するデータの指定であるtweet.fieldsレスポンスついてはこちらで確認出来ます。

var getpoint = "https://api.twitter.com/2/tweets/search/recent?query="; //ツイートを検索取得する為のエンドポイント

//特定ワードでツイートを取得する
function gettweet(){
  //トークン確認
  var service = checkOAuth(appname);

  //検索オプション(20件で指定)
  var option = "&tweet.fields=author_id,id,text,created_at&max_results=20";

  //検索キーワード(URI Encodeする)
  var keyword = encodeURIComponent("\"google apps script\" -is:retweet lang:ja")

  //検索キーワードとURL構築
  var url = getpoint + keyword + option;

  //リクエスト実行
  var response = JSON.parse(service.fetch(url));

  //リクエスト結果からnext tokenを取得する
  var next = response.meta.next_token;

  //dataの中身を配列に書き出す
  var array = [];
  var data = response.data;
  var length = data.length;

  for(var i = 0;i<length;i++){
    //一時配列を用意
    var temparr = [];

    //レスポンスデータを配列に書き出す
    temparr.push(data[i].author_id);
    temparr.push(data[i].text);
    temparr.push(data[i].created_at);

    //書込み用配列に追加
    array.push(temparr);
  }

  //次の20件を取得する(nexttokenを使用する)
  option = option + "&next_token=" + next;

  //URL構築
  url = getpoint + keyword + option;

  //リクエスト実行
  response = JSON.parse(service.fetch(url));

  //スリープを10秒入れる
  Utilities.sleep(10 * 1000);

  //次の20件のデータをarrayに追加
  data = response.data;
  length = data.length;

  for(var i = 0;i<length;i++){
    //一時配列を用意
    var temparr = [];

    //レスポンスデータを配列に書き出す
    temparr.push(data[i].author_id);
    temparr.push(data[i].text);
    temparr.push(data[i].created_at);

    //書込み用配列に追加
    array.push(temparr);
  }

  //arrayをスプレッドシートに上書きで書込み
  var ui = SpreadsheetApp.getUi();
  var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("シート1");
  var colcnt = array[0].length;
  var rowcnt = array.length;
  ss.getRange(2,1,rowcnt,colcnt).setValues(array);

  //完了メッセージ
  ui.alert("ツイートデータの取得が完了しました。")
}
  • optionにてツイート内容のうち取得する項目をtweet.fieldsで指定。またmax_resultで1回当たりの取得ツイート数を指定
  • キーワードはURL Encodeが必要なのでencodeURIComponentで加工。
  • キーワードに於いて、ダブルコーテーションで括って絞り込みが必要な時は、バックスラッシュでダブルコーテーションをエスケープする必要があります。
  • また、今回はリツイートは取得しないので、-is:retweetで排除し、日本語だけを取得するので、lang:jaを指定しています。
  • 取得データを配列に加えて最後に一発でスプレッドシートに書込みをしています。
  • また、今回は20件を2回実行して、合計40件のツイートを取得するので、2回目以降ではoptionにnext_tokenを加える必要があるので、1回目のレスポンスデータに入ってるnext_tokenをoptionに追記させてリクエストしています(これで次の20件が取れる)
  • リミットにかからないように、2回目のリクエスト前にUtilities.sleepで10秒間スリープを入れています。

図:無事にツイートを取得できた

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)