Skip to content

Instantly share code, notes, and snippets.

@donpdonp
Last active March 11, 2020 16:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save donpdonp/aa62f50b2a83a09f354ccc8011842d3b to your computer and use it in GitHub Desktop.
Save donpdonp/aa62f50b2a83a09f354ccc8011842d3b to your computer and use it in GitHub Desktop.
gluon Portland 911 watch
(function() {
return {name:"911"}
})
var alert_channel = '#pdxbots'
var reports
var reportdate
function go(msg) {
if (msg.method == "clocktower" && (new Date(Date.parse(msg.params.time))).getMinutes() == 15) {
fresh911(bot.admin_channel)
userlist_load(function(users){
users.forEach(function(user){
check911(msg.params.channel, user, function(report, track_age_mins){
if(track_age_mins < 60) {
var report_age_mins = age_minutes(new Date(), report.date)
if(report_age_mins < 45 && report.distance < 500 && report.priority != 'COLD') {
bot.say(bot.admin_channel, "911 alerting "+user+" in "+alert_channel)
bot.say(alert_channel, makereport(user, report, track_age_mins))
}
}
})
})
})
}
if (msg.method == "irc.privmsg") {
var settled_match = /^!911$/.exec(msg.params.message)
if(settled_match) {
fresh911(msg.params.channel)
check911(msg.params.channel, msg.params.nick, function(report, track_age_mins){
if(report) {
bot.say(bot.admin_channel, JSON.stringify(report))
bot.say(msg.params.channel, makereport(msg.params.nick, report, track_age_mins))
} else {
bot.say(msg.params.channel, "no 911 reports found for "+msg.params.nick)
}
})
}
var settled_match = /^!911all$/.exec(msg.params.message)
if(settled_match) {
fresh911(msg.params.channel)
userlist_load(function(users){
users.forEach(function(user){
check911(msg.params.channel, user, function(report, track_age_mins){
bot.say(msg.params.channel, makereport(user,report, track_age_mins))
})
})
})
}
var settled_match = /^!911status$/.exec(msg.params.message)
if(settled_match) {
fresh911(bot.admin_channel)
status(msg.params.channel)
}
}
if(msg.method == "clocktower") {
var now = new Date(msg.params.time)
if(now.getMinutes() % 15 == 0) {
fresh911()
check911()
}
}
}
function status(channel) {
bot.say(channel, "status "+JSON.stringify(reports.slice(0,2)))
}
function check911(channel, user, cb) {
user_track_load(user, function(track){
var reportd = reports.map(function(report){
if(report.location && track.location) {
report.distance = pointDistance(report.location, track.location)
}
return report
})
var closest = reportd.sort(function(a,b){return a.distance - b.distance})
var report = closest[0]
var track_age_mins = (new Date() - new Date(track.location.date))/60/1000
cb(report, track_age_mins)
})
}
function makereport(user, report, track_age_mins) {
var min_ago = (new Date() - report.date)/1000/60
var emoji = report.department
if(report.department == "police") { emoji = '🚓' }
if(report.department == "medical") { emoji = '🚑' }
if(report.department == "fire") { emoji = '🚒' }
return "[911 "+emoji+"] "+
minutes_to_words(min_ago)+" old. "+
report.distance.toFixed(0)+" meters from "+
user+"("+minutes_to_words(track_age_mins)+" ago) "+
report.name + " [" + report.priority + "] @ " + report.address
}
function fresh911(channel) {
var report_age = age_minutes(new Date(), reportdate)
if(!reports || report_age >= 15) {
var url = "https://www.portlandonline.com/scripts/911incidents-kml_link.cfm"
var xml = http.get(url).replace(/\n/g,"").replace(/\r/g,"")
var parts = xml.split('</Placemark>')
reports = parts.map(xml911)
reportdate = new Date()
reports.forEach(function(report, idx){
if(report.name) {
if(report.name.indexOf('SHOT') >= 0) {
//bot.say('#pdxbots', report.name+' '+report.address)
}
} else {
//bot.say(channel, "#"+idx+" NO NAME: "+JSON.stringify(report))
}
})
//bot.say(channel, "911 KML refreshed with "+reports.length+" reports. last report "+reports[0].date+" "+reports[0].datestr)
}
}
function xml911(part){
var report = {xml: part}
part = part.substring(part.indexOf('<Placemark>'))
var department_bit = /<styleUrl>#(.*)<\/styleUrl>/.exec(part)
if(department_bit) {
report.department = department_bit[1]
}
var name_bit = /<name>(.*)<\/name>/.exec(part)
if(name_bit) {
var long_name = name_bit[1]
report.name = long_name
var addr_parts = /(.*) at (.*)/.exec(long_name)
if(addr_parts) {
report.name = addr_parts[1]
report.address = addr_parts[2]
}
var prio_parts = /([^-]*)\s+-\s+([ A-Z]*)/.exec(report.name)
if(prio_parts) {
report.name = prio_parts[1]
report.priority = prio_parts[2]
}
}
var coord_bit = /<coordinates>(.*),(.*)<\/coordinates>/.exec(part)
if(coord_bit) {
report.location = {latitude: parseFloat(coord_bit[2]),
longitude: parseFloat(coord_bit[1])}
}
var desc_bit = /<description>.*? (\w+) (\d+), (\d+) (\d+):(\d\d) (\w\w).*<\/description>/.exec(part)
if(desc_bit) {
var day = (desc_bit[2].length == 1 ? "0" : "") + desc_bit[2]
var datestr = desc_bit[3]+"-"+monthNameToNum(desc_bit[1])+"-"+day
var hour = parseInt(desc_bit[4])
var hour_pad = desc_bit[6] == "AM" ? (hour == 12 ? -12 : 0) : (hour == 12 ? 0 : 12)
var timestr = (hour+hour_pad)+":"+desc_bit[5]+"-07:00" // PT Daylight
report.datestr = datestr+"T"+timestr
report.date = new Date(report.datestr)
}
return report
}
function monthNameToNum(name) {
var months = {"January": 1, "February": 2, "March": 3, "April": 4, "May":5, "June": 6, "July": 7,
"August": 8, "September": 9, "October": 10, "November": 11, "December": 12}
var month_num = months[name]
var padding = month_num < 10 ? "0" : ""
return padding+month_num
}
function user_track_load(username, cb, missing_cb) {
db.get('icecondor:user'+':'+username+':track', function(trackJson){
var track
if (trackJson) {
track = JSON.parse(trackJson)
cb(track)
}
})
}
function userlist_load(cb) {
var key_userlist = 'icecondor:userlist'
db.get(key_userlist, function(listJson){
if(listJson) {
list = JSON.parse(listJson)
} else {
bot.say(bot.admin_channel, 'icecondor initializing userlist')
list = []
db.set(key_userlist, JSON.stringify(list))
}
cb(list)
})
}
function promise_all(cbs, done_cb) {
var cbs_done = Array(cbs.length)
var cbs_results = Array(cbs.length)
cbs.forEach(function(cb, idx) {
if(typeof(cb) == "function") {
cbs_done[idx] = false
var done = function(result) {
cbs_done[idx] = true
cbs_results[idx] = result
if(cbs_done.every(function(x){return x})) { done_cb(cbs_done, cbs_results) }
}
cb(done)
}
})
if(cbs_done.every(function(x){return x === null})) {
// no callbacks were passed
done_cb(cbs_done)
}
}
// from http://www.movable-type.co.uk/scripts/latlong.html
function pointDistance(la, lb) {
var lat1 = la.latitude, lon1 = la.longitude
var lat2 = lb.latitude, lon2 = lb.longitude
var dLat = numberToRadius(lat2 - lat1),
dLon = numberToRadius(lon2 - lon1),
a = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(numberToRadius(lat1))
* Math.cos(numberToRadius(lat2)) * Math.pow(Math.sin(dLon / 2), 2),
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return (6371 * c) * 1000; // returns meters
}
function numberToRadius(number) {
return number * Math.PI / 180;
}
function numberToDegree(number) {
return number * 180 / Math.PI
}
function age_minutes(a,b) {
return ((new Date(a)).getTime() - (new Date(b)).getTime())/1000/60
}
function minutes_to_words(mins) {
if (mins < 1) {
return (mins*60).toFixed(0)+" secs"
} else if (mins < 60) {
return mins.toFixed(1)+" mins"
} else if (mins < 60*48) {
return (mins/60).toFixed(1)+" hours"
} else {
return (mins/60/24).toFixed(1)+" days"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment