🍡

Slack で集約チャンネルの作り方

2024/03/20に公開

https://zenn.dev/genda_jp/articles/4ba71288928da6

前回に引き続き、Slackオートメーションプラットフォームの紹介です。

今回は複数のチャンネルの内容を1つのチャンネルに集約して、時系列で確認できるようなワークフローを作っていきます。(具体的には、全てのtimes(分報)チャンネルでの投稿を指定したチャンネルに集約してくれるワークフローです)

Trigger

集約したいチャンネルに作成したSlackアプリを追加するだけで、trigger に設定することができます。他の trigger と同居してるSlackアプリの場合は、channel_ids にも追加して、指定のチャンネルだけで今回の trigger が動くようにすると良さそうです。また、1つの trigger ファイルで指定できる channel_id は 20個までなので、20個以上のチャンネルを集約したい場合は、複数の trigger ファイルに分けるなどする対応が必要です。

import { Trigger } from "deno-slack-api/types.ts";
import {
  TriggerContextData,
  TriggerEventTypes,
  TriggerTypes,
} from "deno-slack-api/mod.ts";
import TransferMessage from "../workflows/transfer_message.ts";

/**
 * Triggers determine when Workflows are executed. A trigger
 * file describes a scenario in which a workflow should be run,
 * such as a user pressing a button or when a specific event occurs.
 * https://api.slack.com/future/triggers
 */

const postMessageTrigger: Trigger<typeof TransferMessage.definition> = {
  type: TriggerTypes.Event,
  name: "post message trigger",
  description: "Trigger when user post a message",
  workflow: `#/workflows/${TransferMessage.definition.callback_id}`,
  event: {
    event_type: TriggerEventTypes.MessagePosted,
    channel_ids: [ // max 20
      "C0A23456789", // times-yamada
      "C0AAAABBBCC", // times-tanaka
      ...
      "C0AABCDEFG9", // times-7kaji
    ],
    filter: {
      version: 1,
      root: {
        statement: "{{data.channel_type}} == public", // public チャンネルのみが対象
      },
    },
  },
  inputs: {
    channel_id: { value: TriggerContextData.Event.MessagePosted.channel_id },
    message_ts: { value: TriggerContextData.Event.MessagePosted.message_ts },
  },
};

export default postMessageTrigger;

workflow

workflow では、 function に channel_id と message の timestamp を渡すだけです。

import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
import { PostPermalinkMessageFunctionDefinition } from "../functions/post_permalink_message.ts";

const TransferMessageWorkflow = DefineWorkflow({
  callback_id: "transfer_message_workflow",
  title: "transfer message to another channel.",
  input_parameters: {
    properties: {
      channel_id: { type: Schema.slack.types.channel_id },
      message_ts: { type: Schema.types.string },
    },
    required: [
      "channel_id",
      "message_ts",
    ],
  },
});

TransferMessageWorkflow.addStep(PostPermalinkMessageFunctionDefinition, {
  channel_id: TransferMessageWorkflow.inputs.channel_id,
  message_ts: TransferMessageWorkflow.inputs.message_ts,
  to_channel_id: "C0AABBCCDDE", // 集約する先のチャンネルID
});

export default TransferMessageWorkflow;

function

function では、元の投稿のURL(パーマリンクの形式)で取得して、そのURLを集約チャンネルに投稿しています。スレッドに投稿した内容もそのまま集約チャンネルに反映させています。

import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";
import { SlackAPI } from "deno-slack-api/mod.ts";

/**
 * Functions are reusable building blocks of automation that accept
 * inputs, perform calculations, and provide outputs. Functions can
 * be used independently or as steps in workflows.
 * https://api.slack.com/automation/functions/custom
 */
export const PostPermalinkMessageFunctionDefinition = DefineFunction({
  callback_id: "post_message_funciton",
  title: "Post a permalink message.",
  description: "Post a permalink message.",
  source_file: "functions/post_permalink_message.ts",
  input_parameters: {
    properties: {
      channel_id: {
        type: Schema.slack.types.channel_id,
        description: "from channel_id",
      },
      message_ts: {
        type: Schema.types.string,
        description: "Timestamp of the message",
      },
      to_channel_id: {
        type: Schema.slack.types.channel_id,
        description: "to channel_id",
      },
    },
    required: ["channel_id", "message_ts", "to_channel_id"],
  },
  output_parameters: {
    properties: {},
    required: [],
  },
});

/**
 * SlackFunction takes in two arguments: the CustomFunction
 * definition (see above), as well as a function that contains
 * handler logic that's run when the function is executed.
 * https://api.slack.com/automation/functions/custom
 */
export default SlackFunction(
  PostPermalinkMessageFunctionDefinition,
  async ({ inputs, token }) => {
    const client = SlackAPI(token);

    try {
      // 投稿のpermalinkの取得
      const permalinkRes = await client.chat.getPermalink({
        channel: inputs.channel_id,
        message_ts: inputs.message_ts,
      });

      // 集約チャネルへの投稿
      await client.chat.postMessage({
        channel: inputs.to_channel_id,
        text: permalinkRes.permalink as string,
      });
    } catch (e) {
      console.log(e)
    } 

    return { outputs: {} };
  },
);

運用してみて

少しの間ですが、運用してみて1つのチャンネルに分報が集約され、社内の様子を眺めることができるようになってとても便利だなぁと思っています。今回は times を集約してみましたが、複数のサービスや組織に関わることが多い場合はいろんな単位で集約チャンネルを作成することで、情報のキャッチアップがしやすくなると感じました。

できていないこと

メッセージを編集、削除した trigger がない(自分が把握してないだけかも) ので、元のメッセージが変わったときに集約したチャンネルでは反映することができていません。リアルタイム性はなくなりますが、元の投稿がされて1分後に反映させるなどして対応するのもよいかなぁと考えています。

これから

Slackオートメーションプラットフォームでは、チャンネルを作成したとき・チャンネルをアーカイブしたときなど trigger として設定することができるので集約する対象のチャンネルを(半)自動で管理することができそうだなとみております。また後ほど紹介できればなと思います。

注意

この機能を実装すると、プレミアムワークフローになってしまい、無料枠除くと$0.05/回の費用が発生してしまいます。弊社では、今のところ無料枠内での運用で収まっている状態でしたが、チャンネルや投稿数が増えると課金対象になる可能性があるので注意が必要です。超過分の実行に制限を設定することができるので、やっておくと良さそうです(要admin権限)。また、独自の function 作らずに、message の text をそのまま投稿するだけ (パーマリンク形式をやめる) の機能であれば、 組み込みの Schema.slack.functions.SendMessage を用いることで対応することができます。これであれば、スタンダードワークフローの範囲内で運用することができそうです。組織の規模や使い方によっては、検討してみてください。

2024/10/10 追記: 米国時間 2024/9/25 以降、全ての機能は Slack の有料プランに含まれ、一切の追加請求は行わないというポリシー変更が発表されました。

Special Thanks

ryo_kawamata さん

すでに、同様の実装をしていてとても参考になりました。ありがとうございます。

GENDA

Discussion