Giter Site home page Giter Site logo

nestjs-slack-listener's Introduction

NestJS Slack Listeners and Handlers

๐Ÿฅฐ Any kinds of contributions are welcome! ๐Ÿฅฐ


Features

  • Class decorator @SlackEventListener
    • Decorate the class to use it as a Slack event listener, just like decorating HTTP listeners with @Controller.
  • Method decorator @SlackEventHandler
    • Decorate the method to use it as a Slack event handler, just like decorating HTTP handlers with @Get, @Post, etc.
    • You can filter events by event type, or your custom filtering function.
  • Slack Web API Client
    • Inject the Slack Web API client to your service class with @InjectSlackClient decorator.
    • You can use the client to send messages to Slack, or whatever you want to do.

Installation

yarn add nestjs-slack-listener

Usage

Please refer to the example for more details.

Settings

Import SlackModule

Import the module at your app module.

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    SlackModule.forRootAsync({
      useFactory: async (config: ConfigService<EnvVars>) => ({
        botToken: config.get('SLACK_BOT_TOKEN'),
      }),
      inject: [ConfigService],
    }),
  ],
  controllers: [AppController],
  providers: [],
})
export class AppModule {}

Event and Interactivity Subscription

You need to set event and interactivity subscriptions URL of your slack app so that the app can receive events and interactivity from slack.

Decorators

Slack Event

Decorate the controller to use it as slack event listener.

@Controller('on-boarding')
@SlackEventListener()
export class OnBoardingController {}

Decorate the method of the controller to use it as slack event handler.

@Controller('on-boarding')
@SlackEventListener()
export class OnBoardingController {
  constructor(private readonly onboardingService: OnBoardingService) {}

  @SlackEventHandler('team_join')
  async onTeamJoin({ event: { user } }: IncomingSlackEvent<TeamJoinEvent>) {
    this.onboardingService.startOnBoarding({ user });
  }
}

Slack Interactivity

You can also decorate the listeners and handlers for slack-interactivity.

@Controller('on-boarding')
@SlackEventListener()
@SlackInteractivityListener()
export class OnBoardingController {
  constructor(private readonly onboardingService: OnBoardingService) {}

  @SlackEventHandler('team_join')
  async onTeamJoin({ event: { user } }: IncomingSlackEvent<TeamJoinEvent>) {
    this.onboardingService.startOnBoarding({ user });
  }

  @SlackInteractivityHandler(ACTION_ID.COMPLETE_QUEST)
  async completeOnBoarding({
    user: { id: userSlackId },
    actions: [{ value }],
  }: IncomingSlackInteractivity) {
    return this.onboardingService.completeOnBoarding({ userSlackId, value });
  }
}

Filters

Filter the events with function argument filter and string argument eventType of the decorator SlackEventHandler.

@Controller('memo')
@SlackEventListener()
export class MemoController {
  constructor(private readonly memoService: MemoService) {}

  @SlackEventHandler({
    eventType: 'message',
    filter: ({ event }) => event.text.includes('write this down!'),
  })
  async takeMemo({ event: { message } }: IncomingSlackEvent<MessageEvent>) {
    this.memoService.takeMemo({ message });
  }
}

You can also filter the events at the decorator SlackEventListener

@Controller('memo')
@SlackEventListener(({ event }) => event.channel === MEMO_CHANNEL)
export class OnBoardingController {
  constructor(private readonly memoService: MemoService) {}

  @SlackEventHandler({
    eventType: 'message',
    filter: ({ event }) => event.text.includes('write this down!'),
  })
  async onTeamJoin({ event: { user } }: IncomingSlackEvent<MessageEvent>) {
    this.memoService.takeMemo({ message });
  }
}

Slack Client

Use InjectSlackClient to use the slack web api client.

@Injectable()
export class OnBoardingService {
  constructor(
    private readonly userRepository: UserRepository,
    @InjectSlackClient()
    private readonly slack: SlackClient,
  ) {}
  ...
}

The injected SlackClient is identical to the official Slack Web API Client

await this.slack.chat.postMessage({
  channel: user.id,
  text: 'Hi there! ๐Ÿ‘‹๐Ÿป',
  blocks: [
    {
      type: 'header',
      text: {
        type: 'plain_text',
        text: 'Hi there! ๐Ÿ‘‹๐Ÿป',
      },
    },
    {
      type: 'section',
      text: {
        type: 'mrkdwn',
        text: `Hello! Nice to meet you, ${user.name}! I'm *hanch*, a slack bot that helps you with onboarding process.`,
      },
    },
  ],
});

nestjs-slack-listener's People

Contributors

dependabot[bot] avatar hanchchch avatar robcoward avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

nestjs-slack-listener's Issues

View Submission Interactivity handler response is incorrect

As per https://api.slack.com/surfaces/modals#close_current_view - when processing a view_submission payload sent to the slack interactivity URL, in order to close a view, the 200 response either needs to be accompanied by an empty payload, or by returning

{
  "response_action": "clear"
}

The current handling of interaction payloads results in a payload that is a list of all the defined handler responses, similar to below:

[
  null,
  null,
  { response_action: 'clear' },
  null,
  null
]

Slack does not recognise this as a valid response and shows as an error in the modal when trying to submit the form.

This problem arises in the handleInteractivity(payload: IncomingSlackInteractivity) method in slack-handler-service.ts where this._interactivityHandlers.map() is returning a list of the responses from each this.handleSingleInteractivity(payload, handlerConfig) call.

Does it work with fastify?

I experience getting infinite loading when slack tries to hit the slack/interactivity endpoint. If I do curl -X POST http://localhost:3000/slack/interactivity it works just fine and responds, although with an error since there's no payload

SlackInteractivityHandler does not handle view_submission payloads from modals

The handleSingleInteractivity() function expects the payload to include payload.actions[] but in a view_submission payload (see https://api.slack.com/reference/interaction-payloads/views#view_submission ) sent when submitting a modal form, there is no list of actions in the payload so the code errors with:

[Nest] 76  - 07/04/2023, 6:08:45 PM   ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'filter')
TypeError: Cannot read properties of undefined (reading 'filter')
    at SlackHandler.<anonymous> (/usr/src/app/node_modules/nestjs-slack-listener/dist/slack/slack-handler.service.js:59:37)

Perhaps the code could be changed to be:

    if (actionId) {
      if (
        payload.actions &&
        payload.actions.filter((action) => action.action_id == actionId)
          .length == 0
      ) {
        return;
      }
    }

In fact the slack documentation seems to suggest using the callback_id field on a modal view to correctly handle returned view_submission payloads, so perhaps also adding:

    if (actionId) {
      if (
        payload.actions &&
        payload.actions.filter((action) => action.action_id == actionId)
          .length == 0
      ) {
        return;
      }
      if (
        payload.view &&
        payload.view.callback_id != actionId
      ) {
        return;
      }
    }

configuration help

Question

how should we configure these in .env?
the name of env variables?
where are these being injected ?
please help to add a sample.

[Event and Interactivity Subscription](https://github.com/hanchchch/nestjs-slack-listener#event-and-interactivity-subscription)
You need to set event and interactivity subscriptions URL of your slack app so that the app can receive events and interactivity from slack.

Event subscription
https://api.slack.com/apps/your-app-id/event-subscriptions
http://<hostname>/slack/events
Interactivity subscription
https://api.slack.com/apps/your-app-id/interactive-messages
http://<hostname>/slack/interactivity

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.