RelationsService.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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 isPermissionsForSingleUseCommands(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. const permission = relation.permissionsForSingleUseCommands[growiCommandType];
  62. if (permission == null) {
  63. return false;
  64. }
  65. if (Array.isArray(permission)) {
  66. return permission.includes(channelName);
  67. }
  68. return permission;
  69. }
  70. async isPermissionsUseBroadcastCommands(relation:RelationMock, growiCommandType:string, channelName:string, baseDate:Date):Promise<boolean> {
  71. const syncedRelation = await this.syncRelation(relation, baseDate);
  72. if (syncedRelation == null) {
  73. return false;
  74. }
  75. const permission = relation.permissionsForBroadcastUseCommands[growiCommandType];
  76. if (permission == null) {
  77. return false;
  78. }
  79. if (Array.isArray(permission)) {
  80. return permission.includes(channelName);
  81. }
  82. return permission;
  83. }
  84. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  85. postNotAllowedMessage(relations:RelationMock[], commandName:string, body:any):Promise<ChatPostEphemeralResponse> {
  86. const botToken = relations[0].installation?.data.bot?.token;
  87. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  88. const client = generateWebClient(botToken!);
  89. return client.chat.postEphemeral({
  90. text: 'Error occured.',
  91. channel: body.channel_id,
  92. user: body.user_id,
  93. blocks: [
  94. markdownSectionBlock(`It is not allowed to run *'${commandName}'* command to this GROWI.`),
  95. ],
  96. });
  97. }
  98. async checkPermissionForInteractions(
  99. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  100. relation:RelationMock, channelName:string, callbackId:string, actionId:string, body:any, relations:RelationMock[],
  101. ):Promise<void> {
  102. const baseDate = new Date();
  103. const syncedRelation = await this.syncRelation(relation, baseDate);
  104. let isPermittedForInteractions = false;
  105. if (syncedRelation == null) {
  106. isPermittedForInteractions = false;
  107. return;
  108. }
  109. const singleUse = Object.keys(relation.permissionsForSingleUseCommands);
  110. const broadCastUse = Object.keys(relation.permissionsForBroadcastUseCommands);
  111. let permissionForInteractions:boolean|string[];
  112. [...singleUse, ...broadCastUse].forEach(async(commandName) => {
  113. // case: singleUse
  114. permissionForInteractions = relation.permissionsForSingleUseCommands[commandName];
  115. // case: broadcastUse
  116. if (permissionForInteractions == null) {
  117. permissionForInteractions = relation.permissionsForBroadcastUseCommands[commandName];
  118. }
  119. if (permissionForInteractions === true) {
  120. isPermittedForInteractions = true;
  121. return;
  122. }
  123. // check permission at channel level
  124. if (Array.isArray(permissionForInteractions)) {
  125. isPermittedForInteractions = permissionForInteractions.includes(channelName);
  126. return;
  127. }
  128. // ex. search OR search:handlerName
  129. const commandRegExp = new RegExp(`(^${commandName}$)|(^${commandName}:\\w+)`);
  130. // skip this forEach loop if the requested command is not in permissionsForBroadcastUseCommands and permissionsForSingleUseCommands
  131. if (!commandRegExp.test(actionId) && !commandRegExp.test(callbackId)) {
  132. return;
  133. }
  134. if (!isPermittedForInteractions) {
  135. this.postNotAllowedMessage(relations, commandName, body);
  136. }
  137. });
  138. }
  139. }