Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/yayadrian/node-red-slack
A node-red module to post to Slack.com
https://github.com/yayadrian/node-red-slack
Last synced: 3 months ago
JSON representation
A node-red module to post to Slack.com
- Host: GitHub
- URL: https://github.com/yayadrian/node-red-slack
- Owner: travisghansen
- License: mit
- Created: 2015-01-29T21:45:17.000Z (almost 10 years ago)
- Default Branch: master
- Last Pushed: 2024-03-17T06:30:34.000Z (8 months ago)
- Last Synced: 2024-05-19T04:53:11.740Z (6 months ago)
- Language: JavaScript
- Size: 116 KB
- Stars: 21
- Watchers: 7
- Forks: 29
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-nodered - slack - Interact with the Slack API. (Nodes / Social)
README
# node-red-contrib-slack
A Node-RED node to interact with
the Slack
API.# Install
Run the following command in the root directory of your Node-RED install:
```
npm install --save node-red-contrib-slack
```Version `2.x` of this package is **NOT** compatible with older versions. Plese
refer to the [migration](#migration-from-012-or-earlier) section for help.# Usage
The nodes included in this package are **purposely** generic in nature. The
usage very closely mimics the
Slack API. Your best source
of reference for input/output specifics will be from:- https://api.slack.com
- https://api.slack.com/rtm
- https://api.slack.com/web
- https://api.slack.com/methods4 nodes are provided:
- [`slack-rtm-in`](#slack-rtm-in)
- [`slack-rtm-out`](#slack-rtm-out)
- [`slack-web-out`](#slack-web-out)
- [`slack-state`](#slack-state)The `rtm` API/node(s) are connected to slack via web sockets and are useful for
receiving a real-time stream of events/data.The `web` API/node(s) are useful for making traditional web service calls and
have a much broader use-case.Combining both `rtm` and `web` APIs provides a full solution to interact with
the Slack API in it's
entirety facilitating powerful flows in `Node-RED`. Which nodes are
appropriate to use for any given use-case can be subjective so familiarizing
youself with the documentation links above is **extremely** beneficial.## invoking methods
To invoke methods ([`slack-rtm-out`](#slack-rtm-out),
[`slack-web-out`](#slack-web-out)) set the `msg.topic` to the name of the
method and set the `msg.payload` to the args/params.The `token` property is **NOT** required to be set in any `msg.payload`.
As an example of invoking the
`chat.meMessage`
method with the [`slack-web-out`](#slack-web-out) node you would do the
following:```
msg.topic = "chat.meMessage";
msg.payload = {
channel: "...",
text: "..."
}return msg;
```## `dressed` output
The following nodes each provide
Slack API output:- [`slack-rtm-in`](#slack-rtm-in) - view details of each event in the
documentation
- [`slack-rtm-out`](#slack-rtm-out) - view details of each event in the
documentation
- [`slack-web-out`](#slack-web-out) - view `response` section of each method in
the documentationThe respective events/responses are generally left unaltered and are directly
passed through as `msg.payload`. However, before outputting the `msg` the data
is traversed to enrich the `msg.payload` (examples provided below) with
complete object data where otherwise only internal Slack IDs are present.All of the lookups are done dynamically/generically so regardless of what API
response you get if the node finds an attribute that appears to be a
supported object (`user`/`channel`/`team`/`bot`) in some shape or form, a
corresponding `Object` attribute with the lookup value will be
added.For example, if the response contains a `bot_id` attribute you would see
`bot_idObject` added, or if it found an attribute called `bot` it would add
`botObject` etc. Ultimately all the lookups come from
[`slackState`](#slackstate) (see below) so it could be done on your own but
it's added to simplify and for convenience.An example
`user_typing`
event from the [`slack-rtm-in`](#slack-rtm-in) node:```
{
"type": "user_typing",
"channel": "...",
"user": "..."
}
```is `dressed` to be sent as:
```
{
"type": "user_typing",
"channel": "...",
"user": "...",
"channelObject": {
"id": "...",
"name": "...",
"is_channel": true,
"is_group": false,
"is_im": false,
"created": 1434735155,
...
},
"userObject": {
"id": "...",
"name": "...",
"real_name": "...",
...
}
}
```## Helpers
As a convenience for both the [`slack-web-out`](#slack-web-out) and
[`slack-rtm-out`](#slack-rtm-out) nodes a special interface is supported that
allows you to send a message with a simplified structure (`msg.topic` starts
with `@` or `#`):```
msg.topic = "@some_user";
# or
msg.topic = "#some_channel";msg.payload = "a special message just for you"
return msg
```As an additional convenience, if you are invoking the
`chat.meMessage`,
`chat.postEphemeral`,
`chat.postMessage`
methods ([`slack-web-out`](#slack-web-out)), or
`message` method
([`slack-rtm-out`](#slack-rtm-out)) and the `channel` starts with `@` or `#`
the node will automatically lookup the appropriate `channel.id` from
[`slackState`](#slackstate) (see below) and set it for you.## `slackState`
All outputs for all nodes include a `msg.slackState` object which has several
properties containing the lists of `members`/`channels`/`bots`/`team`/etc so
downstream nodes can do 'lookups' without re-hitting the API (this data is
currently refreshed in the background every 10 minutes). Additionally the
internal state is connected to all relevant slack events and updates are
reflected real-time in any new messages (ie: a user getting created
automatically updates the `slackState` even before the 10 minute refresh).## nodes
### `slack-rtm-in`
The [`slack-rtm-in`](#slack-rtm-in) node listens to
Slack RTM events and
outputs the [`dressed`](#dressed-output) response as the `msg.payload`.By default the node will listen to ALL events. You can however filter
event types by setting the node Slack Events property to a value
taking the form of `type[::subtype][,type[::subtype],...]`. For example
`message` to receive only events of type `message` or `message::bot_message` to
receive only events of type `message` which additionally have a `subtype` of
`bot_message`.Example output:
```
{
"payload" {
"type": "user_typing",
"channel": "...",
"user": "...",
"channelObject": {
"id": "...",
"name": "...",
"is_channel": true,
"is_group": false,
"is_im": false,
"created": 1434735155,
...
},
"userObject": {
"id": "...",
"name": "...",
"real_name": "...",
...
}
},
"slackState": {
...
}
}
```### `slack-rtm-out`
Invokes a Slack RTM
method and outputs the [`dressed`](#dressed-output) response as the
`msg.payload`.Available methods:
- `message`: send a message
- `ping`: pong
- `presence_sub`:
to subscribe to
`presence_change`
events
- `presence_query`:
to request a one-time
`presence_change`
status
- `typing`: to send typing indicatorsUsing [`slack-rtm-out`](#slack-rtm-out) for sending messages should only be
used for very basic messages, preference would be to use the
`chat.postMessage`,
method of the [`slack-web-out`](#slack-web-out) node for anything beyond the
simplest messaging use-case as it supports
`attachments`
as well as many other features.`presence_sub`
is a powerful [`slack-rtm-out`](#slack-rtm-out) method that allows you to
receive
`presence_change`
events on the [`slack-rtm-in`](#slack-rtm-in) node. See the
[presence](#presence) example below for further details.Example input:
```
msg.topic = 'presence_query';
msg.payload = {
ids: [
'...'
]
}
return msg;
```Example output:
```
{
"topic": "presence_query",
"payload": {
"ok":true,
"type":"presence_query"
},
"slackState": {
...
}
}
```### `slack-web-out`
Invokes a Slack Web
method and outputs the [`dressed`](#dressed-output) response as the
`msg.payload`.See the [sending a message](#sending-a-message) example for advanced message
sending.Example input:
```
msg.topic = "chat.meMessage";
msg.payload = {
channel: "...",
text: "..."
}return msg;
```Example output:
```
{
"topic": "chat.meMessage",
"payload": {
"channel": "...",
"ts": "1552705036.049000",
"ok": true,
"scopes": [
"identify",
"read",
"post",
"client",
"apps"
],
"acceptedScopes": [
"chat:write:user",
"post"
],
"channelObject": {
"id": "...",
...
"userObject": {
"id": "...",
...
}
}
},
"slackState": {
...
}
}
```### `slack-state`
[`slack-state`](#slack-state) outputs a message with
[`msg.slackState`](#slackstate) added. If the `msg.payload` sent to
[`slack-state`](#slack-state) is `true` then it will first do a full refresh of
the state (should not generally be necessary) and then output the `msg`.The `state events` (2nd) output of the node emits a signal when the state has
been fully initialized after (re)connect. This can be useful if you want to
perform any post initilization tasks (ie:
`presence_sub`
).Example input:
```
msg.payload = true; // force a refresh
return msg;
```Example output (state):
```
{
"slackState": {
...
}
}
```Example output (state events):
```
{
"payload": {
"type":"ready"
},
"slackState": {
...
}
}
```# Examples / Advanced
## sending a message
While you can send messages using the simplified syntax (`msg.topic` starts
with `@` or `#` and `msg.payload` is the message) using either the
[`slack-web-out`](#slack-web-out) or the [`slack-rtm-out`](#slack-rtm-out)
nodes, your use-case may require more control. The most advanced message
sending can be accomplished by invoking the
`chat.postMessage`
method of the [`slack-web-out`](#slack-web-out) node:```
var topic = "chat.postMessage";var payload = {
// channel: "@someuser",
// or
// channel: "#somechannel",
text: "hi from bot",
...
// review linked documentation for all options
}msg = {
topic: topic,
payload: payload
}return msg;
```## respond to keyword
A simple respond to `keyword` example `function` node to place between a
[`slack-rtm-in`](#slack-rtm-in) node and a [`slack-web-out`](#slack-web-out)
node:```
// ignore anything but messages
if (msg.payload.type != "message") {
return null;
}// ignore deleted messages
if (msg.payload.subtype == "message_deleted") {
return null;
}// ignore messages from bots
if (msg.payload.bot_id || (msg.payload.userObject && msg.payload.userObject.is_bot)) {
return null;
}// if you only want to watch a specific channel put name here
var channel = "";
if (channel && !msg.payload.channelObject) {
return null;
}if (channel && msg.payload.channelObject.name != channel.replace(/^@/, "").replace(/^#/, "")) {
return null;
}// only specific users
var username = "";
if (username && !msq.payload.userObject) {
return null;
}if (username && msq.payload.userObject.name.replace(/^@/, "") != username)) {
return null;
}// check for keyword
// could use regex etc
if (!msg.payload.text.includes("keyword")) {
return null;
}// prepare outbound response
var topic = "chat.postMessage";
var payload = {
channel: msg.payload.channel, // respond to same channel
//text: '<@' + msg.payload.userObject.name + '>, thanks for chatting',
text: '<@' + msg.payload.user + '>, thanks for chatting',
//as_user: false,
//username: "",
//attachments: [],
//icon_emoji: "",
}msg = {
topic: topic,
payload: payload
}return msg;
```## presence
While [`slackState`](#slackstate) does not automatically subscribe to
`presence_change`
events for you, it will keep track of `presence` details in
[`slackState`](#slackstate) if any
`presence_change`
events are received (this is all done behind the scenes).To subscribe to
`presence_change`
events for all your users place the following `function` node between the
`slack events` output of the [`slack-state`](#slack-state) node and the
[`slack-rtm-out`](#slack-rtm-out) node:```
msg.topic = 'presence_sub';
var ids = [];for (var id in msg.slackState.members) {
if (msg.slackState.members.hasOwnProperty(id)) {
ids.push(id)
}
}msg.payload = {
ids: ids
}
return msg;
```The theory of operation is:
1. wait for the [`slackState`](#slackstate) to be initialized so you have a
complete list of `members`
1. iterate that list to build up the appropriate request to
[`slack-rtm-out`](#slack-rtm-out)
1. subscribe to presence events by sending the message to
[`slack-rtm-out`](#slack-rtm-out)
1. receive presence events on the [`slack-rtm-in`](#slack-rtm-in) nodeImmediately after the request is sent you will see a flood of
`presence_change`
events emitted on the [`slack-rtm-in`](#slack-rtm-in) node. Once the initial
flood of messages has passed continued updates will come through as
appropriate. Again, behind the scenes the [`slack-state`](#slack-state) nodes
are listening for these events and updating the
[`slackState.presence`](#slackstate) values appropriately for general
usage/consumption in your flow(s).If you are really interested in keeping the data updated you could capture
`team_join` events from a [`slack-rtm-in`](#slack-rtm-in) node and wire those
to the above `function` node as well triggering the same procedure when new
users join the `team`. You may need to put a `delay` node before the
`function` node just to give [`slackState`](#slackstate) enough time to process
this same event and update.An alternative would be to wire an `inject` node to the `function` node and put
it on a sane `interval` such as every 10 minutes.If you wanted to be **really** sure you are receiving all
`presence_change`
events for the whole `team` do all the above.# migration from `0.1.2` or earlier
In order to replicate the previous behavior it is possible to introduce simple
`function` nodes.Roughly speaking the node equivalents are:
| `0.1.2` | `2.x` |
| --------------------------------- | --------------------------------- |
| [`slack`](#slack) | [`slack-web-out`](#slack-web-out) |
| [`Slack Bot In`](#slack-bot-in) | [`slack-rtm-in`](#slack-rtm-in) |
| [`Slack Bot Out`](#slack-bot-out) | [`slack-rtm-out`](#slack-rtm-out) |## `slack`
To replicate the `slack` node simply place the following `function` node just
before the new [`slack-web-out`](#slack-web-out) node:```
// https://api.slack.com/methods/chat.postMessage
msg.topic = "chat.postMessage"
var payload = {
text: msg.payload
};// set default username (replicate the node configuration value)
var username = "";
if (username) {
payload.username = username;
payload.as_user = false;
} else if (msg.username) {
payload.username = msg.username;
payload.as_user = false;
}// set default emojiIcon (replicate the node configuration value)
var emojiIcon = "";
if (emojiIcon) {
payload.icon_emoji = emojiIcon;
} else if (msg.emojiIcon) {
payload.icon_emoji = msg.emojiIcon;
}// set default channel (replicate the node configuration value)
var channel = "";
if (channel) {
payload.channel = channel;
} else if (msg.channel) {
payload.channel = msg.channel
}if (msg.attachments) {
payload.attachments = msg.attachments;
}msg.payload = payload;
return msg;
```## `Slack Bot In`
To replicate the `Slack Bot In` node simply place the following `function` node
downstream from the new [`slack-rtm-in`](#slack-rtm-in) node:```
// https://api.slack.com/events/messageif (msg.payload.type != "message") {
return null;
}// if you only want to watch a specific channel put name here
var channel = "";
if (channel && !msg.payload.channelObject) {
return null;
}if (channel && msg.payload.channelObject.name != channel.replace(/^@/, "").replace(/^#/, "")) {
return null;
}var payload = "";
if (msg.payload.text) {
payload += msg.payload.text;
}if (msg.payload.attachments) {
if (payload) {
payload += "\n";
}msg.payload.attachments.forEach((attachment, index) => {
if (index > 0) {
payload += "\n";
}
payload += attachment.fallback;
})
}var slackObj = {
id: msg.payload.client_msg_id,
type: msg.payload.type,
text: msg.payload.text,
channelName: msg.payload.channelObject.name,
channel: msg.payload.channelObject,
fromUser: (msg.payload.userObject) ? msg.payload.userObject.name : "",
attachments: msg.payload.attachments
};msg = {
payload: payload,
slackObj: slackObj
}return msg;
```## `Slack Bot Out`
To replicate the `Slack Bot Out` node simply place the following `function`
node just before the new [`slack-rtm-out`](#slack-rtm-out) node:```
// set channel
var channel = "";
if (channel) {
// do nothing, use the provided channel
} else if (msg.channel) {
channel = msg.channel;
} else if (msg.slackObj && msg.slackObj.channel) {
channel = msg.slackObj.channel
} else {
node.error("'slackChannel' is not defined, check you are specifying a channel in the message (msg.channel) or the node config.");
node.error("Message: '" + JSON.stringify(msg));
return null;
}msg = {
topic: channel,
payload: msg.payload
}return msg;
```# Additional Resources
- Emoji Cheat Sheet
- Slack Attachments
- https://slack.dev/node-slack-sdk/
- https://slack.dev/node-slack-sdk/rtm_api
- https://slack.dev/node-slack-sdk/web_api