diff --git a/examples/message-metadata/.gitignore b/examples/message-metadata/.gitignore new file mode 100644 index 000000000..94973a50c --- /dev/null +++ b/examples/message-metadata/.gitignore @@ -0,0 +1,4 @@ +# node / npm +node_modules/ +package-lock.json +.env diff --git a/examples/message-metadata/README.md b/examples/message-metadata/README.md new file mode 100644 index 000000000..ef1e68aba --- /dev/null +++ b/examples/message-metadata/README.md @@ -0,0 +1,44 @@ +# Bolt for JavaScript Message Metadata + +This is a quick example app to test [Message Metadata](https://api.slack.com/metadata) with Bolt for JavaScript. + +Before we get started, make sure you have a development workspace where you have permissions to install apps. If you don’t have one setup, go ahead and [create one](https://slack.com/create). You also need to [create a new app](https://api.slack.com/apps?new_app=1) if you haven’t already. You will need to enable Socket Mode and generate an App Level Token. + +## Install Dependencies + +``` +npm install +``` + +## Subscribe to Message Metadata events + +Go to the Events Subscription page from your app configuration, and subcribe to `message_metadata_deleted`, `message_metadata_posted`, and `message_metadata_updated` bot events. Additionally, go to the App Manifest page and update the `metadata subscriptions` like the following: + +``` +settings: + event_subscriptions: + bot_events: + - message_metadata_deleted + - message_metadata_posted + - message_metadata_updated + metadata_subscriptions: + - app_id: YOUR_APP_ID + event_type: my_event +``` + +## Setup Environment Variables + +This app requires you setup a few environment variables. You can find these values in your [app configuration](https://api.slack.com/apps). + +```bash +export SLACK_BOT_TOKEN=YOUR_SLACK_BOT_TOKEN +export SLACK_APP_TOKEN=YOUR_SLACK_APP_TOKEN +``` + +## Run the App + +Start the app with the following command: + +``` +npm start +``` \ No newline at end of file diff --git a/examples/message-metadata/app-manifest.json b/examples/message-metadata/app-manifest.json new file mode 100644 index 000000000..911ec2f07 --- /dev/null +++ b/examples/message-metadata/app-manifest.json @@ -0,0 +1,51 @@ +{ + "display_information": { + "name": "Message Metadata Example" + }, + "features": { + "bot_user": { + "display_name": "Message Metadata Bot", + "always_online": false + }, + "slash_commands": [ + { + "command": "/post", + "description": "Post Message Metadata", + "should_escape": false + } + ] + }, + "oauth_config": { + "redirect_urls": [ + "https://localhost" + ], + "scopes": { + "bot": [ + "metadata.message:read", + "chat:write", + "commands" + ] + } + }, + "settings": { + "event_subscriptions": { + "bot_events": [ + "message_metadata_deleted", + "message_metadata_posted", + "message_metadata_updated" + ], + "metadata_subscriptions": [ + { + "app_id": "[app id]", + "event_type": "my_event" + } + ] + }, + "interactivity": { + "is_enabled": true + }, + "org_deploy_enabled": false, + "socket_mode_enabled": true, + "token_rotation_enabled": false + } +} diff --git a/examples/message-metadata/app.js b/examples/message-metadata/app.js new file mode 100644 index 000000000..5143ac8af --- /dev/null +++ b/examples/message-metadata/app.js @@ -0,0 +1,55 @@ +const { App, LogLevel } = require('@slack/bolt'); + +const app = new App({ + token: process.env.SLACK_BOT_TOKEN, + appToken: process.env.SLACK_APP_TOKEN, + socketMode: true, + logLevel: LogLevel.DEBUG +}); + +(async () => { + await app.start(); + console.log('⚡️ Bolt app started'); +})(); + + +// Listen to slash command +// Post a message with Message Metadata +app.command('/post', async ({ ack, command, say }) => { + await ack(); + await say({ + text: "Message Metadata Posting", + metadata: { + "event_type": "my_event", + "event_payload": { + "key": "value" + } + } + }); +}); + +app.event('message_metadata_posted', async ({ event, say }) => { + const { message_ts: thread_ts } = event; + await say({ + text: "Message Metadata Posted", + blocks: [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Message Metadata Posted" + } + }, + { + "type": "context", + "elements": [ + { + "type": "mrkdwn", + "text": `${JSON.stringify(event.metadata)}` + } + ] + } + ], + thread_ts + }) +}); diff --git a/examples/message-metadata/package.json b/examples/message-metadata/package.json new file mode 100644 index 000000000..ade4abc16 --- /dev/null +++ b/examples/message-metadata/package.json @@ -0,0 +1,15 @@ +{ + "name": "bolt-message-metadata-example", + "version": "1.0.0", + "description": "Example app to manage Message Metadata events.", + "main": "index.js", + "scripts": { + "start": "node app.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Slack Technologies, LLC", + "license": "MIT", + "dependencies": { + "@slack/bolt": "^3.12.0" + } +} diff --git a/package.json b/package.json index 573de3623..abc0d7785 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@slack/logger": "^3.0.0", "@slack/oauth": "^2.5.1", "@slack/socket-mode": "^1.3.0", - "@slack/types": "^2.4.0", + "@slack/types": "^2.7.0", "@slack/web-api": "^6.7.1", "@types/express": "^4.16.1", "@types/node": ">=12", diff --git a/src/types/events/base-events.ts b/src/types/events/base-events.ts index 51241f2d4..378ae67e2 100644 --- a/src/types/events/base-events.ts +++ b/src/types/events/base-events.ts @@ -1,5 +1,5 @@ import { View, MessageAttachment, KnownBlock, Block } from '@slack/types'; -import { MessageEvent as AllMessageEvents } from './message-events'; +import { MessageEvent as AllMessageEvents, MessageMetadataEvent as AllMessageMetadataEvents } from './message-events'; /** * All known event types in Slack's Events API @@ -54,6 +54,7 @@ export type SlackEvent = | MemberJoinedChannelEvent | MemberLeftChannelEvent | MessageEvent + | MessageMetadataEvent | PinAddedEvent | PinRemovedEvent | ReactionAddedEvent @@ -585,6 +586,8 @@ export interface MemberLeftChannelEvent { export type MessageEvent = AllMessageEvents; +export type MessageMetadataEvent = AllMessageMetadataEvents; + export interface PinnedMessageItem { client_msg_id?: string; type: string; diff --git a/src/types/events/message-events.spec.ts b/src/types/events/message-events.spec.ts index 4c4ddec95..563e99a38 100644 --- a/src/types/events/message-events.spec.ts +++ b/src/types/events/message-events.spec.ts @@ -1,5 +1,5 @@ import { assert } from 'chai'; -import { BotMessageEvent, GenericMessageEvent, MessageEvent } from './message-events'; +import { BotMessageEvent, GenericMessageEvent, MessageEvent, MessageMetadataEvent } from './message-events'; describe('Events API payload types (message events)', () => { it('should be compatible with bot_message payload', () => { @@ -182,4 +182,68 @@ describe('Events API payload types (message events)', () => { }; assert.isNotEmpty(payload); }); + it('should be compatible with message_metadata_posted payload', () => { + const payload: MessageMetadataEvent = { + type: 'message_metadata_posted', + app_id: 'A222', + bot_id: 'B999', + user_id: 'U123', + team_id: 'T111', + channel_id: 'C111', + metadata: { + event_type: '', + event_payload: { + key: 'value', + }, + }, + message_ts: '', + event_ts: '', + }; + assert.isNotEmpty(payload); + }); + it('should be compatible with message_metadata_updated payload', () => { + const payload: MessageMetadataEvent = { + type: 'message_metadata_updated', + channel_id: 'C111', + event_ts: '', + previous_metadata: { + event_type: '', + event_payload: { + key: 'value', + }, + }, + app_id: 'A222', + bot_id: 'B999', + user_id: 'U123', + team_id: 'T111', + message_ts: '', + metadata: { + event_type: '', + event_payload: { + key: 'value', + }, + }, + }; + assert.isNotEmpty(payload); + }); + it('should be compatible with message_metadata_deleted payload', () => { + const payload: MessageMetadataEvent = { + type: 'message_metadata_deleted', + channel_id: 'C111', + event_ts: '', + previous_metadata: { + event_type: '', + event_payload: { + key: 'value', + }, + }, + app_id: 'A222', + bot_id: 'B999', + user_id: 'U123', + team_id: 'T111', + message_ts: '', + deleted_ts: '' + }; + assert.isNotEmpty(payload); + }); }); diff --git a/src/types/events/message-events.ts b/src/types/events/message-events.ts index 0735646fa..c64fe144a 100644 --- a/src/types/events/message-events.ts +++ b/src/types/events/message-events.ts @@ -1,4 +1,4 @@ -import { MessageAttachment, KnownBlock, Block } from '@slack/types'; +import { MessageAttachment, KnownBlock, Block, MessageMetadata } from '@slack/types'; export type MessageEvent = | GenericMessageEvent @@ -19,6 +19,11 @@ export type MessageEvent = | MessageRepliedEvent | ThreadBroadcastMessageEvent; +export type MessageMetadataEvent = +| MessageMetadataPostedEvent +| MessageMetadataUpdatedEvent +| MessageMetadataDeletedEvent; + export interface GenericMessageEvent { type: 'message'; subtype: undefined; @@ -281,6 +286,44 @@ export interface ThreadBroadcastMessageEvent { channel_type: channelTypes; } +export interface MessageMetadataPostedEvent { + type: 'message_metadata_posted'; + app_id: string; + bot_id?: string; + user_id: string; + team_id: string; + channel_id: string; + metadata: MessageMetadata; + message_ts: string; + event_ts: string; +} + +export interface MessageMetadataUpdatedEvent { + type: 'message_metadata_updated'; + channel_id: string; + event_ts: string; + previous_metadata: MessageMetadata; + app_id: string; + bot_id?: string; + user_id: string; + team_id: string; + message_ts: string; + metadata: MessageMetadata; +} + +export interface MessageMetadataDeletedEvent { + type: 'message_metadata_deleted'; + channel_id: string; + event_ts: string; + previous_metadata: MessageMetadata; + app_id: string; + bot_id?: string; + user_id: string; + team_id: string; + message_ts: string; + deleted_ts: string; +} + export type channelTypes = 'channel' | 'group' | 'im' | 'mpim' | 'app_home'; interface BotProfile {