#きっかけ・やったこと
Twitter apiの制限が厳しくなって、1アプリあたり300ツイートしかできなくなる(なった?)そうなので、
自分専用のbotアプリをひとつ作った。
#実装方針
Firebaseとapp engineでcloud functionを定期実行し、その関数からtwitter apiを叩く
#Firebaseプロジェクトの作成
まずはFirebaseでプロジェクトを作成するところから。Firebaseにアクセス。
以下の画面から、「プロジェクトを追加」を選択。(お仕事で使ってるプロジェクトも写ってるのでそこは隠してます)
すると次のようなポップアップが出るので赤丸のとこにチェックを入れてプロジェクトを作成。プロジェクトIDはあとで使うのでメモっておく。
プロジェクトを作成できたら料金プランを従量制のBlazeにアップグレードする。(外部のAPIにアクセスできないので)
#定期実行のためのcronjobの作成
cloud functionを定期実行するサンプルが以下のページに掲載されていたので、そこを参考にする。
Cloud Functions for Firebase でジョブをスケジューリング(cron)する
##初期設定
上記のリンク先と内容はほぼ同じであるが、こちらでも改めて書いておく。
上記を参考にリポジトリを 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を実行してくれる
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に以下のエンドポイントを追加
// そもそもトピックが作られていなかったりすのでトピックを作成するエンドポイントを作成
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();
}
});
さらにトピックメッセージが空だとエラーになるので、そこも修正する。
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リクエスト
{
"name":"topicName"
}
これでトピックが新しく作られる。
#twitter開発者登録
Qiitaのこの記事を参考に、開発者登録を行う。
途中で使用目的を英語で求められてしまうため非常にめんどくさい。(300文字以上)
#Typescriptを使えるようにする
非同期処理を書きやすいのでTypeScriptを利用する。
functions
直下に src
ディレクトリを作成、index.jsをこの中に移動する。
functions
直下に以下の内容の 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
}
ビルド用のコンフィグファイルも作成
{
"compilerOptions": {
"lib": ["es6"],
"module": "commonjs",
"noImplicitReturns": true,
"outDir": "lib",
"sourceMap": true,
"target": "es6"
},
"compileOnSave": true,
"include": [
"src"
]
}
#実際に実行されるcloud functionの作成
以下のように2ファイルを作成
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;
}
});
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
デプロイ。
#動作結果
つぶやけた。
あとは呟く内容をそれっぽくするだけなので省略。
#感想
記事書くのってすっごい時間かかるんだなぁと実感。もうちょっとスムーズに作れるようにしたい。
(コーディング1時間、記事執筆は数えることをやめた)
#宣伝
週末フリーランスやってます。お問い合わせはこちら
本当にこんなこと頼んでもいいのかな〜ということでも構わないので、「エンジニアにたのめばいいんじゃない?」と思ったあなたはぜひご連絡ください。