しくじり日記

筋肉つけたい

Cloud Functions for Firebase でslackへの通知を定期実行する ~その2~

tossyisland.hatenablog.com

前回の続きです。 今回はGithubのトレンドを取得してSlackに通知する流れを書いていきます。

Githubのトレンドを取得

公式のAPIだけでトレンドを取得できるものはなかったので、npmのパッケージから探してみたところ以下のパッケージが良さそうでした。

www.npmjs.com

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のメッセージを投稿するには、以下の二つのやり方があります。

api.slack.com

  • 直接WebAPIを叩く
    • @slack/web-api をインストール
    • 同じく、chat.postMessageメソッドを用いる
    • botTokenは不要

slack.dev

個人的に使ってみた所感としては、カスタムコマンドを作成したり、イベントとのフック(チャンネルに参加したメンバーがいた時〇〇する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の機能を増やしていくこともできます。

セットアップ

firebase.google.com

まずはドキュメントに沿ってセットアップを進めていきます。

特筆することとして、今回自分は有料プラン(Blaze)を選んでいます。 理由としては、以下の二つが挙げられます。

1.有料プランでないとNode10以降のバージョンが使えない

後述するのですが、Nodeでslack用の環境を整えるのであれば、公式ドキュメントが豊富でサポートも手厚いBoltを使うのがおすすめです。ただ、このBoltはNode10以前のバージョンだと対応していない部分があるので、ほぼNode10以降を使える環境が必要になります。

slack.dev

参考までに、Cloud FunctionsのNodeのバージョンはpackage.jsonに記載されているので、この値を変更することでバージョンを更新することができます。

"engines": {
    "node": "10"
 }

2.他のプロダクトの管理が不要

定期実行を行う際に、内部的にはGoogle Cloud Pub/Sub と Cloud Scheduler APIが動いています。しかし、これら他のプロダクトの管理をしなくても、Cloud Functionsの書き方でスケジューリングをするだけで、簡単に定期実行ができるようになります。

実際、相当CPUやメモリを使ったり、かなりの回数関数を呼び出さない限りは無料枠の範疇で使えます。(参考: 月間呼び出し回数が200万回までは無料)

また、プロジェクト単位でプランを変えられるのは何となく意外に感じました。AWSGCPなど他のサービスがどうなってるのかを知らないので、どのレベルで違いがあるのかも知っていきたいですね。

定期実行の書き方

firebase.google.com

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.jsondevDependencies に記載されていると、デプロイ先で上手く動かなくなります。 (こんなエラーが出ました)

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に翻訳をかけてどんなリポジトリなのかをパッと見て取れるようにすることで、少しでもリポジトリを見にいく心理的ハードルを下げることを試みました。もちろん英語はとても重要なスキルなのですが、今はそれ以上に継続して技術を追っていくことの方が大切だと感じた次第です。

(こんな感じです。) f:id:tsk110:20201004231151p:plain

有名な方の技術ブログを通知

こちらはslackの機能を使えば非常に簡単です。

  1. サイドバーのApp から RSS を選択する f:id:tsk110:20201003104852p:plain

  2. URLと投稿先のチャンネルを入力する f:id:tsk110:20201003105215p:plain

これだけで、Webサイトの更新時に自動でslackへと通知をしてくれます。 アドカレなどもRSSで通知をするようにしておくと便利ですね!

ただ、あまりに講読する数を増やすと追いきれなくなってしまうので、私の場合は多くても1日2~3記事程度に抑えておくつもりです。あまり詳しくない分野については、毎日記事を読むよりも時間があるときにまとめて調べ直す方が効率が良いのかなと思います。

技術系まとめブログ

自分はよくQiitaやdevelopers.ioを見ているのですが、これらの記事一覧を通知したところで、結局ほとんどの記事を見なくなるのが容易に予測できました。 (元々たくさんの記事を見ていたというより、タイトルをざっと眺めて知らない用語をなくすといったサイトの使い方をしていたので...)

一応、ある程度ジャンルを絞ってタイトルだけ通知するなどの運用を考えたのですが、それならば直接サイトを眺めている方が効率的と言う結論になりました。 もし何か上手い方法を思いついたら取り入れていこうと思います。

まとめ

情報のキャッチも大事ですが、「あ〜これ過去に見たけどなんだっけな〜」といった経験が多いので、インプットの取り入れ方も随時検討していきたいところです。

RustのTemplate repositoryを作成する

Rustを始めてみたが、開発環境やCIを毎回用意するのも面倒なのでベースとなるリポジトリを作っておきたかった。

で、作成したのがこちら。

github.com

工夫したところ

  • VSCodeのdevcontainerを用いることで簡単にDocker環境を作成した。
  • コンテナ内のエディタも結構使うのでzshに変更 + プロンプトにstarshipを入れている。
  • Rustの拡張機能デバッグには少しvscodeの設定を弄らなければいけないので、.vscode以下の設定ファイルも入れている。
  • GithubActionを用いてCI + masterマージ時に複数OS向けにビルドを行う。
  • パッケージ名を一括置換 + .devcontainer.vscode などの新規リポジトリにはgit管理に含めたくないファイルの整理をスクリプト化している。
    • 余談だが、自身のファイルを削除するbashが問題なく動くことを初めて知った。

Template repositoryを使って簡単に新規リポジトリを作成する

さらに、最初はこのリポジトリを毎回cloneする運用を考えていたが、実はもっと簡単なやり方があった。

まず、テンプレートとなるリポジトリを、 Template repository として設定する。

f:id:tsk110:20200403153530p:plain

すると、リポジトリ作成時にテンプレートが選べるようになっている。

f:id:tsk110:20200403153306p:plain

これで、簡単にテンプレートと同じリポジトリを複製することが出来る。

(今まで initial commitと、Iを小文字にしていたのが文化違いだったことを学んだ。)

f:id:tsk110:20200403155113p:plain

参考

help.github.com

dev.classmethod.jp

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.jsonnpm scriptsを記載
  • npxコマンド

のいずれかのやり方を用いないとコマンドを実行することは出来ません。個人的にはnpm5.2.0以降であればnpxコマンドを使わない理由はないように思います。

devDependencies

$npm i -D <package>
$npm i -save-dev <package>

この場合、package.jsondevDependenciesに記載されます。インストール先はカレントディレクトリにnode_modulesが作られその下になります。開発環境用の、テストや検証ツール等の本番環境で不要なパッケージはこちらに記載します。

dependencies

$npm i  <package>
$npm i -S <package>
$npm i -save <package>
$npm i -P <package>

この場合同じくカレントディレクトリのnode_modules以下にインストールされますが、package.jsondependenciesに記載されます。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。