<commit_before>Robot   = require('hubot').Robot
Adapter = require('hubot').Adapter
TextMessage = require('hubot').TextMessage
HTTPS = require 'https'

class Slack extends Adapter
  log: console.log.bind console
  logError: console.error.bind console

  send: (params, strings...) ->
    @log "Sending message"

    user = @userFromParams(params)

    strings.forEach (str) =>
      str = @escapeHtml str

      args = JSON.stringify({"channel": user.reply_to, "text": str, username: @robot.name})
      @post "/services/hooks/hubot", args

  reply: (params, strings...) ->
    @log "Sending reply"

    user = @userFromParams(params)
    strings.forEach (str) =>
      @send params, "#{user.name}: #{str}"

  topic: (params, strings...) ->
    # TODO: Set the topic

  escapeHtml: (string) ->
    string
      # Escape entities
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')

      # Linkify. We assume that the bot is well-behaved and
      # consistently sending links with the protocol part
      .replace(/((\bhttp)\S+)/g, '<$1>')

  unescapeHtml: (string) ->
    string
      # Unescape entities
      .replace(/&amp;/g, '&')
      .replace(/&lt;/g, '<')
      .replace(/&gt;/g, '>')

      # Convert markup into plain url string.
      .replace(/<((\bhttps?)[^|]+)(\|(.*))+>/g, '$1')
      .replace(/<((\bhttps?)(.*))?>/g, '$1')


  userFromParams: (params) ->
    # hubot < 2.4.2: params = user
    # hubot >= 2.4.2: params = {user: user, ...}
    params = if params.user then params.user else params

    # Ghetto hack to make robot.messageRoom work with Slack's adapter
    #
    # Note: Slack's API here uses rooom ID's, not room names. They look
    # something like C0capitallettersandnumbershere
    params.reply_to ||= params.room

    params

  parseOptions: ->
    @options =
      token:   process.env.HUBOT_SLACK_TOKEN or null
      team:    process.env.HUBOT_SLACK_TEAM or null
      name:    process.env.HUBOT_SLACK_BOTNAME or 'slackbot'

  getMessageFromRequest: (req) ->
    # Parse the payload
    hubot_msg = req.param('text')
    return unless hubot_msg

    @unescapeHtml hubotMsg

  getAuthorFromRequest: (req) ->
    from = req.param('user_id')
    from_name = req.param('user_name')
    channel = req.param('channel_id')
    channel_name = req.param('channel_name')

    # Construct an author object
    author = {}
    author.id = from
    author.name = from_name
    author.reply_to = channel
    author.room = channel_name

    author

  run: ->
    self = @

    @parseOptions()
    @log "Slack adapter options:", @options

    unless @options.token
      @logError "No services token provided to Hubot"
      return

    unless @options.team
      @logError "No team provided to Hubot"
      return

    # Listen to incoming webhooks from slack
    self.robot.router.post "/hubot/slack-webhook", (req, res) ->
      self.log "Incoming message received"

      hubot_msg = self.getMessageFromRequest req
      author = self.getAuthorFromRequest req

      if hubot_msg and author
        # Pass to the robot
        self.log "Received #{hubot_msg} from #{author.name}"
        self.receive new TextMessage(author, hubot_msg)

      # Just send back an empty reply, since our actual reply,
      # if any, will be async above
      res.end ""

    # Provide our name to Hubot
    self.robot.name = @options.name

    # Tell Hubot we're connected so it can load scripts
    @log "Successfully 'connected' as", self.robot.name
    self.emit "connected"

  # Convenience HTTP Methods for sending data back to slack
  get: (path, callback) ->
    @request "GET", path, null, callback

  post: (path, body, callback) ->
    @request "POST", path, body, callback

  request: (method, path, body, callback) ->
    self = @

    #console.log method, path, body
    host = @options.team + '.slack.com'
    headers = "Host": host

    path += "?token=" + @options.token

    req_options =
      "agent"  : false
      "hostname"   : host
      "port"   : 443
      "path"   : path
      "method" : method
      "headers": headers

    if method is "POST"
      headers["Content-Type"] = "application/x-www-form-urlencoded"
      req_options.headers["Content-Length"] = body.length

    request = HTTPS.request req_options, (response) ->
      data = ""
      response.on "data", (chunk) ->
        data += chunk

      response.on "end", ->
        if response.statusCode >= 400
          self.logError "Slack services error: #{response.statusCode}"
          self.logError data

        #console.log "HTTPS response:", data
        if callback
          callback null, data

        response.on "error", (err) ->
          self.logError "HTTPS response error:", err
          if callback
            callback err, null

    if method is "POST"
      request.end(body, 'binary')
    else
      request.end()

    request.on "error", (err) ->
      self.logError "HTTPS request error:", err
      self.logError err.stack
      if callback
        callback err

exports.use = (robot) ->
  new Slack robot
<commit_msg>Make style more consistent and simplify a few things<commit_after>{Robot, Adapter, TextMessage} = require 'hubot'
https = require 'https'

class Slack extends Adapter
  ###################################################################
  # Slightly abstract logging, primarily so that it can
  # be easily altered for unit tests.
  ###################################################################
  log: console.log.bind console
  logError: console.error.bind console


  ###################################################################
  # Communicating back to the chat rooms. These are exposed
  # as methods on the argument passed to callbacks from
  # robot.respond, robot.listen, etc.
  ###################################################################
  send: (params, strings...) ->
    @log "Sending message"
    user = @userFromParams params

    strings.forEach (str) =>
      str = @escapeHtml str
      args = JSON.stringify
        username : @robot.name
        channel  : user.reply_to
        text     : str

      @post "/services/hooks/hubot", args

  reply: (params, strings...) ->
    @log "Sending reply"

    user = @userFromParams params
    strings.forEach (str) =>
      @send params, "#{user.name}: #{str}"

  topic: (params, strings...) ->
    # TODO: Set the topic


  ###################################################################
  # HTML helpers.
  ###################################################################
  escapeHtml: (string) ->
    string
      # Escape entities
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')

      # Linkify. We assume that the bot is well-behaved and
      # consistently sending links with the protocol part
      .replace(/((\bhttp)\S+)/g, '<$1>')

  unescapeHtml: (string) ->
    string
      # Unescape entities
      .replace(/&amp;/g, '&')
      .replace(/&lt;/g, '<')
      .replace(/&gt;/g, '>')

      # Convert markup into plain url string.
      .replace(/<((\bhttps?)[^|]+)(\|(.*))+>/g, '$1')
      .replace(/<((\bhttps?)(.*))?>/g, '$1')


  ###################################################################
  # Parsing inputs.
  ###################################################################
  userFromParams: (params) ->
    # hubot < 2.4.2: params = user
    # hubot >= 2.4.2: params = {user: user, ...}
    params = params.user or params

    # Ghetto hack to make robot.messageRoom work with Slack's adapter
    #
    # Note: Slack's API here uses rooom ID's, not room names. They look
    # something like C0capitallettersandnumbershere
    params.reply_to ||= params.room

    params

  parseOptions: ->
    @options =
      token : process.env.HUBOT_SLACK_TOKEN
      team  : process.env.HUBOT_SLACK_TEAM
      name  : process.env.HUBOT_SLACK_BOTNAME or 'slackbot'

  getMessageFromRequest: (req) ->
    # Parse the payload
    hubotMsg = req.param 'text'
    @unescapeHtml hubotMsg if hubotMsg

  getAuthorFromRequest: (req) ->
    # Return an author object
    id       : req.param 'user_id'
    name     : req.param 'user_name'
    reply_to : req.param 'channel_id'
    room     : req.param 'channel_name'


  ###################################################################
  # The star.
  ###################################################################
  run: ->
    self = @
    @parseOptions()

    @log "Slack adapter options:", @options

    return @logError "No services token provided to Hubot" unless @options.token
    return @logError "No team provided to Hubot" unless @options.team

    # Listen to incoming webhooks from slack
    self.robot.router.post "/hubot/slack-webhook", (req, res) ->
      self.log "Incoming message received"

      hubotMsg = self.getMessageFromRequest req
      author = self.getAuthorFromRequest req

      if hubotMsg and author
        # Pass to the robot
        self.log "Received #{hubotMsg} from #{author.name}"
        self.receive new TextMessage(author, hubotMsg)

      # Just send back an empty reply, since our actual reply,
      # if any, will be async above
      res.end ""

    # Provide our name to Hubot
    self.robot.name = @options.name

    # Tell Hubot we're connected so it can load scripts
    @log "Successfully 'connected' as", self.robot.name
    self.emit "connected"


  ###################################################################
  # Convenience HTTP Methods for sending data back to slack.
  ###################################################################
  get: (path, callback) ->
    @request "GET", path, null, callback

  post: (path, body, callback) ->
    @request "POST", path, body, callback

  request: (method, path, body, callback) ->
    self = @

    host = "#{@options.team}.slack.com"
    headers =
      Host: host

    path += "?token=#{@options.token}"

    reqOptions =
      agent    : false
      hostname : host
      port     : 443
      path     : path
      method   : method
      headers  : headers

    if method is "POST"
      reqOptions.headers["Content-Type"] = "application/x-www-form-urlencoded"
      reqOptions.headers["Content-Length"] = body.length

    request = https.request reqOptions, (response) ->
      data = ""
      response.on "data", (chunk) ->
        data += chunk

      response.on "end", ->
        if response.statusCode >= 400
          self.logError "Slack services error: #{response.statusCode}"
          self.logError data

        #console.log "HTTPS response:", data
        callback? null, data

        response.on "error", (err) ->
          self.logError "HTTPS response error:", err
          callback? err, null

    if method is "POST"
      request.end body, "binary"
    else
      request.end()

    request.on "error", (err) ->
      self.logError "HTTPS request error:", err
      self.logError err.stack
      callback? err


###################################################################
# Exports to handle actual usage and unit testing.
###################################################################
exports.use = (robot) ->
  new Slack robot

{'fraction_non_alphanumeric': 0.2012009320666786, 'fraction_numerical': 0.002867897472665352, 'mean_word_length': 2.739611260053619, 'pattern_counts': {'":': 4, '<': 15, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '5784690', 'n_tokens_mistral': 3346, 'n_tokens_neox': 3192, 'n_words': 927}