【GAS、Google Spreadsheet】Twitterにランダムなツイートを投稿するBotを作る

Code

はじまり

リサちゃん
リサちゃん

ねえねえ、一つお願いがあるんだけどさあ。

135ml
135ml

お? どうした?

リサちゃん
リサちゃん

「カーディナル・ジョージ」ってTwitterで呟くBotを作って欲しいんだよね。

135ml
135ml

・・・へ? どういうこと?

リサちゃん
リサちゃん

だから、「カーディナル・ジョージ」ってTwitterで呟くBotを作って欲しいんだよね。

135ml
135ml

・・・??????

リサちゃん
リサちゃん

だめだな。埒が明かん。下に紹介したものがあります!

それでは、行ってみよ〜!

135ml
135ml

・・・え? 俺が悪かったの?

ソース全体

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用のBotに、ツイートを投稿するためにAPI keyやOAuthで認証する設定の部分は、以下の記事でまとめています。

投稿する内容を決定する部分

今回の本題に入ります。

本ツールに使用するスプレッドシートの中身はこんな感じになっています。

処理の流れとしては、以下になります。

  1. ツイートに使う単語を選ぶ
  2. 実際にツイートする文言を決定する。
  3. ツイートする。

以下に実際に解説します。

1. ツイートに使う単語を選ぶ

ツイートに使う単語を選ぶ流れは以下のとおりです。

  1. 日本語アカウントか英語アカウントのどちらで呟くかを決める。
  2. 「カーディナル」な部分と「ジョージ」な部分を、まとめて選ぶか、それぞれ分けて選ぶかを決める。
  3. まとめて選ぶ場合は、イレギュラー列から単語を選んで返す。
  4. それぞれ分けて選ぶ場合は、「カーディナル」列および「ジョージ」列からそれぞれ単語を選んで返す。

2.で、まとめて選ぶ場合になると、3.で「秋道チョウジ」や「枕草子」が選ばれます。

それぞれ分けて選ぶ場合になると、3.は行われず、4.で「カーニバル」と「お家」や「グーを出す」と「上司」などが返り値として選ばれます。

2. 実際にツイートする文言を決定する。

ツイートに使う単語が選ばれたら、実際にどのように呟くかどうかを整形する処理になります。

日本語の場合だと「・」を間に入れて、英語の場合だとスペースを入れる仕様としています。

3. ツイートする。

実際にツイートする文言が決まったため、ツイートします。引数が”JP”か”EN”かで日本語アカウントか英語アカウントで呟くかどうかを区別しています。

実際につぶやきに成功すると、以下のようになります。

トリガーを設置する

ツイートを自動で行うために、GAS上でトリガーを設定します。

以下の画面でトリガーが発火する時間を設定できます。

おしまい

リサちゃん
リサちゃん

やった〜、「カーディナル・ジョージ」って呟くbotが出来た〜!

135ml
135ml

おお、良かったな・・・。要件定義エグいな・・・。

リサちゃん
リサちゃん

日本語と英語でどれぐらいアクセスが違うのかを見てみたいから、2種類アカウントを作りました。どうなるのか楽しみ〜。

135ml
135ml

英語圏の人にこういうネタが通じるのか見ものだね。

リサちゃん
リサちゃん

カーディナル・ジョージのアカウントのURLは、https://twitter.com/CardinalGeorg6で、

Cardinal GeorgeのアカウントのURLは、https://twitter.com/CardinalGeorge4です。

以上になります!

コメント

タイトルとURLをコピーしました