Skip to content
This repository was archived by the owner on Aug 5, 2021. It is now read-only.

Commit 600ed01

Browse files
authored
Refactor the sentiment plugin to use the slack events api (#66)
* refactor the sentiment plugin
1 parent 1aae0b7 commit 600ed01

9 files changed

+517
-100
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ Start direct messages with people
6868
6969
incoming-webhook
7070
Post messages to specific channels in Slack
71+
72+
users: read
73+
View people in the workspace
7174
```
7275

7376
3. Install the app: [https://api.slack.com/start/building/bolt#install](https://api.slack.com/start/building/bolt#install)

config.js

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const config = {
4343
path.join('plugins', 'system.js'),
4444
path.join('plugins', 'wolframalpha.js'),
4545
path.join('plugins', 'spellcheck.js'),
46+
path.join('plugins', 'sentiment.js'),
4647
path.join('plugins', 'wikipedia.js'),
4748
path.join('plugins', 'lira.js')
4849
]

docs/sentiment_analysis.md

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
# sentiment_analysis
22

3-
Provides sentiment analysis over a user's last N-messages.
3+
Provides sentiment analysis over a user's last N-messages. The plugin uses
4+
[DatumBox's Sentiment Analysis API](http://www.datumbox.com/api-sandbox/#!/Document-Classification/SentimentAnalysis_post_0)
5+
and you must provide the API key in your `secret.json`:
6+
7+
```json
8+
{
9+
...
10+
"datumbox": "api-token-here"
11+
}
12+
```
413

514
### Example Usage
615

@@ -9,4 +18,5 @@ analyse omar
918
1019
#> Output
1120
omar has recently been positive
12-
```
21+
```
22+

package-lock.json

+12-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/sentiment.js

+98-77
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,118 @@
1-
const request = require('request')
21
const winston = require('winston')
2+
const fetch = require('node-fetch')
3+
const { URLSearchParams } = require('url')
4+
const match = require('@menadevs/objectron')
5+
const { pre } = require('../utils.js')
6+
const secret = require('../secret.json')
37

4-
const Plugin = require('../utils.js').Plugin
8+
const verbose = `
9+
How to use this pluginL
510
6-
const META = {
7-
name: 'sentiment',
8-
short: 'provides a sentiment analysis on the last 10 messages of a user',
9-
examples: [
10-
'analyse jordan'
11-
]
12-
}
11+
analyse jordan
12+
`
1313

14-
function findUser (bot, name) {
15-
return new Promise((resolve, reject) => {
16-
const members = bot.users.filter(m => m.name === name)
14+
/**
15+
* find the user based on the passed name and return the first one found
16+
* returns undefined if no user was found
17+
* @param {*} users
18+
* @param {*} name
19+
*/
20+
async function findUser (users, name) {
21+
const { members } = await users.list()
1722

18-
if (members.length !== 1) {
19-
reject(`I don't know of a ${name}`)
20-
} else {
21-
resolve(members[0])
22-
}
23-
})
23+
return members.filter(member => member.profile.display_name.toLowerCase() === name.toLowerCase())[0]
2424
}
2525

26-
function pickTarget (bot, channel) {
27-
return [...bot.channels, ...bot.groups].filter(c => c.id === channel)[0]
28-
}
26+
/**
27+
* find the channel or group in the list of workspace channels/groups
28+
* returns undefined if the channel/group didn't match
29+
* @param {*} conversations (could be a channel, a group or an im)
30+
* @param {*} channel
31+
*/
32+
async function getTargetChannel (conversations, channel) {
33+
const { channels } = await conversations.list()
2934

30-
function loadRecentMessages (options, channel, user) {
31-
return new Promise((resolve, reject) => {
32-
const target = pickTarget(options.bot, channel)
33-
let source = options.web.channels
34-
35-
if (target.is_group) {
36-
source = options.web.groups
37-
}
38-
39-
source.history(channel, { count: 1000 }, (error, response) => {
40-
if (error) {
41-
reject(error)
42-
} else {
43-
let messages = response.messages.filter(m => m.user === user.id)
35+
return channels.filter(c => c.id === channel && c.is_archived === false)[0]
36+
}
4437

45-
if (messages.length > options.config.plugins.sentiment.recent) {
46-
messages = messages.slice(1, options.config.plugins.sentiment.recent)
47-
}
38+
/**
39+
* get the user messages in the last 100 messages in the provided channel
40+
* @param {*} channel
41+
* @param {*} user
42+
*/
43+
async function getUserMessagesInChannel (conversations, channel, user) {
44+
const { messages } = await conversations.history({ channel: channel.id, limit: 1000 })
45+
// fetching first 10 messages from the list of messages
46+
const lastTenMessagesByUser = messages.filter(message => message.user === user.id).slice(0, 10)
4847

49-
if (messages.length === 0) {
50-
reject('User has not spoken recently')
51-
} else {
52-
const text = messages.map(m => m.text).join('\n')
53-
resolve(text)
54-
}
55-
}
56-
})
57-
})
48+
return lastTenMessagesByUser
5849
}
5950

60-
function analyseSentiment (secret, messages) {
61-
return new Promise((resolve, reject) => {
62-
request.post({
63-
url: 'http://api.datumbox.com/1.0/SentimentAnalysis.json',
64-
form: {
65-
api_key: secret.datumbox,
66-
text: messages
67-
},
68-
headers: {
69-
'User-Agent': 'request'
70-
}
71-
}, (error, response, body) => {
72-
if (error) {
73-
reject(error)
74-
} else {
75-
resolve(JSON.parse(body))
76-
}
77-
})
78-
})
51+
/**
52+
* send the messages to the api and return response
53+
* @param {*} secret
54+
* @param {*} messages
55+
*/
56+
async function analyseSentiment (messages) {
57+
const params = new URLSearchParams()
58+
params.set('api_key', secret.datumbox)
59+
params.append('text', messages)
60+
const response = await fetch('http://api.datumbox.com/1.0/SentimentAnalysis.json', { method: 'POST', body: params })
61+
const jsonResult = await response.json()
62+
return jsonResult
7963
}
8064

81-
function analyse (options, message, target) {
82-
findUser(options.bot, target)
83-
.then(user => loadRecentMessages(options, message.channel, user))
84-
.then(messages => analyseSentiment(options.secret, messages))
85-
.then(sentiment => message.reply_thread(`${target} has recently been ${sentiment.output.result}`))
86-
.catch(error => winston.error(`${META.name} - Error: ${error}`))
65+
/**
66+
* analyse the user messages in a channel or group
67+
* @param {*} options
68+
* @param {*} message
69+
* @param {*} target
70+
*/
71+
async function analyse (options, message, target) {
72+
try {
73+
const user = await findUser(options.web.users, target)
74+
if (!user) {
75+
message.reply_thread(`I don't know of a ${target}. Please validate you entered the correct person's name.`)
76+
return
77+
}
78+
79+
const targetChannel = await getTargetChannel(options.web.conversations, message.channel)
80+
if (!targetChannel) {
81+
message.reply_thread('Are you in a channel or group? sentiment doesn\'t work in a direct message.')
82+
return
83+
}
84+
85+
const messages = await getUserMessagesInChannel(options.web.conversations, targetChannel, user)
86+
if (messages.length !== 0) {
87+
const response = await analyseSentiment(messages.map(m => m.text).join('\n'))
88+
message.reply_thread(`${target} has recently been ${response.output.result}.`)
89+
} else {
90+
message.reply_thread(`User ${target} has not spoken recently.`)
91+
}
92+
} catch (error) {
93+
message.reply_thread(`Something went wrong! this has nothing to do with the sentiments of ${target}. Please check the logs.`)
94+
options.logger.error(`${module.exports.name} - something went wrong. Here's the error: ${pre(error)}`)
95+
}
8796
}
8897

89-
function register (bot, rtm, web, config, secret) {
90-
const plugin = new Plugin({ bot, rtm, web, config, secret })
91-
plugin.route(/^analyse (.+)/, analyse, {})
98+
const events = {
99+
message: (options, message) => {
100+
match(message, {
101+
type: 'message',
102+
text: /^analyse (?<name>.+)/
103+
}, result => analyse(options, message, result.groups.name))
104+
}
92105
}
93106

94107
module.exports = {
95-
register,
96-
META
108+
name: 'sentiment',
109+
help: 'provides a sentiment analysis on the last 10 messages of a user',
110+
verbose,
111+
events,
112+
findUser,
113+
getTargetChannel,
114+
getUserMessagesInChannel,
115+
analyseSentiment,
116+
analyse
97117
}
118+

plugins/spellcheck.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const events = {
3232

3333
module.exports = {
3434
name: 'spellcheck',
35-
help: 'see if the bot is alive, or ask it to ping others',
35+
help: 'spell checks any word in a sentence',
3636
verbose,
3737
events,
3838
spell

plugins/system.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const cp = require('child_process')
22
const config = require('../config')
3-
const pre = require('../utils.js').pre
3+
const { pre } = require('../utils.js')
44
const storage = require('node-persist')
55
const match = require('@menadevs/objectron')
66

0 commit comments

Comments
 (0)