【Twitter】Twitter(X)自動投稿の設定(2024年版) - Google Apps Scriptを使った方法

【Twitter】Twitter(X)自動投稿の設定(2024年版) - Google Apps Scriptを使った方法

2024-10-06

2024-10-17

Twitterの自動投稿を設定する方法について、初心者の方にもわかりやすく解説していきます。Twitter API v2(X API Free)を使用して、スプレッドシートから投稿内容を取得して自動投稿を行う手順を順を追って説明していきます。

Twitter Developer Portalに登録する

まずは、Twitter Developer Portalに登録する必要があります。

  1. Twitter Developer Portalにアクセスします。
  2. 右上の「Developer Portal(開発者ポータル)」クリックします。 Twitter Developer Portalのホームページ
  3. Twitterアカウントでログインしていない場合は、ログインします。 Twitterアカウントのログイン画面
  4. 「Sign up for Free Account」を選択します。 Twitter APIの無料アカウント登録ボタン
  5. 必要事項を英語で入力します。使用目的などを簡潔に説明してください。 Twitter APIアカウント登録フォーム
  6. 利用規約に同意し、「Submit」をクリックします。

アプリを作成し、APIキーを取得する

Developer Portalに登録できたら、次はアプリを作成してAPIキーを取得します。

  1. Developer Portalのダッシュボードから、自動生成されたプロジェクトとアプリを確認します。

  2. 左側メニューからあなたのアプリ名をクリックします。 Twitter Developer Portalのアプリ選択画面

    • You understand that you may not resell anything you receive via the Twitter APIs
      Twitter APIを通じて入手した情報やデータを、他人に売ることは禁止されています。この規則を理解し、守ることに同意します。
    • You understand your Developer account may be terminated if you violate the Developer Agreement or any of the Incorporated Developer Terms
      開発者向けの規約やルールに違反した場合、あなたの開発者アカウントが停止または削除される可能性があることを理解し、同意します。
    • You accept the Terms & Conditions
      Twitter(X)の開発者向けサービスを利用するにあたり、すべての利用規約を読み、その内容を理解し、従うことに同意します。
  3. 「User authentication settings」の「Set up」をクリックします。 User authentication設定ページ

  4. 以下の設定を行います。 User authenticationの詳細設定画面

    • App permissions: Read and write
    • Type of App: Web App, Automated App or Bot
    • Callback URI / Redirect URL: (後述の手順で取得します)
     https://script.google.com/macros/d/[YOUR_SCRIPT_ID]/usercallback
    

    注意: [YOUR_SCRIPT_ID]は後述のGASのスクリプトIDに置き換えます。

    • Website URL: あなたのウェブサイトURL(なければTwitterのURLでも可)
  5. 設定を保存します。

  6. 「Keys and tokens」タブから、以下のキーを取得し、安全な場所に保存します。 APIキーの表示画面 APIキーの詳細表示

    • Client ID
    • Client Secret

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

  1. Google ドライブにアクセスし、新しいGoogle スプレッドシートを作成します。

  2. スプレッドシートを開き、「拡張機能」>「Apps Script」を選択します。

  3. Apps Scriptプロジェクトが開いたら、「デプロイ」>「新しいデプロイ」をクリックします。 Google Apps Scriptプロジェクト画面

  4. 「種類の選択」ドロップダウンから「ウェブアプリ」を選択します。 ウェブアプリのデプロイ設定画面

  5. 以下の設定を行います。 ウェブアプリデプロイの詳細設定画面

    • 説明:任意の説明を入力(例:「Twitter自動投稿アプリ」)
    • 次のユーザーとして実行:自分(あなたのメールアドレス)
    • アクセスできるユーザー:全員
  6. 「デプロイ」ボタンをクリックします。

  7. デプロイが完了すると、「ウェブアプリのURL」が表示されます。

  8. このURLから以下の形式でコールバックURLを作成します:

    https://script.google.com/macros/d/[YOUR_SCRIPT_ID]/usercallback
    

    注意: [YOUR_SCRIPT_ID]は、ウェブアプリのURLに含まれるスクリプトIDに置き換えます。

  9. 作成したコールバックURLをTwitter Developer Portalの「Callback URI / Redirect URL」設定欄に入力します。

GASにOAuth2ライブラリを追加する

スクリプトを使用する前に、OAuth2ライブラリをプロジェクトに追加する必要があります。

  1. Google Apps Scriptのプロジェクト画面を開きます。 Google Apps Scriptのプロジェクト画面

  2. 左側のメニューから「ライブラリ +」をクリックします。 Google Apps Scriptのライブラリ追加画面

  3. 「スクリプトID」の入力欄に以下のIDを貼り付けます。

    1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF
    
  4. 「検索」ボタンをクリックします。 Google Apps Scriptのライブラリ検索結果画面

  5. 表示されたライブラリ(“OAuth2”という名前のはずです)を選択します。

  6. バージョンは最新のものを選択します。

  7. 「追加」ボタンをクリックして、ライブラリをプロジェクトに追加します。 ライブラリ追加の確認画面

スプレッドシートの準備

Googleスプレッドシートに投稿内容をリストとして準備します。

  1. スプレッドシートに以下のような構造でデータを入力します:

    • A列: ツイートのID(ユニークな識別子)
    • B列: ツイート内容
    • C列: 投稿回数(最初は0としておきます)
    • D列: 最終投稿日時
    IDツイート内容投稿回数最終投稿日時
    1これは最初のツイートです。0
    2こちらは二つ目のツイートです。0

GASでTwitter投稿スクリプトを作成する

OAuth2ライブラリを追加したら、スプレッドシートからランダムにツイートを取得し、投稿するスクリプトを作成します。以下のコードを貼り付けます。

  1. GASのエディタを開きます。

  2. 以下のスクリプトをコピーして貼り付けます。

    const TWITTER_CLIENT_ID = 'YOUR_CLIENT_ID';
    const TWITTER_CLIENT_SECRET = 'YOUR_CLIENT_SECRET';
    const TWITTER_SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID';
    
    function getServiceTwitter() {
        const pkce = pkceChallengeVerifier();
        return OAuth2.createService('twitter')
            .setAuthorizationBaseUrl('https://twitter.com/i/oauth2/authorize')
            .setTokenUrl('https://api.twitter.com/2/oauth2/token')
            .setClientId(TWITTER_CLIENT_ID)
            .setClientSecret(TWITTER_CLIENT_SECRET)
            .setCallbackFunction('doGet')
            .setPropertyStore(PropertiesService.getUserProperties())
            .setScope('tweet.read tweet.write users.read offline.access')
            .setParam('response_type', 'code')
            .setParam('code_challenge_method', 'S256')
            .setParam('code_challenge', pkce.challenge)
            .setTokenHeaders({
                'Authorization': 'Basic ' + Utilities.base64Encode(TWITTER_CLIENT_ID + ':' + TWITTER_CLIENT_SECRET),
                'Content-Type': 'application/x-www-form-urlencoded'
            })
            .setTokenPayloadHandler(function(tokenPayload) {
                tokenPayload.code_verifier = pkce.verifier;
                return tokenPayload;
            });
    }
    
    function pkceChallengeVerifier() {
        var userProps = PropertiesService.getUserProperties();
        var verifier = userProps.getProperty("code_verifier");
        var challenge = userProps.getProperty("code_challenge");
        
        if (!verifier || !challenge) {
            verifier = generateCodeVerifier();
            challenge = generateCodeChallenge(verifier);
            userProps.setProperty("code_verifier", verifier);
            userProps.setProperty("code_challenge", challenge);
        }
        
        console.log('PKCE Verifier (from pkceChallengeVerifier): ' + verifier);
        console.log('PKCE Challenge (from pkceChallengeVerifier): ' + challenge);
        return { verifier: verifier, challenge: challenge };
    }
    
    function generateCodeVerifier() {
        var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
        var verifier = "";
        for (var i = 0; i < 128; i++) {
            verifier += possible.charAt(Math.floor(Math.random() * possible.length));
        }
        return verifier;
    }
    
    function generateCodeChallenge(verifier) {
        var sha256Hash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, verifier);
        return Utilities.base64Encode(sha256Hash)
            .replace(/\+/g, '-')
            .replace(/\//g, '_')
            .replace(/=+$/, '');
    }
    
    function doGet(e) {
        const service = getServiceTwitter();
        if (e.parameter.code) {
            const authorized = service.handleCallback(e);
            if (authorized) {
                return HtmlService.createHtmlOutput('認証成功!このタブを閉じてください。');
            } else {
                return HtmlService.createHtmlOutput('認証失敗。このタブを閉じてください。');
            }
        } else {
            const authorizationUrl = service.getAuthorizationUrl();
            return HtmlService.createHtmlOutput('<a href="' + authorizationUrl + '">Twitterで認証する</a>');
        }
    }
    
    function postTweet(status) {
        const service = getServiceTwitter();
        if (service.hasAccess()) {
            const url = 'https://api.twitter.com/2/tweets';
            const payload = {
                'text': status
            };
            const options = {
                'method': 'POST',
                'headers': {
                    'Authorization': 'Bearer ' + service.getAccessToken(),
                    'Content-Type': 'application/json'
                },
                'payload': JSON.stringify(payload),
                'muteHttpExceptions': true
            };
            const response = UrlFetchApp.fetch(url, options);
            console.log(response.getContentText());
        } else {
            console.log('認証が必要です');
        }
    }
    
    function postRandomTweet() {
        const sheet = SpreadsheetApp.openById(TWITTER_SPREADSHEET_ID).getActiveSheet();
        const data = sheet.getDataRange().getValues();
        
        let candidate = null;
        let rowIndex = -1;
    
        for (let i = 0; i <= 100; i++) {
            const filteredData = data.filter((row, index) => (Number(row[2]) || 0) === i);
            
            if (filteredData.length > 0) {
                const randomIndex = Math.floor(Math.random() * filteredData.length);
                candidate = filteredData[randomIndex];
                rowIndex = data.findIndex(row => row[0] === candidate[0]);
                break;
            }
        }
    
        if (!candidate) {
            console.log('No candidates found');
            return;
        }
    
        postTweet(candidate[1]);
    
        sheet.getRange(rowIndex + 1, 3).setValue((Number(candidate[2]) || 0) + 1);
        sheet.getRange(rowIndex + 1, 4).setValue(new Date());
    }
    
    function resetAuthAndGetNewUrl() {
        PropertiesService.getUserProperties().deleteAllProperties();
        const pkce = pkceChallengeVerifier();
        const service = getServiceTwitter();
        service.reset();
        return service.getAuthorizationUrl();
    }
    
    function debugPKCE() {
        const userProps = PropertiesService.getUserProperties();
        const verifier = userProps.getProperty("code_verifier");
        const challenge = userProps.getProperty("code_challenge");
        console.log('Stored Code Verifier: ' + verifier);
        console.log('Stored Code Challenge: ' + challenge);
        console.log('Regenerated Challenge: ' + generateCodeChallenge(verifier));
    }
    
    function resetAuthAndGetNewUrl() {
        PropertiesService.getUserProperties().deleteAllProperties();
        const pkce = pkceChallengeVerifier();
        const service = getServiceTwitter();
        service.reset();
        const newAuthUrl = service.getAuthorizationUrl();
        console.log('New Auth URL: ' + newAuthUrl);
        console.log('PKCE Verifier (from reset): ' + pkce.verifier);
        console.log('PKCE Challenge (from reset): ' + pkce.challenge);
        return newAuthUrl;
    }
    
    function clearProperties() {
        PropertiesService.getUserProperties().deleteAllProperties();
        console.log('All properties cleared');
    }
    
    function debugStoredPKCE() {
        const userProps = PropertiesService.getUserProperties();
        console.log('Stored Verifier: ' + userProps.getProperty("code_verifier"));
        console.log('Stored Challenge: ' + userProps.getProperty("code_challenge"));
    }
    
    function debugAuth() {
        const userProps = PropertiesService.getUserProperties();
        console.log('Code Verifier: ' + userProps.getProperty("code_verifier"));
        console.log('Code Challenge: ' + userProps.getProperty("code_challenge"));
    }
    
  3. YOUR_CLIENT_IDYOUR_CLIENT_SECRETを、Twitter Developer Portalで取得したキーに置き換えます。

スクリプトを実行し、認証を行う

  1. スクリプトを保存し、新しくデプロイします。
  2. resetAuthAndGetNewUrl関数を実行します。 GASのデプロイ画面
  3. 権限の確認画面が出た場合には”無題のプロジェクトに移動”に進みます。 権限確認画面
  4. ログに表示される認証URLをコピーし、ブラウザで開きます。 認証用URLをコピーする画面
  5. Twitterの認証画面が表示されるので、アプリを認証します。 Twitterの認証画面 Twitterの認証画面
  6. 認証が完了したら、postRandomTweet関数を実行してツイートを投稿します。 ツイート投稿後の確認画面 ツイート投稿後の確認画面

自動投稿のスケジュールを設定する

  1. GASエディタで「トリガー」を選択します。 トリガー
  2. 「トリガーを追加」をクリックします。 トリガーを追加
  3. 関数にpostRandomTweetを選択し、イベントのソースを「時間主導型」に設定します。
    好みの頻度(例:12時間おき)を設定し、保存します。 設定

以上で、Twitterの自動投稿の設定が完了です!設定した頻度で自動的にツイートが投稿されるようになります。

注意点

  • Twitter APIの利用制限に注意しましょう。無料プランでは1日50件までの投稿制限があります。
  • 投稿内容は適切なものを心がけ、スパムと判断されないよう注意しましょう。
  • 定期的にスクリプトの動作を確認し、必要に応じて調整を行いましょう。

Recommend