はじまり

おし、今回は、TwitterのBotを作るという題材で行くぞよ。

お〜、自動で呟くというわけだね。

うむ、そのために今回は準備段階として、Twitter APIへ認証する部分を紹介します。
ソース全体
以下、ソースの全体になります。今回はこのソースの上部の方の解説になります。
main.gs
// 方法A: TwitterWebServiceを使った認証およびコールバック: Start ----------------------------------------------
// 方法Aの日本語アカウント用: Start ----------------------------------------
// //認証用インスタンスの生成
// const twitter = TwitterWebService.getInstance(
// apiKeyTwitterAccountJp, //API Key
// apiKeySecretTwitterAccountJp //API secret key
// );
// //アプリを連携認証する
// function authorize() {
// twitter.authorize();
// }
// //認証を解除する
// function reset() {
// twitter.reset();
// }
// //認証後のコールバック
// function authCallback(request) {
// return twitter.authCallback(request);
// }
// 方法Aの日本語アカウント用: End ----------------------------------------
// 方法Aの英語アカウント用: Start ----------------------------------------
// //認証用インスタンスの生成
// const twitter = TwitterWebService.getInstance(
// apiKeyTwitterAccountEn, //API Key
// apiKeySecretTwitterAccountEn //API secret key
// );
// //アプリを連携認証する
// function authorize() {
// twitter.authorize();
// }
// //認証を解除する
// function reset() {
// twitter.reset();
// }
// //認証後のコールバック
// function authCallback(request) {
// return twitter.authCallback(request);
// }
// 方法Aの英語アカウント用: End ----------------------------------------
// 方法A: TwitterWebServiceを使った認証およびコールバック: End ----------------------------------------------
// 方法B: OAuth1.createServiceを使った認証およびコールバック: Start ----------------------------------------------
// 方法Bの日本語アカウント用: Start ----------------------------------------
// function getTwitterServiceJp() {
// // Create a new service with the given name. The name will be used when
// // persisting the authorized token, so ensure it is unique within the
// // scope of the property store.
// return OAuth1.createService('twitter')
// // Set the endpoint URLs.
// .setAccessTokenUrl('https://api.twitter.com/oauth/access_token')
// .setRequestTokenUrl('https://api.twitter.com/oauth/request_token')
// .setAuthorizationUrl('https://api.twitter.com/oauth/authorize')
// // Set the consumer key and secret.
// .setConsumerKey(apiKeyTwitterAccountJp)
// .setConsumerSecret(apiKeySecretTwitterAccountJp)
// // Set the name of the callback function in the script referenced
// // above that should be invoked to complete the OAuth flow.
// .setCallbackFunction('authCallback')
// // Set the property store where authorized tokens should be persisted.
// .setPropertyStore(PropertiesService.getUserProperties());
// }
function getTwitterServiceJp() {
return OAuth1.createService("Twitter")
.setAccessTokenUrl("https://api.twitter.com/oauth/access_token")
.setRequestTokenUrl("https://api.twitter.com/oauth/request_token")
.setAuthorizationUrl("https://api.twitter.com/oauth/authorize")
.setConsumerKey(apiKeyTwitterAccountJp)
.setConsumerSecret(apiKeySecretTwitterAccountJp)
.setAccessToken(accessTokenTwitterAccountJp, accessTokenSecretTwitterAccountJp);
};
function showSidebarToAuthJp() {
let twitterService = getTwitterServiceJp();
if (!twitterService.hasAccess()) {
let authorizationUrl = twitterService.authorize();
let template = HtmlService.createTemplate(
'<a href="<?= authorizationUrl ?>" target="_blank">Authorize</a>. ' +
'Reopen the sidebar when the authorization is complete.');
template.authorizationUrl = authorizationUrl;
let page = template.evaluate();
SpreadsheetApp.getUi().showSidebar(page);
} else {
//
}
}
function authCallbackJp(request) {
let twitterService = getTwitterServiceJp();
let isAuthorized = twitterService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
// 方法Bの日本語アカウント用: End ----------------------------------------
// 方法Bの英語アカウント用: Start ----------------------------------------
// function getTwitterServiceEn() {
// // Create a new service with the given name. The name will be used when
// // persisting the authorized token, so ensure it is unique within the
// // scope of the property store.
// return OAuth1.createService('twitter')
// // Set the endpoint URLs.
// .setAccessTokenUrl('https://api.twitter.com/oauth/access_token')
// .setRequestTokenUrl('https://api.twitter.com/oauth/request_token')
// .setAuthorizationUrl('https://api.twitter.com/oauth/authorize')
// // Set the consumer key and secret.
// .setConsumerKey(apiKeyTwitterAccountEn)
// .setConsumerSecret(apiKeySecretTwitterAccountEn)
// // Set the name of the callback function in the script referenced
// // above that should be invoked to complete the OAuth flow.
// .setCallbackFunction('authCallback')
// // Set the property store where authorized tokens should be persisted.
// .setPropertyStore(PropertiesService.getUserProperties());
// }
function getTwitterServiceEn() {
return OAuth1.createService("Twitter")
.setAccessTokenUrl("https://api.twitter.com/oauth/access_token")
.setRequestTokenUrl("https://api.twitter.com/oauth/request_token")
.setAuthorizationUrl("https://api.twitter.com/oauth/authorize")
.setConsumerKey(apiKeyTwitterAccountEn)
.setConsumerSecret(apiKeySecretTwitterAccountEn)
.setAccessToken(accessTokenTwitterAccountEn, accessTokenSecretTwitterAccountEn);
};
function showSidebarToAuthEn() {
let twitterService = getTwitterServiceEn();
if (!twitterService.hasAccess()) {
let authorizationUrl = twitterService.authorize();
let template = HtmlService.createTemplate(
'<a href="<?= authorizationUrl ?>" target="_blank">Authorize</a>. ' +
'Reopen the sidebar when the authorization is complete.');
template.authorizationUrl = authorizationUrl;
let page = template.evaluate();
SpreadsheetApp.getUi().showSidebar(page);
} else {
//
}
}
function authCallbackEn(request) {
let twitterService = getTwitterServiceEn();
let isAuthorized = twitterService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
// 方法Bの英語アカウント用: End ----------------------------------------
// 方法B: OAuth1.createServiceを使った認証およびコールバック: End ----------------------------------------------
function postTweet(sentence, language) {
// const service = twitter.getService(); // 方法Aによるサービス
let service;
let bearerTokenTwitterAccount;
if(language === 'JP'){
service = getTwitterServiceJp(); // 方法Bによるサービス
bearerTokenTwitterAccount = bearerTokenTwitterAccountJp;
}else if(language === 'EN'){
service = getTwitterServiceEn(); // 方法Bによるサービス
bearerTokenTwitterAccount = bearerTokenTwitterAccountEn;
}
const endPointUrl = 'https://api.twitter.com/1.1/statuses/update.json';
const options = {
'method': 'post',
// 'headers' : {Authorization: 'Bearer ' + bearerTokenTwitterAccount},
// "muteHttpExceptions" : true,
"payload": {
status: sentence
}
}
try {
let response = service.fetch(endPointUrl, options);
console.log(response);
} catch(e) {
// 例外エラー処理
console.log('Error:')
console.log(e)
}
}
function decideSentenceToTweet(wordArray, language){
const funcName = 'decideSentenceToTweet';
let sentenceToTweet = ''
if(wordArray.length === 1){
sentenceToTweet = wordArray[0];
console.log(`${funcName}: ${getStrRepeatedToMark('a')}: `);
return sentenceToTweet;
}
if(language === 'JP'){
sentenceToTweet = `${wordArray[0]}・${wordArray[1]}`;
}
if(language === 'EN'){
sentenceToTweet = `${wordArray[0]} ${wordArray[1]}`;
}
console.log(`${funcName}: ${getStrRepeatedToMark('b')}: `);
console.log(`sentenceToTweet: ${sentenceToTweet}`);
return sentenceToTweet;
}
function selectRowAtRandom(amountOfRow, offsetAmountOfRow){
let randomFloat = 0;
while(randomFloat === 0){
randomFloat = Math.random();
}
return Math.floor(randomFloat * (amountOfRow)) + offsetAmountOfRow;
}
function selectWordsToTweet(language) {
const funcName = 'selectWordsToTweet';
const defaultRatio = 0.3;
const irregularRatio = 0.2;
let isIrregular = false;
let spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
let sheet = spreadsheet.getSheetByName(sheetNameDisseminating1st);
let irregularIndex;
let amountOfWords;
let selectedRow;
let wordArrayToTweet = [];
// decide regular or irregular.
irregularIndex = Math.random();
console.log(`${funcName}: ${getStrRepeatedToMark('a')}: irregularIndex is ${irregularIndex}`);
if(irregularIndex < irregularRatio){
isIrregular = true;
}
console.log(`${funcName}: ${getStrRepeatedToMark('b')}: isIrregular is ${isIrregular}`);
if(isIrregular === true){
// irregular process.
let cell_amount_of_word_language;
let column_word_language;
if(language === 'JP'){
cell_amount_of_word_language = cell_amount_of_word_jp_for_irregular_1st;
column_word_language = column_word_jp_for_irregular_1st;
}
if(language === 'EN'){
cell_amount_of_word_language = cell_amount_of_word_en_for_irregular_1st;
column_word_language = column_word_en_for_irregular_1st;
}
amountOfWords = Number(sheet.getRange(cell_amount_of_word_language).getValue());
selectedRow = selectRowAtRandom(amountOfWords, row_start_of_word_list);
const irregularWord = String(sheet.getRange(selectedRow, column_word_language, 1, 1).getValue());
console.log(`${funcName}: ${getStrRepeatedToMark('c')}: irregularWord is ${irregularWord}`);
wordArrayToTweet.push(irregularWord);
console.log(`${funcName}: ${getStrRepeatedToMark('d')}: `);
console.log(wordArrayToTweet);
return wordArrayToTweet;
}
console.log(`${funcName}: ${getStrRepeatedToMark('e')}: `);
// regular process.
let cell_amount_of_word_cardinal,
column_word_cardinal,
cell_amount_of_word_george,
column_word_george;
if(language === 'JP'){
cell_amount_of_word_cardinal = String(cell_amount_of_word_jp_for_cardinal_1st);
column_word_cardinal = Number(column_word_jp_for_cardinal_1st);
cell_amount_of_word_george = String(cell_amount_of_word_jp_for_george_1st);
column_word_george = Number(column_word_jp_for_george_1st);
}
if(language === 'EN'){
cell_amount_of_word_cardinal = String(cell_amount_of_word_en_for_cardinal_1st);
column_word_cardinal = Number(column_word_en_for_cardinal_1st);
cell_amount_of_word_george = String(cell_amount_of_word_en_for_george_1st);
column_word_george = Number(column_word_en_for_george_1st);
}
console.log(`${funcName}: ${getStrRepeatedToMark('e')}: `);
console.log(`cell_amount_of_word_cardinal: ${cell_amount_of_word_cardinal}`);
console.log(`column_word_cardinal: ${column_word_cardinal}`);
console.log(`cell_amount_of_word_george: ${cell_amount_of_word_george}`);
console.log(`column_word_george: ${column_word_george}`);
const amountOfWordsCardinal = Number(sheet.getRange(cell_amount_of_word_cardinal).getValue());
selectedRow = selectRowAtRandom(amountOfWordsCardinal, row_start_of_word_list);
const wordCardinal = String(sheet.getRange(selectedRow, column_word_cardinal, 1, 1).getValue());
const amountOfWordsGeorge = Number(sheet.getRange(cell_amount_of_word_george).getValue());
selectedRow = selectRowAtRandom(amountOfWordsGeorge, row_start_of_word_list);
const wordGeorge = String(sheet.getRange(selectedRow, column_word_george, 1, 1).getValue());
console.log(`${funcName}: ${getStrRepeatedToMark('e')}: `);
console.log(`amountOfWordsCardinal: ${amountOfWordsCardinal}`);
console.log(`wordCardinal: ${wordCardinal}`);
console.log(`amountOfWordsGeorge: ${amountOfWordsGeorge}`);
console.log(`wordGeorge: ${wordGeorge}`);
wordArrayToTweet = [wordCardinal, wordGeorge];
console.log(`${funcName}: ${getStrRepeatedToMark('f')}: `);
console.log(wordArrayToTweet)
return wordArrayToTweet;
}
function main() {
// Japanese mode.
let wordArray = selectWordsToTweet('JP');
let sentence = decideSentenceToTweet(wordArray, 'JP');
postTweet(sentence, 'JP');
// English mode.
wordArray = selectWordsToTweet('EN');
sentence = decideSentenceToTweet(wordArray, 'EN');
postTweet(sentence, 'EN');
}
constants.gs
const sheetNameDisseminating1st = 'tweet';
const sheetNameDisseminating2nd = 'tweetメモ書き';
const sheetNameDisseminating3rd = 'replyRecord';
const sheetNameOthers1st = '';
const cell_amount_of_word_jp_for_cardinal_1st = 'A1';
const column_word_jp_for_cardinal_1st = 2;
const cell_amount_of_word_jp_for_george_1st = 'C1';
const column_word_jp_for_george_1st = 4;
const cell_amount_of_word_jp_for_irregular_1st = 'E1';
const column_word_jp_for_irregular_1st = 6;
const cell_amount_of_word_en_for_cardinal_1st = 'G1';
const column_word_en_for_cardinal_1st = 8;
const cell_amount_of_word_en_for_george_1st = 'I1';
const column_word_en_for_george_1st = 10;
const cell_amount_of_word_en_for_irregular_1st = 'K1';
const column_word_en_for_irregular_1st = 12;
const row_start_of_word_list = 3;
const defaultWord_jp_cardinal_1st = 'カーディナル';
const defaultWord_jp_george_1st = 'ジョージ';
const defaultWord_en_cardinal_1st = 'Cardinal';
const defaultWord_en_george_1st = 'George';
const bearerTokenTwitterAccountJp = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const accessTokenTwitterAccountJp = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const accessTokenSecretTwitterAccountJp = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const apiKeyTwitterAccountJp = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const apiKeySecretTwitterAccountJp = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const bearerTokenTwitterAccountEn = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const accessTokenTwitterAccountEn = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const accessTokenSecretTwitterAccountEn = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const apiKeyTwitterAccountEn = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const apiKeySecretTwitterAccountEn = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
lib.gs
function getStrRepeatedToMark(repeatStr, repeatNumberToMark=15){
return repeatStr.repeat(repeatNumberToMark);
};
Twitter Appで認証を受ける準備を行う
まずは、Twitter側で設定を行う部分になります。
Twitter Appの作成
まずは、Botとして動かすTwitterアカウントを作成します。
そして、Developer Portalにアクセスします。

アカウントを作成したばかりですとまだアプリが出来ていないと思うので、アプリを作成します。
以下のように、色々と入力して、アプリ名なども埋めていくと、API key、API key secret、Bearer Tokenが表示されたページに飛ぶと思います。
この時点の認証情報は、ツイートするためには使いませんのでメモしなくて大丈夫です。

OAuthのレベルの設定
次に、以下の画像内の「User authentication settings」から、OAuth設定をします。「Set up」をクリックします。

「Set up」をクリックしてこの画面に遷移したら、「OAuth 1.0a」を選択します。(「OAuth 2.0」を選ぶとツイートの投稿ができません。)
そして、App permissionsでは「Read and write」もしくは「Read and write and Direct message」を選択します。

そして、コールバックURIを入力します。
このコールバックURIは、https://script.google.com/macros/d/<スクリプトID>/usercallback
という形式になります。
GASの編集画面でここから確認できます。それ以外の項目は何でも良さそうなので、僕は以下のように入力しました。


Consumer Keysの取得:API keyとAPI key secret
OAuth設定を変えると、Access TokenやConsumer API Keyを再発行しないと認証できなくなりますので、このページからkeyなどの再発行をします。(「API Key and Secret」の部分)

そして、それらの認証情報をメモしておきます。
(ちなみに、僕がBotを作った際は、「Bearer Token」は使いませんでした。)
Tokenの取得:Access TokenとAccess Token secret
次に、上の画像内にある「Access Token and Secret」からAccess TokenとAccess Token secretを発行します。
一応、発行した画面から、API keyなども確認することが出来ます。

GAS→Twitter Appへの認証を行う
GASからTwitter APIを使うための情報が手に入ったので、今度はGASで処理を組み立てていきます。GoogleスプレッドシートからApps Scriptを立ち上げ、作成していきました。
2通りの方法で行いました。どちらかを試してみてください。
方法A:TwitterWebServiceの利用
まず、TwitterWebServiceを利用する方法です。こちらを利用するためにTwitterWebServiceのライブラリを追加しなければなりません。
TwitterWebServiceのスクリプトIDは、「1rgo8rXsxi1DxI_5Xgo_t3irTw1Y5cxl2mGSkbozKsSXf2E_KBBPC3xTF」です。僕はバージョン2を使用しました。

そして、TwitterWebServiceを使った認証は、以下のソースで行いました。まず、authorize()
を実行します。
// 方法A: TwitterWebServiceを使った認証およびコールバック: Start ----------------------------------------------
//認証用インスタンスの生成
const twitter = TwitterWebService.getInstance(
'XXXXXXXXXXXXXXXXXXXXXXXXX', //API Key
'XXXXXXXXXXXXXXXXXXXXXXXXX' //API secret key
);
//アプリを連携認証する
function authorize() {
twitter.authorize();
}
//認証を解除する
function reset() {
twitter.reset();
}
//認証後のコールバック
function authCallback(request) {
return twitter.authCallback(request);
}
// 方法A: TwitterWebServiceを使った認証およびコールバック: End ----------------------------------------------
すると、コンソールのログの方に認証用のURLが表示されるので、そのURLにアクセスするとこんな感じの画面が出てきます。

方法B:OAuth1.createServiceの利用
こちらでは、OAuth1のcreateServieという機能を使います。
OAuth1のスクリプトIDは、「1CXDCY5sqT9ph64fFwSzVtXnbjpSfWdRymafDrtIZ7Z_hwysTY7IIhi7s」です。僕はバージョン18を使用しました。と同様に設定します。
そして、ライブラリを入れたら、以下のソースで認証を行いました。showSidebarToAuthEn()を実行します。すると、スプレッドシートの方にサイドバーが表示され、そこから方法Aと同様の認証画面に遷移することができます。
// 方法Bの英語アカウント用: Start ----------------------------------------
function getTwitterServiceEn() {
return OAuth1.createService("Twitter")
.setAccessTokenUrl("https://api.twitter.com/oauth/access_token")
.setRequestTokenUrl("https://api.twitter.com/oauth/request_token")
.setAuthorizationUrl("https://api.twitter.com/oauth/authorize")
.setConsumerKey('XXXXXXXXXXXXXXXXXXXXXXXXX') // ConsumerAPIKey
.setConsumerSecret('XXXXXXXXXXXXXXXXXXXXXXXXX') // ConsumerAPIKeySecret
.setAccessToken('XXXXXXXXXXXXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXXXXXXXXXXXX'); // AccessTokenとAccessTokenSecret
};
function showSidebarToAuthEn() {
let twitterService = getTwitterServiceEn();
if (!twitterService.hasAccess()) {
let authorizationUrl = twitterService.authorize();
let template = HtmlService.createTemplate(
'<a href="<?= authorizationUrl ?>" target="_blank">Authorize</a>. ' +
'Reopen the sidebar when the authorization is complete.');
template.authorizationUrl = authorizationUrl;
let page = template.evaluate();
SpreadsheetApp.getUi().showSidebar(page);
} else {
//
}
}
function authCallbackEn(request) {
let twitterService = getTwitterServiceEn();
let isAuthorized = twitterService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
// 方法Bの英語アカウント用: End ----------------------------------------
認証出来たので、postTweet()
を実行します。
function postTweet(sentence, language) {
// const service = twitter.getService(); // 方法Aによるサービス
let service;
let bearerTokenTwitterAccount;
if(language === 'JP'){
service = getTwitterServiceJp(); // 方法Bによるサービス
bearerTokenTwitterAccount = bearerTokenTwitterAccountJp;
}else if(language === 'EN'){
service = getTwitterServiceEn(); // 方法Bによるサービス
bearerTokenTwitterAccount = bearerTokenTwitterAccountEn;
}
const endPointUrl = 'https://api.twitter.com/1.1/statuses/update.json';
const options = {
'method': 'post',
// 'headers' : {Authorization: 'Bearer ' + bearerTokenTwitterAccount},
// "muteHttpExceptions" : true,
"payload": {
status: sentence
}
}
try {
let response = service.fetch(endPointUrl, options);
console.log(response);
} catch(e) {
// 例外エラー処理
console.log('Error:')
console.log(e)
}
}
実行してツイートできていなかったら、もしかしたら次の項の部分が出来ていないかもです・・・
アクセスレベルを「Essential」から「Elevated」以上にする。
これまで紹介した方法で認証部分は出来たのですが、まだ出来ない場合があります。
その出来ない場合というのは、アクセスレベルがEssential access
だと以下のエラー文が出てきて、APIでツイートの投稿を実行できないことがあります。
"{ [Exception: Request failed for https://api.twitter.com returned code 403. Truncated server response: {"errors":[{"message":"You currently have Essential access which includes access to Twitter API v2 endpoints only. If you need access to this endpo... (use muteHttpExceptions option to examine full response)] name: 'Exception' }"
どうやら現在のTwitter APIでは、このアクセスレベルによって、行える処理に制限がかかるそうです。そして、今回行いたいツイートの投稿は、Essential access
では出来ません。
「Essential」だとAPI v2.0しか利用できず、「Elevated」からAPI v1.1が利用できる仕様だそうです。
また、このアクセスレベルというものはTwitterの公式ではこのように発表されています。

そのため、アクセスレベルをデベロッパーポータル上で、「Elevated」以上にします。

この「Elevated」以上にするには、数々の英文による質問文に回答しなければなりません。その部分は割愛しますが、意外とちゃんとした文法ではない英文を使い回しても審査が一瞬で完了したりします。一体どのような内容だとアウトなのでしょうか・・・?
Elevatedのアクセスレベルになると、以下の画面になります。

Twitter APIでツイートを投稿する
認証周りの設定が完了したので、ツイートを投稿します。
function postTweet(sentence, language) {
// const service = twitter.getService(); // 方法Aによるサービス
let service;
let bearerTokenTwitterAccount;
if(language === 'JP'){
service = getTwitterServiceJp(); // 方法Bによるサービス
bearerTokenTwitterAccount = bearerTokenTwitterAccountJp;
}else if(language === 'EN'){
service = getTwitterServiceEn(); // 方法Bによるサービス
bearerTokenTwitterAccount = bearerTokenTwitterAccountEn;
}
const endPointUrl = 'https://api.twitter.com/1.1/statuses/update.json';
const options = {
'method': 'post',
// 'headers' : {Authorization: 'Bearer ' + bearerTokenTwitterAccount},
// "muteHttpExceptions" : true,
"payload": {
status: sentence
}
}
try {
let response = service.fetch(endPointUrl, options);
console.log(response);
} catch(e) {
// 例外エラー処理
console.log('Error:')
console.log(e)
}
}
以下のレスポンスが返ってくれば、ツイートが出来ていると思われます。
{ toString: [Function],
getResponseCode: [Function],
getContent: [Function],
getAllHeaders: [Function],
getHeaders: [Function],
getContentText: [Function],
getAs: [Function],
getBlob: [Function] }
おしまい

ふ〜、終わったあ。こういう認証部分がけっこう面倒いのよな。

けっこう簡単かと思いきや、色々仕様変更があって、手こずるわけだね。

「Essential」のアクセスレベルが原因だったときはかなりハマったから、ちゃんとエラー文を読むように気をつけないとね。

英語へのアレルギーを無くしていかねばなあ・・・
実際に投稿する内容を編集する部分

こちらに実際に投稿する内容を編集する部分を掲載しています。
良ければ、見てみてください!
以上になります!
コメント