This is translation of Slack Next-gen Platform – “reaction_added” Event Trigger – DEV Community π©βπ»π¨βπ» written by Kaz (SDK Engineering & DevRel at Slack), I do not include any personal opinion here except guessing when meaning of original text is not clear for me)
μ΄ κΈμ μ¬λμ SDK Engineering / DevRel μΈ Kaz μ κΈμ λ²μνκ²μ λλ€. μ μ κ°μΈ μ견μ λ€μ΄κ°μ§ μμΌλ©°, μΌλΆ μλ¬Έμ μλ―Έκ° μ λ§€ν κ²½μ°μλ§ λΆμ° μ€λͺ μ λ¬μμ΅λλ€.
μ΄λ² νν 리μΌμμλ μ±λ ID λ₯Ό νμλ‘ νμ§ μλ event νΈλ¦¬κ±°λ₯Ό μ¬μ©νλ λ°©λ²μ λν΄μ λ°°μλ³Ό κ²μ λλ€. event νΈλ¦¬κ±°λ μ°κ²°λμ΄μλ Slack μν¬μ€νμ΄μ€μμ νΉμ ν μ΄λ²€νΈκ° λ°μνμ λ νΈλ¦¬κ±° λ μ μμ΅λλ€. κ°κ°μ event νΈλ¦¬κ±° νμ μ μ λ ₯μ μν μ체μ μΈ λ°μ΄ν° μ€ν€λ§λ₯Ό κ°μ§κ³ μκ³ , μν¬νλ‘κ° νΈλ¦¬κ±°λ‘λΆν° νμν μ 보λ€μ λ°μλ³Ό μ μμ΅λλ€.
κ·Έλ¦¬κ³ 2κ°μ§ νμ μ event νΈλ¦¬κ±°κ° μμ΅λλ€.
첫λ²μ§Έλ μν¬μ€νμ΄μ€ μ 체μ μ΄λ²€νΈλ₯Ό μΊ‘μ³ν μ μλ νΈλ¦¬κ±° μ λλ€. μ΄λ¬ν μ΄λ²€νΈμ μλ‘ “channel_created” μ΄λ²€νΈλ₯Ό κΌ½μ μ μκ² μ΅λλ€. (μ±λμ΄ μμ±λμμ) κ·ΈμΈμλ “dnd_updated”(ν΄λΉ λ©€λ²μ Do not Disturb μ ν μ΄ λ³κ²½λμμ), “emoji_changed” (컀μ€ν μ΄λͺ¨μ§κ° μΆκ°λμκ±°λ λ³κ²½λμμ), “user_joined_team”(μ λ©€λ²κ° μ‘°μΈνμμ) λ±μ΄ μκ² μ£ .
λ€λ₯Έ νκ°μ§λ νΉμ μ±λμμ λ°μνλ μ΄λ²€νΈλ₯Ό μΊ‘μ³ν μ μλ νΈλ¦¬κ±° μ λλ€. μ΄λ¬ν μ΄λ²€νΈμ μλ‘λ “message_posted” (μ±λμ λ©μΈμ§κ° μμ±λμμ), “reaction_added” (λ©€λ²κ° μ΄λͺ¨μ§ 리μ‘μ μ λ©μΈμ§μ μΆκ°νμμ), “user_joined_channel” (μ μ κ° κ³΅κ° λλ λΉκ³΅κ° μ±λμ μ‘°μΈνμμ), “user_left_channel” (μ μ κ° κ³΅κ° λλ λΉκ³΅κ° μ±λμμ λκ°μ) λ±μ΄ μκ² μ΅λλ€.
μ΄λ² νν λ¦¬μΌ μμλ μ λͺ©μμ μΈκΈν λ°μ κ°μ΄ “reaction_added” λ₯Ό κ°μ§κ³ μ§ν ν΄λ³΄κ² μ΅λλ€.
Prerequisites
μ°¨μΈλ νλ«νΌμ μ νλ κ²μ΄ μ²μμ΄μλΌλ©΄, μ΄μ μ νν 리μΌμΈ βThe Simplest Hello Worldβ λΆν° μ½μ΄μ£ΌμΈμ. μμ½νλ©΄ μ λ£ Slack μν¬μ€νμ΄μ€μ ν΄λΉ μν¬μ€νμ΄μ€μμ βbeta featureβ λ₯Ό μ¬μ©ν μ μλ κΆνμ΄ νμν©λλ€. κ°μ§κ³ κ³μλ€λ©΄ Slack CLI μ μν¬μ€νμ΄μ€λ₯Ό μ°κ²°νκΈ°λ§ νλ©΄ λ©λλ€.
μ€λΉλμλ€λ©΄ μ±μ νλ² λ§λ€μ΄λ³΄λλ‘ νμ£ . μμν΄λ³΄κ² μ΅λλ€.
Create a Blank Project
slack create λͺ λ Ήμ΄λ₯Ό μ¬μ©νμ¬, μλ‘μ΄ νλ‘μ νΈλ₯Ό μμν μ μμ΅λλ€. μ΄ νν 리μΌμμλ μ무κ²λ μλ μνμμ μ±μ λ§λ€μ΄λ³΄κ² μ΅λλ€. βBlank Projectβ λ₯Ό μ ννμ¬ μ£Όμμμ.
$ slack create
? Select a template to build from:
Hello World
A simple workflow that sends a greeting
Scaffolded project
A solid foundational project that uses a Slack datastore
> Blank project
A, well.. blank project
To see all available samples, visit github.com/slack-samples.
νλ‘μ νΈκ° μμ±λλ©΄, slack run λͺ λ Ήμ΄κ° μ μμ μΌλ‘ λμνλμ§ νμΈν΄λ³΄μΈμ. μ΄ λͺ λ Ήμ΄λ βdevβ λ²μ μ μ±μ μν¬μ€νμ΄μ€μ μ€μΉν©λλ€. μ΄ μ±μ λ΄ μ μ κ° μμ±λκ³ , μ΄ λ΄μ API νΈμΆμ μν λ΄ ν ν° κ°μ κ°μ§κ³ μμ΅λλ€.
$ cd affectionate-panther-654
$ slack run
? Choose a workspace seratch T03E94MJU
App is not installed to this workspace
Updating dev app install for workspace "Acme Corp"
Outgoing domains
No allowed outgoing domains are configured
If your function makes network requests, you will need to allow the outgoing domains
Learn more about upcoming changes to outgoing domains: https://api.slack.com/future/changelog
seratch of Acme Corp
Connected, awaiting events
Connected, awaiting events λ‘κ·Έ λ©μΈμ§λ₯Ό 보μ λ€λ©΄, μ΄ μ±μ΄ μ±κ³΅μ μΌλ‘ μν¬μ€νμ΄μ€μ μ°κ²°λ κ²μ λλ€. βCtrl +Cβ λ₯Ό λλ¬ λ‘컬 μ± νλ‘μΈμ€λ₯Ό μ’ λ£ν©λλ€.
Define Workflow and Trigger
κ°λ¨ν λ°λͺ¨ μν¬νλ‘μ link νΈλ¦¬κ±°λ₯Ό μ μν΄λ³΄κ² μ΅λλ€. workflow_and_trigger.ts λ‘ μ μ₯ν©λλ€.
// ----------------
// Workflow Definition
// ----------------
import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
export const workflow = DefineWorkflow({
callback_id: "example-workflow",
title: "Example Workflow",
input_parameters: {
properties: {
// All the possible inputs from the "reaction_added" event trigger
channel_id: { type: Schema.slack.types.channel_id },
user_id: { type: Schema.slack.types.user_id },
message_ts: { type: Schema.types.string },
reaction: { type: Schema.types.string },
},
required: ["channel_id", "user_id", "message_ts", "reaction"],
},
});
// TODO: Add function steps here
// ----------------
// Trigger Definition
// ----------------
import { Trigger } from "deno-slack-api/types.ts";
const trigger: Trigger<typeof workflow.definition> = {
type: "event", // Event Trigger
name: "Trigger the example workflow",
workflow: `#/workflows/${workflow.definition.callback_id}`,
event: {
// "reaction_added" event trigger
event_type: "slack#/events/reaction_added",
channel_ids: ["C04FB5UF1C2"], // TODO: Update this list
// The condition to filter events
filter: {
version: 1,
// Start the workflow only when the reaction is :eyes:
root: { statement: "{{data.reaction}} == eyes" },
},
},
inputs: {
channel_id: { value: "{{data.channel_id}}" },
user_id: { value: "{{data.user_id}}" },
message_ts: { value: "{{data.message_ts}}" },
reaction: { value: "{{data.reaction}}" },
},
};
export default trigger;
νΈλ¦¬κ±°μ event.event_type μ λ°λμ “slack#/events/reaction_added” μ¬μΌ ν©λλ€. μ¬κΈ°μ νΈλ¦¬κ±°λ‘λΆν° 5κ°μ§μ μ λ ₯κ°μ κ°μ§κ³ μ¬ μ μμ΅λλ€. μ΄ μ λ ₯κ°μ λν΄μ μμΈν μμ보μ€λ €λ©΄ 곡μ λ¬Έμμ data νλ‘νΌν° νλͺ©μ λν΄μ 보μλ©΄ λκ² μ΅λλ€.
λν, channel_ids: [“C04DPBYUQUC”], // TODO: Update this list λΆλΆλ μ λ°μ΄νΈ λμ΄μΌ ν©λλ€. μΌλ¨ μ΄ μν¬νλ‘λ₯Ό μΆκ°ν κ³΅κ° μ±λμ κ³ λ₯΄μΈμ (νμμ μμλ 곡κ°μ±λλ§ μ§μν©λλ€.) κ·Έλ¦¬κ³ ν΄λΉ μ±λμ ID λ₯Ό 볡μ¬νμ¬ λΆμ΄λ£μΌμκΈ° λ°λλλ€. Slack UI μμμ μ±λμ΄λ¦μ ν΄λ¦νλ©΄, νμ λͺ¨λ¬μ΄ μ΄λ¦½λλ€. κ±°κΈ°μ μλμͺ½μΌλ‘ μ€ν¬λ‘€ λ€μ΄μ νλ©΄ λ€μκ³Ό κ°μ΄ μ±λ ID λ₯Ό νμΈνμ€ μ μμ΅λλ€.

κ·Έλ¦¬κ³ μ΄ μν¬νλ‘λ₯Ό manifest.ts μ μΆκ°ν©λλ€.
import { Manifest } from "deno-slack-sdk/mod.ts";
import { workflow as DemoWorkflow } from "./workflow_and_trigger.ts";
export default Manifest({
name: "distracted-bison-253",
description: "Demo workflow",
icon: "assets/default_new_app_icon.png",
workflows: [DemoWorkflow],
outgoingDomains: [],
botScopes: [
"commands",
"chat:write",
"reactions:read", // required for the "reaction_added" event trigger
"channels:history", // will use in custom functions later
"channels:join", // will use in custom functions later
],
});
μ¬κΈ°μλ μν¬νλ‘ μΆκ°λΏλ§ μλλΌ event νΈλ¦¬κ±°λ₯Ό μν “reactions:read” μ€μ½νλ botScopes λ΄μ μΆκ°ν΄μΌ ν©λλ€. λ€λ₯Έ μ΄λ²€νΈ νΈλ¦¬κ±°λ₯Ό μ¬μ©νλ κ²½μ°λ μμΌμ€ν λ°, νμλ‘ νλ μ€μ½νλ λ€μ λ§ν¬μμ νμΈν΄λ³΄μκΈ° λ°λλλ€.
Create an Event Trigger
λ€μμΌλ‘ λκ°μ μλμ° ν°λ―Έλμ μ΄μ΄μ£ΌμΈμ. νλλ slack run μ μ€ννκ³ , λ€λ₯Έ νλμμλ slack trigger create λͺ λ Ήμ΄λ₯Ό μ€νν κ²μ λλ€.
μν¬νλ‘λ₯Ό λ±λ‘νκΈ° μν΄μ, 첫λ²μ§Έ ν°λ―Έλμμ slack run λͺ λ Ήμ΄λ₯Ό μ€νμ£Όμκ³ , κ·Έ λ€μ slack triggers create –trigger-def workflow_and_trigger.ts λ₯Ό λ€λ₯Έ ν°λ―Έλμμ μ€νν΄μ£Όμμμ. λ€μκ³Ό κ°μ κ²°κ³Όλ¬Όμ λ³΄μ€ μ μμ΅λλ€.
$ slack triggers create --trigger-def ./workflow_and_trigger.ts
? Choose an app seratch (dev) T03E*****
distracted-bison-253 (dev) A04FNE*****
β‘ Trigger created
Trigger ID: Ft04EJ8*****
Trigger Type: event
Trigger Name: Trigger the example workflow
Add π to A Message in the Channel
μ μ΄μ μ΄λ»κ² μν¬νλ‘κ° λμνλμ§ νμΈν΄λ³΄κ² μ΅λλ€. π :eyes: μ΄λͺ¨μ§λ₯Ό 리μ‘μ μ μ±λλ΄μ λ©μΈμ§μ μΆκ°ν΄λ³΄μΈμ. slack run ν°λ―Έλμμ λ€μκ³Ό κ°μ κ²°κ³Όλ¬Όμ λ³΄μ€ κ²λλ€.
$ slack run
? Choose a workspace seratch T03E94MJU
distracted-bison-253 A04FACHPQ5R
Updating dev app install for workspace "Acme Corp"
β οΈ Outgoing domains
No allowed outgoing domains are configured
If your function makes network requests, you will need to allow the outgoing domains
Learn more about upcoming changes to outgoing domains: https://api.slack.com/future/changelog
β¨ seratch of Acme Corp
Connected, awaiting events
2022-12-15 10:03:50 [info] [Fn04FCVD67J8] (Trace=Tr04G077TW80) Function execution started for workflow function 'Example Workflow'
2022-12-15 10:03:50 [info] [Wf04FP576X3K] (Trace=Tr04FP5F70HX) Execution started for workflow 'Example Workflow'
2022-12-15 10:03:51 [info] [Fn04FCVD67J8] (Trace=Tr04G077TW80) Function execution completed for function 'Example Workflow'
2022-12-15 10:03:51 [info] [Wf04FP576X3K] (Trace=Tr04FP5F70HX) Execution completed for workflow 'Example Workflow'
λ€λ₯Έ μ΄λͺ¨μ§μ κ²½μ°λ λμνμ§ μμ΅λλ€. :wave: μ΄λͺ¨μ§λ₯Ό μΆκ°νλλΌλ μ무μΌλ λ²μ΄μ§μ§ μμ΅λλ€.

Access The Channel Content
λ§μ½ κΈ°μ‘΄μ Events API κ° μ΅μνμ λΆμ΄λΌλ©΄, Events API μ μ°¨μΈλνλ«νΌμ Event νΈλ¦¬κ±°μ μ°¨μ΄μ μ λν΄μ ν΄κΉλ¦¬μ€ μ μμ΅λλ€.
Events API λ μ±λλ΄μμ μ±μ λ©€λ²μ½μ κ³μν΄μ μꡬν©λλ€. κ·Έ λ§μ μ±μ΄ μ΄λ²€νΈλ₯Ό μμ ν λ, μ΄ μ±μ νμ μ±λ 컨ν μΈ μ μ κ·Όνκ² λ©λλ€. (μ±λμ λ©€λ²μ¬μΌ νλ€λ μ΄μΌκΈ°)
λ°λλ‘, μ°¨μΈλνλ«νΌμ Event νΈλ¦¬κ±°λ, μ±μ λ΄μ μ κ° μ΄λ²€νΈκ° λ°μνλ μ±λμ λ©€λ²κ° μλμ¬λ νΈλ¦¬κ±° λ μ μμ΅λλ€.
μμ μν¬νλ‘μ μ±λμ 컨ν μΈ μ μ κ·Όν μ μλλ‘ ν΄μ£Όλ κ°λ¨ν νμ μ νλ μΆκ°ν΄λ³΄κ² μ΅λλ€. functions.ts λΌλ μ΄λ¦μΌλ‘ λ€μμ μμ€μ½λλ₯Ό μ μ₯ν©λλ€.
import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";
export const def = DefineFunction({
callback_id: "reply_to_reaction",
title: "Reply to a reaction in a channel",
source_file: "function.ts",
input_parameters: {
properties: {
channel_id: { type: Schema.slack.types.channel_id },
user_id: { type: Schema.slack.types.user_id },
reaction: { type: Schema.types.string },
message_ts: { type: Schema.types.string },
},
required: ["channel_id", "user_id", "reaction", "message_ts"],
},
output_parameters: {
properties: { ts: { type: Schema.types.string } },
required: ["ts"],
},
});
export default SlackFunction(def, async ({ inputs, client }) => {
// https://api.slack.com/methods/conversations.join
// requires "channels:join" scope in manifest.ts
const joinResponse = await client.conversations.join({
channel: inputs.channel_id,
});
if (joinResponse.error) {
const error = `Failed to join the channel due to ${joinResponse.error}`;
return { error };
}
// https://api.slack.com/methods/conversations.history
// requires "channels:history" scope in manifest.ts
const historyResponse = await client.conversations.history({
channel: inputs.channel_id,
latest: inputs.message_ts,
inclusive: true,
limit: 1,
});
if (historyResponse.error) {
const error =
`Failed to fetch the channel content due to ${joinResponse.error}`;
return { error };
}
const messageText = (historyResponse.messages[0].text ||
"(Failed to fetch the message text)").replaceAll("\n", "\n>");
const replyText =
`Hey <@${inputs.user_id}>, thanks for adding :${inputs.reaction}: to the following message:\n>${messageText}`;
// https://api.slack.com/methods/chat.postMessage
// requires "chat:write" scope in manifest.ts
const replyResponse = await client.chat.postMessage({
channel: inputs.channel_id,
text: replyText,
});
if (replyResponse.error) {
const error = `Failed to post a message due to ${replyResponse.error}`;
return { error };
}
return { outputs: { ts: replyResponse.ts } };
});
곡κ°μ±λμ λ¨μν λ©μΈμ§λ₯Ό λ¨κΈ°κΈ° μν΄μλ, chat:write_public μ€μ½νλ₯Ό κ°μ§ chat.postMessage API μΈμ λ³λμ μΆκ°μ μΈ API μ½μ νμλ‘ νμ§ μμ΅λλ€. κ·Έλ¬λ λ§μ½ μ±μ΄ μ΄μ μ ν¬μ€ν λμλ λ©μΈμ§μ λ΄μ©μ΄λ κ·ΈμΈ λ€λ₯Έ κ²λ€μ μμμΌ νλ€λ©΄, μ΄ νμ μ λκ°μ Slack API (conversations.join κ³Ό conversations.history) μ½μ ν©λλ€.
κ·Έλ¦¬κ³ λμ, λ€μ νμ μ€ν μ workflow_and_trigger.ts λ΄μ μν¬νλ‘μ μΆκ°ν©λλ€.
import { def as reply } from "./function.ts";
workflow.addStep(reply, {
channel_id: workflow.inputs.channel_id,
user_id: workflow.inputs.user_id,
reaction: workflow.inputs.reaction,
message_ts: workflow.inputs.message_ts,
});
λ€μ μν¬νλ‘λ₯Ό μ€νν΄λ³΄μλ©΄, μ±μ λ΄μ μ κ° μλμΌλ‘ μ±λμ μ‘°μΈμ νκ³ , ν¬μ€ν λμλ λ©μΈμ§λ₯Ό ν¬ν¨ν μλ‘μ΄ λ©μΈμ§λ₯Ό μ±λμ λ¨κΈ°λ κ²μ λ³΄μ€ μ μμ΅λλ€.

Don’t Want to Hard-code the channel_ids?
μ±λ ID λ₯Ό νλμ½λ© νκΈ°λ₯Ό μνμμ§ μμ μλ μλλ°μ, λ¬Όλ‘ νλμ½λ©μ΄ λμ΄μμΌλ©΄ νΈλ¦¬κ±°λ₯Ό λ€μ νμ©νκΈ°λ μ΄λ ΅κ³ , κ΄λ¦¬νκΈ°λ μ΄λ ΅μ΅λλ€. (λ€λ₯Έ μ±λμμλ νκΈ°μν΄μλ νΈλ¦¬κ±°λ₯Ό μλ‘ λ§λ€μ΄μΌ νκ³ μ)
κ·Έλ¬λ μνκΉκ²λ, μμ€μ½λ νμΌμ μ΄μ©νμ¬ νΈλ¦¬κ±°λ₯Ό μμ±ν λλ μ±λID λ₯Ό λΊμκ° μμ΅λλ€. κ·Έλ¬λ λ€λ₯Έ λ°©λ²μΌλ‘ νΈλ¦¬κ±° λ°νμμ λ§λ€ μλ μμ΅λλ€. λ°λ‘ 컀μ€ν νμ μμ νΈλ¦¬κ±°λ₯Ό μμ±/μμ (generation/modification) νλ API μ½μ νλ κ²μ λλ€. 곡μλ¬Έμλ₯Ό μ°Έμ‘°νμΈμ. λ€λ₯Έ νν 리μΌμμλ νλ² λ€λ£¨μ΄ λ³΄κ² μ΅λλ€.
Wrapping Up
μ΄λ² νν 리μΌμμλ λ€μκ³Ό κ°μ λ΄μ©μ λ°°μ보μμ΅λλ€.
- μ±λ κΈ°λ°μ event νΈλ¦¬κ±°λ₯Ό μ μνκ³ μ¬μ©νλ λ°©λ²
μ΄ νλ‘μ νΈλ λ€μ μ£Όμμμλ νμΈνμ€ μ μμ΅λλ€. https://github.com/seratch/slack-next-generation-platform-tutorials/tree/main/08_channel_created_Event_Trigger
μ΄λ² νν 리μΌλ μ¦κ±°μ°μ ¨μΌλ©΄ μ’κ² λ€μ. λ§μ°¬κ°μ§λ‘ νΌλλ°±μ΄λ μ½λ©νΈκ° μμΌμλ€λ©΄ νΈμν° (@seratch) λ‘ μ°λ½μ£Όμκ±°λ, μ¬κΈ° λ¨κ²¨μ£ΌμΈμ.
Happy hacking with Slackβs next-generation platform π