Firebase를 구사해 Twitter를 bot화해 보았다

계기 · 한 일



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-idcd appenginenpm install
이것으로 초기 설정은 완료.
(덧붙여서, 튜토리얼에게는 gcloud app create 도 실행하라고 쓰고 있지만, firebase 프로젝트를 만든 단계에서 이미 프로젝트가 되어 있기 때문에 이번 경우에는 실행할 필요가 없다,)

스케줄링 설정


cron.yaml 를 편집하여 실행 일정을 설정합니다. 다음과 같이 하면 매일 지정된 시간에 function을 실행해준다

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에 다음 엔드 포인트 추가

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.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 요청

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

이것으로 주제가 새롭게 만들어진다.

트위터 개발자 등록



Qiita의이 기사 을 참고로 개발자 등록을 한다.
도중에 사용 목적을 영어로 요구되어 버리기 때문에 매우 귀찮습니다. (300문자 이상)

Typescript 사용 가능



비동기 처리를 쓰기 쉽기 때문에 TypeScript를 이용한다.functions 바로 아래에 src 디렉토리를 작성해, index.js 를 이 안에 이동한다.functions 바로 아래에 다음 내용의 package.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
}


빌드용 구성 파일도 작성

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


실제로 실행되는 cloud function 만들기



다음과 같이 두 파일을 만듭니다.

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;
    }
  });

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 배포.

동작 결과




중얼거렸다.
그리고는 중얼거리는 내용을 그렇게 할 뿐이므로 생략.

감상



기사 쓰기는 굉장한 시간이 걸린다고 실감. 좀 더 부드럽게 만들 수 있도록 하고 싶다.
(코딩 1시간, 기사 쓰기는 세는 것을 그만두었다)

홍보



주말 프리랜서를하고 있습니다. 문의는 여기
정말 이런 일을 부탁해도 좋을까~라는 것도 상관없기 때문에, 「엔지니어에 태우면 좋지 않아?」라고 생각한 당신은 꼭 연락해 주세요.

좋은 웹페이지 즐겨찾기