Firebaseを駆使してTwitterをbot化してみた


#きっかけ・やったこと
Twitter apiの制限が厳しくなって、１アプリあたり３００ツイートしかできなくなる（なった？）そうなので、
自分専用のbotアプリをひとつ作った。

#実装方針
Firebaseとapp engineでcloud functionを定期実行し、その関数からtwitter apiを叩く

#Firebaseプロジェクトの作成
まずはFirebaseでプロジェクトを作成するところから。[Firebase](https://console.firebase.google.com/)にアクセス。
以下の画面から、「プロジェクトを追加」を選択。(お仕事で使ってるプロジェクトも写ってるのでそこは隠してます)![スクリーンショット 2018-09-15 19.12.00.png](https://qiita-image-store.s3.amazonaws.com/0/153926/c7f2ca49-756e-699d-8297-6a25b2b27b58.png)

すると次のようなポップアップが出るので赤丸のとこにチェックを入れてプロジェクトを作成。プロジェクトIDはあとで使うのでメモっておく。
![スクリーンショット 2018-09-15 19.15.41.png](https://qiita-image-store.s3.amazonaws.com/0/153926/a2391d5a-0d09-c7aa-4dfd-d68c9df13c02.png)

プロジェクトを作成できたら料金プランを従量制のBlazeにアップグレードする。(外部のAPIにアクセスできないので)

#定期実行のためのcronjobの作成
cloud functionを定期実行するサンプルが以下のページに掲載されていたので、そこを参考にする。
[Cloud Functions for Firebase でジョブをスケジューリング（cron）する](https://developers-jp.googleblog.com/2017/04/how-to-schedule-cron-jobs-with-cloud.html)

##初期設定
上記のリンク先と内容はほぼ同じであるが、こちらでも改めて書いておく。
上記を参考にリポジトリを `twitter-bot` ディレクトリにクローンし
`git clone https://github.com/firebase/functions-cron twitter-bot`

そのディレクトリに移動
`cd twitter-bot`

そして初期設定色々
`gcloud config set project your-project-id`
`cd appengine`
`npm install`

これで初期設定は完了。
（ちなみに、チュートリアルの方には`gcloud app create`も実行せよと書いてあるが、firebaseプロジェクトを作った段階ですでにプロジェクトができているため今回の場合は実行する必要がない、）

##スケジューリングの設定
`cron.yaml` を編集して実行スケジュールを設定する。以下のようにすれば毎日指定の時間にfunctionを実行してくれる

```yaml:cron.yaml
cron:
- description: Push a "tick" onto pubsub every day
  url: /publish/daily-tick
  timezone: Asia/Tpkyo
  schedule: every day 12:00
```
そしてデプロイ
`gcloud app deploy --project your-project-id`



##pubsubトピックの発行
リポジトリのソースのままだと動かないので、appengine/app.jsに以下のエンドポイントを追加

```javascript:app.js
// そもそもトピックが作られていなかったりすのでトピックを作成するエンドポイントを作成
app.post('/create', async (req, res) => {
  try {
    await pubsubClient.createTopic(`${req.body.name}`);
    res.status(200).send(`Published to ${req.body.name}`).end();
  } catch (error) {
    res.status(500).send('' + e).end();
  }
});
```

さらにトピックメッセージが空だとエラーになるので、そこも修正する。

```javascript:app.js
app.get('/publish/:topic', async (req, res) => {
  const topic = req.params['topic'];

  try {
    await pubsubClient.topic(topic)
        .publisher()
        .publish(Buffer.from('{"hoge":"huga"}'));//なんでもいいから　jsonStringをメッセージに乗せる

    res.status(200).send('Published to ' + topic).end();
  } catch (e) {
    res.status(500).send('' + e).end();
  }
});
```

これをデプロイする
`gcloud app deploy app.yaml \cron.yaml`


そしてさっき追加したエンドポイントに以下のbodyを乗せてPOSTリクエスト

```json:post-body
{
  "name":"topicName"
}
```

これでトピックが新しく作られる。

#twitter開発者登録
[Qiitaのこの記事](https://qiita.com/tdkn/items/521686c240b0c5bc6207)を参考に、開発者登録を行う。
途中で使用目的を英語で求められてしまうため非常にめんどくさい。（３００文字以上）

#Typescriptを使えるようにする
非同期処理を書きやすいのでTypeScriptを利用する。
`functions` 直下に `src` ディレクトリを作成、index.jsをこの中に移動する。
`functions` 直下に以下の内容の `package.json` を作成。パッケージ以外にも色々エイリアスを作成。

```json:package.json
{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "deploy": "firebase deploy --only functions --project your-project-id",
    "logs": "firebase functions:log --project your-project-id"
  },
  "main": "lib/index.js",
  "dependencies": {
    "es6-promise": "^4.2.4",
    "eslint": "^5.2.0",
    "firebase-admin": "~5.13.0",
    "firebase-functions": "^2.0.5",
    "google-auth-library": "^2.0.0",
    "googleapis": "^33.0.0",
    "prettier": "^1.14.2",
    "tslint-plugin-prettier": "^1.3.0",
    "twitter": "^1.7.1"
  },
  "devDependencies": {
    "tslint": "~5.8.0",
    "typescript": "~2.8.3"
  },
  "private": true
}

```

ビルド用のコンフィグファイルも作成

```json:tsconfig.json
{
  "compilerOptions": {
    "lib": ["es6"],
    "module": "commonjs",
    "noImplicitReturns": true,
    "outDir": "lib",
    "sourceMap": true,
    "target": "es6"
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}

```

#実際に実行されるcloud functionの作成
以下のように２ファイルを作成

```typescript:index.ts
import * as functions from 'firebase-functions';
import { twitter_credentials } from './twitter_credentials';
const Twitter = require('twitter');

export const tweet = functions.pubsub
  .topic('daily-tick')
  .onPublish(async event => {
    try {
      const client = new Twitter(twitter_credentials);
      await client.post('statuses/update', {status: 'test'});
    } catch (error) {
      throw error;
    }
  });
```

```typescript:twitter_credentials.ts
export const twitter_credentials = {
  consumer_key: 'your_api_key',
  consumer_secret: 'your_api_secret_key',
  access_token_key: 'your_access_token',
  access_token_secret: 'your_access_token_secret'
}
```

credentialsはちゃんとgitignoreに入れておくように。
これを `npm run build`　でビルドして、 `npm run deploy` デプロイ。

#動作結果
![スクリーンショット 2018-09-23 14.24.09.png](https://qiita-image-store.s3.amazonaws.com/0/153926/faa8edbb-cdcc-d704-61d2-697f5fecf6a1.png)
つぶやけた。
あとは呟く内容をそれっぽくするだけなので省略。

#感想
記事書くのってすっごい時間かかるんだなぁと実感。もうちょっとスムーズに作れるようにしたい。
（コーディング1時間、記事執筆は数えることをやめた）

#宣伝
週末フリーランスやってます。お問い合わせは[こちら](https://peraichi.com/landing_pages/view/t1work)
本当にこんなこと頼んでもいいのかな〜ということでも構わないので、「エンジニアにたのめばいいんじゃない？」と思ったあなたはぜひご連絡ください。