RelationsService.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import { Inject, Service } from '@tsed/di';
  2. import axios from 'axios';
  3. import { addHours } from 'date-fns';
  4. // import { Relation } from '~/entities/relation';
  5. import { markdownSectionBlock, generateWebClient } from '@growi/slack';
  6. import { ChatPostEphemeralResponse } from '@slack/web-api';
  7. import { RelationMock } from '~/entities/relation-mock';
  8. // import { RelationRepository } from '~/repositories/relation';
  9. import { RelationMockRepository } from '~/repositories/relation-mock';
  10. import loggerFactory from '~/utils/logger';
  11. const logger = loggerFactory('slackbot-proxy:services:RelationsService');
  12. @Service()
  13. export class RelationsService {
  14. @Inject()
  15. // relationRepository: RelationRepository;
  16. relationMockRepository: RelationMockRepository;
  17. async getSupportedGrowiCommands(relation:RelationMock):Promise<any> {
  18. // generate API URL
  19. const url = new URL('/_api/v3/slack-integration/supported-commands', relation.growiUri);
  20. return axios.get(url.toString(), {
  21. headers: {
  22. 'x-growi-ptog-tokens': relation.tokenPtoG,
  23. },
  24. });
  25. }
  26. async syncSupportedGrowiCommands(relation:RelationMock): Promise<RelationMock> {
  27. const res = await this.getSupportedGrowiCommands(relation);
  28. const { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands } = res.data;
  29. relation.permissionsForBroadcastUseCommands = permissionsForBroadcastUseCommands;
  30. relation.permissionsForSingleUseCommands = permissionsForSingleUseCommands;
  31. relation.expiredAtCommands = addHours(new Date(), 48);
  32. return this.relationMockRepository.save(relation);
  33. }
  34. async syncRelation(relation:RelationMock, baseDate:Date):Promise<RelationMock|null> {
  35. const distanceMillisecondsToExpiredAt = relation.getDistanceInMillisecondsToExpiredAt(baseDate);
  36. if (distanceMillisecondsToExpiredAt < 0) {
  37. try {
  38. return await this.syncSupportedGrowiCommands(relation);
  39. }
  40. catch (err) {
  41. logger.error(err);
  42. return null;
  43. }
  44. }
  45. // 24 hours
  46. if (distanceMillisecondsToExpiredAt < 1000 * 60 * 60 * 24) {
  47. try {
  48. this.syncSupportedGrowiCommands(relation);
  49. }
  50. catch (err) {
  51. logger.error(err);
  52. }
  53. }
  54. return relation;
  55. }
  56. async checkPermissionForCommands(relation:RelationMock, growiCommandType:string, channelName:string, baseDate:Date):Promise<boolean> {
  57. const syncedRelation = await this.syncRelation(relation, baseDate);
  58. if (syncedRelation == null) {
  59. return false;
  60. }
  61. // case: singleUse
  62. let permission = relation.permissionsForSingleUseCommands[growiCommandType];
  63. // case: broadCastUse
  64. if (permission == null) {
  65. permission = relation.permissionsForBroadcastUseCommands[growiCommandType];
  66. }
  67. // both case: null
  68. if (permission == null) {
  69. return false;
  70. }
  71. // check permission at channel level
  72. if (Array.isArray(permission)) {
  73. return permission.includes(channelName);
  74. }
  75. return permission;
  76. }
  77. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  78. postNotAllowedMessage(relations:RelationMock[], commandName:string, body:any):Promise<ChatPostEphemeralResponse> {
  79. const botToken = relations[0].installation?.data.bot?.token;
  80. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  81. const client = generateWebClient(botToken!);
  82. return client.chat.postEphemeral({
  83. text: 'Error occured.',
  84. channel: body.channel_id,
  85. user: body.user_id,
  86. blocks: [
  87. markdownSectionBlock(`It is not allowed to run *'${commandName}'* command to this GROWI.`),
  88. ],
  89. });
  90. }
  91. isPermittedForInteractions = false;
  92. permissionForInteractions:boolean|string[];
  93. async checkPermissionForInteractions(
  94. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  95. relation:RelationMock, channelName:string, callbackId:string, actionId:string, body:any, relations:RelationMock[],
  96. ):Promise<void> {
  97. const baseDate = new Date();
  98. const syncedRelation = await this.syncRelation(relation, baseDate);
  99. if (syncedRelation == null) {
  100. this.isPermittedForInteractions = false;
  101. return;
  102. }
  103. const singleUse = Object.keys(relation.permissionsForSingleUseCommands);
  104. const broadCastUse = Object.keys(relation.permissionsForBroadcastUseCommands);
  105. [...singleUse, ...broadCastUse].forEach(async(commandName) => {
  106. // case: singleUse
  107. this.permissionForInteractions = relation.permissionsForSingleUseCommands[commandName];
  108. // case: broadcastUse
  109. if (this.permissionForInteractions == null) {
  110. this.permissionForInteractions = relation.permissionsForBroadcastUseCommands[commandName];
  111. }
  112. if (this.permissionForInteractions === true) {
  113. this.isPermittedForInteractions = true;
  114. return;
  115. }
  116. // check permission at channel level
  117. if (Array.isArray(this.permissionForInteractions)) {
  118. this.isPermittedForInteractions = this.permissionForInteractions.includes(channelName);
  119. return;
  120. }
  121. // ex. search OR search:handlerName
  122. const commandRegExp = new RegExp(`(^${commandName}$)|(^${commandName}:\\w+)`);
  123. // skip this forEach loop if the requested command is not in permissionsForBroadcastUseCommands and permissionsForSingleUseCommandskey
  124. if (!commandRegExp.test(actionId) && !commandRegExp.test(callbackId)) {
  125. return;
  126. }
  127. if (!this.isPermittedForInteractions) {
  128. this.postNotAllowedMessage(relations, commandName, body);
  129. }
  130. });
  131. }
  132. }