Skip to content
This repository has been archived by the owner on Jun 8, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1578 from tableau-mkt/master
Browse files Browse the repository at this point in the history
Add features and fix some bugs in remind.coffee
  • Loading branch information
technicalpickles committed Jun 5, 2015
2 parents d63a814 + c17c17e commit 1d58f03
Showing 1 changed file with 124 additions and 66 deletions.
190 changes: 124 additions & 66 deletions src/scripts/remind.coffee
@@ -1,99 +1,157 @@
# Description:
# Forgetful? Add reminders
# Forgetful? Add reminders!
#
# Dependencies:
# None
# "chrono-node": "^0.1.10"
# "moment": "^2.8.1"
# "lodash": "^2.4.1"
#
# Configuration:
# None
#
# Commands:
# hubot remind me in <time> to <action> - Set a reminder in <time> to do an <action> <time> is in the format 1 day, 2 hours, 5 minutes etc. Time segments are optional, as are commas
# hubot remind me (on <date>|in <time>) to <action> - Set a reminder in <time> to do an <action> <time> is in the format 1 day, 2 hours, 5 minutes etc. Time segments are optional, as are commas
# hubot delete reminder <action> - Delete reminder matching <action> (exact match required)
# hubot show reminders
#
# Author:
# whitman
# jtwalters

_ = require('lodash')
moment = require('moment')
chrono = require('chrono-node')
timeoutIds = {}

class Reminders
constructor: (@robot) ->
@cache = []
@current_timeout = null
@currentTimeout = null

@robot.brain.on 'loaded', =>
# Load reminders from brain, on loaded event
@robot.brain.on('loaded', =>
if @robot.brain.data.reminders
@cache = @robot.brain.data.reminders
@cache = _.map(@robot.brain.data.reminders, (item) ->
new Reminder(item)
)
console.log("loaded #{@cache.length} reminders")
@queue()
)

# Persist reminders to the brain, on save event
@robot.brain.on('save', =>
@robot.brain.data.reminders = @cache
)

add: (reminder) ->
@cache.push reminder
@cache.sort (a, b) -> a.due - b.due
@robot.brain.data.reminders = @cache
@cache.push(reminder)
@cache.sort((a, b) -> a.due - b.due)
@queue()

removeFirst: ->
reminder = @cache.shift()
@robot.brain.data.reminders = @cache
return reminder

queue: ->
clearTimeout @current_timeout if @current_timeout
if @cache.length > 0
now = new Date().getTime()
@removeFirst() until @cache.length is 0 or @cache[0].due > now
if @cache.length > 0
trigger = =>
reminder = @removeFirst()
@robot.reply reminder.msg_envelope, 'you asked me to remind you to ' + reminder.action
@queue()
# setTimeout uses a 32-bit INT
extendTimeout = (timeout, callback) ->
if timeout > 0x7FFFFFFF
@current_timeout = setTimeout ->
extendTimeout (timeout - 0x7FFFFFFF), callback
, 0x7FFFFFFF
else
@current_timeout = setTimeout callback, timeout
extendTimeout @cache[0].due - now, trigger
return if @cache.length is 0
now = (new Date).getTime()
trigger = =>
reminder = @removeFirst()
@robot.reply(reminder.msg_envelope, 'you asked me to remind you to ' + reminder.action)
@queue()
# setTimeout uses a 32-bit INT
extendTimeout = (timeout, callback) ->
if timeout > 0x7FFFFFFF
setTimeout(->
extendTimeout(timeout - 0x7FFFFFFF, callback)
, 0x7FFFFFFF)
else
setTimeout(callback, timeout)
reminder = @cache[0]
duration = reminder.due - now
duration = 0 if duration < 0
clearTimeout(timeoutIds[reminder])
timeoutIds[reminder] = extendTimeout(reminder.due - now, trigger)
console.log("reminder set with duration of #{duration}")

class Reminder
constructor: (@msg_envelope, @time, @action) ->
@time.replace(/^\s+|\s+$/g, '')

periods =
weeks:
value: 0
regex: "weeks?"
days:
value: 0
regex: "days?"
hours:
value: 0
regex: "hours?|hrs?"
minutes:
value: 0
regex: "minutes?|mins?"
seconds:
value: 0
regex: "seconds?|secs?"

for period of periods
pattern = new RegExp('^.*?([\\d\\.]+)\\s*(?:(?:' + periods[period].regex + ')).*$', 'i')
matches = pattern.exec(@time)
periods[period].value = parseInt(matches[1]) if matches

@due = new Date().getTime()
@due += ((periods.weeks.value * 604800) + (periods.days.value * 86400) + (periods.hours.value * 3600) + (periods.minutes.value * 60) + periods.seconds.value) * 1000

dueDate: ->
dueDate = new Date @due
dueDate.toLocaleString()
constructor: (data) ->
{@msg_envelope, @action, @time, @due} = data

if @time and !@due
@time.replace(/^\s+|\s+$/g, '')

periods =
weeks:
value: 0
regex: "weeks?"
days:
value: 0
regex: "days?"
hours:
value: 0
regex: "hours?|hrs?"
minutes:
value: 0
regex: "minutes?|mins?"
seconds:
value: 0
regex: "seconds?|secs?"

for period of periods
pattern = new RegExp('^.*?([\\d\\.]+)\\s*(?:(?:' + periods[period].regex + ')).*$', 'i')
matches = pattern.exec(@time)
periods[period].value = parseInt(matches[1]) if matches

@due = (new Date).getTime()
@due += (
(periods.weeks.value * 604800) +
(periods.days.value * 86400) +
(periods.hours.value * 3600) +
(periods.minutes.value * 60) +
periods.seconds.value
) * 1000

formatDue: ->
dueDate = new Date(@due)
duration = dueDate - new Date
if duration > 0 and duration < 86400000
'in ' + moment.duration(duration).humanize()
else
'on ' + moment(dueDate).format("dddd, MMMM Do YYYY, h:mm:ss a")

module.exports = (robot) ->
reminders = new Reminders(robot)

robot.respond(/show reminders$/i, (msg) ->
text = ''
for reminder in reminders.cache
text += "#{reminder.action} #{reminder.formatDue()}\n"
msg.send(text)
)

reminders = new Reminders robot
robot.respond(/delete reminder (.+)$/i, (msg) ->
query = msg.match[1]
prevLength = reminders.cache.length
reminders.cache = _.reject(reminders.cache, {action: query})
reminders.queue()
msg.send("Deleted reminder #{query}") if reminders.cache.length isnt prevLength
)

robot.respond /remind me in ((?:(?:\d+) (?:weeks?|days?|hours?|hrs?|minutes?|mins?|seconds?|secs?)[ ,]*(?:and)? +)+)to (.*)/i, (msg) ->
time = msg.match[1]
action = msg.match[2]
reminder = new Reminder msg.envelope, time, action
reminders.add reminder
msg.send 'I\'ll remind you to ' + action + ' on ' + reminder.dueDate()
robot.respond(/remind me (in|on) (.+?) to (.*)/i, (msg) ->
type = msg.match[1]
time = msg.match[2]
action = msg.match[3]
options =
msg_envelope: msg.envelope,
action: action
time: time
if type is 'on'
# parse the date (convert to timestamp)
due = chrono.parseDate(time).getTime()
if due.toString() isnt 'Invalid Date'
options.due = due
reminder = new Reminder(options)
reminders.add(reminder)
msg.send "I'll remind you to #{action} #{reminder.formatDue()}"
)

0 comments on commit 1d58f03

Please sign in to comment.