diff --git a/config.json.example b/config.json.example index 2fd677c7d4..5b03d26791 100644 --- a/config.json.example +++ b/config.json.example @@ -74,5 +74,9 @@ "APP_ID": "appId", "KEY": "key", "SECRET": "secret" + }, + "SLACK": { + "FLAGGING_URL": "https://hooks.slack.com/services/id/id/id", + "FLAGGING_FOOTER_LINK": "https://crookedneighbor.github.io/flag-o-rama/" } } diff --git a/package.json b/package.json index 71da316005..046b6c81b4 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "version": "3.37.0", "main": "./website/server/index.js", "dependencies": { + "@slack/client": "slackhq/node-slack-sdk#2ee794cd31326c54f38c518eef2b9d223327d939", "accepts": "^1.3.2", "amazon-payments": "0.0.4", "amplitude": "^2.0.3", @@ -89,12 +90,12 @@ "superagent": "^1.8.3", "swagger-node-express": "lefnire/swagger-node-express#habitrpg", "universal-analytics": "~0.3.2", + "useragent": "2.1.9", "uuid": "^2.0.1", "validator": "^4.9.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", - "winston": "^2.1.0", - "useragent": "2.1.9" + "winston": "^2.1.0" }, "private": true, "engines": { diff --git a/test/api/v3/unit/libs/slack.js b/test/api/v3/unit/libs/slack.js new file mode 100644 index 0000000000..b3d579349a --- /dev/null +++ b/test/api/v3/unit/libs/slack.js @@ -0,0 +1,97 @@ +/* eslint-disable camelcase */ +import { IncomingWebhook } from '@slack/client'; +import slack from '../../../../../website/server/libs/slack'; +import { TAVERN_ID } from '../../../../../website/server/models/group'; + +describe('slack', () => { + describe('sendFlagNotification', () => { + let flagger, group, message; + + beforeEach(() => { + sandbox.stub(IncomingWebhook.prototype, 'send'); + flagger = { + id: 'flagger-id', + profile: { + name: 'flagger', + }, + }; + group = { + id: 'group-id', + privacy: 'private', + name: 'Some group', + type: 'guild', + }; + message = { + id: 'chat-id', + user: 'Author', + uuid: 'author-id', + text: 'some text', + }; + }); + + afterEach(() => { + IncomingWebhook.prototype.send.restore(); + }); + + it('sends a slack webhook', () => { + slack.sendFlagNotification({ + flagger, + group, + message, + }); + + expect(IncomingWebhook.prototype.send).to.be.calledOnce; + expect(IncomingWebhook.prototype.send).to.be.calledWith({ + text: 'flagger (flagger-id) flagged a message', + attachments: [{ + fallback: 'Flag Message', + color: 'danger', + author_name: 'Author - author-id', + title: 'Flag in Some group - (private guild)', + title_link: undefined, + text: 'some text', + footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/), + mrkdwn_in: [ + 'text', + ], + }], + }); + }); + + it('includes a title link if guild is public', () => { + group.privacy = 'public'; + + slack.sendFlagNotification({ + flagger, + group, + message, + }); + + expect(IncomingWebhook.prototype.send).to.be.calledWithMatch({ + attachments: [sandbox.match({ + title: 'Flag in Some group', + title_link: sandbox.match(/.*\/#\/options\/groups\/guilds\/group-id/), + })], + }); + }); + + it('links to tavern', () => { + group.privacy = 'public'; + group.name = 'Tavern'; + group.id = TAVERN_ID; + + slack.sendFlagNotification({ + flagger, + group, + message, + }); + + expect(IncomingWebhook.prototype.send).to.be.calledWithMatch({ + attachments: [sandbox.match({ + title: 'Flag in Tavern', + title_link: sandbox.match(/.*\/#\/options\/groups\/tavern/), + })], + }); + }); + }); +}); diff --git a/website/server/controllers/api-v3/chat.js b/website/server/controllers/api-v3/chat.js index ea74d4238e..4dded03e9b 100644 --- a/website/server/controllers/api-v3/chat.js +++ b/website/server/controllers/api-v3/chat.js @@ -8,6 +8,7 @@ import { import _ from 'lodash'; import { removeFromArray } from '../../libs/collectionManipulators'; import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/email'; +import slack from '../../libs/slack'; import pusher from '../../libs/pusher'; import nconf from 'nconf'; import Bluebird from 'bluebird'; @@ -265,6 +266,12 @@ api.flagChat = { {name: 'GROUP_URL', content: groupUrl}, ]); + slack.sendFlagNotification({ + flagger: user, + group, + message, + }); + res.respond(200, message); }, }; diff --git a/website/server/libs/slack.js b/website/server/libs/slack.js new file mode 100644 index 0000000000..e67ab26030 --- /dev/null +++ b/website/server/libs/slack.js @@ -0,0 +1,48 @@ +/* eslint-disable camelcase */ +import { IncomingWebhook } from '@slack/client'; +import { TAVERN_ID } from '../models/group'; +import nconf from 'nconf'; + +const SLACK_FLAGGING_URL = nconf.get('SLACK:FLAGGING_URL'); +const SLACK_FLAGGING_FOOTER_LINK = nconf.get('SLACK:FLAGGING_FOOTER_LINK'); +const BASE_URL = nconf.get('BASE_URL'); + +let flagSlack = new IncomingWebhook(SLACK_FLAGGING_URL); + +function sendFlagNotification ({ + flagger, + group, + message, +}) { + let titleLink; + let title = `Flag in ${group.name}`; + let text = `${flagger.profile.name} (${flagger.id}) flagged a message`; + + if (group.id === TAVERN_ID) { + titleLink = `${BASE_URL}/#/options/groups/tavern`; + } else if (group.privacy === 'public') { + titleLink = `${BASE_URL}/#/options/groups/guilds/${group.id}`; + } else { + title += ` - (${group.privacy} ${group.type})`; + } + + flagSlack.send({ + text, + attachments: [{ + fallback: 'Flag Message', + color: 'danger', + author_name: `${message.user} - ${message.uuid}`, + title, + title_link: titleLink, + text: message.text, + footer: `<${SLACK_FLAGGING_FOOTER_LINK}?groupId=${group.id}&chatId=${message.id}|Flag this message>`, + mrkdwn_in: [ + 'text', + ], + }], + }); +} + +module.exports = { + sendFlagNotification, +};