Google Apps Scriptで（大体）1時間毎にTweetするBotを作る

PHP + cron で動いていた Twitter Bot を Google Apps Script (GAS) で置き換えました。
スクリプトをざっくりメモ書き程度に書いておきます。

スクリプトの全容は [Gist/gasTwitterBot.gs](https://gist.github.com/blue0513/f276b71e59950dc1e18f89b203cbae12) に置いておきます。

## Bot の要件

1. Tweetを投稿する
2. Tweetする内容はスプレッドシートからランダムに取得する
3. 投稿時間は1時間に1度のペース
4. Botっぽくないように **いい感じ** にバラけた時間に投稿する
5. 投稿は9時から21時の間まで

## 最終結果

雰囲気はこんな感じです。
Tweet 候補をシートに記載しておいて、コードを実行することであとは寝ているだけで動き出します。

<img width="1198" alt="Screen Shot 2019-09-03 at 22.14.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144707/9f849896-83a1-1135-06ff-c384b69caa8c.png">

実際に Tweet が投稿された時間帯です。
いい感じにバラけた時間に投稿しているので、Botっぽさが低減されているかと。

|起動|9:13|
|---|---|
|1回目|10:01|
|2回目|10:33|
|3回目|12:09|
|4回目|13:07|
|5回目|13:50|
|6回目|14:52|
|7回目|15:33|
|8回目|16:32|
|9回目|18:07|
|10回目|18:51|
|11回目|19:46|

## 使い方（参考）

1か2、好きな方をお好みで。

1. `createEveryHoursScheduler()` をトリガー画面から実行する
2. 以下の `init()` を実行して、好きな時間に `createEveryHoursScheduler()` の起動を予約する

```js
function init() {
  const time = "好きな時間";
  ScriptApp.newTrigger("createEveryHoursScheduler").timeBased().at(time).create();
}
```

## 実現方法

### 1. Tweetを投稿する

GASでTweetする系の記事はいくつもあるので、それを参考にしていただければ！

参考: [Qiita | Google Apps Script (GAS) でTwitterへ投稿するだけの機能を実装してみる](https://qiita.com/akkey2475/items/ad190a507b4a7b7dc17c)
参考: [Gist | TwitterWebService.gs](https://gist.github.com/M-Igashi/750ab08718687d11bff6322b8d6f5d90)

```js
var twitter = TwitterWebService.getInstance(
  'xxxxx', // 作成したアプリケーションのConsumer Key
  'xxxxx'  // 作成したアプリケーションのConsumer Secret
);

// Twitterの認証
function authorize() {
  twitter.authorize();
}

// Tweetをする投稿
function postTweet(tweet) {
  const service  = twitter.getService();
  const response = service.fetch('https://api.twitter.com/1.1/statuses/update.json', {
    method: 'post',
    payload: { status: tweet }
  });
}

// 実際に実行する関数
function execute() {
  const tweet = "HOGE"; // TODO: とりあえず固定
  postTweet(tweet);
}
```

### 2. Tweetする内容はスプレッドシートからランダムに取得する

"Tweet" シートのA列に各Tweetが並んでいるのが前提です。
そして「1. Tweetを投稿する」の `execute()` を少し変更します。

```js
function selectRandomTweet() {
  // Tweetシートを取得する
  const sheetData = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Tweet");

  // 1 ~ 最終行までの行をランダムに選択する
  const lastRow = sheetData.getLastRow();
  const selectedRow = Math.floor(Math.random() * lastRow) + 1; 

  // 選んだ行の内容を取得する
  const selectedTweet = sheetData.getRange(selectedRow, 1).getValue();
  return selectedTweet;
}

function execute() {
  const tweet = selectedTweet(); // tweetをランダムにする
  postTweet(tweet);
}
```

### 3. 投稿は1時間に1度のペース

[トリガー](https://developers.google.com/apps-script/reference/script/trigger)を設定して、１時間に一回 `execute()` を実行するようにします。

参考: [Google App Script | Trigger](https://developers.google.com/apps-script/reference/script/trigger)
参考: [Google App Script | everyHours(n)](https://developers.google.com/apps-script/reference/script/clock-trigger-builder#everyHours(Integer))

```js
function createEveryHoursScheduler() {
  ScriptApp.newTrigger("execute").timeBased().everyHours(1).create();
}
```

### 4. botっぽくないようにいい感じにバラけた時間に投稿する

1. `schedulePost()` を1時間に1度実行
2. 次の `schedulePost()` が実行されるまでの1時間の間で、ランダムな時刻を取得
3. 取得したランダムな時刻に `execute()` を実行

参考: [Google App Script | at(Date)](https://developers.google.com/apps-script/reference/script/clock-trigger-builder#at(Date))
注意: 実は `at(Date)` は **±15分の誤差** が生まれます（cf. [at(Date)](https://developers.google.com/apps-script/reference/script/clock-trigger-builder#at(Date))）

```js

function createEveryHoursScheduler() {
  // schedulePost() を1時間に1度実行するように変更
  ScriptApp.newTrigger("schedulePost").timeBased().everyHours(1).create();
}

function schedulePost() {
  const scheduledTime = getScheduledTime();
  // スケジュールした時刻に execute() を実行する
  ScriptApp.newTrigger('execute').timeBased().at(scheduledTime).create();
}

function getScheduledTime() {
  var setTime = new Date();
  const minutes = setTime.getMinutes();
  const randomMinute = Math.floor(Math.random() * 59);
  
  // 現在時刻 + 0~59分 の時刻を設定する
  setTime.setMinutes(minutes + randomMinute); 
  return setTime;
}
```

### 5. 投稿は9時から21時の間まで

24時間ずっと投稿し続けていると人間らしくありません。
そこで9:00~21:00の間でしか投稿しないように制限します。

```js
function schedulePost() {
  const scheduledTime = getScheduledTime();
  // スケジューリングした時間をバリデーションにかける
  if (!validateHourRange(scheduledTime.getHours())) return;

  ScriptApp.newTrigger('execute').timeBased().at(scheduledTime).create();
}

function validateHourRange(hour) {
  if (hour >= 9 || hour <= 21) return true;  // 投稿したい時間を指定する
  return false;
}
```

## 感想

Google Apps Script、めちゃめちゃ多機能で便利だなーと思いました。

+ Google Spreadsheet でコンテンツ管理可能
+ 基本は JavaScript で書ける
+ Debugger 機能
+ 定期実行機能