<commit_before>_ = require 'lodash'
Q              = require 'q'
Fs             = require 'fs'
Log            = require 'log'
Path           = require 'path'
HttpClient     = require 'scoped-http-client'
{EventEmitter} = require 'events'

User = require './user'
RobotSegment = require './robot-segment'
Response = require './response'
{Listener,TextListener} = require './listener'
{EnterMessage,LeaveMessage,TopicMessage,CatchAllMessage,TextMessage} = require './message'

BROBBOT_DEFAULT_ADAPTERS = [
  'campfire'
  'shell'
]

BROBBOT_DEFAULT_BRAINS = [
  'dumb'
]

BROBBOT_DOCUMENTATION_SECTIONS = [
  'description'
  'dependencies'
  'configuration'
  'commands'
  'notes'
  'author'
  'authors'
  'examples'
  'tags'
  'urls'
]

class Robot
  # Robots receive messages from a chat source (Campfire, irc, etc), and
  # dispatch them to matching listeners.
  #
  # scripts     - An array of modules to load
  # adapterPath - A String of the path to local adapters.
  # adapter     - A String of the adapter name.
  # brainPath - A String of the path to local brains.
  # brain     - A String of the brain name.
  # httpd       - A Boolean whether to enable the HTTP daemon.
  # name        - A String of the robot name, defaults to Brobbot.
  #
  # Returns nothing.
  constructor: (scripts, adapterPath, adapter, brainPath, brain, httpd, name = 'Brobbot') ->
    @name      = name
    @nameRegex = new RegExp "^\\s*#{name}:?\\s+", 'i'
    @events    = new EventEmitter
    @alias     = false
    @adapter   = null
    @Response  = Response
    @commands  = []
    @listeners = []
    @respondListeners = []
    #TODO namespaced logger per-script
    @logger    = new Log process.env.BROBBOT_LOG_LEVEL or 'info'
    @pingIntervalId = null

    @parseVersion()
    if httpd
      @setupExpress()
    else
      @setupNullRouter()

    @brainReady = @loadBrain brainPath, brain
    @scriptsReady = @loadScripts scripts
    @adapterReady = @loadAdapter adapterPath, adapter

    @ready = Q.all [@brainReady, @scriptsReady]
    @connected = @adapterReady

    @adapterName = adapter
    @errorHandlers = []

    @on 'error', (err, msg) =>
      @invokeErrorHandlers(err, msg)
    process.on 'uncaughtException', (err) =>
      @logger.error err.stack
      @emit 'error', err


  # Public: Adds a Listener that attempts to match incoming messages based on
  # a Regex.
  #
  # regex    - A Regex that determines if the callback should be called.
  # callback - A Function that is called with a Response object.
  #
  # Returns nothing.
  hear: (regex, callback) ->
    @listeners.push new TextListener(@, regex, callback)

  # Public: Adds a Listener that attempts to match incoming messages directed
  # at the robot based on a Regex. All regexes treat patterns like they begin
  # with a '^'
  #
  # regex    - A Regex that determines if the callback should be called.
  # callback - A Function that is called with a Response object.
  #
  # Returns nothing.
  respond: (regex, callback) ->
    @respondListeners.push new TextListener(@, regex, callback)

  # Public: Adds a Listener that triggers when anyone enters the room.
  #
  # callback - A Function that is called with a Response object.
  #
  # Returns nothing.
  enter: (callback) ->
    @listeners.push new Listener(
      @,
      ((msg) -> msg instanceof EnterMessage),
      callback
    )

  # Public: Adds a Listener that triggers when anyone leaves the room.
  #
  # callback - A Function that is called with a Response object.
  #
  # Returns nothing.
  leave: (callback) ->
    @listeners.push new Listener(
      @,
      ((msg) -> msg instanceof LeaveMessage),
      callback
    )

  # Public: Adds a Listener that triggers when anyone changes the topic.
  #
  # callback - A Function that is called with a Response object.
  #
  # Returns nothing.
  topic: (callback) ->
    @listeners.push new Listener(
      @,
      ((msg) -> msg instanceof TopicMessage),
      callback
    )

  # Public: Adds an error handler when an uncaught exception or user emitted
  # error event occurs.
  #
  # callback - A Function that is called with the error object.
  #
  # Returns nothing.
  error: (callback) ->
    @errorHandlers.push callback

  # Calls and passes any registered error handlers for unhandled exceptions or
  # user emitted error events.
  #
  # err - An Error object.
  # msg - An optional Response object that generated the error
  #
  # Returns nothing.
  invokeErrorHandlers: (err, msg) ->
    @logger.error err.stack
    for errorHandler in @errorHandlers
     try
       errorHandler(err, msg)
     catch errErr
       @logger.error "while invoking error handler: #{errErr}\n#{errErr.stack}"

  # Public: Adds a Listener that triggers when no other text matchers match.
  #
  # callback - A Function that is called with a Response object.
  #
  # Returns nothing.
  catchAll: (callback) ->
    @listeners.push new Listener(
      @,
      ((msg) -> msg instanceof CatchAllMessage),
      ((msg) -> msg.message = msg.message.message; callback msg)
    )

  messageIsToMe: (message) ->
    if @alias
      @aliasRegex = new RegExp "^\\s*#{@alias}:?\\s+", 'i'
    else
      @aliasRegex = false

    @nameRegex.test message.text or (@aliasRegex and @aliasRegex.test message.text)

  # Public: Passes the given message to any interested Listeners.
  #
  # message - A Message instance. Listeners can flag this message as 'done' to
  #           prevent further execution.
  #
  # Returns nothing.
  receive: (message) ->
    @connected.then =>
      matched = false

      message.isAddressedToBrobbot = @messageIsToMe message

      for listener in @listeners
        try
          matched = listener.call(message) or matched
          break if message.done
        catch error
          @emit('error', error, new @Response(@, message, []))

      if message.isAddressedToBrobbot
        #for respond listeners, chop off the brobbot's name/alias<commit_msg>fix alias handling<commit_after>_ = require 'lodash'
Q              = require 'q'
Fs             = require 'fs'
Log            = require 'log'
Path           = require 'path'
HttpClient     = require 'scoped-http-client'
{EventEmitter} = require 'events'

User = require './user'
RobotSegment = require './robot-segment'
Response = require './response'
{Listener,TextListener} = require './listener'
{EnterMessage,LeaveMessage,TopicMessage,CatchAllMessage,TextMessage} = require './message'

BROBBOT_DEFAULT_ADAPTERS = [
  'campfire'
  'shell'
]

BROBBOT_DEFAULT_BRAINS = [
  'dumb'
]

BROBBOT_DOCUMENTATION_SECTIONS = [
  'description'
  'dependencies'
  'configuration'
  'commands'
  'notes'
  'author'
  'authors'
  'examples'
  'tags'
  'urls'
]

class Robot
  # Robots receive messages from a chat source (Campfire, irc, etc), and
  # dispatch them to matching listeners.
  #
  # scripts     - An array of modules to load
  # adapterPath - A String of the path to local adapters.
  # adapter     - A String of the adapter name.
  # brainPath - A String of the path to local brains.
  # brain     - A String of the brain name.
  # httpd       - A Boolean whether to enable the HTTP daemon.
  # name        - A String of the robot name, defaults to Brobbot.
  #
  # Returns nothing.
  constructor: (scripts, adapterPath, adapter, brainPath, brain, httpd, name = 'Brobbot') ->
    @name      = name
    @nameRegex = new RegExp "^\\s*#{name}:?\\s+", 'i'
    @events    = new EventEmitter
    @alias     = false
    @adapter   = null
    @Response  = Response
    @commands  = []
    @listeners = []
    @respondListeners = []
    #TODO namespaced logger per-script
    @logger    = new Log process.env.BROBBOT_LOG_LEVEL or 'info'
    @pingIntervalId = null

    @parseVersion()
    if httpd
      @setupExpress()
    else
      @setupNullRouter()

    @brainReady = @loadBrain brainPath, brain
    @scriptsReady = @loadScripts scripts
    @adapterReady = @loadAdapter adapterPath, adapter

    @ready = Q.all [@brainReady, @scriptsReady]
    @connected = @adapterReady

    @adapterName = adapter
    @errorHandlers = []

    @on 'error', (err, msg) =>
      @invokeErrorHandlers(err, msg)
    process.on 'uncaughtException', (err) =>
      @logger.error err.stack
      @emit 'error', err


  # Public: Adds a Listener that attempts to match incoming messages based on
  # a Regex.
  #
  # regex    - A Regex that determines if the callback should be called.
  # callback - A Function that is called with a Response object.
  #
  # Returns nothing.
  hear: (regex, callback) ->
    @listeners.push new TextListener(@, regex, callback)

  # Public: Adds a Listener that attempts to match incoming messages directed
  # at the robot based on a Regex. All regexes treat patterns like they begin
  # with a '^'
  #
  # regex    - A Regex that determines if the callback should be called.
  # callback - A Function that is called with a Response object.
  #
  # Returns nothing.
  respond: (regex, callback) ->
    @respondListeners.push new TextListener(@, regex, callback)

  # Public: Adds a Listener that triggers when anyone enters the room.
  #
  # callback - A Function that is called with a Response object.
  #
  # Returns nothing.
  enter: (callback) ->
    @listeners.push new Listener(
      @,
      ((msg) -> msg instanceof EnterMessage),
      callback
    )

  # Public: Adds a Listener that triggers when anyone leaves the room.
  #
  # callback - A Function that is called with a Response object.
  #
  # Returns nothing.
  leave: (callback) ->
    @listeners.push new Listener(
      @,
      ((msg) -> msg instanceof LeaveMessage),
      callback
    )

  # Public: Adds a Listener that triggers when anyone changes the topic.
  #
  # callback - A Function that is called with a Response object.
  #
  # Returns nothing.
  topic: (callback) ->
    @listeners.push new Listener(
      @,
      ((msg) -> msg instanceof TopicMessage),
      callback
    )

  # Public: Adds an error handler when an uncaught exception or user emitted
  # error event occurs.
  #
  # callback - A Function that is called with the error object.
  #
  # Returns nothing.
  error: (callback) ->
    @errorHandlers.push callback

  # Calls and passes any registered error handlers for unhandled exceptions or
  # user emitted error events.
  #
  # err - An Error object.
  # msg - An optional Response object that generated the error
  #
  # Returns nothing.
  invokeErrorHandlers: (err, msg) ->
    @logger.error err.stack
    for errorHandler in @errorHandlers
     try
       errorHandler(err, msg)
     catch errErr
       @logger.error "while invoking error handler: #{errErr}\n#{errErr.stack}"

  # Public: Adds a Listener that triggers when no other text matchers match.
  #
  # callback - A Function that is called with a Response object.
  #
  # Returns nothing.
  catchAll: (callback) ->
    @listeners.push new Listener(
      @,
      ((msg) -> msg instanceof CatchAllMessage),
      ((msg) -> msg.message = msg.message.message; callback msg)
    )

  messageIsToMe: (message) ->
    if @alias
      @aliasRegex = new RegExp "^\\s*#{@alias}:?\\s+", 'i'
    else
      @aliasRegex = false

    @nameRegex.test(message.text) or (@aliasRegex and @aliasRegex.test(message.text))

  # Public: Passes the given message to any interested Listeners.
  #
  # message - A Message instance. Listeners can flag this message as 'done' to
  #           prevent further execution.
  #
  # Returns nothing.
  receive: (message) ->
    @connected.then =>
      matched = false

      message.isAddressedToBrobbot = @messageIsToMe message

      for listener in @listeners
        try
          matched = listener.call(message) or matched
          break if message.done
        catch error
          @emit('error', error, new @Response(@, message, []))

      if message.isAddressedToBrobbot
        #for respond listeners, chop off the brobbot's name/alias
{'fraction_non_alphanumeric': 0.09928481278923013, 'fraction_numerical': 0.0, 'mean_word_length': 2.72018779342723, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4071663', 'n_tokens_mistral': 3570, 'n_tokens_neox': 3507, 'n_words': 1225}