@flink-app/slack-plugin

中文文档
2.0.0-alpha.134发布于 2026/11/07MITTypeScriptCommonJS近 30 天下载 2187

Flink plugin for bi-directional Slack messaging with auto-discovered SlackHandler files

原始使用文档
文档目录45 节
  1. Features
  2. Installation
  3. Setup
  4. 1. Configure the compiler plugin
  5. 2. Register the plugin
  6. 3. Extend your app context type
  7. 4. Create handler files
  8. Handler Examples
  9. Respond to @mentions
  10. Handle DMs
  11. Match by pattern
  12. Match by channel
  13. Catch-all (no Route)
  14. Route Properties
  15. Plugin Context
  16. send(opts)
  17. reply(message, text, opts?)
  18. getThread(message)
  19. getChannelHistory(opts)
  20. withConnection(id)
  21. addConnection(id, connection)
  22. removeConnection(id)
  23. listConnections()
  24. sendFile(opts)
  25. downloadFile(file)
  26. react(message, emoji)
  27. client
  28. Connection Modes
  29. Socket Mode (recommended for development)
  30. HTTP Mode
  31. Plugin Options
  32. Multi-Tenant / Dynamic Connections
  33. Multiple connections at startup
  34. Load connections from database
  35. Add/remove connections at runtime
  36. Proactive sending to a specific connection
  37. Route filtering by connection
  38. Accessing connection info in handlers
  39. CLI
  40. Slack App Setup
  41. 1. Bot Token Scopes
  42. 2. Event Subscriptions
  43. 3. Socket Mode (recommended)
  44. 4. Install to workspace
  45. Multi-tenant setup

@flink-app/slack-plugin

Bi-directional Slack messaging plugin for Flink. Receive and respond to Slack messages using auto-discovered handler files — the same pattern as @flink-app/inbound-email-plugin.

Features

  • Receive channel messages, DMs, mentions, and thread replies
  • Send messages to channels and threads
  • Upload and download files with typed SlackFile metadata
  • Add emoji reactions
  • Retrieve thread and channel history for context
  • Auto-discover SlackHandler files via Flink compiler plugin
  • Socket Mode (no public URL needed) and HTTP mode
  • Bot message filtering to prevent infinite loops
  • Automatic <@BOTID> mention stripping
  • requestContext integration for user/permission injection

Installation

pnpm add @flink-app/slack-plugin

Setup

1. Configure the compiler plugin

In your flink.config.js:

const { compilerPlugin } = require("@flink-app/slack-plugin/compiler");

module.exports = {
  compilerPlugins: [compilerPlugin()],
};

This tells the Flink compiler to scan src/slack-handlers/ for handler files.

2. Register the plugin

import { slackPlugin } from "@flink-app/slack-plugin";

const app = new FlinkApp<AppCtx>({
  plugins: [
    slackPlugin<AppCtx>({
      connection: {
        mode: "socket",
        token: process.env.SLACK_BOT_TOKEN!,
        appToken: process.env.SLACK_APP_TOKEN!,
        signingSecret: process.env.SLACK_SIGNING_SECRET!,
      },
      // Optional: resolve your app's user from a Slack user ID
      resolveUser: async (msg, ctx) => {
        return ctx.repos.userRepo.findOne({ slackUserId: msg.userId });
      },
    }),
  ],
});

3. Extend your app context type

Import SlackPluginCtx and pass it as the generic argument to FlinkContext. This gives you full type-safety on ctx.plugins.slack throughout your app.

// src/Ctx.ts
import { FlinkContext } from "@flink-app/flink";
import { SlackPluginCtx } from "@flink-app/slack-plugin";

export interface Ctx extends FlinkContext<SlackPluginCtx> {
  repos: {
    // your repos here
  };
}

ctx.plugins.slack is then fully typed — send messages, manage connections, and access the raw WebClient.

Note: Import SlackPluginCtx (not SlackApi) when extending your Ctx. SlackApi is the inner API type and is not intended for this purpose.

4. Create handler files

Create handlers in src/slack-handlers/. Each file exports a default handler function and an optional Route.

Handler Examples

Respond to @mentions

// src/slack-handlers/HandleMention.ts
import { SlackHandler, SlackRouteProps } from "@flink-app/slack-plugin";

export const Route: SlackRouteProps = { isMention: true };

const handler: SlackHandler<AppCtx> = async ({ ctx, message, slack }) => {
  const thread = await slack.getThread(message);
  await slack.reply(message, `You said: "${message.text}". Thread has ${thread.messages.length} messages.`);
};
export default handler;

Handle DMs

// src/slack-handlers/HandleDM.ts
import { SlackHandler, SlackRouteProps } from "@flink-app/slack-plugin";

export const Route: SlackRouteProps = { isDM: true };

const handler: SlackHandler<AppCtx> = async ({ message, slack }) => {
  await slack.reply(message, `Got your DM: "${message.text}"`);
};
export default handler;

Match by pattern

// src/slack-handlers/HandleHelp.ts
import { SlackHandler, SlackRouteProps } from "@flink-app/slack-plugin";

export const Route: SlackRouteProps = { pattern: /^help$/i };

const handler: SlackHandler<AppCtx> = async ({ message, slack }) => {
  await slack.reply(message, "Here's what I can do…");
};
export default handler;

Match by channel

// src/slack-handlers/HandleSupport.ts
import { SlackHandler, SlackRouteProps } from "@flink-app/slack-plugin";

export const Route: SlackRouteProps = { channel: "support" };

const handler: SlackHandler<AppCtx> = async ({ ctx, message, slack }) => {
  await slack.reply(message, "A support agent will be with you shortly.");
};
export default handler;

Catch-all (no Route)

// src/slack-handlers/Fallback.ts
import { SlackHandler } from "@flink-app/slack-plugin";

const handler: SlackHandler<AppCtx> = async ({ message, slack }) => {
  console.log(`Unrouted message from ${message.userId}: ${message.text}`);
};
export default handler;

Route Properties

PropertyTypeDescription
channelstring | RegExp | (msg) => booleanMatch by channel ID
patternstring | RegExp | (msg) => booleanMatch on message text
isDMbooleanOnly match direct messages
isMentionbooleanOnly match @mentions
isThreadbooleanOnly match thread replies
connectionIdstring | string[]Only match messages from specific connection(s)

An empty or omitted Route matches all messages (catch-all).

Plugin Context

The plugin exposes ctx.plugins.slack with these methods:

send(opts)

Send a message to any channel.

await ctx.plugins.slack.send({
  channel: "general",
  text: "Hello from Flink!",
  threadTs: "1234567890.123456", // optional: reply in thread
  blocks: [],                    // optional: Block Kit blocks
});

reply(message, text, opts?)

Reply to a message. Continues the thread if the message is already in one, otherwise starts a new thread.

await slack.reply(message, "Got it!");

getThread(message)

Fetch all messages in a thread.

const thread = await slack.getThread(message);
// thread.messages: SlackThreadMessage[]

getChannelHistory(opts)

Fetch recent messages from a channel.

const history = await slack.getChannelHistory({
  channelId: message.channelId,
  limit: 10,
});

withConnection(id)

Get a scoped context bound to a specific connection. Useful for proactive sending from jobs or HTTP handlers.

const acmeSlack = ctx.plugins.slack.withConnection("acme");
await acmeSlack.send({ channel: "general", text: "Hello!" });

addConnection(id, connection)

Add a new connection at runtime.

await ctx.plugins.slack.addConnection("acme", {
  mode: "socket",
  token: "xoxb-...",
  appToken: "xapp-...",
  signingSecret: "...",
});

removeConnection(id)

Stop and remove a connection.

await ctx.plugins.slack.removeConnection("acme");

listConnections()

List active connection IDs.

const ids = ctx.plugins.slack.listConnections(); // ["default", "acme"]

sendFile(opts)

Upload a file to a channel or thread.

await ctx.plugins.slack.sendFile({
  channel: message.channelId,
  file: Buffer.from("hello world"),
  filename: "hello.txt",
  title: "My File",              // optional
  initialComment: "Here you go", // optional: message alongside the file
  threadTs: message.threadTs,    // optional: upload into thread
});

downloadFile(file)

Download a file attached to an incoming message. Uses the bot token for authentication since Slack file URLs are private.

if (message.files?.length) {
  const content = await slack.downloadFile(message.files[0]);
  // content is a Buffer
}

react(message, emoji)

Add an emoji reaction to a message.

await slack.react(message, "thumbsup");

client

Raw @slack/web-api WebClient for anything not covered by the helpers. Returns the default connection's client.

await ctx.plugins.slack.client.reactions.add({
  channel: message.channelId,
  timestamp: message.ts,
  name: "thumbsup",
});

Connection Modes

No public URL required. Uses WebSocket via an app-level token.

connection: {
  mode: "socket",
  token: "xoxb-...",
  appToken: "xapp-...",
  signingSecret: "...",
}

HTTP Mode

Receives events via HTTP. Requires a publicly accessible URL configured in your Slack app.

connection: {
  mode: "http",
  token: "xoxb-...",
  signingSecret: "...",
  port: 3100, // default
}

Plugin Options

OptionTypeDefaultDescription
connectionSlackConnectionOptions—Single connection (stored as "default").
connectionsRecord<string, SlackConnectionOptions>—Multiple named connections at startup.
loadConnections(ctx) => Promise<Record<string, SlackConnectionOptions>>—Load connections dynamically at init (e.g. from database).
stripMentionbooleantrueRemove <@BOTID> prefix from text.
resolveUser(msg, ctx) => Promise<any>—Resolve app user from Slack message.
resolvePermissions(user, ctx) => Promise<string[]>—Resolve permissions for resolved user.
onUnhandled(msg, ctx) => Promise<void>—Called when no handler matches.

You can use connection, connections, loadConnections, or any combination. All are optional — you can start with zero connections and add them dynamically at runtime.

Multi-Tenant / Dynamic Connections

For SaaS apps where each tenant connects their own Slack workspace, the plugin supports multiple concurrent connections. Handlers work identically — the slack argument is automatically scoped to the connection that received the message.

Multiple connections at startup

slackPlugin<AppCtx>({
  connections: {
    acme: {
      mode: "socket",
      token: process.env.ACME_SLACK_TOKEN!,
      appToken: process.env.ACME_SLACK_APP_TOKEN!,
      signingSecret: process.env.ACME_SIGNING_SECRET!,
    },
    beta: {
      mode: "socket",
      token: process.env.BETA_SLACK_TOKEN!,
      appToken: process.env.BETA_SLACK_APP_TOKEN!,
      signingSecret: process.env.BETA_SIGNING_SECRET!,
    },
  },
});

Load connections from database

slackPlugin<AppCtx>({
  loadConnections: async (ctx) => {
    const tenants = await ctx.repos.tenantRepo.findAll({ slackEnabled: true });
    return Object.fromEntries(
      tenants.map(t => [t.id, {
        mode: "socket" as const,
        token: t.slackToken,
        appToken: t.slackAppToken,
        signingSecret: t.signingSecret,
      }])
    );
  },
});

Add/remove connections at runtime

// Add a new tenant's Slack connection
await ctx.plugins.slack.addConnection("acme", {
  mode: "socket",
  token: "xoxb-...",
  appToken: "xapp-...",
  signingSecret: "...",
});

// Remove it
await ctx.plugins.slack.removeConnection("acme");

// List active connections
const ids = ctx.plugins.slack.listConnections(); // ["acme", "beta"]

Proactive sending to a specific connection

From jobs, HTTP handlers, or anywhere with access to the plugin context:

await ctx.plugins.slack.withConnection("acme").send({
  channel: "general",
  text: "Hello from Flink!",
});

Route filtering by connection

Handlers can declare which connections they listen on:

export const Route: SlackRouteProps = {
  isMention: true,
  connectionId: "acme",              // only this connection
  // connectionId: ["acme", "beta"], // specific set
  // omit connectionId → listen on ALL connections (default)
};

Accessing connection info in handlers

Every SlackMessage includes a connectionId field identifying which connection received it:

const handler: SlackHandler<AppCtx> = async ({ message, slack }) => {
  console.log(`Message from connection: ${message.connectionId}`);
  await slack.reply(message, "Got it!"); // uses the correct connection automatically
};

CLI

Send a test message from the command line:

flink-slack send --token xoxb-... --channel general "Hello from CLI"

Or using an environment variable:

export SLACK_BOT_TOKEN=xoxb-...
flink-slack send --channel general "Hello from CLI"

Slack App Setup

Create a Slack app at api.slack.com/apps and configure the following. See the Slack Bolt getting started guide for detailed walkthrough.

1. Bot Token Scopes

Under OAuth & Permissions → Scopes, add:

ScopePurpose
chat:writeSend messages
files:readDownload files from messages
files:writeUpload files
reactions:writeAdd emoji reactions
channels:historyRead channel messages
groups:historyRead private channel messages
im:historyRead DMs
mpim:historyRead group DMs

2. Event Subscriptions

Under Event Subscriptions → Subscribe to bot events, add:

  • message.channels — channel messages
  • message.groups — private channel messages
  • message.im — DMs
  • app_mention — @mentions

Under Settings → Socket Mode, toggle it on and generate an app-level token with the connections:write scope. This gives you the xapp-... token used in the appToken config field. No public URL needed.

See Slack Socket Mode docs for details.

4. Install to workspace

Under Install App, click Install to Workspace. Copy the Bot User OAuth Token (xoxb-...) and the Signing Secret (from Basic Information).

Multi-tenant setup

For multi-tenant apps, each tenant installs the Slack app to their own workspace via OAuth v2. After the OAuth flow completes, store each tenant's token, appToken, and signingSecret, then call addConnection() to start receiving messages. See the Slack distributing apps guide for the full OAuth flow.

版本与发布记录

@flink-app/slack-plugin 共发布 75 个版本,首次发布于 2026/03/03;过去 12 个月发布了 75 个版本。

版本发布日期距今
2.0.0-alpha.134latest2026/11/078 天前
2.0.0-alpha.1332026/11/069 天前
2.0.0-alpha.1322026/11/0510 天前
2.0.0-alpha.1312026/10/2818 天前
2.0.0-alpha.1302026/10/2521 天前
2.0.0-alpha.1292026/10/2521 天前

关于 @flink-app/slack-plugin 的常见问题

@flink-app/slack-plugin 是什么?

Flink plugin for bi-directional Slack messaging with auto-discovered SlackHandler files

如何安装 @flink-app/slack-plugin?

在项目目录运行 npm install @flink-app/slack-plugin,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@flink-app/flink(>=2.0.0-alpha.134)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 flink-slack,可以用 npx flink-slack 直接运行。

@flink-app/slack-plugin 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@flink-app/slack-plugin 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@flink-app/slack-plugin 可以商用吗?

@flink-app/slack-plugin 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@flink-app/slack-plugin 的下载量有多少?

根据 npm 官方下载统计,@flink-app/slack-plugin 在 2026/10/15 – 2026/11/13 这 30 天内被下载 2,187 次,日均约 73 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@flink-app/slack-plugin 还在维护吗?

最新版本 2.0.0-alpha.134 发布于 2026/11/07(8 天前);至今共发布 75 个版本,其中过去 12 个月发布了 75 个;首次发布于 2026/03/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。