Slack platformを使って絵文字登録の通知をしてくれるBotを作ってみた

# 目的
ちょっと前にDenoを利用したSlackの新プラットフォームがオープンベータとしてリリースされました
https://api.slack.com/future
TypeScriptでの実装ができ、かつインフラを自前で用意する必要がないため手を出しやすい物になってます
せっかくなのでこれを活用して需要のありそうなBotアプリを作ってみたので紹介したいと思います

:::note warn
Slack platform BETAは2022/11/03時点では有料プラン契約のみで利用できます
利用には管理者権限以上の規約同意と作成したアプリのインストール承諾が必要となります
また、記事作成時点では作成や利用は無料ですが今後、従量課金等が検討されているようなので注意してください
:::

# 環境
* MacBookPro M1(zsh)
* Slack CLI 1.14.0
* deno-slack-sdk 1.3.0
* deno-slack-api 1.2.0

成果物
https://github.com/seki-shinnosuke/slack-deno-emoji-notice

# 内容
今回は作り物の話だけでなく以下の点も記載したいと思います
* Slack platformで抑えるべきTrigger/Workflow/Functionの概要と流れ
* 作成したBotアプリのソースコード説明

記載しないこと
* Slack platformのインストール方法やコマンドの説明(公式サイトを読んでください)
https://api.slack.com/future/quickstart

## Slack platformで抑えるべきTrigger/Workflow/Functionの概要と流れ
まずはSlack platformで作成するアプリは3つの項目で構成されています
| 項目 | 説明 |
|:---:|:---|
| Trigger | 何を起因にWorkflowを開始するか定義するよ！<br />例: Botに対してメンションをしたら起動！ |
| Workflow | どういう順番でFunctionを実行していくか定義するよ！<br />例: 入力フォームを表示させて！入力が完了したらFunction①を呼び出して！ |
| Function | 実際の処理を書くよ！<br />例: 入力された内容はしかと受け取った！メッセージを送ろう |

上から順にTrigger -> Workflow <-> Functionと実行され処理を完了させてくれる動きとなります
WorkflowとFunctionは一方通行な関係では無く、Functionの処理結果から次にどのFunctionを呼び出すのかをWorkflowで制御することも可能です
また、Workflowの中ではデフォルトで備わっている組み込み機能（メッセージ送信やチャンネル作成など）を利用することができるのでFunctionを作らなくても処理を完結させることもできます

## 作成したBotアプリのソースコード説明
それではここからは作成したBotアプリについて説明していきます

概要
Slackワークスペースに登録された絵文字を特定のチャンネルに通知してくれるBot

実際のソースをみていきましょう
まずは起因となるTriggerから
```typescript:emoji_notice_event_trigger.ts
import { Trigger } from "deno-slack-api/types.ts";
import EmojiNoticeWorkflow from "../workflows/emoji_notice_workflow.ts";

const emojiNoticeEventTrigger: Trigger<typeof EmojiNoticeWorkflow.definition> =
  {
    type: "event",
    event: {
      event_type: "slack#/events/emoji_changed",
    },
    name: "Emoji notification",
    description: "新規登録された絵文字を通知してくれます",
    workflow: "#/workflows/emoji_notice_workflow",
    inputs: {
      subtype: {
        value: "{{data.subtype}}",
      },
      emojiName: {
        value: "{{data.name}}",
      },
    },
  };

export default emojiNoticeEventTrigger;
```
Triggerにはショートカットリンク押下やWebhookなども設定可能ですが今回はイベント検知の仕組みを採用しました
絵文字の更新が行われた場合はワークスペース全体に対し、イベント通知が送信されます
`event_type`に設定している"emoji_changed"は絵文字更新のイベント通知を受信を起因にするという意味になり、Workflowにinputデータとしてsubtypeとnameを渡して起動するような定義になっています
https://api.slack.com/events/emoji_changed

WorkflowはとてもシンプルでTriggerから受け取った値をそのままFunctionに流すだけの1フローとなってます
```typescript:emoji_notice_workflow.ts
import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
import { EmojiNoticeFunction } from "../functions/emoji_notice_function.ts";

const EmojiNoticeWorkflow = DefineWorkflow({
  callback_id: "emoji_notice_workflow",
  title: "Emoji notice",
  description: "新規登録された絵文字を通知してくれます",
  input_parameters: {
    properties: {
      subtype: {
        type: Schema.types.string,
      },
      emojiName: {
        type: Schema.types.string,
      },
    },
    required: ["subtype"],
  },
});

EmojiNoticeWorkflow.addStep(
  EmojiNoticeFunction,
  {
    subtype: EmojiNoticeWorkflow.inputs.subtype,
    emojiName: EmojiNoticeWorkflow.inputs.emojiName,
  },
);

export default EmojiNoticeWorkflow;
```

Functionでは受け取った値から絵文字のイベントが登録かそれ以外を判定しメッセージを作って指定したチャンネルに送信するような処理となってます
```typescript:emoji_notice_function.ts
import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";
import { SlackAPI } from "deno-slack-api/mod.ts";
import env from "../../env.ts";

export const EmojiNoticeFunction = DefineFunction({
  callback_id: "emoji_notice_function",
  title: "Emoji notice",
  source_file: "src/functions/emoji_notice_function.ts",
  input_parameters: {
    properties: {
      subtype: {
        type: Schema.types.string,
        description: "Subtype",
      },
      emojiName: {
        type: Schema.types.string,
        description: "Emoji name",
      },
    },
    required: ["subtype"],
  },
});

export default SlackFunction(EmojiNoticeFunction, ({ inputs, token }) => {
  let message = "";
  if (inputs.subtype === "add" && inputs.emojiName != undefined) {
    message = `新しい絵文字「${inputs.emojiName}」が追加されました\n\n:${inputs.emojiName}:`;
  } else {
    return {
      outputs: {},
    };
  }

  const client = SlackAPI(token, {});
  client.chat.postMessage({
    channel: env.EMOJI_NOTIFICATION_CHANNEL_ID,
    text: message,
  });
  return {
    outputs: {},
  };
});
```

作り物としてはこれだけです
あとはBotに絵文字のイベント検知権限を与えるためにManifestに`emoji:read`を追加しenv.tsに通知を送りたいチャンネルのIDを記載します
```typescript:manifest.ts
export default Manifest({
  name: "emoji-notice-bot",
  description: "登録された絵文字を通知してくれるBot",
  icon: "assets/icon.png",
  workflows: [EmojiNoticeWorkflow],
  outgoingDomains: [],
  botScopes: ["commands", "chat:write", "chat:write.public", "emoji:read"],
});
```
```typescript:env.ts
export default {
  "EMOJI_NOTIFICATION_CHANNEL_ID": "CXXXXXXXXXX", // 絵文字を通知するチャンネルのID
};
```

それでは実際にアプリをワークスペースにデプロイし、Triggerを登録します
```zsh
% slack deploy
% slack trigger create --trigger-def ./src/triggers/emoji_notice_event_trigger.ts
```

Slackの「ワークスペースのカスタマイズ」ページから絵文字を登録してみると1,2秒後くらいに指定したチャンネルに通知が飛んできます
![screenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728120/980f64c0-5570-3b5d-ee65-4f34cf09eb6e.png)

これでアプリの説明は以上となります

# まとめ
今回作ったアプリは
* 登録された絵文字を活用してコミュニケーション活性につなげたい！
* コンプライアンスに違反してるような絵文字を登録してないか監視したい。。。

と企業に取っては意外と需要があるのではと思ってます

作ってみると意外と簡単なんですがハマる(つまづく)ポイントとして公式サイトの説明やサンプルソースが正直イケてないです。。。
説明がかなり端的で伝わらない、そのままだと全く動かないことがほとんどです
ですがインフラ面でSlackBot開発を戸惑っていた方はせっかくなのでBot開発にトライしてみるのも良いかもしれません