【画像投稿も可】Google Apps ScriptでTwitter API v2を使う方法
概要
この記事では、Google Apps Script (GAS) を使用して Twitter API v2 を操作する方法を説明します。具体的には、ツイートの投稿、削除、ユーザ情報の取得、画像付きツイートの投稿などを行うクラスTwitterV2
の使用方法について解説します。
初期設定
プロパティストア
まず、TwitterのAPIキーとクライアントID、クライアントシークレットを取得し、GASのプロパティストアに設定します。以下のコードをスクリプトエディタで実行してください。
PropertiesService.getScriptProperties().setProperty('TW_ACCOUNT_NAME', 'あなたのTwitterアカウント名');
PropertiesService.getScriptProperties().setProperty('TW_API_KEY', 'あなたのAPIキー');
PropertiesService.getScriptProperties().setProperty('TW_API_SECRET', 'あなたのAPIシークレット');
PropertiesService.getScriptProperties().setProperty('TW_CLIENT_ID', 'あなたのクライアントID');
PropertiesService.getScriptProperties().setProperty('TW_CLIENT_SECRET', 'あなたのクライアントシークレット');
ライブラリのインストール
次に、OAuth1とOAuth2の認証を行うためのライブラリをインストールします。スクリプトエディタの「リソース」メニューから「ライブラリ」を選択し、以下のライブラリIDを追加してください。
- OAuth1:
1CXDCY5sqT9ph64fFwSzVtXnbjpSfWdRymafDrtIZ7Z_hwysTY7IIhi7s
- OAuth2:
1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF
認証
Twitter APIを使用するためには、Twitterの認証が必要です。以下のコードを実行して認証を行います。
function authorizeTwitterOAuth1() {
const twitter = new TwitterV2();
twitter.authorizeOAuth1();
}
function authorizeTwitterOAuth2() {
const twitter = new TwitterV2();
twitter.authorizeOAuth2();
}
これらの関数を実行すると、ログに表示されるURLを開き、Twitterで認証を行ってください。
Twitter側の設定
Twitterの開発者ページから、コールバックURLを設定する必要があります。以下の形式でコールバックURLを設定してください。
https://script.google.com/macros/d/***スクリプトID***/usercallback
サンプルコード
以下に、TwitterV2クラスの各メソッドの使用例を示します。
// ツイートを投稿する
function postTweet() {
const twitter = new TwitterV2();
const response = twitter.post('Hello, Twitter!');
console.log(response);
}
// ツイートを削除する
function deleteTweet() {
const twitter = new TwitterV2();
const tweetId = '1234567890'; // 削除するツイートのID
const response = twitter.destroy(tweetId);
console.log(response);
}
// ユーザ情報を取得する
function getUserInfo() {
const twitter = new TwitterV2();
const response = twitter.getUser();
console.log(response);
}
// 画像付きツイートを投稿する
function postImageTweet() {
const twitter = new TwitterV2();
const tweetText = 'Hello, Twitter with image!';
const imageUrl = 'https://example.com/image.jpg'; // 画像のURL
const response = twitter.postImg(tweetText, [imageUrl]);
console.log(response);
}
コード一覧
最後に、全てのコードをまとめておきます。このコードをスクリプトエディタに貼り付け、適切に設定を行えば、Twitter API v2をGASから操作することができます。
/**
* Twitterを扱うクラスです。
* APIv2を使用しています。
*
* ※事前にtwitterのAPIkeyの取得とパーミッションの設定が必要です。
* 2.0のAPI_KEYは「STANDALONE APPS」から発行したKEYは使えません。
* DEVELOPMENT APPからキーを発行してください。
*
* ディベロップURL
* https://developer.twitter.com/en/docs/twitter-api/data-dictionary/introduction
* 依存するライブラリ
* --- OAuth1 ---
* 1CXDCY5sqT9ph64fFwSzVtXnbjpSfWdRymafDrtIZ7Z_hwysTY7IIhi7s
* --- OAuth2 ---
* 1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF
*
*/
class TwitterV2 {
/**
* TwitterV2クラスのコンストラクタ
* @param {string} accountName - Twitterアカウント名
* @param {string} clientId - TwitterクライアントID
* @param {string} clientSecret - Twitterクライアントシークレット
*/
constructor(
accountName = PropertiesService.getScriptProperties().getProperty('TW_ACCOUNT_NAME'),
apiKey = PropertiesService.getScriptProperties().getProperty('TW_API_KEY'),
apiSecret = PropertiesService.getScriptProperties().getProperty('TW_API_SECRET'),
clientId = PropertiesService.getScriptProperties().getProperty('TW_CLIENT_ID'),
clientSecret = PropertiesService.getScriptProperties().getProperty('TW_CLIENT_SECRET')
) {
this.accountName = accountName;
this.apiKey = apiKey
this.apiSecret = apiSecret
this.clientId = clientId;
this.clientSecret = clientSecret;
}
/**
* ツイートを投稿する
*
* @param {string} tweetText - ツイートのテキスト
* @return {object} response - レスポンスオブジェクト
*/
post(tweetText) {
const service = this.getServiceOAuth2();
if (service.hasAccess()) {
const url = 'https://api.twitter.com/2/tweets';
const payload = {
'text': tweetText
};
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);
return JSON.parse(response.getContentText());
} else {
throw new Error('Service not authorized.');
}
}
/**
* 画像をつけてツイートする
*
* @param {string} postMsg - 文字列
* @return {string.<URL>||blob} files 配列で指定 -
*/
postImg(postMsg, files) {
const service = this.getServiceOAuth2();
const url = 'https://api.twitter.com/2/tweets';
if (files === undefined) return this.post(postMsg)
if (files[0] === "") return this.post(postMsg)
const mediaIds = files.map(file => {
const fileBlob = (typeof (file) === "string") ? this._convertImg(file) : file
const mediaId = this._uploadImgBlob(fileBlob)['media_id_string']
return mediaId
})
const payload = {
'text': postMsg,
'media': {
'media_ids': mediaIds
}
}
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);
const result = JSON.parse(response.getContentText());
return result;
}
/**
* URLから画像を取得する。
*
* @param {string} imgUrl - 画像のURL
* @return {blob} blob - blob
*/
_convertImg(imgUrl) {
const response = UrlFetchApp.fetch(imgUrl);
const fileBlob = response.getBlob();
return fileBlob
}
/**
* blobをtwitterにアプロードする。
*
* @param {Blob} blob - アップロードする画像のBlob
* @return {object} media_id_string - アップロードした画像のID
*/
_uploadImgBlob(blob) {
const service = this.getServiceOAuth1();
const url = 'https://upload.twitter.com/1.1/media/upload.json';
const respBase64 = Utilities.base64Encode(blob.getBytes());//Blobを経由してBase64に変換
const payload = {
'media_data': respBase64
}
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
payload: payload,
muteHttpExceptions: true
};
const response = service.fetch(url, options);
const result = JSON.parse(response.getContentText());
return result;
}
/**
* ツイートを削除する
*
* @param {string} tweetId - id
* @return {object} data - data
*/
destroy(tweetId) {
const service = this.getServiceOAuth2();
const url = `https://api.twitter.com/2/tweets/${tweetId}`;
const options = {
method: 'DELETE',
headers: {
Authorization: `Bearer ${service.getAccessToken()}`,
'Content-Type': 'application/json'
},
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
const result = JSON.parse(response.getContentText());
return result;
}
/**
* GET /2/users/meを実行して自分のユーザ情報を取得する
*
* @return {object} data - ユーザ情報
* Note : https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me
*/
getUser() {
const service = this.getServiceOAuth2();
const url = 'https://api.twitter.com/2/users/me?user.fields=created_at,description,entities,id,location,name,pinned_tweet_id,profile_image_url,protected,public_metrics,url,username,verified,verified_type,withheld';
const options = {
method: 'get',
headers: {
Authorization: `Bearer ${service.getAccessToken()}`,
'Content-Type': 'application/json'
},
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
const result = JSON.parse(response.getContentText());
return result;
}
/**
* OAuth1サービスを取得する
* @returns {Service} OAuth2サービス
*/
getServiceOAuth1() {
return OAuth1.createService(this.accountName)
.setAccessTokenUrl('https://api.twitter.com/oauth/access_token')
.setRequestTokenUrl('https://api.twitter.com/oauth/request_token')
.setAuthorizationUrl('https://api.twitter.com/oauth/authorize')
.setConsumerKey(this.apiKey)
.setConsumerSecret(this.apiSecret)
.setCallbackFunction('TwitterV2.authCallbackOAuth1')
.setPropertyStore(PropertiesService.getUserProperties())
}
/**
* OAuth1のコールバック関数
* @param {Object} request - リクエストオブジェクト
* @returns {HtmlOutput} 認証結果のHTML出力
*/
static authCallbackOAuth1(request) {
const twitter = new TwitterV2()
const service = twitter.getServiceOAuth1();
const isAuthorized = service.handleCallback(request);
const mimeType = ContentService.MimeType.TEXT;
if (isAuthorized) {
return ContentService.createTextOutput('認証完了です! authCallbackOAuth1').setMimeType(mimeType);
} else {
return ContentService.createTextOutput('Denied').setMimeType(mimeType);
}
}
/**
* Twitterを認証します。
* 事前にtwitter ディベロッパーページからコールバックURLを指定する必要があります。
* https://developer.twitter.com/en/portal/projects/1352079192913920001/apps/20768938/settings
* コールバックURLの形式:
* https://script.google.com/macros/d/***スクリプトID***/usercallback
*/
authorizeOAuth1() {
const service = this.getServiceOAuth1();
if (service.hasAccess()) {
console.log('Already authorized');
} else {
const authorizationUrl = service.authorize();
console.log('Open the following URL and re-run the script: %s', authorizationUrl);
}
}
/**
* Twitterの認証解除するメソッド
*/
resetOAuth1() {
const service = this.getServiceOAuth1();
service.reset();
}
/**
* OAuth2サービスを取得する
* @returns {Service} OAuth2サービス
*/
getServiceOAuth2() {
this.pkceChallengeVerifier();
const userProps = PropertiesService.getUserProperties();
return OAuth2.createService(this.accountName)
.setAuthorizationBaseUrl('https://twitter.com/i/oauth2/authorize')
.setTokenUrl('https://api.twitter.com/2/oauth2/token?code_verifier=' + userProps.getProperty("code_verifier"))
.setClientId(this.clientId)
.setClientSecret(this.clientSecret)
.setCallbackFunction('TwitterV2.authCallbackOAuth2')
.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(this.clientId + ':' + this.clientSecret),
'Content-Type': 'application/x-www-form-urlencoded'
});
}
/**
* OAuth2のコールバック関数
* @param {Object} request - リクエストオブジェクト
* @returns {HtmlOutput} 認証結果のHTML出力
*/
static authCallbackOAuth2(request) {
let twitter = new TwitterV2();
const service = twitter.getServiceOAuth2();
const authorized = service.handleCallback(request);
if (authorized) {
return HtmlService.createHtmlOutput('認証完了です!(authCallbackOAuth2)');
} else {
return HtmlService.createHtmlOutput('Denied.');
}
}
/**
* PKCE (Proof Key for Code Exchange) のチャレンジとバリデータを生成します。
* すでに生成されている場合は何もしません。
*/
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);
}
}
/**
* Twitterを認証します。
* 事前にtwitter ディベロッパーページからコールバックURLを指定する必要があります。
* https://developer.twitter.com/en/portal/projects/1352079192913920001/apps/20768938/settings
* コールバックURLの形式:
* https://script.google.com/macros/d/***スクリプトID***/usercallback
*/
authorizeOAuth2() {
const service = this.getServiceOAuth2();
if (service.hasAccess()) {
console.log('Already authorized');
} else {
const authorizationUrl = service.getAuthorizationUrl();
console.log('Open the following URL and re-run the script: %s', authorizationUrl);
}
}
/**
* Twitterの認証解除するメソッド
*/
resetOAuth2() {
const service = this.getServiceOAuth2();
service.reset();
}
}
補足: OAuth1を使用する理由
Twitter API v2では、基本的にOAuth2を使用して認証を行います。しかし、画像付きツイートの投稿については、現在のところTwitter API v2ではサポートされていません。そのため、画像付きツイートを投稿するためには、まだOAuth1を使用する必要があります。
以上で、GASを使用してTwitter API v2を操作する準備が整いました。ツイートの自動投稿や、特定のユーザのツイートの取得など、自由にカスタマイズしてご利用ください。
ディスカッション
コメント一覧
まだ、コメントがありません