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('保存されていたトークンをクリアしました。');
}