Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to generate models per-user #74

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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 src/config.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,7 @@ module.exports = (hash) ->
ignoreMessageList: listSetting(hash, 'HUBOT_MARKOV_IGNORE_MESSAGE_LIST', [])
learningListenMode: stringSetting(hash, 'HUBOT_MARKOV_LEARNING_LISTEN_MODE', 'catch-all')
respondListenMode: stringSetting(hash, 'HUBOT_MARKOV_RESPOND_LISTEN_MODE', 'catch-all')
createUserModels: boolSetting(hash, 'HUBOT_MARKOV_CREATE_USER_MODELS', false)
userModelBlackList: listSetting(hash, 'HUBOT_MARKOV_USER_MODEL_BLACKLIST', [])
userModelWhiteList: listSetting(hash, 'HUBOT_MARKOV_USER_MODEL_WHITELIST', [])
}
144 changes: 81 additions & 63 deletions src/default-listeners.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -5,71 +5,96 @@

processors = require './processors'

module.exports = (robot, config) ->
activeModelNames = []
reportErr = (msg, err) ->
msg.send ":boom:\n```#{err.stack}```"

reportErr = (msg, err) ->
msg.send ":boom:\n```#{err.stack}```"
setupModelResponder = (robot, pattern, markovGeneratorFn) ->
robot.respond pattern, (msg) ->
markovGeneratorFn msg.match[2] or '', (err, text) ->
return reportErr(err) if err?
msg.send text

if config.defaultModel
robot.markov.createModel 'default_forward', {}
activeModelNames.push 'default_forward'
getModelGenerationCallback = (robot, config, modelName) ->
return (seed, callback) ->
robot.markov.modelNamed modelName, (model) ->
model.generate seed, config.generateMax, callback

robot.markov.generateForward = (seed, callback) ->
robot.markov.modelNamed 'default_forward', (model) ->
model.generate seed, config.generateMax, callback
generateDefaultModel = (robot, config) ->
robot.markov.createModel 'default_forward', {}
robot.markov.generateForward = getModelGenerationCallback(robot, config, 'default_forward')
# Generate markov chains on demand, optionally seeded by some initial state.
setupModelResponder robot, /markov(\s+(.+))?$/i, robot.markov.generateForward

# Generate markov chains on demand, optionally seeded by some initial state.
robot.respond /markov(\s+(.+))?$/i, (msg) ->
robot.markov.generateForward msg.match[2] or '', (err, text) ->
return reportErr(err) if err?
msg.send text
return 'default_forward'

if config.reverseModel
robot.markov.createModel 'default_reverse', {}, (model) ->
model.processWith processors.reverseWords
generateReverseModel = (robot, config) ->
robot.markov.createModel 'default_reverse', {}, (model) ->
model.processWith processors.reverseWords

robot.markov.generateReverse = getModelGenerationCallback(robot, config, 'default_reverse')

activeModelNames.push 'default_reverse'
# Generate reverse markov chains on demand, optionally seeded by some end state
setupModelResponder robot, /remarkov(\s+(.+))?$/i, robot.markov.generateReverse

return 'default_reverse'

generateMiddleModel = (robot, config) ->
robot.markov.generateMiddle = (seed, callback) ->
generateRight = getModelGenerationCallback(robot, config, 'default_forward')

generateRest = (right, cb) ->
words = processors.words.pre(right)
rightSeed = words.shift() or ''

robot.markov.generateReverse = (seed, callback) ->
robot.markov.modelNamed 'default_reverse', (model) ->
model.generate seed, config.generateMax, callback
model.generate rightSeed, config.generateMax, (err, left) ->
return cb(err) if err?
cb(null, [left, words...].join ' ')

# Generate reverse markov chains on demand, optionally seeded by some end state
robot.respond /remarkov(\s+(.+))?$/i, (msg) ->
robot.markov.generateReverse msg.match[2] or '', (err, text) ->
return reportErr(err) if err?
msg.send text
generateRight (err, right) ->
return callback(err) if err?
generateRest right, callback

if config.defaultModel and config.reverseModel
# Generate markov chains with the seed in the middle
setupModelResponder robot, /mmarkov(\s+(.+))?$/i, robot.markov.generateMiddle

robot.markov.generateMiddle = (seed, callback) ->
generateRight = (cb) ->
robot.markov.modelNamed 'default_forward', (model) ->
model.generate seed, config.generateMax, cb
return 'default_middle'

generateRest = (right, cb) ->
words = processors.words.pre(right)
rightSeed = words.shift() or ''
getUserModel = (username, config, robot)->
userModelName = 'user_' + username

robot.markov.modelNamed 'default_reverse', (model) ->
model.generate rightSeed, config.generateMax, (err, left) ->
return cb(err) if err?
cb(null, [left, words...].join ' ')
unless robot.markov.byName[userModelName]
robot.markov.createModel userModelName, {}
markovGenerateUser = getModelGenerationCallback(robot, config, userModelName)

generateRight (err, right) ->
return callback(err) if err?
generateRest right, callback
# Generate user markov chains on demand, optionally seeded by some end state
umarkovUserPattern = "umarkov " + username + "(\s+(.+))?$"
setupModelResponder robot, RegExp(umarkovUserPattern), markovGenerateUser

# Generate markov chains with the seed in the middle
robot.respond /mmarkov(\s+(.+))?$/i, (msg) ->
robot.markov.generateMiddle msg.match[2] or '', (err, text) ->
return reportErr(err) if err?
msg.send text
return userModelName

if activeModelNames.length isnt 0
attachRobotListener = (robot, listenMode, listener) ->
if listenMode == 'hear-all'
robot.hear /.*/i, listener
else if listenMode == 'catch-all'
robot.catchAll listener
else
robot.hear RegExp("" + listenMode), listener

learningListener = (msg) ->
module.exports = (robot, config) ->
activeModelNames = []

if config.defaultModel
activeModelNames.push generateDefaultModel(robot, config)

if config.reverseModel
activeModelNames.push generateReverseModel(robot, config)

if config.defaultModel and config.reverseModel
generateMiddleModel robot, config

if activeModelNames.length isnt 0 or config.createUserModels?
attachRobotListener robot, config.learningListenMode, (msg) ->
# Ignore empty messages
return if !msg.message.text

Expand All @@ -87,26 +112,19 @@ module.exports = (robot, config) ->
for name in activeModelNames
robot.markov.modelNamed name, (model) -> model.learn msg.message.text

if config.learningListenMode == 'hear-all'
robot.hear /.*/i, learningListener
else if config.learningListenMode == 'catch-all'
robot.catchAll learningListener
else
robot.hear RegExp("" + config.learningListenMode), learningListener
# add per-user models to the list as we see them
username = msg.envelope.user.name
if config.createUserModels and username not in config.userModelBlackList
if config.userModelWhiteList.length is 0 or config.userModelWhiteList[username]
userModelName = getUserModel username, config, robot
robot.markov.modelNamed userModelName, (model) -> model.learn msg.message.text

if config.respondChance > 0
respondListener = (msg) ->
attachRobotListener robot, config.respondListenMode, (msg) ->
if Math.random() < config.respondChance
randomWord = msg.random(processors.words.pre(msg.message.text)) or ''
randomWord = msg.random(processors.words.pre msg.message.text) or ''

if config.reverseModel
robot.markov.generateMiddle randomWord, (text) -> msg.send text
else
robot.markov.generateForward randomWord, (text) -> msg.send text

if config.respondListenMode == 'hear-all'
robot.hear /.*/i, respondListener
else if config.respondListenMode == 'catch-all'
robot.catchAll respondListener
else
robot.hear RegExp("" + config.respondListenMode), respondListener