ゆずめも

メモ的なブログです。主に勉強した事について書いてます。

slackに毎日情報を流しているbotをcloudflare workersに移植してみる

前の記事で調べていた時にschedule triggerを使えば無料枠でもcron的な事ができそうだったので移植で素振りしてみる

yuzu441.hateblo.jp

cloudflare workersでslack botを動かすには@slack/botは動かないらしいので、以下の記事で紹介されているものを使う

zenn.dev

各種slack用のトークンは上記の記事を参照

cron triggerについて

developers.cloudflare.com

wrangler.ymlなどにcron設定を書くとその設定に従って以下の用に定義した関数を実行してくれる

時間設定

wrangler.ymlに設定するのはこんな感じ(ドキュメントに書かれているサンプル)

[triggers]
# Schedule cron triggers:
# - At every 3rd minute
# - At 3PM on first day of the month
# - At 11:59PM on the last weekday of the month
crons = [ "*/3 * * * *", "0 15 1 * *", "59 23 LW * *" ]

cronsに設定できる時間は3つまでらしい

実装

初期のテンプレートだとsrc/index.tsscheduled関数を定義するとそれが実行される

export default {
  async scheduled(event: ScheduledEvent, env: SlackEdgeAppEnv, ctx: ExecutionContext): Promise<void> {
  // 処理 
};

eventには実行された時間と発火したcronの設定値("*/3 * * * *"のようなもの)が入っている。なのでevent.cronの値を見て3つで出し分ける事も可能だと思う。

実際に作ってみる

実際に実行するときはcloudflareに環境変数として設定するがローカルでテストする時のために.dev.varsにtoken設定などを書いておく

SLACK_BOT_TOKEN=xoxb-xxxxx
SLACK_SIGNING_SECRET=xapp-xxxxx

scheduleのコードは以下。exec関数でslackに投稿するデータの取得や組み立て、投稿までを行っている。

import { SlackApp, SlackEdgeAppEnv } from "slack-cloudflare-workers";
import * as xxx from "./features/xxx/index";

export default {
  async scheduled(
    _event: ScheduledEvent,
    env: SlackEdgeAppEnv,
    _ctx: ExecutionContext,
  ): Promise<void> {
    try {
      const app = new SlackApp({ env });
      await xxx.exec(app);
      console.log("done");
    } catch (e) {
      console.error(e);
      throw e;
    }
  },
};


// execの中のイメージ
export function exec(app: SlackApp<SlackEdgeAppEnv>) {
  const data = await xxxApi();
  const text = processing(data);
  await app.client.chat.postMessage({
    channel: "random",
    text,
  });

これだけで簡単に投稿することができた

テスト

wrangler devを実行する際に--test-scheduledというオプションを付けることでschedule発火用のエンドポイントが出てくる。

developers.cloudflare.com

実行後以下のようにリクエストを投げることでscheduled関数を実行させることができる

curl "http://localhost:8787/__scheduled?cron=0+0+*+*+*"

疑問点

cron triggerで実行しconsole.logでログを出すようにしてみたがどこで確認できるのかわからず、失敗した時のデバッグで困った。

またcronの設定のTZについてUTC固定?なので日本時間で実行する場合は+9時間後に実行される事に注意する必要がある。

cloudflare workerを始める

cloudflare workerずっと気になってたのでhelloworldとそのテストを触ってみたメモ

事前にアカウントは作っておいたのでプロジェクト作る所から。

プロジェクトを作る

pnpm create cloudflare@latest

これを実行するとcliでログインしていなければブラウザが起動して、cloudflareにログインする

コード

hello worldのテンプレートを選択して、生成されたsrc/index.tsを確認すると以下のコードが生成されていた

export interface Env {
}

export default {
    async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
        return new Response('Hello World!');
    },
};

Responseをnewしてreturnするとレスポンスを返せるのがわかる

メモ

トリガー

実行方法としては他にも

  • Alarm
  • Email
  • Queue
  • Scheduled(cron的な)
    • 3つまでの制限あり
  • Tail
    • リアルタイムでログを処理しロギングサービスや分析サービスに送信するために使うらしい

などトリガーにすることもできる*1

developers.cloudflare.com

Node.js Compatibility

Cloudflare workerは完全にnodejsのAPIをサポートしてるわけではない

developers.cloudflare.com

ただNode.js compatibilityというのがありwrangler.tomlcompatibility_flags = [ "nodejs_compat" ]と書いてフラグをオンにするとnode:bufferなど一部のAPIが使えるようになるらしい

developers.cloudflare.com

環境変数

基本的にはwrangler.toml[vars]部分に記述するらしいが、gitignoreの対象ではないので内容によっては書くのに抵抗がある

そういう時はローカルで動かす時は.dev.varsというファイルを作り、その中に.envのように記述するとwrangler devした時に引数のenvに入る。

developers.cloudflare.com

debug

デバッグwrangler devで行う事ができる。実行するとローカルサーバーが起動する。 なのでdevを実行してるターミナルと、httpie*2を使ってリクエストを送るターミナルの2つを開いてやってみる。

# terminal A
pnpm run dev # 裏でwrangler devを実行している

# terminal B
http localhost:8787
HTTP/1.1 200 OK
Content-Length: 12
Content-Type: text/plain;charset=UTF-8

Hello World!

テスト

テストにはwranglerパッケージから出てるunstable_devを使う

developers.cloudflare.com

hello worldのテンプレートにはテストツールが入っていないのでvitestを入れた*3
公式のサンプルで示されてるのはjsなのでtsとして書いた

import {unstable_dev, type UnstableDevWorker} from 'wrangler'
import {describe, beforeAll, afterAll, it, expect} from 'vitest'

describe("Worker", () => {
  let worker: UnstableDevWorker

  beforeAll(async () => {
    worker = await unstable_dev('src/index.ts', {
      experimental: { disableExperimentalWarning: true }
    })
  })

  afterAll(async () => {
    await worker.stop()
  })

  it('should return Hello World', async () => {
    const resp = await worker.fetch()
    if(resp) {
      const text = await resp.text()
      expect(text).toMatchInlineSnapshot(`"Hello World!"`)
    }
  })
})

今回は文字列を返すシンプルなものだが、KVなどを使い始めるとminiflareを使えばテストできるのか今後確認したい

miniflare.dev

その他

quickstartsとしてHonoを使っているテンプレートやGraphQL Yogaのテンプレートのようなものまであるようなのでgenerateしてみて中を見るのも良さそう。

developers.cloudflare.com

ただwarngler generateを実行すると

[WARNING] The generate command is no longer supported and will be removed in a future version.

と表示されるのでそのうち動かなくなりそう

またasync fetch以外にもasync cronのように書くと定期実行できるみたいなのでそっちもそのうち試してみる

*1:cloudflareのコンソール上にそれぞれTriggersとして定義されている

*2:https://httpie.io/

*3:ts-nodeとか使うとnodeのテストランナーでもできるとは思うTest runner | Node.js v21.2.0 Documentation