hubot-ingress
Version:
Ingress commands for Hubot
218 lines (181 loc) • 8.1 kB
text/coffeescript
# Description:
# Calculate Speticycle times.
#
# Dependencies:
# Moment-timezone.js
#
# Configuration:
# HUBOT_CYCLE_TIME_FMT: set the display format for times (uses Moment-timezone.js)
# HUBOT_CYCLE_TZ_OFFSET: set the timezone offset (uses Moment-timezone.js)
# HUBOT_CYCLE_TZ_NAME: set the timezone name (uses Moment-timezone.js) (name wins if set)
#
# Commands:
# hubot septicycle|cycle [count]
# hubot checkpoint|cp [count]
# hubot cycle offset
# hubot cycle set offset [offset]
# hubot cycle set offsetname [offset name]
# hubot checkpoints|cps on [date] [timezone]
# hubot mindunits|mindunit|mu average [ours]|[ours]k [theirs]|theirs]k
# hubot mindunits|mindunit|mu needed [ours]|[ours]k [theirs]|theirs]k
#
# Author:
# impleri
moment = require "moment-timezone"
# Environment variables
dayFormat = process.env.HUBOT_CYCLE_DAY_FMT or "ddd"
dateFormat = process.env.HUBOT_CYCLE_DAY_FMT or "ddd, MMMM Do YYYY"
timeFormat = process.env.HUBOT_CYCLE_TIME_FMT or "hA"
daytimeFormat = process.env.HUBOT_CYCLE_DAYTIME_FMT or "#{dayFormat} #{timeFormat}"
tzOffset = process.env.HUBOT_CYCLE_TZ_OFFSET or moment().format("Z")
tzName = process.env.HUBOT_CYCLE_TZ_NAME
# Basic variables
checkpoint = 5 * 60 * 60 # 5 hours per checkpoint
checkpointsInCycle = 35
cycle = checkpoint * checkpointsInCycle
seconds = 1000
localizeTime = (time) ->
if tzName then moment(time).tz tzName else moment(time).utcOffset tzOffset
formatTime = (time, format = daytimeFormat) ->
m = localizeTime time
m.format " #{format}"
calculateSomeCycle = (whenish, next = 1) ->
start = seconds * cycle * Math.floor whenish / (cycle * seconds)
start + cycle * seconds * next
calculateNextCycle = (next = 1) ->
calculateSomeCycle new Date().getTime(), next
getNextCycle = (next = 1, format = daytimeFormat) ->
formatTime calculateNextCycle next, format
calculateSomeCheckpoint = (whenish, next = 1) ->
start = checkpoint * seconds * Math.floor whenish / (checkpoint * seconds)
start + checkpoint * seconds * next
calculateNextCheckpoint = (next = 1) ->
calculateSomeCheckpoint new Date().getTime(), next
getSomeCheckpoint = (whenish, next = 1, format = daytimeFormat) ->
time = calculateSomeCheckpoint whenish, next
formatTime time, format
getNextCheckpoint = (next = 1) ->
formatTime calculateNextCheckpoint next
getCheckpointsDone = () ->
t0 = new Date('Wed, 08 Jan 2014 03:00:00 +0000');
t = new Date();
currentCheckpointNumber = Math.floor((t - t0) / (seconds * checkpoint)) % checkpointsInCycle
getCheckpointsRemaining = () ->
checkpointsDone = getCheckpointsDone()
checkpointsRemaining = checkpointsInCycle - checkpointsDone
calculateMuDifference = (ours, theirs) ->
startCycle = calculateNextCycle 0
lastCheckpoint = calculateNextCheckpoint 0
timeElapsed = (lastCheckpoint - startCycle) / seconds
checkpointsDone = timeElapsed / checkpoint
ourScore = checkpointsDone * ours
theirScore = checkpointsDone * theirs
difference = theirScore - ourScore
difference = -difference if difference < 1
difference + 1
getMuNeededNow = (ours, theirs) ->
calculateMuDifference ours, theirs
getMuNeededAverage = (ours, theirs) ->
checkpointsRemaining = getCheckpointsRemaining()
difference = calculateMuDifference ours, theirs
Math.ceil difference / checkpointsRemaining
module.exports = (robot) ->
robot.respond /cycle offset/i, (msg) ->
offset = tzName or tzOffset
msg.send "Current timezone offset is #{offset}."
robot.respond /cycle set offset (.*)/i, (msg) ->
tzOffset = msg.match[1]
msg.send "Timezone offset is set to #{tzOffset}. I hope you know what you are doing."
robot.respond /cycle set offsetname (.*)/i, (msg) ->
tzName = msg.match[1]
msg.send "Timezone offset name is set to #{tzName}. I hope you know what you are doing."
robot.respond /(septi)?cycle\s*([0-9])?$/i, (msg) ->
count = +msg.match[2]
count = 1 unless count > 1
times = []
times.push getNextCycle number for number in [1..count]
msg.send "The next #{count} cycle(s) occur at: #{times}."
robot.respond /c(heck)?p(oint)?(\s+[0-9]+)?$/i, (msg) ->
count = +msg.match[3]
count = 1 unless count > 1
times = []
times.push getNextCheckpoint number for number in [1..count]
msg.send "The next #{count} checkpoint(s) occur at: #{times}."
robot.respond /c(heck)?p(oint)?s\s+on\s+((this|next)\s+)?([a-z]+day)/i, (msg) ->
today = localizeTime moment()
whenish = today.clone().day(msg.match[5]).startOf "day"
unless whenish.isAfter today
whenish.add 7, "days"
day = formatTime whenish, dateFormat
whenish.subtract 1, "minute"
times = []
times.push getSomeCheckpoint whenish, number, timeFormat for number in [1..5]
msg.send "The checkpoints on #{day} occur at: #{times}."
robot.respond /c(heck)?p(oint)?s on (.*)/i, (msg) ->
return if msg.match[3].match /day$/i
today = localizeTime moment().isoWeekday()
whenish = localizeTime(new Date(msg.match[3])).startOf "day"
day = formatTime whenish, dateFormat
whenish.subtract 1, "minute"
times = []
times.push getSomeCheckpoint whenish, number, timeFormat for number in [1..5]
msg.send "The checkpoints on #{day} occur at: #{times}."
robot.respond /m(ind\s*)?u(nits?)?( needed)?\s+([0-9]+k?)\s+([0-9]+k?)/i, (msg) ->
ours = +msg.match[4]
ours = 1000 * +msg.match[4].slice 0, -1 if "k" is msg.match[4].slice -1
ours = 0 unless ours > 0
theirs = +msg.match[5]
theirs = 1000 * +msg.match[5].slice 0, -1 if "k" is msg.match[5].slice -1
theirs = 0 unless theirs > 0
needed = getMuNeededNow ours, theirs
if ours > theirs
winning = 'RES'
losing = 'ENL'
else
winning = 'ENL'
losing = 'RES'
checkpointsDone = getCheckpointsDone()
checkpointsRemaining = getCheckpointsRemaining()
nextCheckpoint = getNextCheckpoint 1
if checkpointsDone == 0
summary = "No checkpoints have been completed in this cycle, please check back after #{nextCheckpoint}."
else
summary = "Current ENL score: #{theirs.toLocaleString()}\n" +
"Current RES score: #{ours.toLocaleString()}\n" +
"Checkpoints Done: #{checkpointsDone}\n" +
"Checkpoints Remaining: #{checkpointsRemaining}\n" +
"Next Checkpoint: #{nextCheckpoint}\n" +
"\n" +
"#{losing} needs #{needed.toLocaleString()} total MU to win the cycle in the next checkpoint, " +
"assuming #{winning} score doesn't change."
msg.send summary
robot.respond /m(ind\s*)?u(nits?)? average\s+([0-9]+k?)\s+([0-9]+k?)/i, (msg) ->
ours = +msg.match[3]
ours = 1000 * +msg.match[3].slice 0, -1 if "k" is msg.match[3].slice -1
ours = 0 unless ours > 0
theirs = +msg.match[4]
theirs = 1000 * +msg.match[4].slice 0, -1 if "k" is msg.match[4].slice -1
theirs = 0 unless theirs > 0
needed = getMuNeededAverage ours, theirs
checkpointsRemaining = getCheckpointsRemaining()
if ours > theirs
winning = 'RES'
losing = 'ENL'
else
winning = 'ENL'
losing = 'RES'
checkpointsDone = getCheckpointsDone()
checkpointsRemaining = getCheckpointsRemaining()
nextCheckpoint = getNextCheckpoint()
if checkpointsDone == 0
summary = "No checkpoints have been completed in this cycle, please check back after #{nextCheckpoint}."
else
summary = "Current ENL score: #{theirs.toLocaleString()}\n" +
"Current RES score: #{ours.toLocaleString()}\n" +
"Checkpoints Done: #{checkpointsDone}\n" +
"Checkpoints Remaining: #{checkpointsRemaining}\n" +
"Next Checkpoint: #{nextCheckpoint}\n" +
"\n" +
"#{losing} needs #{needed.toLocaleString()} MU per checkpoint in the remaining #{checkpointsRemaining} checkpoint(s) to win the cycle, " +
"assuming #{winning} score doesn't change."
msg.send summary