今回はAPIの変更に伴って使い方が大きく変わったTwitterAPIの使い方を解説していきたいと思います。
GASを使ってTwitterに自動でツイートするような仕組みを作ることが目標です。
準備
まずは下記のデベロッパーサイトにアクセスをしてAPIを使える準備をしていきます。
様々な記事で紹介されているのでここでは簡単にのみ記載をしておきます。
まずはCreate an Appをクリックして基本的に指示に従って進めてください。電話番号認証が住んでいない場合には認証が必要となります。
プランの選択がありますが基本的にフリーアカウントで今回やりたいことはできます。
利用目的を記載しておく必要がありますが以前のように審査があるわけではないのでだいたいを記入しておけば大丈夫だと思います。
登録が終了すると開発者ポータルが開きます。
左側のメニューからプロジェクトとその下にあるアプリを選択するとアプリ情報のページに飛びます。
APIの設定
TwitterAPIをRead and Writeに設定する必要があるのでSettingsタブからUser authentication settingsのEditを押してRead and Writeに変更しておきます。
続いてアプリタイプを選択します。
これは2番目のWeb App, Automated App or Botを選択します
最後のApp infoはGASのスプレッドシートのコールバックURLを設定しますが、こちらは後ほど設定していくので今は飛ばします。
その下にあるWebsiteURLはサイトがある場合はそのサイトのURLを入れておくのが望ましいですが、存在しない場合は自分のtwitterのurlなどを設定しておけば大丈夫のようです。
saveすると
ClientIDとSecretが表示されるのでこれらをメモしておきます。
OAuth1の場合はSettingsのページのAPIキーを使用しますが今回はOauth2のためこちらを使います。
GASの準備
今回はスプレッドシートのGoogleappScriptを使ってTwitterAPIを使っていきたいと思います。まずは新しいスプレッドシートを開いて、拡張機能のApps Scriptから新しいGASのプロジェクトを作成します。
ここで先ほど飛ばしたコールバックURLを設定することができるのでその部分について説明していきます。
コールバックURLには
を設定します。
[GASのスクリプトID]はApps ScripptのURLのprojects/の後ろ部分にあたります
ここで確認したコールバックURLを、Twitterの開発者画面のセッティングで登録しておきます。
ちなみにこのコールバックURLは複数のURLを指定できるので、複数のスプレッドシートを作成しても同じように追加登録すれば大丈夫です。
ライブラリの追加
TwitterAPIをGASで簡単に使うにはOAuth2ライブラリーを使うのがおすすめです。
Apps Scriptの左側にあるライブラリから追加します。
スクリプトIDに
1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF
を入力して検索します
こちらを検索してOAuth2というライブラリーを追加してください。
続いてスクリプトの部分に以下のコードを貼り付けます。
// Twitter APIの認証情報
const CLIENT_ID = PropertiesService.getScriptProperties().getProperty('client_id');
const CLIENT_SECRET = PropertiesService.getScriptProperties().getProperty('client_secret');
// OAuth2サービスを取得する関数
function getService() {
pkceChallengeVerifier();
const userProps = PropertiesService.getUserProperties();
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('認証が成功しました。');
} else {
return HtmlService.createHtmlOutput('認証に失敗しました。');
}
}
// PKCEのコードチャレンジ生成関数
function pkceChallengeVerifier() {
const userProps = PropertiesService.getUserProperties();
if (!userProps.getProperty("code_verifier")) {
let verifier = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
for (let i = 0; i < 128; i++) {
verifier += possible.charAt(Math.floor(Math.random() * possible.length));
}
const sha256Hash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, verifier)
const challenge = Utilities.base64Encode(sha256Hash)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
userProps.setProperty("code_verifier", verifier);
userProps.setProperty("code_challenge", challenge);
}
}
function sendTweet(tweetdata) {
let twitterText = tweetdata;
const service = getService();
if (service.hasAccess()) {
const message = {
text: twitterText
}
const response = UrlFetchApp.fetch("https://api.twitter.com/2/tweets", {
method: "post",
headers: {
Authorization: 'Bearer ' + service.getAccessToken()
},
muteHttpExceptions: true,
payload: JSON.stringify(message),
contentType: "application/json"
});
const result = JSON.parse(response.getContentText());
Logger.log(JSON.stringify(result, null, 2));
} else {
Logger.log("未認証");
return null;
}
}
function authorize(){
const service = getService();
if (!service.hasAccess()) {
const authorizationUrl = service.getAuthorizationUrl();
Logger.log('以下のURLを開いて認証してください: %s', authorizationUrl);
return;
}
}
function main() {
authorize();
sendTweet("APIからのテストツイート!") ;
}
コードの詳しい説明は後ほど行っていきます。
ClientID, Secretの設定
Twitterの開発者画面で取得したClientIDなどの設定を行います。
GASのコードに直接貼り付けるのはあまり良い方法ではないので、スクリプトのプロパティを使って管理していきます。
歯車マークからプロジェクトの設定を開きスクリプトのプロパティという一番下にある項目のスクリプトプロパティの編集をクリックして、
client_id : 取得したID
client_secret: 取得したSecretキー
を入力して保存しましょう。
実行
まずはテストとして「APIからのテストツイート!」というツイートが投稿されるようにしています。
GASでauthorize関数を実行します。
すると最初に実行した場合には承認を求められるので、指示に従って承認してください。
また、OAuth認証を行うのでそのためのURLがログに表示されます。表示されたURLをコピー&ペーストしてブラウザからアクセスしてください。
するとTwitterのアカウントを連携するかというような画面になるので許可するようにしてください。ここで連携したアカウントでツイートされます。
ツイートするアカウントを変更したい場合については後ほど見ていきます。
もし、「問題が発生しましたアプリにアクセスを許可できません。前に戻ってもう一度ログインしてください。」というエラーが表示された場合はコールバックURLが間違っているか、ClientIDよSecretが間違っている可能性が高いので再度確認してください。
認証が終了したら、認証完了画面になります。
続いてmain関数を実行しましょう。
うまくいっていれば先ほど連携したアカウントでツイートが完了していると思います。
別のアカウントでツイートしたい場合
アカウントを変更したい場合には再度認証を行う必要があります。
まずはブラウザでツイートしたい別のアカウントにログインし直してください。
その後以下のコードを追記し、この関数を実行してください。
function reauthorize(){
const service = getService();
const authorizationUrl = service.getAuthorizationUrl();
Logger.log('以下のURLを開いて認証してください: %s', authorizationUrl);
}
これにより別のアカウントで認証設定が完了するのでそのアカウントでのツイートが可能になります。
定時にツイートするポットを作ろう!
それではここまでの内容を少し応用して定期的にツイートする夫を作成したいと思います。
ツイートしたい内容はあらかじめスプレッドシートの「ツイート」シートの1列2行目から入力するようにしておいてください。
また「シート1」の1行1列目に2と入力しておきます。
以下の関数を追記しメイン関数についても書き換えます。
function processTweetData() {
const sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート1');
const tweetSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('ツイート');
const rowNumber = sheet1.getRange(1, 1).getValue();
Logger.log('取得した行番号: ' + rowNumber);
const targetRowData = tweetSheet.getRange(rowNumber, 1, 1, tweetSheet.getLastColumn()).getValues()[0];
if (targetRowData !== "") {
Logger.log('対象の行のデータ: ' + targetRowData);
const updatedValue = rowNumber + 1;
sheet1.getRange(1, 1).setValue(updatedValue);
Logger.log('更新後の値: ' + updatedValue);
} else {
targetRowData = tweetSheet.getRange(2, 1, 1, tweetSheet.getLastColumn()).getValues()[0];
const updatedValue = 3;
sheet1.getRange(1, 1).setValue(updatedValue);
Logger.log('対象の行のデータ: ' + targetRowData);
Logger.log('空白の場合、更新後の値: ' + updatedValue);
}
Logger.log(targetRowData);
sendTweet(targetRowData[0]);
}
function main() {
authorize();
processTweetData();
}
これでメイン関数を実行するとツイートシートの1列目に記入しているツイート内容を年間数を1回実行するごとに1行ずつツイートすることができます。
次に何行目をツイートするかをシート1の1列1行目で管理しています。
あとは定期スケジュールを使って好きな時間に設定すればツイートボットの完成です。
コードの解説
- Twitter APIの認証情報の設定
const CLIENT_ID = PropertiesService.getScriptProperties().getProperty('client_id');
const CLIENT_SECRET = PropertiesService.getScriptProperties().getProperty('client_secret');
この部分は、Twitter APIの認証に使用するクライアントIDとクライアントシークレットをスクリプトのプロパティから取得しています。これらの情報は後でOAuth2サービスを作成する際に使用されます。
- OAuth2サービスを取得する関数
function getService() {
pkceChallengeVerifier();
const userProps = PropertiesService.getUserProperties();
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'
});
}
この関数では、OAuth2サービスを作成しています。まず、pkceChallengeVerifier()
関数を呼び出してPKCE(Proof Key for Code Exchange)のコードチャレンジを生成します。その後、ユーザープロパティから情報を取得し、Twitter APIへのアクセスを行うためのOAuth2サービスを構成します。setAuthorizationBaseUrl()
とsetTokenUrl()
は、Twitter APIの認証エンドポイントのURLを設定しています。setClientId()
とsetClientSecret()
は、クライアントIDとクライアントシークレットを設定しています。setCallbackFunction()
は、認証後のコールバック関数を設定します。setPropertyStore()
は、OAuth2のトークンを保存するプロパティストアを指定します。setScope()
は、Twitter APIにアクセスするためのスコープを設定します。setParam()
は、認証に関連するパラメーターを設定します。setTokenHeaders()
は、OAuth2トークンをリクエストする際に必要なヘッダー情報を設定します。
PKCE (Proof Key for Code Exchange) は、OAuth 2.0 認証のセキュリティ向上のための仕様です。PKCEは、特に公開型のアプリケーション(SPAやモバイルアプリなど)に対して、Authorization Code Grantフローを安全に使用するために設計されています。
PKCEは、Authorization Code Grantフローでの認可コードの取得とトークンの交換時に追加のセキュリティを提供します。通常のAuthorization Code Grantフローでは、認可コードを取得する際にクライアントシークレット(Client Secret)を使用しますが、公開型アプリケーションではクライアントシークレットを保持することが難しいため、セキュリティ上のリスクがあります。
PKCEでは、クライアントが認証リクエスト時にランダムなコードバリデータ(Code Verifier)を生成し、認証コードの取得時にそのコードバリデータのハッシュを認可コードとともに送信します。そして、トークンの交換時にも元のコードバリデータを使用して、認証コードの正当性を確認します。これにより、公開型のアプリケーションでも安全にAuthorization Code Grantフローを利用できるようになります。
pkceChallengeVerifier
関数は、このPKCEのコードバリデータを生成するためのものです。関数内でランダムな文字列(コードバリデータ)を生成し、SHA-256ハッシュ関数を使用してそのハッシュ値をコードチャレンジとして計算します。そして、生成されたコードバリデータとコードチャレンジをPropertiesServiceに保存して、後続のOAuth 2.0認証リクエストで使用するために用意します。
このようにして、アプリケーションがAuthorization Code Grantフローをセキュリティ向上できる仕組みを提供しています。
- 認証後のコールバック関数
function authCallback(request) {
const service = getService();
const authorized = service.handleCallback(request);
if (authorized) {
return HtmlService.createHtmlOutput('認証が成功しました。');
} else {
return HtmlService.createHtmlOutput('認証に失敗しました。');
}
}
この関数は、OAuth2認証後のコールバック処理を行います。getService()
関数を呼び出してOAuth2サービスを取得し、handleCallback()
関数で認証リクエストを処理します。認証が成功した場合は「認証が成功しました。」と表示し、失敗した場合は「認証に失敗しました。」と表示します。
- PKCEのコードチャレンジ生成関数
function pkceChallengeVerifier() {
const userProps = PropertiesService.getUserProperties();
if (!userProps.getProperty("code_verifier")) {
let verifier = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
for (let i = 0; i < 128; i++) {
verifier += possible.charAt(Math.floor(Math.random() * possible.length));
}
const sha256Hash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, verifier)
const challenge = Utilities.base64Encode(sha256Hash)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
userProps.setProperty("code_verifier", verifier);
userProps.setProperty("code_challenge", challenge);
}
}
この関数は、PKCE(Proof Key for Code Exchange)のコードチャレンジを生成します。PKCEは、セキュアなOAuth2認証を行うために使用される仕組みです。関数はまず、ユーザープロパティからコードバリデーターが存在しない場合に限り、ランダムな128文字の文字列を生成します。次に、SHA-256ハッシュ関数を使用してコードバリデーターからコードチャレンジを生成します。最後に、コードバリデーターとコードチャレンジをユーザープロパティに保存します。
- ツイートを送信する関数
function sendTweet(tweetdata) {
let twitterText = tweetdata;
const service = getService();
if (service.hasAccess()) {
const message = {
text: twitterText
}
const response = UrlFetchApp.fetch("https://api.twitter.com/2/tweets", {
method: "post",
headers: {
Authorization: 'Bearer ' + service.getAccessToken()
},
muteHttpExceptions: true,
payload: JSON.stringify(message),
contentType: "application/json"
});
const result = JSON.parse(response.getContentText());
Logger.log(JSON.stringify(result, null, 2));
} else {
Logger.log("未認証");
return null;
}
}
この関数は、OAuth2サービスを使用してTwitter APIにアクセスし、ツイートを投稿するための処理を行います。まず、ツイートの内容を変数twitterText
に格納します。getService()
関数を呼び出してOAuth2サービスを取得し、hasAccess()
関数で認証済みかを確認
します。認証済みの場合、ツイート内容を含むメッセージを作成し、UrlFetchApp.fetch()
を使ってTwitter APIのhttps://api.twitter.com/2/tweets
エンドポイントにPOSTリクエストを送信します。リクエストには、BearerトークンがAuthorizationヘッダーに含まれています。レスポンスから結果を取得して、ログに出力します。
- ツイートデータの処理関数
function processTweetData() {
const sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート1');
const tweetSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('ツイート');
const rowNumber = sheet1.getRange(1, 1).getValue();
Logger.log('取得した行番号: ' + rowNumber);
let targetRowData = tweetSheet.getRange(rowNumber, 1, 1, tweetSheet.getLastColumn()).getValues()[0];
if (targetRowData !== "") {
Logger.log('対象の行のデータ: ' + targetRowData);
const updatedValue = rowNumber + 1;
sheet1.getRange(1, 1).setValue(updatedValue);
Logger.log('更新後の値: ' + updatedValue);
} else {
targetRowData = tweetSheet.getRange(2, 1, 1, tweetSheet.getLastColumn()).getValues()[0];
const updatedValue = 3;
sheet1.getRange(1, 1).setValue(updatedValue);
Logger.log('対象の行のデータ: ' + targetRowData);
Logger.log('空白の場合、更新後の値: ' + updatedValue);
}
Logger.log(targetRowData);
sendTweet(targetRowData[0]);
}
この関数は、スプレッドシートからツイートデータを取得し、sendTweet()
関数を使ってツイートを送信するための処理を行います。まず、シート「シート1」から行番号を取得します。次に、行番号に対応するデータを「ツイート」という名前のシートから取得します。データが存在する場合は、そのデータをログに出力し、行番号を更新して次回の処理で次のデータを取得します。データが存在しない場合は、2行目のデータを取得し、行番号を3に更新して次回の処理に備えます。最後に、取得したツイートデータの1つ目の要素(テキスト)をsendTweet()
関数に渡してツイートを送信します。
- 認証関数
function authorize(){
const service = getService();
if (!service.hasAccess()) {
const authorizationUrl = service.getAuthorizationUrl();
Logger.log('以下のURLを開いて認証してください: %s', authorizationUrl);
return;
}
}
この関数は、OAuth2サービスを使用してユーザーの認証を試みます。まず、getService()
関数を呼び出してOAuth2サービスを取得し、hasAccess()
関数で認証済みかを確認します。未認証の場合、getAuthorizationUrl()
を使って認証用のURLを取得し、ログに表示します。
- 再認証関数
function reauthorize(){
const service = getService();
const authorizationUrl = service.getAuthorizationUrl();
Logger.log('以下のURLを開いて認証してください: %s', authorizationUrl);
}
この関数は、再度認証を行うための認証URLをログに表示します。認証済みの場合でも、この関数を使って再認証を行うことができます。
- メイン関数
function main() {
authorize();
processTweetData();
}
この関数は、スクリプトのエントリーポイントです。authorize()
関数を呼び出して認証を試み、その後processTweetData()
関数を呼び出してツイートデータを処理します。
これらの関数はお互いに連携して、スプレッドシートのツイートデータをTwitterに投稿するための処理を行っています。