-
Notifications
You must be signed in to change notification settings - Fork 398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
app.message payload arg compatibility in TypeScript #904
Comments
👋 any news here? If I start a new project using the given sample project, will I have troubles? Is it recommended to not use Typescript with Bolt? |
My advice: don't bother trying to use Bolt with Typescript at this point. Perhaps one day slack will genuinely prioritize Typescript. |
The In my opinion, the correct way to make the example work is: // This will match any message that contains 👋
app.message(':wave:', async ({ message, say }) => {
if (!message.subtype) {
await say(`Hello, <@${message.user}>`);
}
}); |
It's a pity to see that after a year since this issue has been created we still don't have a ts support of the very basic example in the README :-/ |
This one works for me 😄 if (
message.subtype !== "message_deleted" &&
message.subtype !== "message_replied" &&
message.subtype !== "message_changed"
) {
await say(`Hello, <@${message.user}>`);
} As you can see in ...
export interface MessageChangedEvent {
type: 'message';
subtype: 'message_changed';
event_ts: string;
hidden: true;
channel: string;
channel_type: channelTypes;
ts: string;
message: MessageEvent;
previous_message: MessageEvent;
}
export interface MessageDeletedEvent {
type: 'message';
subtype: 'message_deleted';
event_ts: string;
hidden: true;
channel: string;
channel_type: channelTypes;
ts: string;
deleted_ts: string;
previous_message: MessageEvent;
}
export interface MessageRepliedEvent {
type: 'message';
subtype: 'message_replied';
event_ts: string;
hidden: true;
channel: string;
channel_type: channelTypes;
ts: string;
message: MessageEvent & {
thread_ts: string;
reply_count: number;
replies: MessageEvent[];
};
}
... these three interfaces don't have a |
Any updates on proper support or updating the documentation so it works as described using TS? |
I just did this casting to solve the problem in my case:
|
Just ran into the complete lack of proper TypeScript support by BoltJS 😢
My workaround is to put all the Bolt logic in |
Is Slack still planning to support bolt app, or should developers consider not using this? I mean this seems to me such a fundamental entry point (listening to message), and it has been 2 years since this is opened and no appropriate response yet. |
I used this hacky typeguard to get around this quickly:
|
I decided to use What a mess to use a correct MessageEvent types, TS always complains about something like I'm senior c++ developer so I know what a strong typed language is but really I don't see how to compile my simple code without hacking the TS type system. import { App } from '@slack/bolt';
// Types definitions for @slack/bolt
import type { SlackEventMiddlewareArgs } from '@slack/bolt';
import type { MeMessageEvent } from '@slack/bolt/dist/types/events/message-events.d.ts';
//import type { MessageEvent } from '@slack/bolt/dist/types/events/base-events.d.ts';
// Custom types definition
type MessageEventArgs = SlackEventMiddlewareArgs<'message'> & { message: GenericMessageEvent };
class ChannelHandler {
private app: App;
private channelMessageHandlers: Map<string, (args: MessageEventArgs) => void>;
constructor(app: App) {
this.app = app;
this.channelMessageHandlers = new Map();
this.setupGlobalMessageListener();
}
private setupGlobalMessageListener(): void {
this.app.message(async (args) => {
const { message } = args;
const handler = this.channelMessageHandlers.get(message.channel);
if (handler) { handler(args); }
});
}
async createChannel(channelName: string, messageHandler: (args: MessageEventArgs) => void): Promise<void> {
try {
const result = await this.app.client.conversations.create({
token: process.env.SLACK_BOT_TOKEN,
name: channelName,
is_private: true
});
const channelId = result.channel?.id;
if (!channelId) { throw new Error(`Channel ID is undefined`); }
console.log(`Channel created: ${channelId}`);
// Register the message handler for this channel
this.channelMessageHandlers.set(channelId, messageHandler);
} catch (error) {
console.error(`Error creating channel: ${error}`);
}
}
}
export type { MessageEventArgs };
export default ChannelHandler; The type definition for /**
*
* @param listeners Middlewares that process and react to a message event
*/
message<MiddlewareCustomContext extends StringIndexed = StringIndexed>(...listeners: MessageEventMiddleware<AppCustomContext & MiddlewareCustomContext>[]): void; so /**
* Arguments which listeners and middleware receive to process an event from Slack's Events API.
*/
export interface SlackEventMiddlewareArgs<EventType extends string = string> {
payload: EventFromType<EventType>;
event: this['payload'];
message: EventType extends 'message' ? this['payload'] : never;
body: EnvelopedEvent<this['payload']>;
say: WhenEventHasChannelContext<this['payload'], SayFn>;
ack: undefined;
} TL;DR; I'm lost here, what is the type to define in case of recieving a channel message from a user (user writting into a slack channel) ? |
as @seratch posted in the initial message:
|
Yes I ended up casting the message type as follow, I found it a bit hacky and came here to see if a proper solution existed but apparentlt not. import type { AllMiddlewareArgs, SlackEventMiddlewareArgs } from "@slack/bolt";
import type { GenericMessageEvent } from "@slack/bolt/dist/types/events/message-events.d.ts";
type MessageEventArgs = AllMiddlewareArgs & SlackEventMiddlewareArgs<"message">;
const botMessageCallback = async (args: MessageEventArgs) => {
const { message, client, body, say } = args;
try {
const genericMessage = message as GenericMessageEvent;
//...
// Happily using genericMessage.text and genericMessage.channel now, all that type import / cast for that ...
}
} |
Basic message type has no I successfuly integrated AI services like openai to my Slack App in multiple Workspaces and Channels but the routing was hard to design |
We hear that this could be confusing and frustrating. The message event payload data type is a combination of multiple subtypes. Therefore, when it remains of union type, only the most common properties are displayed in your coding editor. A currently available solution to access text etc. is to check subtype to narrow down possible payload types as demonstrated below: app.message('hello', async ({ message, say }) => {
// Filter out message events with subtypes (see https://api.slack.com/events/message)
if (message.subtype === undefined || message.subtype === 'bot_message') {
// Inside the if clause, you can access text etc. without type casting
}
}); The full example can be found at: https://github.com/slackapi/bolt-js/blob/%40slack/bolt%403.17.1/examples/getting-started-typescript/src/app.ts#L18-L19 We acknowledge that this isn't a fundamental solution, so we are considering making breaking changes to improve this in future manjor versions: #1801. Since this is an impactful change for existing apps, we are releasing it with extreme care. The timeline for its release remains undecided. However, once it's launched, handling message events in bolt-js for TypeScript users will be much simplified compared to now. |
Ahhh, I haven't seen that before. That makes much more sense. |
@seratch Ok, thats prettier than casting but the problem I see is that you have to re-write the if condition inside the callback function that treats message if you use a middleware to filter message like in the following I wrote for my app. import logger from "../../logger/winston.ts";
import prisma from "../../prisma/client.ts";
import { assistantMessageCallback } from "./assistant-message.ts";
import type { AllMiddlewareArgs, SlackEventMiddlewareArgs, Context, App } from "@slack/bolt";
import type { GenericMessageEvent } from "@slack/bolt/dist/types/events/message-events.d.ts";
import type { MessageElement } from "@slack/web-api/dist/types/response/ConversationsHistoryResponse.js";
type MessageEventArgs = AllMiddlewareArgs & SlackEventMiddlewareArgs<"message">;
export function isBotMessage(message: GenericMessageEvent | MessageElement): boolean {
return message.subtype === "bot_message" || !!message.bot_id;
}
export function isSytemMessage(message: GenericMessageEvent | MessageElement): boolean {
return !!message.subtype && message.subtype !== "bot_message";
}
async function isMessageFromAssistantChannel(message: GenericMessageEvent, context: Context): Promise<boolean> {
const channelId = message.channel;
const assistant = await prisma.assistant.findFirst({
where: {
OR: [{ slackChannelId: channelId }, { privateChannelsId: { has: channelId } }]
}
});
if (assistant) context.assistant = assistant;
return !!assistant;
}
export async function filterAssistantMessages({ message, context, next }: MessageEventArgs) {
const genericMessage = message as GenericMessageEvent;
// Ignore messages without text
if (genericMessage.text === undefined || genericMessage.text.length === 0) return;
// Ignore messages from the bot
if (isBotMessage(genericMessage)) {
logger.debug("Ignoring message from bot");
return;
}
// Ignore system messages
if (isSytemMessage(genericMessage)) {
logger.debug("Ignoring system message");
return;
}
// Accept messages from the assistant channel and store the retrieved assistant in the context
if (await isMessageFromAssistantChannel(genericMessage, context)) await next();
}
const register = (app: App) => {
app.message(filterAssistantMessages, assistantMessageCallback);
};
export default { register }; I don't know if this is the correct way to use the SDK logic but in |
I don't know this could be helpful for your use case, but this repo had a little bit hackey example in the past. The function |
|
I ran into this today. The DX around this is pretty bad as others have pointed out there. It looks like this will get improved in the 4.0 release. Is there any sense for then that release might come about? |
@Scalahansolo we are working first on updating the underlying node SDKs that power bolt-js: https://github.com/slackapi/node-slack-sdk and its various sub-packages. Over the past 6 months or so we have released major new versions for many of these but a few still remain to do (rtm-api, socket-mode and, crucially for this issue, the types sub-package). I am slowly working my through all the sub-packages; admittedly, it is slow going, and I apologize for that. Our team responsible for the node, java and python SDKs (both lower-level ones as well as the bolt framework) is only a few people and our priorities are currently focussed on other parts of the Slack platform. I am doing my best but releasing major new versions of several packages over the months is challenging and sensitive; we have several tens of thousands of active Slack applications using these modules that we want to take care in supporting through major new version releases. This means doing the utmost to ensure backwards compatibility and providing migration guidance where that is not possible. I know this is a frustrating experience for TypeScript users leveraging bolt-js. Once the underlying node.js Slack sub-packages are updated to new major versions, we will turn our attention to what should be encompassed in a new major version of bolt-js, which this issue is at the top of the list for. |
That all makes total sense and I think provides a good bit of color to this thread / issue / conversation. It was just feeling like at face value that this wasn't going to get addressed given the age of the issue here. Really appreciate all the context and will keep an eye out for updates. |
Thanks for the update @filmaj ! Much appreciated :) |
My pleasure! FWIW, progress here:
How the community can help: if there are specific issues you have with TypeScript support generally in bolt that is not captured in this issue or any other issues labeled with "TypeScript specific", feel free to at-mention me in any TypeScript-related issue in this repo, or file a new one. Still a ways away but inching closer! |
Update:
|
Is there any recently updates here related to improved types with socket mode? |
@Scalahansolo re: socket-mode types (I assume you mean types for event payloads), the relevant issue would be this one: slackapi/node-slack-sdk#1395. IMO the I am in the process of going through the backlog of bolt-js issues and identifying other areas within bolt-js that may be better consolidated into the The current state of the |
The
message
argument inapp.message
listeners does not provide sufficient properties in TypeScript.A workaround is to cast the
message
value by(message as GenericMessageEvent).user
but needless to say, this is not great.I ran into this today also. Trying to use the official example from https://slack.dev/bolt-js/concepts
and immediately getting a ts compile error. This is not a great first-time experience. Not even sure how to get it working.
Originally posted by @memark in #826 (comment)
The text was updated successfully, but these errors were encountered: