<commit_before># Description:
#   Allows Hubot to interact with Harvest's (http://harvestapp.com) time-tracking
#   service.
#
# Dependencies:
#   None
# Configuration:
#   HUBOT_HARVEST_SUBDOMAIN
#     The subdomain you access the Harvest service with, e.g.
#     if you have the Harvest URL http://yourcompany.harvestapp.com
#     you should set this to "yourcompany" (without the quotes).
#
# Commands:
#
#   hubot remember my harvest account <email> with password <password> - Make hubot remember your Harvest credentials
#   hubot forget my harvest account - Make hubot forget your Harvest credentials again
#   hubot start harvest at <project>/<task>: <notes> - Start a Harvest timer at a given project-task combination
#   hubot stop harvest [at project/task] - Stop the most recent Harvest timer or the one for the given project-task combination.
#   hubot daily harvest [of <user>] - Show a user's Harvest timers for today (or yours, if noone is specified)
#   hubot list harvest tasks [of <user>] - Show the Harvest project-task combinations available to a user (or you, if noone is specified)
# 
# Notes:
#   All commands and command arguments are case-insenitive. If you work
#   on a project "FooBar", hubot will unterstand "foobar" as well. This
#   is also true for abbreviations, so if you don't have similary named
#   projects, "foob" will do as expected.
#
#   Some examples:
#   > hubot remember my harvest account <EMAIL> with password <PASSWORD>
#   > hubot list harvest tasks
#   > hubot start harvest at myproject/important-task: Some notes go here.
#   > hubot start harvest at myp/imp: Some notes go here.
#   > hubot daily harvest of nickofotheruser
#
#   Full command descriptions:
#
#   hubot remember my harvest account <email> with password <password>
#     Saves your Harvest credentials to allow Hubot to track
#     time for you.
#
#   hubot forget my harvest account
#     Deletes your account credentials from Hubt's memory.
#
#   hubot start harvest at <project>/<task>: <notes>
#     Starts a timer for a task at a project (both of which may
#     be abbreviated, Hubot will ask you if your input is
#     ambigious). An existing timer (if any) will be stopped.
#
#   hubot stop harvest [at <project>/<task>]
#     Stops the timer for a task, if any. If no project is given,
#     stops the first active timer it can find. The project and
#     task arguments may be abbreviated as with start.
#
#   hubot daily harvest [of <user>]
#     Hubot responds with your/a specific user's entries
#     for today.
#
#   hubot list harvest tasks [of <user>]
#     Gives you a list of all project/task combinations available
#     to you or a specific user. You can use these for the start command.
# 
# Author:
#   Quintus @ Asquera

unless process.env.HUBOT_HARVEST_SUBDOMAIN
  console.log "Please set HUBOT_HARVEST_SUBDOMAIN in the environment to use the harvest plugin script."

# Checks if we have the information necessary for making requests
# for a user. If we don't, reply accordingly and return null. Otherwise,
# return the user object.
# If `test_user` is supplied, checks the credentials for the user
# with that name, otherwise the sender of `msg` is checked.
check_user = (robot, msg, test_user = null) ->
  # Detect the user; if none is passed, assume the sender.
  user = null
  if test_user
    user = robot.userForName(test_user)
    unless user
      msg.reply "#{msg.match[2]}? Whoʼs that?"
      return null
  else
    user = msg.message.user
    
  # Check if we know the detected user's credentials.
  unless user.harvest_account
    if user == msg.message.user
      msg.reply "You have to tell me your Harvest credentials first."
    else
      msg.reply "I didnʼt crack #{user.name}ʼs Harvest credentials yet, but Iʼm working on it… Sorry for the inconvenience."
    return null
    
  return user

### Definitions for hubot ###
module.exports = (robot) ->

  # Provide facility for saving the account credentials.
  robot.respond /remember my harvest account (.+) with password (.+)/i, (msg) ->
    account = new HarvestAccount msg.match[1], msg.match[2]

    # If the credentials are valid, remember them, otherwise
    # tell the user they are wrong.
    account.test msg, (valid) ->
      if valid
        msg.message.user.harvest_account = account
        msg.reply "Thanks, Iʼll remember your credentials. Have fun with Harvest."
      else
        msg.reply "Uh-oh – I just tested your credentials, but they appear to be wrong. Please specify the correct ones."

  # Allows a user to delete his credentials.
  robot.respond /forget my harvest account/i, (msg) ->
    msg.message.user.harvest_account = null
    msg.reply "Okay, I erased your credentials from my memory."

  # Retrieve your or a specific user's timesheet for today.
  robot.respond /daily harvest( of (.+))?/i, (msg) ->
    unless user = check_user(robot, msg, msg.match[2])
      return

    user.harvest_account.daily msg, (status, body) ->
      if 200 <= status <= 299
        msg.reply "Your entries for today, #{user.name}:"
        for entry in body.day_entries
          if entry.ended_at == ""
            msg.reply "• #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [running since #{entry.started_at} (#{entry.hours}h)]"
          else
            msg.reply "• #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [#{entry.started_at} – #{entry.ended_at} (#{entry.hours}h)]"
      else
        msg.reply "Request failed with status #{status}."

  # List all project/task combinations that are available to a user.
  robot.respond /list harvest tasks( of (.+))?/i, (msg) ->
    unless user = check_user(robot, msg, msg.match[2])
      return

    user.harvest_account.daily msg, (status, body) ->
      if 200 <= status <= 299
        msg.reply "The following project/task combinations are available for you, #{user.name}:"
        for project in body.projects
          msg.reply "• Project #{project.name}"
          for task in project.tasks
            msg.reply "  ‣ #{task.name} (#{if task.billable then 'billable' else 'non-billable'})"
      else
        msg.reply "Request failed with status #{status}."

  # Kick off a new timer, stopping the previously running one, if any.
  robot.respond /start harvest at (.+)\/(.+): (.*)/i, (msg) ->
    unless user = check_user(robot, msg)
      return

    project = msg.match[1]
    task    = msg.match[2]
    notes   = msg.match[3]
    
    user.harvest_account.start msg, project, task, notes, (status, body) ->
      if 200 <= status <= 299
        if body.hours_for_previously_running_timer?
          msg.reply "Previously running timer stopped at #{body.hours_for_previously_running_timer}h."
        msg.reply "OK, I started tracking you on #{body.project}/#{body.task}."
      else
        msg.reply "Request failed with status #{status}."

  # Stops the timer running for a project/task combination,
  # if any. If no combination is given, stops the first
  # active timer available.
  robot.respond /stop harvest( at (.+)\/(.+))?/i, (msg) ->
    unless user = check_user(robot, msg)
      return
    
    if msg.match[1]
      project = msg.match[2]
      task    = msg.match[3]
      user.harvest_account.stop msg, project, task, (status, body) ->
        if 200 <= status <= 299
          msg.reply "Timer stopped (#{body.hours}h)."
        else
          msg.reply "Request failed with status #{status}."
          msg.reply body
    else
      user.harvest_account.stop_first msg, (status, body) ->
        if 200 <= status <= 299
          msg.reply "Timer stopped (#{body.hours}h)."
        else
          msg.reply "Request failed with status #{status}."

# Class managing the Harvest account associated with
# a user. Keeps track of the user's credentials and can
# be used to query the Harvest API on behalf of that user.
#
# The API calls are asynchronous, i.e. the methods executing
# the request immediately return. To process the response,
# you have to attach a callback to the method call, which
# unless documtened otherwise will receive two arguments,
# the first being the response's status code, the second
# one is the response's body as a JavaScript object created
# via `JSON.parse`.
class HarvestAccount

  # Create a new harvest account. Pass in the account's email and the
  # password used to access harvest. These credentials are the same you
  # use for logging into Harvest's web service.
  constructor: (email, password) -><commit_msg>Try/catch blocks for catching connection errors<commit_after># Description:
#   Allows Hubot to interact with Harvest's (http://harvestapp.com) time-tracking
#   service.
#
# Dependencies:
#   None
# Configuration:
#   HUBOT_HARVEST_SUBDOMAIN
#     The subdomain you access the Harvest service with, e.g.
#     if you have the Harvest URL http://yourcompany.harvestapp.com
#     you should set this to "yourcompany" (without the quotes).
#
# Commands:
#
#   hubot remember my harvest account <email> with password <password> - Make hubot remember your Harvest credentials
#   hubot forget my harvest account - Make hubot forget your Harvest credentials again
#   hubot start harvest at <project>/<task>: <notes> - Start a Harvest timer at a given project-task combination
#   hubot stop harvest [at project/task] - Stop the most recent Harvest timer or the one for the given project-task combination.
#   hubot daily harvest [of <user>] - Show a user's Harvest timers for today (or yours, if noone is specified)
#   hubot list harvest tasks [of <user>] - Show the Harvest project-task combinations available to a user (or you, if noone is specified)
# 
# Notes:
#   All commands and command arguments are case-insenitive. If you work
#   on a project "FooBar", hubot will unterstand "foobar" as well. This
#   is also true for abbreviations, so if you don't have similary named
#   projects, "foob" will do as expected.
#
#   Some examples:
#   > hubot remember my harvest account <EMAIL> with password <PASSWORD>
#   > hubot list harvest tasks
#   > hubot start harvest at myproject/important-task: Some notes go here.
#   > hubot start harvest at myp/imp: Some notes go here.
#   > hubot daily harvest of nickofotheruser
#
#   Full command descriptions:
#
#   hubot remember my harvest account <email> with password <password>
#     Saves your Harvest credentials to allow Hubot to track
#     time for you.
#
#   hubot forget my harvest account
#     Deletes your account credentials from Hubt's memory.
#
#   hubot start harvest at <project>/<task>: <notes>
#     Starts a timer for a task at a project (both of which may
#     be abbreviated, Hubot will ask you if your input is
#     ambigious). An existing timer (if any) will be stopped.
#
#   hubot stop harvest [at <project>/<task>]
#     Stops the timer for a task, if any. If no project is given,
#     stops the first active timer it can find. The project and
#     task arguments may be abbreviated as with start.
#
#   hubot daily harvest [of <user>]
#     Hubot responds with your/a specific user's entries
#     for today.
#
#   hubot list harvest tasks [of <user>]
#     Gives you a list of all project/task combinations available
#     to you or a specific user. You can use these for the start command.
# 
# Author:
#   Quintus @ Asquera

unless process.env.HUBOT_HARVEST_SUBDOMAIN
  console.log "Please set HUBOT_HARVEST_SUBDOMAIN in the environment to use the harvest plugin script."

# Checks if we have the information necessary for making requests
# for a user. If we don't, reply accordingly and return null. Otherwise,
# return the user object.
# If `test_user` is supplied, checks the credentials for the user
# with that name, otherwise the sender of `msg` is checked.
check_user = (robot, msg, test_user = null) ->
  # Detect the user; if none is passed, assume the sender.
  user = null
  if test_user
    user = robot.userForName(test_user)
    unless user
      msg.reply "#{msg.match[2]}? Whoʼs that?"
      return null
  else
    user = msg.message.user
    
  # Check if we know the detected user's credentials.
  unless user.harvest_account
    if user == msg.message.user
      msg.reply "You have to tell me your Harvest credentials first."
    else
      msg.reply "I didnʼt crack #{user.name}ʼs Harvest credentials yet, but Iʼm working on it… Sorry for the inconvenience."
    return null
    
  return user

### Definitions for hubot ###
module.exports = (robot) ->

  # Provide facility for saving the account credentials.
  robot.respond /remember my harvest account (.+) with password (.+)/i, (msg) ->
    account = new HarvestAccount msg.match[1], msg.match[2]

    # If the credentials are valid, remember them, otherwise
    # tell the user they are wrong.
    try
      account.test msg, (valid) ->
        if valid
          msg.message.user.harvest_account = account
          msg.reply "Thanks, Iʼll remember your credentials. Have fun with Harvest."
        else
          msg.reply "Uh-oh – I just tested your credentials, but they appear to be wrong. Please specify the correct ones."
    catch error
      msg.reply "Fatal error: #{error}"

  # Allows a user to delete his credentials.
  robot.respond /forget my harvest account/i, (msg) ->
    msg.message.user.harvest_account = null
    msg.reply "Okay, I erased your credentials from my memory."

  # Retrieve your or a specific user's timesheet for today.
  robot.respond /daily harvest( of (.+))?/i, (msg) ->
    unless user = check_user(robot, msg, msg.match[2])
      return

    try
      user.harvest_account.daily msg, (status, body) ->
        if 200 <= status <= 299
          msg.reply "Your entries for today, #{user.name}:"
          for entry in body.day_entries
            if entry.ended_at == ""
              msg.reply "• #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [running since #{entry.started_at} (#{entry.hours}h)]"
            else
              msg.reply "• #{entry.project} (#{entry.client}) → #{entry.task} <#{entry.notes}> [#{entry.started_at} – #{entry.ended_at} (#{entry.hours}h)]"
        else
          msg.reply "Request failed with status #{status}."
    catch error
      msg.reply("Fatal error: #{error}")

  # List all project/task combinations that are available to a user.
  robot.respond /list harvest tasks( of (.+))?/i, (msg) ->
    unless user = check_user(robot, msg, msg.match[2])
      return

    try
      user.harvest_account.daily msg, (status, body) ->
        if 200 <= status <= 299
          msg.reply "The following project/task combinations are available for you, #{user.name}:"
          for project in body.projects
            msg.reply "• Project #{project.name}"
            for task in project.tasks
              msg.reply "  ‣ #{task.name} (#{if task.billable then 'billable' else 'non-billable'})"
        else
          msg.reply "Request failed with status #{status}."
    catch error
      msg.reply "Fatal error: #{error}"

  # Kick off a new timer, stopping the previously running one, if any.
  robot.respond /start harvest at (.+)\/(.+): (.*)/i, (msg) ->
    unless user = check_user(robot, msg)
      return

    project = msg.match[1]
    task    = msg.match[2]
    notes   = msg.match[3]

    try
      user.harvest_account.start msg, project, task, notes, (status, body) ->
        if 200 <= status <= 299
          if body.hours_for_previously_running_timer?
            msg.reply "Previously running timer stopped at #{body.hours_for_previously_running_timer}h."
          msg.reply "OK, I started tracking you on #{body.project}/#{body.task}."
        else
          msg.reply "Request failed with status #{status}."
    catch error
      msg.reply "Fatal error: #{error}"

  # Stops the timer running for a project/task combination,
  # if any. If no combination is given, stops the first
  # active timer available.
  robot.respond /stop harvest( at (.+)\/(.+))?/i, (msg) ->
    unless user = check_user(robot, msg)
      return
    
    if msg.match[1]
      project = msg.match[2]
      task    = msg.match[3]
      try
        user.harvest_account.stop msg, project, task, (status, body) ->
          if 200 <= status <= 299
            msg.reply "Timer stopped (#{body.hours}h)."
          else
            msg.reply "Request failed with status #{status}."
            msg.reply body
      catch error
        msg.reply("Fatal error: #{error}")
    else
      try
        user.harvest_account.stop_first msg, (status, body) ->
          if 200 <= status <= 299
            msg.reply "Timer stopped (#{body.hours}h)."
          else
            msg.reply "Request failed with status #{status}."
      catch error
        msg.reply("Fatal error: #{error}")

# Class managing the Harvest account associated with
# a user. Keeps track of the user's credentials and can
# be used to query the Harvest API on behalf of that user.
#
# The API calls are asynchronous, i.e. the methods executing
# the request immediately return. To process the response,
# you have to attach a callback to the method call, which
# unless documtened otherwise will receive two arguments,
# the first being the response's status code, the second
# one is the response's body as a JavaScript object created
# via `JSON.parse`.
class HarvestAccount

  # Create a new harvest account. Pass in the account's email and the
  # password used to access harvest. These credentials are the same you
  # use for logging into Harvest's web service.
  constructor: (email, password) ->
{'fraction_non_alphanumeric': 0.0983952943863857, 'fraction_numerical': 0.004682770829764148, 'mean_word_length': 2.9494812810103745, 'pattern_counts': {'":': 0, '<': 63, '<?xml version=': 0, '>': 83, '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': '7558756', 'n_tokens_mistral': 5213, 'n_tokens_neox': 5018, 'n_words': 2084}