slack.js 7.5 KB

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