slack.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import {
  2. BodyParams, Controller, Get, Inject, Post, Req, Res,
  3. } from '@tsed/common';
  4. import { parseSlashCommand } from '@growi/slack';
  5. import { Installation } from '~/entities/installation';
  6. import { InstallationRepository } from '~/repositories/installation';
  7. import { RelationRepository } from '~/repositories/relation';
  8. import { OrderRepository } from '~/repositories/order';
  9. import { InstallerService } from '~/services/InstallerService';
  10. import { RegisterService } from '~/services/RegisterService';
  11. @Controller('/slack')
  12. export class SlackCtrl {
  13. @Inject()
  14. installerService: InstallerService;
  15. @Inject()
  16. installationRepository: InstallationRepository;
  17. @Inject()
  18. relationRepository: RelationRepository;
  19. @Inject()
  20. orderRepository: OrderRepository;
  21. @Inject()
  22. registerService: RegisterService;
  23. growiCommandsMappings = {
  24. register: async(body:{[key:string]:string}):Promise<void> => this.registerService.execSlashCommand(body),
  25. };
  26. @Get('/testsave')
  27. testsave(): void {
  28. const installation = new Installation();
  29. installation.data = {
  30. team: undefined,
  31. enterprise: undefined,
  32. user: {
  33. id: '',
  34. token: undefined,
  35. scopes: undefined,
  36. },
  37. };
  38. // const installationRepository = getRepository(Installation);
  39. this.installationRepository.save(installation);
  40. }
  41. @Get('/install')
  42. async install(): Promise<string> {
  43. const url = await this.installerService.installer.generateInstallUrl({
  44. // Add the scopes your app needs
  45. scopes: [
  46. 'channels:history',
  47. 'commands',
  48. 'groups:history',
  49. 'im:history',
  50. 'mpim:history',
  51. 'chat:write',
  52. ],
  53. });
  54. return `<a href="${url}">`
  55. // eslint-disable-next-line max-len
  56. + '<img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcSet="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" />'
  57. + '</a>';
  58. }
  59. @Post('/events')
  60. async handleEvent(@BodyParams() body:{[key:string]:string}, @Res() res: Res): Promise<string> {
  61. // eslint-disable-next-line max-len
  62. // see: https://api.slack.com/apis/connections/events-api#the-events-api__subscribing-to-event-types__events-api-request-urls__request-url-configuration--verification
  63. if (body.type === 'url_verification') {
  64. return body.challenge;
  65. }
  66. if (body.text == null) {
  67. return 'No text.';
  68. }
  69. const parsedBody = parseSlashCommand(body);
  70. const executeGrowiCommand = this.growiCommandsMappings[parsedBody.growiCommandType];
  71. if (executeGrowiCommand == null) {
  72. return 'No executeGrowiCommand';
  73. }
  74. // Send response immediately to avoid opelation_timeout error
  75. // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
  76. res.send();
  77. await executeGrowiCommand(body);
  78. const installation = await this.installationRepository.findByID('1');
  79. if (installation == null) {
  80. throw new Error('installation is reqiured');
  81. }
  82. // Find the latest order by installationId
  83. let order = await this.orderRepository.findOne({
  84. installation: installation.id,
  85. }, {
  86. order: {
  87. createdAt: 'DESC',
  88. },
  89. });
  90. if (order == null || order.isExpired()) {
  91. order = await this.orderRepository.save({ installation: installation.id });
  92. }
  93. return 'This action will be handled by bolt service.';
  94. }
  95. @Get('/oauth_redirect')
  96. async handleOauthRedirect(@Req() req: Req, @Res() res: Res): Promise<void> {
  97. // illegal state
  98. // TODO: https://youtrack.weseek.co.jp/issue/GW-5543
  99. if (req.query.state !== 'init') {
  100. res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
  101. res.end('<html>'
  102. + '<head><meta name="viewport" content="width=device-width,initial-scale=1"></head>'
  103. + '<body style="text-align:center; padding-top:20%;">'
  104. + '<h1>Illegal state, try it again.</h1>'
  105. + '<a href="/slack/install">'
  106. + 'go to install page'
  107. + '</a>'
  108. + '</body></html>');
  109. }
  110. this.installerService.installer.handleCallback(req, res, {
  111. // success: (installation, metadata, req, res) => {},
  112. failure: (error, installOptions, req, res) => {
  113. res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
  114. res.end('<html>'
  115. + '<head><meta name="viewport" content="width=device-width,initial-scale=1"></head>'
  116. + '<body style="text-align:center; padding-top:20%;">'
  117. + '<h1>GROWI Bot installation failed</h1>'
  118. + '<p>Please contact administrators of your workspace</p>'
  119. + 'Reference: <a href="https://slack.com/help/articles/222386767-Manage-app-installation-settings-for-your-workspace">'
  120. + 'Manage app installation settings for your workspace'
  121. + '</a>'
  122. + '</body></html>');
  123. },
  124. });
  125. }
  126. }