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

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

2024-10-17

2024-10-17

  1. Google スプレッドシートを新規作成

    • 新しいスプレッドシートを作成します。
    • スプレッドシートの名前を任意の名前(Threads Contentなど)に変更します。 新規スプレッドシート作成
  2. Apps Script の設定

    • 「拡張機能」メニューから「Apps Script」を選択します。 Apps Script選択
    • 新しいスクリプトファイルが開きます。以下のコードを貼り付けてください:
    const THREADS_CLIENT_ID = 'YOUR_CLIENT_ID';
    const THREADS_CLIENT_SECRET = 'YOUR_CLIENT_SECRET';
    const THREADS_SCOPE = 'threads_basic,threads_content_publish';
    const THREADS_AUTH_URL = 'https://threads.net/oauth/authorize';
    const THREADS_TOKEN_URL = 'https://graph.threads.net/oauth/access_token';
    const THREADS_SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID';
    
    function getServiceThreads() {
        return OAuth2.createService('threads')
            .setAuthorizationBaseUrl(THREADS_AUTH_URL)
            .setTokenUrl(THREADS_TOKEN_URL)
            .setClientId(THREADS_CLIENT_ID)
            .setClientSecret(THREADS_CLIENT_SECRET)
            .setCallbackFunction('authCallback')
            .setPropertyStore(PropertiesService.getUserProperties())
            .setScope(THREADS_SCOPE)
            .setParam('access_type', 'offline')
            .setParam('prompt', 'consent')
            .setTokenHeaders({
            'Authorization': 'Basic ' + Utilities.base64Encode(THREADS_CLIENT_ID + ':' + THREADS_CLIENT_SECRET)
            });
    }
    
    function authCallback(request) {
        var service = getServiceThreads();
        var isAuthorized = service.handleCallback(request);
        if (isAuthorized) {
            return HtmlService.createHtmlOutput('認証成功!このウィンドウを閉じて、スクリプトエディタに戻ってください。');
        } else {
            return HtmlService.createHtmlOutput('認証に失敗しました。もう一度お試しください。');
        }
    }
    
    function getAuthorizationUrl() {
        var service = getServiceThreads();
        if (!service.hasAccess()) {
            var authorizationUrl = service.getAuthorizationUrl();
            Logger.log('以下のURLにアクセスして認証を行ってください:');
            Logger.log(authorizationUrl);
            return authorizationUrl;
        } else {
            Logger.log('すでに認証されています。');
            return null;
        }
    }
    
    function getThreadsToken() {
        var service = getServiceThreads();
        if (service.hasAccess()) {
            var accessToken = service.getAccessToken();
            Logger.log('Access Token: ' + accessToken);
    
            var response = UrlFetchApp.fetch(
            'https://graph.threads.net/me?fields=id',
            {
                headers: {
                Authorization: 'Bearer ' + accessToken
                }
            }
            );
            var userId = JSON.parse(response.getContentText()).id;
            Logger.log('User ID: ' + userId);
    
            return {
                access_token: accessToken,
                user_id: userId
            };
        } else {
            Logger.log('認証が必要です。getAuthorizationUrl()を実行してください。');
            return null;
        }
    }
    
    function postToThreads() {
        if (!checkAndRefreshToken()) {
            Logger.log('トークンの更新に失敗しました。再認証が必要です。');
            return;
        }
        var tokenData = getThreadsToken();
        if (!tokenData) {
            Logger.log('トークンの取得に失敗しました。認証を確認してください。');
            return;
        }
    
        const CREATE_URL = `https://graph.threads.net/v1.0/${tokenData.user_id}/threads`;
        const PUBLISH_URL = `https://graph.threads.net/v1.0/${tokenData.user_id}/threads_publish`;
        const sheet = SpreadsheetApp.openById(THREADS_SPREADSHEET_ID).getActiveSheet();
        const data = sheet.getDataRange().getValues();
    
        let candidates = [];
        let minPostCount = Infinity;
    
        for (let i = 0; i < data.length; i++) {
            if (i === 0 && (data[i][0] === 'ID' || data[i][1] === '投稿内容')) {
                continue;
            }
            let postCount = data[i][2] || 0;
            if (typeof postCount === 'string') postCount = parseInt(postCount, 10) || 0;
    
            if (postCount <= minPostCount) {
                if (postCount < minPostCount) {
                    candidates = [];
                    minPostCount = postCount;
                }
                candidates.push({ content: data[i][1], rowIndex: i });
            }
        }
        if (candidates.length === 0) {
            Logger.log('投稿する内容がありません。');
            return;
        }
        const selected = candidates[Math.floor(Math.random() * candidates.length)];
        const content = selected.content;
        const createOptions = {
            method: "post",
            headers: {
                "Authorization": "Bearer " + tokenData.access_token,
                "Content-Type": "application/x-www-form-urlencoded"
            },
            payload: {
                text: content,
                media_type: "TEXT",
                access_token: tokenData.access_token
            }
        };
        try {
            const createResponse = UrlFetchApp.fetch(CREATE_URL, createOptions);
            if (createResponse.getResponseCode() === 200) {
                const creationId = JSON.parse(createResponse.getContentText()).id;
                const publishOptions = {
                    method: "post",
                    headers: {
                        "Authorization": "Bearer " + tokenData.access_token,
                        "Content-Type": "application/x-www-form-urlencoded"
                    },
                    payload: {
                        creation_id: creationId,
                        access_token: tokenData.access_token
                    }
                };
                const publishResponse = UrlFetchApp.fetch(PUBLISH_URL, publishOptions);
                if (publishResponse.getResponseCode() === 200) {
                    Logger.log("投稿成功: " + content);
                    let currentCount = sheet.getRange(selected.rowIndex + 1, 3).getValue() || 0;
                    sheet.getRange(selected.rowIndex + 1, 3).setValue(currentCount + 1);
                    sheet.getRange(selected.rowIndex + 1, 4).setValue(new Date());
                } else {
                    Logger.log("投稿の公開に失敗: " + content + ", ステータスコード: " + publishResponse.getResponseCode());
                    Logger.log("レスポンス: " + publishResponse.getContentText());
                }
            } else {
                Logger.log("スレッドの作成に失敗: " + content + ", ステータスコード: " + createResponse.getResponseCode());
                Logger.log("レスポンス: " + createResponse.getContentText());
            }
        } catch (error) {
            Logger.log("エラー発生: " + error);
        }
    }
    
    function checkAndRefreshToken() {
        var service = getServiceThreads();
        if (service.hasAccess()) {
            Logger.log('トークンは有効です。');
            return true;
        } else {
            Logger.log('トークンが無効または期限切れです。更新を試みます。');
            try {
            if (service.refresh()) {
                Logger.log('トークンを更新しました。');
                return true;
            } else {
                Logger.log('トークンの更新に失敗しました。再認証が必要です。');
                return false;
            }
            } catch (e) {
            Logger.log('エラーが発生しました: ' + e.toString());
            Logger.log('再認証が必要です。getAuthorizationUrl()を実行してください。');
            return false;
            }
        }
    }
    
    function clearStoredToken() {
        PropertiesService.getUserProperties().deleteProperty('oauth2.threads');
        Logger.log('保存されていたトークンをクリアしました。');
    }
    
  3. スクリプトのデプロイ

    • 「デプロイ」→「新しいデプロイ」をクリックし、ウェブアプリとしてデプロイします。 デプロイメニュー
    • 「種類の選択」で「ウェブアプリ」を選択します。 種類の選択
    • 「次のユーザーとして実行」で「自分」を選択します。
    • 「アクセスできるユーザー」で「全員」を選択します。 デプロイ設定

OAuth2 ライブラリの追加

  1. ライブラリの追加
    • 左側のメニューから「ライブラリ +」をクリックし、以下のIDを追加します。

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

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

    • 表示されたライブラリ(“OAuth2”)を選択します。

    • バージョンは最新のものを選択し、「追加」ボタンをクリックしてライブラリをプロジェクトに追加します。 ライブラリ追加の確認画面

Meta for Developers の設定

  1. Meta for Developers アカウントの設定

    • https://developers.facebook.com/ にアクセスし、マイアプリをクリックします。 マイアプリ画面
    • Metaのデベロッパーアカウントを作成していない場合、メールアドレスを入力して下記の画面に進みます。 メールアドレス入力画面
    • facebookアカウントにブラウザでログインします。
    • 基本データ > 連絡先情報からメールアドレスの登録を行います。
    • SMSの認証画面が表示されるのでコードを入力します。 SMS認証画面
    • 成功すると、アカウントに追加画面が表示されます。 アカウント追加画面
    • 元の画面に戻り、メールアドレスに送信されたコードを入力します。 メールアドレス入力確認
    • アカウント作成画面が表示されるので、適当なものを選択します。 アカウント作成画面
    • マイアプリ画面が表示されます。 マイアプリ表示
  2. ThreadsのアクセストークンとユーザーIDを取得:

    • デベロッパーダッシュボードで「アプリを作成」をクリックします。 アプリ作成画面
    • ビジネスポートフォリオを選択します。 ビジネスポートフォリオ選択画面
    • Threads APIにアクセス を選択します。 Threads APIアクセス画面
    • アプリ名、連絡先を入力します。 アプリ情報入力画面
    • 利用規約、ポリシーを確認し、アプリを作成をクリックします。 利用規約確認画面
    • パスワードを入力します。facebookのログインパスワードで進むことができました。 パスワード入力画面
    • アプリが作成されました と表示されるので、ダッシュボードにアクセスをクリックします。 ダッシュボードアクセス画面
    • 左メニューの「アプリの設定」→「ベーシック」から、ThreadsアプリIDとアプリシークレットをメモします。 アプリID確認画面
  3. アプリの設定:

    • threads_content_publish の追加クリックします。 threads_content_publish 追加画面
    • 左メニューの「アプリの設定」→「ベーシック」を開きます。
    • ダッシュボードのアクセス許可から Threads API にアクセスをクリックします。 Threads APIへのアクセス設定画面
    • ユーザートークン生成ツール から、テスターを追加します。 ユーザートークン生成ツール画面
    • 「テスターを追加」をクリックし、自分のThreadsアカウントを追加します。 ユーザーIDを入力して、アカウントを探すことができます。 テスター追加画面 ユーザーID検索画面
    • Threads にアクセスし、ログインを行います。
    • 左下の設定をクリックします。 Threads設定画面
    • アカウントの招待から同意するをクリックします。 招待同意画面
  4. アプリの詳細設定

    • アプリの設定ページで、以下の項目を設定します:
      • コールバックURLリダイレクト
      • コールバックURLをアンインストール
      • コールバックURLを削除 これらに以下のURLを入力します。
        [YOUR_SCRIPT_ID]は実際のスクリプトIDに置き換えてください。
    https://script.google.com/macros/d/[YOUR_SCRIPT_ID]/usercallback(https://script.google.com/macros/d/[YOUR_SCRIPT_ID]/usercallback)
    

    コールバックURLの設定画面
    Script IDは、Apps Scriptのエディタ画面のURLから確認できます。
    Script ID確認画面

認証プロセスとアクセストークンの取得

  1. 認証URLの生成

    • スクリプト内の CLIENT_IDCLIENT_SECRET を、取得したアプリIDとアプリシークレットに置き換えます。
    • getAuthorizationUrl() 関数を実行し、コンソールに表示されたURLをブラウザで開きます。 実行画面
    • 権限を確認が表示された場合は、詳細から移動をクリックし、許可を行います。(信頼できる場合のみ移動を押すようにしてください。)
      認証画面
    • 認証を行うと、GASのウェブアプリケーションにリダイレクトされ、認証が完了します。
    • コンソールに表示されたURLをブラウザで開きます。
      認証完了画面
    • 認証を行うと、GASのウェブアプリケーションにリダイレクトされます。 ウェブアプリケーションリダイレクト画面
      エラー表示例
      下記のように表示された場合、callback関数に正しくApps ScriptのIDを入力し、設定できていない可能性があります。
    {
        "error_message": "URLはブロックされています: リダイレクトURIがアプリのクライアントOAuth設定でホワイトリストに追加されていないため、リダイレクトできませんでした。クライアントとウェブOAuthログインをオンにして、すべてのアプリドメインを有効なOAuthリダイレクトURIとして追加してください。",
        "error_code": 1349168
    }
    
  2. アクセストークンとユーザーIDの取得

    • 認証後、getThreadsToken() 関数を実行してアクセストークンとユーザーIDを取得します。
      アクセストークン取得実行画面
      トークン取得確認画面

スプレッドシートIDの設定と投稿機能の準備

  1. スプレッドシートIDの設定

    • スプレッドシートを開き、URLからスプレッドシートIDを取得します。
      URLは以下のような形式です。

      https://docs.google.com/spreadsheets/d/[SPREADSHEET_ID]/edit#gid=0  
      
    • スクリプト内の THREADS_SPREADSHEET_ID の値を、取得したIDで置き換えます。

    const THREADS_SPREADSHEET_ID = 'スプレッドシートID';
    
  2. スプレッドシートの準備

    • スプレッドシートの1行目に以下のヘッダーを設定します。
      A列: ID, B列: 投稿内容, C列: 投稿回数, D列: 最終投稿日時
    • B列に投稿したい内容を入力します。
  3. 投稿機能のテスト

    • スクリプトエディタで postToThreads() 関数を実行します。
    • 実行結果をログで確認し、正常に投稿されたか確認します。
      投稿確認画面
    • スプレッドシートを確認し、投稿回数と最終投稿日時が更新されているか確認します。

定期実行の設定

  1. トリガーの設定
    • スクリプトエディタの左サイドバーから「トリガー」アイコンをクリックします。
    • 「トリガーを追加」ボタンをクリックします。
    • 以下の設定を行います。
      • 実行する関数を選択: postToThreads
      • イベントのソースを選択: 時間主導型
      • 時間ベースのトリガーのタイプを選択: 時間ベースのタイマー
      • 時間の間隔を選択: 希望の間隔(例:1時間ごと)
    • 「保存」をクリックします。
      トリガー設定画面

運用とメンテナンス

  1. 定期的なチェック

    • スプレッドシートの内容が定期的に投稿されているか確認します。
    • エラーログがないか、スクリプトエディタの実行ログを確認します。
  2. トークンの更新

    • アクセストークンは60日で期限切れになります。
    • 60日ごとに getAuthorizationUrl()getThreadsToken() を再実行して、トークンを更新します。
  3. コンテンツの管理

    • 定期的にスプレッドシートに新しい投稿内容を追加します。
    • 投稿済みの内容を確認し、必要に応じて削除や更新を行います。

注意点

  • Threadsのコンテンツポリシーを遵守してください。
  • 投稿頻度が高すぎると、アカウントがスパム扱いされる可能性があります。適切な間隔を設定してください。
  • Google Apps Scriptの実行制限(1日あたりのクォータ)に注意してください。

これらの手順に従うことで、Google スプレッドシートとGoogle Apps Scriptを使用して、Threadsへの自動投稿システムを構築・運用することができます。定期的なメンテナンスと監視を行い、システムが正常に機能し続けるようにしてください。

Recommend