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

Commit

Permalink
refactor the sentiment plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
AHaydar committed Jul 18, 2020
1 parent b19654d commit 79ba54d
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 100 deletions.
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.

168 changes: 91 additions & 77 deletions plugins/sentiment.js
Original file line number Diff line number Diff line change
@@ -1,97 +1,111 @@
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)

if (members.length !== 1) {
reject(`I don't know of a ${name}`)
} else {
resolve(members[0])
}
})
}
/**
* 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()

function pickTarget (bot, channel) {
return [...bot.channels, ...bot.groups].filter(c => c.id === channel)[0]
return members.filter(member => member.profile.display_name.toLowerCase() === name.toLowerCase())[0]
}

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
}
/**
* find the channel or group in the list of workspace channels/groups
* 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()

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 })
const lastTenMessagesByUser = messages.filter(message => message.user === user.id).slice(0, 10)

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}`))
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) {
console.error(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

0 comments on commit 79ba54d

Please sign in to comment.