Last active
March 11, 2020 16:25
-
-
Save donpdonp/aa62f50b2a83a09f354ccc8011842d3b to your computer and use it in GitHub Desktop.
gluon Portland 911 watch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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