GASからTwitter API v2でツイートする【Google Apps Script】

Google Apps Script

(2023/05/16) コメントにて、「GASからの初回認証時に『ReferenceError: OAuth2 is not defined』エラーになる」というご指摘をいただきました。こちら、GAS側でライブラリを追加していないと発生するエラーです。ライブラリ追加に関する記載が抜けていたので追加しました。ご指摘ありがとうございました。

(2023/10/13) コメントにて、認証に失敗するというご指摘をいただきました。CLIENT_ID、CLIENT_SECRETを発行する手順について変更・追記しました。ご指摘ありがとうございました。

本記事では、Google Apps Script(GAS)からTwitterのAPI経由でツイッター(現X)に投稿する方法を紹介します。

↑のツイッターアカウントでは、ブログの更新をした際に手動でURLを貼り付けてお知らせをしているのですが、どうしても忘れてしまいます。そこで、ブログ更新のお知らせを自動で投稿できるようにしたいと考えました。とはいえ、最初から色々と機能を盛り込むのも大変なので、まずはAPI経由でツイートするところまでを作成します。

Twitter APIは、3/29に新しいAPIが発表になりましたが、本記事は新しいAPI(Twitter API v2)のFreeプランで動かしています。新しいTwitter APIに関する詳細は以下をご参照ください。

Twitter側の設定

Twitter APIの登録

まず、Twitter Developer Platformを開き、ご自身のTwitterアカウントでログインします。

Twitterアプリケーションの作成

ログインすると以下の画面が開きますので、右上の「Create an App」を押します。

「Apply」を押します。

「Ready to build on Twitter?」という画面が開きます。

(まだの場合のみ)電話番号認証

Twitter APIの利用には電話番号認証が必要となりますので、まだの方は「Verify Phone」を押下して電話番号認証を完了させてください。

電話番号は、最初の0を取った番号を入力します。「080-1111-2222」なら「8011112222」となります。

「Next」を押すと、入力した電話番号にショートメールで認証コードが届きますので、次のページで入力してください。入力後、自分のツイッターの画面に遷移すれば認証完了です。

完了したら、ページを更新(F5キーまたは、ブラウザの左上の更新アイコンを押下)します。すると、先ほど非活性だったボタンが押せるようになっているはずです。

無料/有料プランを選択

画面の一番下にある「Sign up for Free Account」を押下します。有料プランを登録する場合には「Subscribe」から登録します。

利用目的を記載して利用開始

「Developer agreement & policy」という画面では、Twitter APIの利用目的を記載します。250字以上入力する必要がありますが、審査されるわけではないので内容はなんでもOKです。

…といっても、250字って意外と長いです。DeepLなどの翻訳ツールで適当に申請内容っぽい文章を作ると楽です。

「Submit」を押下すると、Dashboard画面に遷移します。

【変更につき本手順は以降で行う】Client ID・Client Secretの取得

(2023/10/13追記) 本手順は以降で行います。無視して以降に進めてくださいm(__)m

左側メニューから、Default project-XXXの下にある、TwitterIDから始まる項目を押下します。

上部のタブから「Keys and tokens」を選択します。

「OAuth 2.0 Client ID and Client Secret」の「Client ID」と「Client Secret」をメモしておきます。(あとでGASのスクリプトに記載します)

Twitter APIの設定

上部のタブから「Settings」を選択します。

「User authentication settings」の「Edit」を押下します。

App permissions

App permissions(required)は「Read and write」を選択します。

Type of App

「Type of App(required)」は「Web App, Automated App or Bot」を選択します。

App info

「App info」は「Callback URI / Redirect URL (required) 」にGASのコールバックURLを設定します。こちらは次章(すぐ下)で詳しく解説しています。

「Website URL (required)」については、そのURLが実在するかどうかまではチェックしていないようなので(参考)、今回は自分のツイッターURLを設定しておきました。

Google Apps Scriptのプロジェクトを作成

Google Apps Script(GAS)を開いて、新しいプロジェクトを作成します。

ここで、コールバックURLを取得しておきます。新規作成したGASのプロジェクトのURLのID部分をコピーして、以下のようにURLを生成します。

https://script.google.com/home/projects/[ここをコピー]/edit

https://script.google.com/macros/d/[コピーしたやつを貼り付け]/usercallback

こちらのコールバックURLを、Twitter APIのApp info>Callback URI / Redirect URL (required) に記載してください。

Client ID・Client Secretの取得

App InfoのWebsite URLに↑を貼り付けたら、「Save」を押します。

「Changing permissions might affect your app」と出ますので、「Yes」を押します。

「OAuth 2.0 Client ID and Client Secret」の「Client ID」と「Client Secret」をメモしておきます。(あとでGASのスクリプトに記載します)

OAuth2ライブラリの追加

左側メニューから「ライブラリ」を押します。

「スクリプトID」を入力します。

OAtuth2のスクリプトID

1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF

こちらをコピーしてそのまま貼り付けて「検索」を押し、OAuth2が出てきたら「追加」を押します。

Twitter API認証処理

GASのエディタを開くとデフォルトで作成されている「myFunction」は全て削除し、Twiter APIの認証処理を記載します。

参考:https://twittercommunity.com/t/cannot-auth-my-google-script-app/177367
https://github.com/googleworkspace/apps-script-oauth2

const CLIENT_ID = 'XXX'
const CLIENT_SECRET = 'XXX'

function getService() {
  pkceChallengeVerifier();
  const userProps = PropertiesService.getUserProperties();
  const scriptProps = PropertiesService.getScriptProperties();
  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(CLIENT_ID)
    .setClientSecret(CLIENT_SECRET)
    .setCallbackFunction('authCallback')
    .setPropertyStore(userProps)
    .setScope('users.read tweet.read tweet.write offline.access')
    .setParam('response_type', 'code')
    .setParam('code_challenge_method', 'S256')
    .setParam('code_challenge', userProps.getProperty("code_challenge"))
    .setTokenHeaders({
      'Authorization': 'Basic ' + Utilities.base64Encode(CLIENT_ID + ':' + CLIENT_SECRET),
      '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)
  }
}

function logRedirectUri() {
  var service = getService();
  Logger.log(service.getRedirectUri());
}

CLIENT_ID、CLIENT_SECRET には、Twitter APIの「Client ID・Client Secretの取得」の部分で説明したとおり、「OAuth 2.0 Client ID and Client Secret」の「Client ID」と「Client Secret」を記載してください。

初回実行時は認証が必要

Twitter APIで初回実行する際には、認証が必要です。以下のコードを先ほどのスクリプトに追加します。

追加場所はどこでも大丈夫ですが、const変数の宣言の下(3行目以降)あたりに入れておけば良いかと思います。

参照:https://qiita.com/yuzinet/items/ae4b9ca2b5cd989de435

function main() {
  const service = getService();
  if (service.hasAccess()) {
    Logger.log("Already authorized");
  } else {
    const authorizationUrl = service.getAuthorizationUrl();
    Logger.log('Open the following URL and re-run the script: %s', authorizationUrl);
  }
}

実行する関数が「main」になっていることを確認して、「実行」を押します。

「承認が必要です」というダイアログが出ますので、「権限を承認」を押します。

「アプリにアクセスを許可」を押します。

「Success!」と表示されたら認証完了です。

承認に成功したら、上記の画面は閉じてしまってOKです。

Bad Requestと出る場合、Google関係のCookieを削除してから再実行するとうまくいくというコメントをいただきました。もし同様のエラーが出た際にはCookie削除を試してみてください。

ツイート処理の実装

ツイート部分の処理を追加します。

参考:https://twittercommunity.com/t/cannot-auth-my-google-script-app/177367
https://qiita.com/yuzinet/items/ae4b9ca2b5cd989de435

function sendTweet() {
  var payload = {
    text: 'Test tweet from API!!!!!'
  }

  var service = getService();
  if (service.hasAccess()) {
    var url = `https://api.twitter.com/2/tweets`;
    var response = UrlFetchApp.fetch(url, {
      method: 'POST',
      'contentType': 'application/json',
      headers: {
        Authorization: 'Bearer ' + service.getAccessToken()
      },
      muteHttpExceptions: true,
      payload: JSON.stringify(payload)
    });
    var result = JSON.parse(response.getContentText());
    Logger.log(JSON.stringify(result, null, 2));
  } else {
    var authorizationUrl = service.getAuthorizationUrl();
    Logger.log('Open the following URL and re-run the script: %s',authorizationUrl);
  }
}

実行する関数が「sendTweet」になっていることを確認して、「実行」を押します。実行結果にはツイートID、ツイートしたテキストが出力されます。

ツイッターを確認すると、設定した内容でツイートされていることを確認できました。

注意点

API経由でツイッター投稿をする際の注意点は以下になります。

過去のツイート内容と同じものはツイートできない

Twitter API経由で、過去にツイートした内容と一言一句同じ内容はツイートできません。

対策としては、過去の投稿を削除するか、ツイート内容を一部分でも変更する必要があります。同じような内容を定期的にツイートしたい場合には、日付や管理番号などを付加すると良いかと思います。

無料版の投稿上限は1500回/月まで

Twitter社は3/29に新しいAPIを発表しました。無料版でツイートできる回数は、一か月に1500回までとなります。月額100ドルのプランでは、一か月に3000回までです。この回数は、ツイートだけでなくDMやリツイート、お気に入り登録なども1回としてカウントされますので、無料版では全然足りないどころか、有料版でも運用が難しいとの声が見受けられます。

今後30日間のあいだに新しいAPIに移行するよう呼び掛けており、その後は従来のAPIは廃止になるとのことです。

参考にさせていただいたサイト

本記事を作成するにあたり、参考にさせていただいたサイトは以下になります。

GASでTwitterのAPIを使って投稿(ツイート)する
認証まわりのスクリプトについて、解説が分かりやすかったです。

OAuth2、API v2でTwitter🐦にツイートできるようにしてみた(GAS)
無駄のない分かりやすい解説でした。初回認証で躓いたので大変参考になりました。

Twitter Developersのフォーラム
サンプルコードを参考にしました。

Apps Script OAuth2 のreadme
GASからOAuth2を利用する際の基本的な流れが書いてあります。

まとめ

今回は、Twitter APIとGASを利用してツイートをする方法について解説しました。私自身、TwitterのAPIを利用するのは初めてでしたので、既に使い込んでいる主人(prtn)に聞きながら、また、ネットの記事を参考にしながらなんとかツイートすることができました。

今後は、Twitter APIを利用して、ブログを更新したらお知らせツイートをしたり、GASのトリガー(定期実行)を使って過去の記事を紹介するツイートなどができたらいいなと思っています。

¥2,860 (2023/10/25 13:57時点 | Amazon調べ)
chaso

文系出身、数字が苦手な平凡主婦。塾講師、大手企業SE、不動産事務、Webライター、QAエンジニアを経て現在RPAエンジニアとして働いています。機械音痴だけど効率化や自動化をこよなく愛しています!お仕事の依頼・ご相談は問い合わせよりお願いいたします♪

chasoをフォローする

コメント

  1. 松田 より:

    はじめまして!
    GAS初心者の松田と申します。

    このサイトに掲載のスクリプトをコピーしてテストしてみたのですが、認証用のmain()関数の実行時に下記のエラーになりました。

    エラー内容
    ReferenceError: OAuth2 is not defined
    getService @Tweet_bot.gs:8
    sendTweet @ninshou.gs:2

    Tweet_bot.gsの8行目は以下になります。

    return OAuth2.createService(‘twitter’)

    サイト掲載のコード自体は何も変えてないのですが・・・

    • chaso chaso より:

      はじめまして!コメントありがとうございます。

      記載いただいたエラーの原因は、CLIENT_ID、CLIENT_SECRETの中身を設定していないことが原因かと思います。
      サンプルコードは、
      const CLIENT_ID = ‘XXX’
      const CLIENT_SECRET = ‘XXX’
      となっていますが、これをご自身の環境に合わせて変更していただく必要があります。
      こちらは、「Client ID・Client Secretの取得」の章にて取得方法を解説していますので、取得した値を入れて実行していただくと
      正しく動くかと思います。
      また試してみて不明点等ございましたらお気軽にコメントいただければと思います。

  2. より:

    この通りにやったらできました!!ありがとうございます‍♀️‍♀️‍♀️

    • chaso chaso より:

      す さん
      コメントありがとうございます。
      お力になれてとても嬉しいです!
      ブログ更新の励みになります♪

  3. J より:

    はじめまして

    参考にさせていただいております

    お書き頂いている通りに行っているのですが、mainで発行されたURLでアプリの許可までは行くのですが『アプリにアクセスを許可』を押下するとローディングの後にエラーでBadRequestと言われてしまいます

    どうすればいいでしょうか?

    お手数ですがご教授いただけると幸いです

    • chaso chaso より:

      はじめまして、コメントありがとうございます。

      BadRequestと出ている場合、クライアント(こちら側)のエラーだと思われます。

      ・「Client ID」と「Client Secret」が正しく記載されているか
      ・発行されたURLを正しくコピペして認証ページを開いているか

      上記を試して解決しなければ、ブラウザのキャッシュ・cookieを削除してみてください。
      もし、ブラウザに拡張機能を入れている場合、メモリ不足を起こしている可能性がありますので一旦無効にしてみてください。

      こちら側で再現できないため、的確な返答ができていなかったらすみません。。
      解決しない場合はまたご相談いただければと思います。

  4. ココス より:

    はじめまして、こちらの記事を参考にさせていただき無事にTwitter(X)への自動投稿に成功いたしました。誠にありがとうございます!

    自動投稿には成功したのですが、数日経つとmain()を実行し認証?をしないと動かなくなってしまいました。
    認証は定期的に手動で行う必要があるのでしょうか?

    また、authCallbackを実行すると以下のようなエラーが発生していました。
    これが原因なのかな?と考えたのですが解決できず…
    もし何かご存知でしたらご教授いただければ幸いです。

    TypeError: Cannot read properties of undefined (reading ‘parameter’)
    Service_.handleCallback @ Service.gs:440
    authCallback @ コード.gs:38

    • chaso chaso より:

      はじめまして、ブログを閲覧いただき誠にありがとうございます!

      GAS側でOAuth2ライブラリは追加されていますでしょうか?
      エラーが出ている箇所ではアクセス権限の認可を行っており、こちらがOAuth2のライブラリ関数となっています。
      そのため、ライブラリを予めGAS内で追加していないとエラーになります。
      本記事では「OAuth2ライブラリの追加」にて解説しています。

      もし、すでにライブラリの追加が完了していたら見当外れなご指摘となってしまい申し訳ございません。

      私自身もこちらのスクリプトでXへの投稿をしていますが、認証は初回時のみで、それ以降は必要ありません。

      また分からない点があればお気軽にコメントいただければと思います。

  5. Yos.Kaw. より:

    はじめまして。コメント失礼します。

    関数「main」を実行すると以下のようなエラーが出てしまいます。
    どうか、ご教授お願いします。
    エラー:
    TypeError: redeclaration of const CLIENT_SECRET.

  6. chimaki より:

    はじめまして。質問失礼いたします。
    記事を参考にさせていただき設定を進めましたが、
    認証の部分がうまくいかず躓いています。

    「○○がXアカウントへのアクセスを求めています.」➡【アプリにアクセスを許可】
    の青ボタンを押下するところまではいくのですが、
    そのあと少しロードを挟んだのち、真っ白な画面の左上に
    「{“message”:”認証に失敗しました。”}」
    と出てしまいます。

    Client ID・Client Secretは最新のものを使用し、
    入力できているはずなのですが・・・
    なにか対策があればご教示いただけますと幸いです。

    • chaso chaso より:

      コメントありがとうございます。
      認証まで進んでいて失敗しているというケースは経験がなく、有効な対策が浮かばないのが正直なところです。
      お力になれず申し訳ございません・・・。
      この記事を書いたのがXに移行する前でしたので、もしかしたら記事の内容では現在できないことも考えられます。
      一度、こちらでも設定しなおしてみて出来るか試してみます。
      何かわかりましたらまたこちらでコメントしますね。
      よろしくお願いいたします!

      • chimaki より:

        早速のお返事ありがとうございます!!
        お忙しい中誠に恐れ入ります・・・
        なにか進捗がありましたら、お返事お待ちしております

        追加で必要な情報(状況・条件など)がございましたら、
        お返事にて補足させていただきますのでお気軽にお申し付けください。
        よろしくお願いいたします。

        • chaso chaso より:

          chimaki 様

          ご連絡が遅くなってしまい、申し訳ございません。
          こちら側で、別のアカウントで一から試してみたのですがどうやらブログを書いた当時と手順が(操作する順番)が変わっているようでした。
          具体的には、CLIENT_IDとCLIENT_SECRETの発行タイミングが記事内容と異なるみたいです・・・

          当方の環境では、記事に記載の手順で自動ツイートができるところまで確認済です。

          記事を修正しましたので、記載の順番で再度試してみていただけますでしょうか。
          また何か、不明点などございましたらお気軽にコメントいただければと思います。
          こちらこそよろしくお願いいたします!

          • chimaki より:

            chaso 様
            先日は早速の検証・ご確認誠にありがとうございました!
            お返事が遅くなり申し訳ございません。

            その後、修正確認していただいた内容で何度か試してみたのですが、
            やはり状況変わらず「{“message”:”認証に失敗しました。”}」
            と出てしまいました(汗)
            当方のシステム上の問題かもしれませんので、折を見て一から確認してみようと思います。

            お忙しい中、迅速なご対応に感謝いたします。
            また、とても分かりやすく参考になる記事をありがとうございます。
            今後も応援しております^^!

          • chaso chaso より:

            chimaki様
            ご連絡、ありがとうございます。
            だめでしたか・・・>< お役に立てず申し訳ございません。。もし、他にアカウントをお持ちでしたら 別のアカウントで試してみてどのような状況になるか見ていただくと原因の切り分けができるかなと思います。 また何かありましたらお気軽にコメントいただければと思います。 こちらこそ、わざわざご連絡いただきありがとうございました(^^)

  7. akmtrm より:

    mainを実行して表示されたURLに進んでも「問題が発生しました」でここから先に進めません

    色々調べてClient ID・Client Secretの設定がうまく言ってない場合に起こるということがわかったのですが双方発行されたものをコピペしているので間違いは無いと思うのですが・・・。

    一体なぜなのでしょうか?

    • chaso chaso より:

      コメントいただきましてありがとうございます。

      少し前に、同様の内容で他の方からもコメントをいただき、
      こちらで再度一連の流れを設定してみたのですが、問題なく自動ツイートすることができました。
      もし、別のアカウントで試せるのであれば、別のアカウントでもエラーになるかどうか試していただけますでしょうか。

      現状、原因がこちらとしても分かっておらず、参考になる回答ができず申し訳ございません。
      よろしくお願いいたします。

  8. alice より:

    はじめまして。こちらの記事、非常に助かりました。
    他の方と似たように認証後BadRequestになりましたが、Cookieをバッサリ削除したら通りました。
    Google系のCookieをガッツリ削りました。

    • chaso chaso より:

      はじめまして、コメントありがとうございます。
      お役に立てて嬉しいです!
      Cookieがエラーの一因だったんですね、有益な情報ありがとうございます。
      記事にも追記しておきます。

      • rap より:

        初めまして、参考にさせていただいております、ありがとうございます。

        sendTweet()を実行すると、以下のエラーが発生して詰まっております。
        何か原因等考えられることがありましたら、ご教示いただけると助かります。
        “`
        {
        “title”: “Forbidden”,
        “type”: “about:blank”,
        “status”: 403,
        “detail”: “Forbidden”
        }
        “`

        ちなみにmain()はすでに実行して、「Success!」の表示が出ることは確認しております。
        また、 console.log(service.getAccessToken()) にて、アクセストークン取得ができていることも確認しております。
        当方では、課金しておらず Freeプランです。

        403になっている原因や調査の方向性など何でも構いませんので、アドバイスいただけると助かります。

        • chaso chaso より:

          コメントありがとうございます。

          403エラーはアクセス権限がない場合などに出ますが、
          考えられる原因として、
          ・複数アカウントをお持ちで、実際にツイートしたいアカウントと異なるアカウントに対して設定をしている
          ・X(Twitter)のアカウント自体に制限がかかっている
          ・対象のアカウントに何等かの問題がある→別アカウントで試してみる
          上記くらいしか思いつきません。申し訳ございません・・・。

          自分自身も本記事の方法にて自動ツイートできており、
          403エラーが発生したことがないため適切なアドバイスができずすみません。

          また何か新しい情報等ありましたら、共有していただけますと幸いです。
          よろしくお願いいたします。

タイトルとURLをコピーしました