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

Refactor the sentiment plugin to use the slack events api #66

Merged
merged 3 commits into from
Jul 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ Start direct messages with people

incoming-webhook
Post messages to specific channels in Slack

users: read
View people in the workspace
```

3. Install the app: [https://api.slack.com/start/building/bolt#install](https://api.slack.com/start/building/bolt#install)
Expand Down
1 change: 1 addition & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const config = {
path.join('plugins', 'system.js'),
path.join('plugins', 'wolframalpha.js'),
path.join('plugins', 'spellcheck.js'),
path.join('plugins', 'sentiment.js'),
path.join('plugins', 'wikipedia.js'),
path.join('plugins', 'lira.js')
]
Expand Down
14 changes: 12 additions & 2 deletions docs/sentiment_analysis.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# sentiment_analysis

Provides sentiment analysis over a user's last N-messages.
Provides sentiment analysis over a user's last N-messages. The plugin uses
[DatumBox's Sentiment Analysis API](http://www.datumbox.com/api-sandbox/#!/Document-Classification/SentimentAnalysis_post_0)
and you must provide the API key in your `secret.json`:

```json
{
...
"datumbox": "api-token-here"
}
```

### Example Usage

Expand All @@ -9,4 +18,5 @@ analyse omar

#> Output
omar has recently been positive
```
```

22 changes: 12 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

175 changes: 98 additions & 77 deletions plugins/sentiment.js
Original file line number Diff line number Diff line change
@@ -1,97 +1,118 @@
const request = require('request')
const winston = require('winston')
const fetch = require('node-fetch')
const { URLSearchParams } = require('url')
const match = require('@menadevs/objectron')
const { pre } = require('../utils.js')
const secret = require('../secret.json')

const Plugin = require('../utils.js').Plugin
const verbose = `
How to use this pluginL

const META = {
name: 'sentiment',
short: 'provides a sentiment analysis on the last 10 messages of a user',
examples: [
'analyse jordan'
]
}
analyse jordan
`

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

if (members.length !== 1) {
reject(`I don't know of a ${name}`)
} else {
resolve(members[0])
}
})
return members.filter(member => member.profile.display_name.toLowerCase() === name.toLowerCase())[0]
}

function pickTarget (bot, channel) {
return [...bot.channels, ...bot.groups].filter(c => c.id === channel)[0]
}
/**
* find the channel or group in the list of workspace channels/groups
Link- marked this conversation as resolved.
Show resolved Hide resolved
* returns undefined if the channel/group didn't match
* @param {*} conversations (could be a channel, a group or an im)
* @param {*} channel
*/
async function getTargetChannel (conversations, channel) {
const { channels } = await conversations.list()

function loadRecentMessages (options, channel, user) {
return new Promise((resolve, reject) => {
const target = pickTarget(options.bot, channel)
let source = options.web.channels

if (target.is_group) {
source = options.web.groups
}

source.history(channel, { count: 1000 }, (error, response) => {
if (error) {
reject(error)
} else {
let messages = response.messages.filter(m => m.user === user.id)
return channels.filter(c => c.id === channel && c.is_archived === false)[0]
}

if (messages.length > options.config.plugins.sentiment.recent) {
messages = messages.slice(1, options.config.plugins.sentiment.recent)
}
/**
* get the user messages in the last 100 messages in the provided channel
* @param {*} channel
* @param {*} user
*/
async function getUserMessagesInChannel (conversations, channel, user) {
const { messages } = await conversations.history({ channel: channel.id, limit: 1000 })
// fetching first 10 messages from the list of messages
const lastTenMessagesByUser = messages.filter(message => message.user === user.id).slice(0, 10)
Link- marked this conversation as resolved.
Show resolved Hide resolved

if (messages.length === 0) {
reject('User has not spoken recently')
} else {
const text = messages.map(m => m.text).join('\n')
resolve(text)
}
}
})
})
return lastTenMessagesByUser
}

function analyseSentiment (secret, messages) {
return new Promise((resolve, reject) => {
request.post({
url: 'http://api.datumbox.com/1.0/SentimentAnalysis.json',
form: {
api_key: secret.datumbox,
text: messages
},
headers: {
'User-Agent': 'request'
}
}, (error, response, body) => {
if (error) {
reject(error)
} else {
resolve(JSON.parse(body))
}
})
})
/**
* send the messages to the api and return response
* @param {*} secret
* @param {*} messages
*/
async function analyseSentiment (messages) {
const params = new URLSearchParams()
params.set('api_key', secret.datumbox)
params.append('text', messages)
const response = await fetch('http://api.datumbox.com/1.0/SentimentAnalysis.json', { method: 'POST', body: params })
const jsonResult = await response.json()
return jsonResult
}

function analyse (options, message, target) {
findUser(options.bot, target)
.then(user => loadRecentMessages(options, message.channel, user))
.then(messages => analyseSentiment(options.secret, messages))
.then(sentiment => message.reply_thread(`${target} has recently been ${sentiment.output.result}`))
.catch(error => winston.error(`${META.name} - Error: ${error}`))
/**
* analyse the user messages in a channel or group
* @param {*} options
* @param {*} message
* @param {*} target
*/
async function analyse (options, message, target) {
try {
const user = await findUser(options.web.users, target)
if (!user) {
message.reply_thread(`I don't know of a ${target}. Please validate you entered the correct person's name.`)
return
}

const targetChannel = await getTargetChannel(options.web.conversations, message.channel)
if (!targetChannel) {
message.reply_thread('Are you in a channel or group? sentiment doesn\'t work in a direct message.')
return
}

const messages = await getUserMessagesInChannel(options.web.conversations, targetChannel, user)
if (messages.length !== 0) {
const response = await analyseSentiment(messages.map(m => m.text).join('\n'))
message.reply_thread(`${target} has recently been ${response.output.result}.`)
} else {
message.reply_thread(`User ${target} has not spoken recently.`)
}
} catch (error) {
message.reply_thread(`Something went wrong! this has nothing to do with the sentiments of ${target}. Please check the logs.`)
options.logger.error(`${module.exports.name} - something went wrong. Here's the error: ${pre(error)}`)
}
}

function register (bot, rtm, web, config, secret) {
const plugin = new Plugin({ bot, rtm, web, config, secret })
plugin.route(/^analyse (.+)/, analyse, {})
const events = {
message: (options, message) => {
match(message, {
type: 'message',
text: /^analyse (?<name>.+)/
}, result => analyse(options, message, result.groups.name))
}
}

module.exports = {
register,
META
name: 'sentiment',
help: 'provides a sentiment analysis on the last 10 messages of a user',
verbose,
events,
findUser,
getTargetChannel,
getUserMessagesInChannel,
analyseSentiment,
analyse
}

2 changes: 1 addition & 1 deletion plugins/spellcheck.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const events = {

module.exports = {
name: 'spellcheck',
help: 'see if the bot is alive, or ask it to ping others',
help: 'spell checks any word in a sentence',
verbose,
events,
spell
Expand Down
2 changes: 1 addition & 1 deletion plugins/system.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const cp = require('child_process')
const config = require('../config')
const pre = require('../utils.js').pre
const { pre } = require('../utils.js')
const storage = require('node-persist')
const match = require('@menadevs/objectron')

Expand Down
Loading