Cloud Functions for Firebase でslackへの通知を定期実行する ~その2~
前回の続きです。 今回はGithubのトレンドを取得してSlackに通知する流れを書いていきます。
Githubのトレンドを取得
公式のAPIだけでトレンドを取得できるものはなかったので、npmのパッケージから探してみたところ以下のパッケージが良さそうでした。
language(プログラミング言語)とperiod(daily or weekly)を指定して絞り込むこともできます。 ただ、ドキュメントやコードを追ったところ、レスポンスの返り値がunknow型となっているため、そのままではデータを扱うことができません。
const response = await trending(); console.log(response[0].author) // これはエラーになる
そのため、Type Guardを使って型を推論させることで、データとして扱えるようにしました。 プロパティ全ての存在チェックをするのはちょっと冗長とは思うのですが、もっと良い書き方が分かりませんでした...何かあれば追記しようと思います。
import trending from "trending-github"; type Repository = { name: string; href: string; description: string; language: string; }; const isRepos = (repos: any): repos is Repository[] => repos.every( (repo: Repository) => repo.name != undefined && repo.href != undefined && repo.description != undefined && repo.language != undefined ); export const getTrend = async () => { const response = await trending(); const repos = isRepos(response) ? response : []; return repos;//Repository型に推論させることで、repo[0].authorなどが扱えるようになる };一応、Type assertionsで型キャストする方法でもコンパイルは通るのですが、それではせっかくの型がある言語を使う意味がないので、あまりよろしくないかと思います。
const repos = response as Repository[];
Slackに通知
TypeScript(JavaScript)を使ってSlackのメッセージを投稿するには、以下の二つのやり方があります。
- 直接WebAPIを叩く
@slack/web-api
をインストール- 同じく、
chat.postMessage
メソッドを用いる - botTokenは不要
個人的に使ってみた所感としては、カスタムコマンドを作成したり、イベントとのフック(チャンネルに参加したメンバーがいた時〇〇するetc)を作成したりなど、拡張性を考えるとBoltに寄せる方が良いと思いました。
ただ、postMessage
にtokenを与えなきゃいけないのがどうも違和感があり、今回はメッセージを投稿するのにはWebAPIの方を使っています。
import { WebClient } from "@slack/web-api"; export class slackBot { web: WebClient; constructor(web: WebClient) { this.web = web; } sendMessage = (channel: string, content: string) => { (async () => { await this.web.chat.postMessage({ channel: channel, text: content, }); })(); }; }
(インスタンスの生成時とメソッドを呼ぶ際で、両方にtokenを与える必要があるのがどうも...)
ハマりポイント
作成したアプリを投稿させたいチャンネルに招待する必要があります。招待していない場合、チャンネルが見つからないと怒られてしまいます。
(node:14215) UnhandledPromiseRejectionWarning: Error: An API error occurred: not_in_channelそんなの当たり前と思う方が大多数だと思いますが、自分はこれに気がつかず時間を溶かしてしまったので教訓として...
まとめ
ここまでで、目的であるGithubのトレンドを取得することと、それをSlackに定期的に通知することまでができました。最後に、英文を翻訳してから通知している部分について次回書いていきます。
Cloud Functions for Firebase でslackへの通知を定期実行する ~その1~
先日slackへの通知を定期実行する仕組みを作っていた際に、色々とハマったところを書いていきます。
はじめに
botや自動化、スクレイピングなどでスクリプトを24時間動かしたいと思う機会は多々あるのかと思います。 もちろん、サーバーを用意することでも実現できるのですが、最近ではよりハードルが低いサーバーレスの仕組みが多々用意されています。
その中で、自分が使用してみたのはCloud Functions for Firebaseです。 Firebaseの豊富な機能を簡単に使えるので、Databaseなどを利用してbotの機能を増やしていくこともできます。
セットアップ
まずはドキュメントに沿ってセットアップを進めていきます。
特筆することとして、今回自分は有料プラン(Blaze)を選んでいます。 理由としては、以下の二つが挙げられます。
1.有料プランでないとNode10以降のバージョンが使えない
後述するのですが、Nodeでslack用の環境を整えるのであれば、公式ドキュメントが豊富でサポートも手厚いBoltを使うのがおすすめです。ただ、このBoltはNode10以前のバージョンだと対応していない部分があるので、ほぼNode10以降を使える環境が必要になります。
参考までに、Cloud FunctionsのNodeのバージョンはpackage.jsonに記載されているので、この値を変更することでバージョンを更新することができます。
"engines": { "node": "10" }
2.他のプロダクトの管理が不要
定期実行を行う際に、内部的にはGoogle Cloud Pub/Sub と Cloud Scheduler APIが動いています。しかし、これら他のプロダクトの管理をしなくても、Cloud Functionsの書き方でスケジューリングをするだけで、簡単に定期実行ができるようになります。
実際、相当CPUやメモリを使ったり、かなりの回数関数を呼び出さない限りは無料枠の範疇で使えます。(参考: 月間呼び出し回数が200万回までは無料)
また、プロジェクト単位でプランを変えられるのは何となく意外に感じました。AWSやGCPなど他のサービスがどうなってるのかを知らないので、どのレベルで違いがあるのかも知っていきたいですね。
定期実行の書き方
index.tsに以下のように記載すると、これだけで1日おきに定期的に処理を行ってくれます。
export const githubTrendSchedule = functions.pubsub .schedule("every 24 hours") .onRun((_) => { #GithubやSlack周りの処理 });
他にCrontab の構文もサポートされているので、毎日〇〇時に処理を実行といった書き方もできます。
export const githubTrendSchedule = functions.pubsub .schedule("0 9 * * *") .timeZone("Asia/Tokyo") .onRun((_) => { githubTrendNotify(); });
デプロイ
最後に、ここまで作った関数を実際にデプロイします。この時コンパイルが通らない時はもちろんですが、ESLintが通らない時もデプロイに失敗します。firebase init functions
で生成されたeslintrc.js
のルールが割と厳しいので、ある程度融通を聞かせるべきかは自分の良心と相談しましょう。
ハマりポイント
必要なパッケージをnpm i -save-dev
でインストールしてしまった場合、つまりpackage.jsonのdevDependencies
に記載されていると、デプロイ先で上手く動かなくなります。
(こんなエラーが出ました)
i functions: updating Node.js 10 function slack(us-central1)... ⚠ functions[slack(us-central1)]: Deployment error. Function failed on loading user code. Error message: Error: please examine your function logs to see the error cause: https://cloud.google.com/functions/docs/monitoring/logging#viewing_logs
推測なのですが、testやlint,コンパイル前のtypescriptのような開発環境用のdevDependenciesに記載されたパッケージは、デプロイ先の環境にはインストールされていないのかと思います。
まとめ
まずは定期的に処理を実行するところまでが完成しました。次はslackへの通知について書いていこうと思います。
最新の技術トレンドを追い直す
最近あまり技術に触れられていないため、時代に置いて行かれそうになってます。 これはまずいと言うことで、自分がトレンドを追うために色々とやってみることにしました。
前提
パッと思いついたのは、自分用のslackワークスペースを作ってそこに通知することです。 言わずもがなですが、slackの豊富な連携機能にある程度任せられるので、自分で何かする部分を最小限に抑えられます。
Githubのトレンドを通知
定期実行の手段としては、GASやAWSのlamdaなどいろいろあるかと思います。 今回は、自分が興味のあったFirebaseのCloud Functionsを使ってみることにしました。 色々とハマりどころがあったので、詳細は別の記事でまた書こうと思います。
一つだけ気にしていたのが、通知特有のそのうち気にしなくなる現象です。これを少しでも防ぐために、自分はdescriptionに翻訳をかけてどんなリポジトリなのかをパッと見て取れるようにすることで、少しでもリポジトリを見にいく心理的ハードルを下げることを試みました。もちろん英語はとても重要なスキルなのですが、今はそれ以上に継続して技術を追っていくことの方が大切だと感じた次第です。
(こんな感じです。)
有名な方の技術ブログを通知
こちらはslackの機能を使えば非常に簡単です。
サイドバーのApp から RSS を選択する
- URLと投稿先のチャンネルを入力する
これだけで、Webサイトの更新時に自動でslackへと通知をしてくれます。 アドカレなどもRSSで通知をするようにしておくと便利ですね!
ただ、あまりに講読する数を増やすと追いきれなくなってしまうので、私の場合は多くても1日2~3記事程度に抑えておくつもりです。あまり詳しくない分野については、毎日記事を読むよりも時間があるときにまとめて調べ直す方が効率が良いのかなと思います。
技術系まとめブログ
自分はよくQiitaやdevelopers.ioを見ているのですが、これらの記事一覧を通知したところで、結局ほとんどの記事を見なくなるのが容易に予測できました。 (元々たくさんの記事を見ていたというより、タイトルをざっと眺めて知らない用語をなくすといったサイトの使い方をしていたので...)
一応、ある程度ジャンルを絞ってタイトルだけ通知するなどの運用を考えたのですが、それならば直接サイトを眺めている方が効率的と言う結論になりました。 もし何か上手い方法を思いついたら取り入れていこうと思います。
まとめ
情報のキャッチも大事ですが、「あ〜これ過去に見たけどなんだっけな〜」といった経験が多いので、インプットの取り入れ方も随時検討していきたいところです。
RustのTemplate repositoryを作成する
Rustを始めてみたが、開発環境やCIを毎回用意するのも面倒なのでベースとなるリポジトリを作っておきたかった。
で、作成したのがこちら。
工夫したところ
- VSCodeのdevcontainerを用いることで簡単にDocker環境を作成した。
- コンテナ内のエディタも結構使うのでzshに変更 + プロンプトにstarshipを入れている。
- Rustの拡張機能やデバッグには少しvscodeの設定を弄らなければいけないので、
.vscode
以下の設定ファイルも入れている。 - GithubActionを用いてCI + masterマージ時に複数OS向けにビルドを行う。
- パッケージ名を一括置換 +
.devcontainer
や.vscode
などの新規リポジトリにはgit管理に含めたくないファイルの整理をスクリプト化している。- 余談だが、自身のファイルを削除するbashが問題なく動くことを初めて知った。
Template repositoryを使って簡単に新規リポジトリを作成する
さらに、最初はこのリポジトリを毎回cloneする運用を考えていたが、実はもっと簡単なやり方があった。
まず、テンプレートとなるリポジトリを、 Template repository
として設定する。
すると、リポジトリ作成時にテンプレートが選べるようになっている。
これで、簡単にテンプレートと同じリポジトリを複製することが出来る。
(今まで initial commit
と、I
を小文字にしていたのが文化違いだったことを学んだ。)
参考
npm installの違いを学ぶ
今まで雰囲気でnpm install
していた。npmでパッケージをインストールする時の--save-dev
や-g
のオプションの違いをちゃんと調べてみた話。
グローバルインストール
$npm i -g <package>
グローバルインストールの場合自動的にパスが通るのでコマンドを実行出来ます。
インストール先は私のコンテナ環境の場合/usr/local/lib/node_modules/
になり、カレントディレクトリより外にインストールされます。
今までコマンドが扱いやすく便利に思っていたのですが、package.json
には記載されないため他の環境で同じパッケージの環境を扱う事は出来ず、出来るだけ避けた方が良いようです。
一応、グローバルインストールされたパッケージを確認するのは以下のコマンドです。
$npm ls -g
ローカルインストール
ローカルインストールされたパッケージはpackage.json
に記載され、npm install
で以下のdevDependenciesとdependenciesの両方のパッケージをインストール出来ます。
この時、グローバルインストールと違って
nodemodule/../..
のパス付きで実行package.json
にnpm scripts
を記載npx
コマンド
のいずれかのやり方を用いないとコマンドを実行することは出来ません。個人的にはnpm5.2.0以降であればnpx
コマンドを使わない理由はないように思います。
devDependencies
$npm i -D <package> $npm i -save-dev <package>
この場合、package.json
のdevDependencies
に記載されます。インストール先はカレントディレクトリにnode_modules
が作られその下になります。開発環境用の、テストや検証ツール等の本番環境で不要なパッケージはこちらに記載します。
dependencies
$npm i <package> $npm i -S <package> $npm i -save <package> $npm i -P <package>
この場合同じくカレントディレクトリのnode_modules
以下にインストールされますが、package.json
のdependencies
に記載されます。npmのデフォルトでdependenciesにインストールされるようになったので-S
や-P
を指定する必要はありません。こちらでは本番環境で必要なパッケージを記載します。
$npm install --production
でdependenciesのパッケージのみインストールされます。これによって本番環境で必要なパッケージだけをdependenciesに記載する運用が出来ます。npm install
しか使わないならあまり意識する事はないですが、覚えておくと幸せになれる事もあるのかなと思います。
参考
TypeScript入門する
概要
今までAngularの公式ドキュメントやUdermyのコースで感覚でTypeScriptを触っていた。 そもそもTypeScriptの知識自体がほとんどなかったので改めて入門しようと思った話。 後増税前って建前で大量に本買っちゃったっていうのも。
こちらを教材にさせて貰い進めていく。 https://www.amazon.co.jp/dp/483996937X/ref=cm_sw_r_tw_dp_U_x_SLH-Cb0N6CDRK
Docker環境構築
まずは環境構築から、ローカル環境にglobalインストールをしたくなかったのでDocker環境を用意する。
Dockerfile
FROM node:10.16.3-alpine WORKDIR /app COPY ./package*.json /app/ RUN npm i COPY . /app
https://hub.docker.com/_/node/
baseのimageは現在のLTS版を選択。
package*.json
だけ先にコピーすることで、パッケージ以外が変更された時に毎回npm i
が走らないようにしている。
ただこの場合最初にpackage.json
を作成しないとイメージをビルド出来ないので、他の処理と合わせて後述のスクリプトで対応する。
docker-compose.yml
version: "3.7" services: app: build: . volumes: - .:/app environment: NODE_ENV: development ports: - "8080:8080" tty: true
init.sh
echo "{}" > package.json echo "node_modules" > .gitignore docker-compose run --rm app npm init -y && npm i -D typescript && npx tsc --init
package.json
と.gitignore
を作成した後、TypeScriptをインストールする。
package.json
に記載するためにグローバルインストールでなくローカルインストールをしているのでtsc
コマンドはそのままでは使えず、npx tsc
コマンドでts.config
ファイルを作成する。
他に最初からインストールしておきたいモジュールについても記載しておいても良い気はするがとりあえず最低限のモジュールだけ。
実行テスト
scriptを実行した時点でのディレクトリ構成は以下の形。
├── node_modules ├── .gitignore ├── Dockerfile ├── docker-compose.yml ├── package-lock.json ├── package.json ├── script │ └── init.sh └── tsconfig.json
コンテナを起動。
docker-compose up -d --build
テスト用に適当なTypescriptファイルを作成。
test.ts
export function test() { return 'test' }
トランスパイル(別の言語へとコンパイルすること)を実行。
npx tsc
これによってtest.ts
と同じ階層にJavaScriptファイルがビルドされていることが確認出来る。
test.js
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function test() { return 'test'; } exports.test = test;
とりあえずこれで始める準備はOK。