slack.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. const debug = require('debug')('growi:util:slack');
  2. const urljoin = require('url-join');
  3. /**
  4. * slack
  5. */
  6. /* eslint-disable no-use-before-define */
  7. module.exports = function(crowi) {
  8. const Slack = require('slack-node');
  9. const { configManager } = crowi;
  10. const slack = {};
  11. const postWithIwh = function(messageObj) {
  12. return new Promise((resolve, reject) => {
  13. const client = new Slack();
  14. client.setWebhook(configManager.getConfig('notification', 'slack:incomingWebhookUrl'));
  15. client.webhook(messageObj, (err, res) => {
  16. if (err) {
  17. debug('Post error', err, res);
  18. debug('Sent data to slack is:', messageObj);
  19. return reject(err);
  20. }
  21. resolve(res);
  22. });
  23. });
  24. };
  25. const postWithWebApi = function(messageObj) {
  26. return new Promise((resolve, reject) => {
  27. const client = new Slack(configManager.getConfig('notification', 'slack:token'));
  28. // stringify attachments
  29. if (messageObj.attachments != null) {
  30. messageObj.attachments = JSON.stringify(messageObj.attachments);
  31. }
  32. client.api('chat.postMessage', messageObj, (err, res) => {
  33. if (err) {
  34. debug('Post error', err, res);
  35. debug('Sent data to slack is:', messageObj);
  36. return reject(err);
  37. }
  38. resolve(res);
  39. });
  40. });
  41. };
  42. const convertMarkdownToMarkdown = function(body) {
  43. const url = crowi.appService.getSiteUrl();
  44. return body
  45. .replace(/\n\*\s(.+)/g, '\n• $1')
  46. .replace(/#{1,}\s?(.+)/g, '\n*$1*')
  47. .replace(/(\[(.+)\]\((https?:\/\/.+)\))/g, '<$3|$2>')
  48. .replace(/(\[(.+)\]\((\/.+)\))/g, `<${url}$3|$2>`);
  49. };
  50. const prepareAttachmentTextForCreate = function(page, user) {
  51. let body = page.revision.body;
  52. if (body.length > 2000) {
  53. body = `${body.substr(0, 2000)}...`;
  54. }
  55. return convertMarkdownToMarkdown(body);
  56. };
  57. const prepareAttachmentTextForUpdate = function(page, user, previousRevision) {
  58. const diff = require('diff');
  59. let diffText = '';
  60. diff.diffLines(previousRevision.body, page.revision.body).forEach((line) => {
  61. debug('diff line', line);
  62. const value = line.value.replace(/\r\n|\r/g, '\n'); // eslint-disable-line no-unused-vars
  63. if (line.added) {
  64. diffText += `${line.value} ... :lower_left_fountain_pen:`;
  65. }
  66. else if (line.removed) {
  67. // diffText += '-' + line.value.replace(/(.+)?\n/g, '- $1\n');
  68. // 1以下は無視
  69. if (line.count > 1) {
  70. diffText += `:wastebasket: ... ${line.count} lines\n`;
  71. }
  72. }
  73. else {
  74. // diffText += '...\n';
  75. }
  76. });
  77. debug('diff is', diffText);
  78. return diffText;
  79. };
  80. const prepareAttachmentTextForComment = function(comment) {
  81. let body = comment.comment;
  82. if (body.length > 2000) {
  83. body = `${body.substr(0, 2000)}...`;
  84. }
  85. if (comment.isMarkdown) {
  86. return convertMarkdownToMarkdown(body);
  87. }
  88. return body;
  89. };
  90. const prepareSlackMessageForPage = function(page, user, channel, updateType, previousRevision) {
  91. const appTitle = crowi.appService.getAppTitle();
  92. const url = crowi.appService.getSiteUrl();
  93. let body = page.revision.body;
  94. if (updateType === 'create') {
  95. body = prepareAttachmentTextForCreate(page, user);
  96. }
  97. else {
  98. body = prepareAttachmentTextForUpdate(page, user, previousRevision);
  99. }
  100. const attachment = {
  101. color: '#263a3c',
  102. author_name: `@${user.username}`,
  103. author_link: urljoin(url, 'user', user.username),
  104. author_icon: user.image,
  105. title: page.path,
  106. title_link: urljoin(url, page.id),
  107. text: body,
  108. mrkdwn_in: ['text'],
  109. };
  110. if (user.image) {
  111. attachment.author_icon = user.image;
  112. }
  113. const message = {
  114. channel: `#${channel}`,
  115. username: appTitle,
  116. text: getSlackMessageTextForPage(page.path, page.id, user, updateType),
  117. attachments: [attachment],
  118. };
  119. return message;
  120. };
  121. const prepareSlackMessageForComment = function(comment, user, channel, path) {
  122. const appTitle = crowi.appService.getAppTitle();
  123. const url = crowi.appService.getSiteUrl();
  124. const body = prepareAttachmentTextForComment(comment);
  125. const attachment = {
  126. color: '#263a3c',
  127. author_name: `@${user.username}`,
  128. author_link: urljoin(url, 'user', user.username),
  129. author_icon: user.image,
  130. text: body,
  131. mrkdwn_in: ['text'],
  132. };
  133. if (user.image) {
  134. attachment.author_icon = user.image;
  135. }
  136. const message = {
  137. channel: `#${channel}`,
  138. username: appTitle,
  139. text: getSlackMessageTextForComment(path, String(comment.page), user),
  140. attachments: [attachment],
  141. };
  142. return message;
  143. };
  144. /**
  145. * For GlobalNotification
  146. *
  147. * @param {string} messageBody
  148. * @param {string} attachmentBody
  149. * @param {string} slackChannel
  150. */
  151. const prepareSlackMessageForGlobalNotification = async(messageBody, attachmentBody, slackChannel) => {
  152. const appTitle = crowi.appService.getAppTitle();
  153. const attachment = {
  154. color: '#263a3c',
  155. text: attachmentBody,
  156. mrkdwn_in: ['text'],
  157. };
  158. const message = {
  159. channel: `#${slackChannel}`,
  160. username: appTitle,
  161. text: messageBody,
  162. attachments: [attachment],
  163. };
  164. return message;
  165. };
  166. const getSlackMessageTextForPage = function(path, pageId, user, updateType) {
  167. let text;
  168. const url = crowi.appService.getSiteUrl();
  169. const pageUrl = `<${urljoin(url, pageId)}|${path}>`;
  170. if (updateType === 'create') {
  171. text = `:rocket: ${user.username} created a new page! ${pageUrl}`;
  172. }
  173. else {
  174. text = `:heavy_check_mark: ${user.username} updated ${pageUrl}`;
  175. }
  176. return text;
  177. };
  178. const getSlackMessageTextForComment = function(path, pageId, user) {
  179. const url = crowi.appService.getSiteUrl();
  180. const pageUrl = `<${urljoin(url, pageId)}|${path}>`;
  181. const text = `:speech_balloon: ${user.username} commented on ${pageUrl}`;
  182. return text;
  183. };
  184. // slack.post = function (channel, message, opts) {
  185. slack.postPage = (page, user, channel, updateType, previousRevision) => {
  186. const messageObj = prepareSlackMessageForPage(page, user, channel, updateType, previousRevision);
  187. return slackPost(messageObj);
  188. };
  189. slack.postComment = (comment, user, channel, path) => {
  190. const messageObj = prepareSlackMessageForComment(comment, user, channel, path);
  191. return slackPost(messageObj);
  192. };
  193. slack.sendGlobalNotification = async(messageBody, attachmentBody, slackChannel) => {
  194. const messageObj = await prepareSlackMessageForGlobalNotification(messageBody, attachmentBody, slackChannel);
  195. return slackPost(messageObj);
  196. };
  197. const slackPost = (messageObj) => {
  198. // when incoming Webhooks is prioritized
  199. if (configManager.getConfig('notification', 'slack:isIncomingWebhookPrioritized')) {
  200. if (configManager.getConfig('notification', 'slack:incomingWebhookUrl')) {
  201. debug('posting message with IncomingWebhook');
  202. return postWithIwh(messageObj);
  203. }
  204. if (configManager.getConfig('notification', 'slack:token')) {
  205. debug('posting message with Web API');
  206. return postWithWebApi(messageObj);
  207. }
  208. }
  209. // else
  210. else {
  211. if (configManager.getConfig('notification', 'slack:token')) {
  212. debug('posting message with Web API');
  213. return postWithWebApi(messageObj);
  214. }
  215. if (configManager.getConfig('notification', 'slack:incomingWebhookUrl')) {
  216. debug('posting message with IncomingWebhook');
  217. return postWithIwh(messageObj);
  218. }
  219. }
  220. };
  221. return slack;
  222. };