Last active
April 16, 2019 17:37
-
-
Save donpdonp/74c68dfbdaaab37b66bda297fbb91411 to your computer and use it in GitHub Desktop.
gluon flightaware
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: "flightaware"} | |
}) | |
function go(msg){ | |
if (msg.method == "irc.privmsg") { | |
var match = /^!flight (.*)/.exec(msg.params.message) | |
if(match) { | |
var flightNum = match[1] | |
var data = flight_data(flightNum.toUpperCase()) | |
var flightIds = Object.keys(data.flights) | |
var flightId = flightIds[0] | |
var flight = data.flights[flightId] | |
bot.say(msg.params.channel, flight.codeShare.ident+" "+flight.origin.iata+"-"+flight.destination.iata+ | |
": "+flight_report(flight)) | |
} | |
} | |
} | |
function flight_report(flight) { | |
if(flight.flightStatus == "airborne") { | |
if (flight.altitude) { | |
var landingTime = new Date(flight.landingTimes.estimated*1000) | |
var until_min = (landingTime - new Date())/1000/60 | |
return "heading "+headingDirection(flight.heading)+"("+flight.heading+"°) "+ | |
flight.groundspeed+" mph @ "+ | |
flight.altitude+"00 feet. "+ | |
minutes_to_words(until_min)+" left to "+ | |
flight.destination.friendlyLocation+ | |
(flight.diverted ? "DIVERTED" : "") | |
} else { | |
var takeoff_scheduled = new Date(flight.takeoffTimes.scheduled*1000) | |
var takeoff_until_min = (takeoff_scheduled - new Date())/1000/60 | |
var takeoff_estimated = new Date(flight.takeoffTimes.estimated*1000) | |
var takeoff_estimated_until_min = (takeoff_estimated - new Date())/1000/60 | |
return "Taxiing. Takeoff scheduled in "+minutes_to_words(takeoff_until_min)+ | |
" estimated in "+minutes_to_words(takeoff_estimated_until_min)+"." | |
} | |
} | |
if(flight.flightStatus == "arrived") { | |
var landingTime = new Date(flight.landingTimes.estimated*1000) | |
var until_min = (new Date() - landingTime)/1000/60 | |
return flight.flightStatus+" "+ | |
minutes_to_words(until_min)+" ago at "+ | |
flight.destination.friendlyLocation | |
} | |
if(flight.flightStatus == "cancelled") { | |
return "Cancelled" | |
} | |
if(flight.flightStatus == "fru") { | |
var parts = [] | |
var takeoff_scheduled = new Date(flight.takeoffTimes.scheduled*1000) | |
var takeoff_until_min = (takeoff_scheduled - new Date())/1000/60 | |
parts.push("Takeoff in "+minutes_to_words(takeoff_until_min)+".") | |
parts.push("Warning: FlightResultUnknown status") | |
return parts.join(' ') | |
} | |
if(flight.flightStatus == "") { | |
var parts = [] | |
var takeoff_scheduled = new Date(flight.takeoffTimes.scheduled*1000) | |
var takeoff_until_min = (takeoff_scheduled - new Date())/1000/60 | |
parts.push("Takeoff in "+minutes_to_words(takeoff_until_min)+".") | |
if (flight.takeoffTimes.scheduled != flight.takeoffTimes.estimated) { | |
var takeoff_estimated = new Date(flight.takeoffTimes.estimated*1000) | |
var delay_min = (takeoff_scheduled - takeoff_estimated)/1000/60 | |
parts.push("(Warning: delayed "+minutes_to_words(delay_min)+")") | |
} | |
if(flight.poweredOn || flight.poweredOff) { | |
parts.push("aircraft engines are "+(flight.poweredOn ? "on" : "")+(flight.poweredOff ? "off" : "")+".") | |
} | |
if(flight.inboundFlight) { | |
var inboundId = flight.inboundFlight.flightId.split('-')[0] | |
parts.push("inbound flight: "+inboundId+".") | |
} | |
if(flight.destination.delays) { | |
flight.destination.delays.forEach(function(delay){ | |
parts.push(flight.destination.iata+" delay:"+delay.time) | |
if(delay.reason) { parts.push(delay.reason) } | |
}) | |
} | |
return parts.join(' ') | |
} | |
return "unknown status: "+flight.flightStatus | |
} | |
function flight_data(flight) { | |
var html = http.get("https://flightaware.com/live/flight/"+flight) | |
var json_match = /trackpollBootstrap = ({.*});/.exec(html) | |
var json = json_match[1] | |
return JSON.parse(json) | |
} | |
function headingDirection(degrees) { | |
var directions = ["north", "northeast", "east", "southeast", "south", "southwest", "west", "northwest"] | |
var anglewidth = 360 / directions.length | |
var halfwidth = anglewidth/2 | |
for(var i=0; i < directions.length; i++) { | |
var j = i == 0 ? directions.length-1 : i-1 | |
var min = (anglewidth*j)+halfwidth | |
var max = (anglewidth*i)+halfwidth | |
var inside | |
if(i==0) { | |
inside = degrees <= max || degrees > min | |
} else { | |
inside = degrees <= max && degrees > min | |
} | |
if(inside) return directions[i] | |
} | |
} | |
function minutes_to_words(mins) { | |
var words = "" | |
if (mins < 0) { | |
words = " ago" | |
mins = mins * -1 | |
} | |
if(mins < 1) { | |
return ""+(mins*60).toFixed(0)+" secs"+words | |
} else if (mins < 60) { | |
return ""+mins.toFixed(0)+" mins"+words | |
} else if (mins < 60*48) { | |
return ""+(mins/60).toFixed(1)+" hours"+words | |
} else { | |
return ""+(mins/60/24).toFixed(1)+" days"+words | |
} | |
} | |
/* | |
{ | |
"version": "2.5", | |
"summary": false, | |
"flights": { | |
"RPA6021-1545460022-airline-0182": { | |
"activityLog": { | |
"flights": [ | |
], | |
"additionalLogRowsAvailable": false | |
}, | |
"adhoc": false, | |
"adhocAvailable": false, | |
"aircraft": { | |
"type": "E170", | |
"tail": null, | |
"owner": null, | |
"ownerLocation": null, | |
"owner_type": null, | |
"canMessage": false, | |
"lifeguard": false, | |
"heavy": false, | |
"friendlyType": "Embraer 170/175 (twin-jet)" | |
}, | |
"airline": { | |
"fullName": "Republic Airways", | |
"shortName": "Republic", | |
"icao": "RPA", | |
"iata": "YX", | |
"callsign": "Brickyard", | |
"url": null | |
}, | |
"altitude": null, | |
"altitudeChange": null, | |
"atcIdent": null, | |
"averageDelays": { | |
"departure": -300, | |
"arrival": -540 | |
}, | |
"blocked": false, | |
"blockedForUser": false, | |
"blockMessage": null, | |
"cabinInfo": { | |
"text": "First / Economy: No meal", | |
"links": null | |
}, | |
"cancelled": false, | |
"cockpitInformation": null, | |
"codeShare": { | |
"ident": "DAL6021", | |
"displayIdent": "DAL6021", | |
"iataIdent": "DL6021", | |
"airline": { | |
"fullName": "Delta Air Lines, Inc.", | |
"shortName": "Delta", | |
"icao": "DAL", | |
"iata": "DL", | |
"callsign": "Delta", | |
"url": "http://www.delta.com/" | |
}, | |
"friendlyIdent": "Delta 6021", | |
"thumbnail": { | |
"imageUrl": "https://e1.flightcdn.com/images/airline_logos/90p/DAL.png", | |
"linkUrl": "/live/fleet/DAL" | |
}, | |
"links": { | |
} | |
}, | |
"coord": null, | |
"destination": { | |
"TZ": ":America/New_York", | |
"isValidAirportCode": true, | |
"altIdent": null, | |
"iata": "SYR", | |
"friendlyName": "Syracuse Hancock Intl", | |
"friendlyLocation": "Syracuse, NY", | |
"coord": [ | |
-76.1063, | |
43.1112 | |
], | |
"icao": "KSYR", | |
"gate": "23", | |
"terminal": null, | |
"delays": null | |
}, | |
"displayIdent": "RPA6021", | |
"distance": { | |
"elapsed": null, | |
"remaining": null, | |
"actual": null | |
}, | |
"diverted": false, | |
"flightId": "RPA6021-1545460022-airline-0182", | |
"flightPlan": { | |
"speed": 387, | |
"altitude": 200, | |
"route": "NEION J223 CORDS CFB V29 SYR", | |
"directDistance": 181, | |
"plannedDistance": 207, | |
"departure": 1545687600, | |
"ete": 4260, | |
"fuelBurn": { | |
"gallons": 530, | |
"pounds": 3600 | |
} | |
}, | |
"flightStatus": "", | |
"friendlyIdent": "Republic 6021", | |
"fruOverride": false, | |
"ga": false, | |
"gateArrivalTimes": { | |
"scheduled": 1545692460, | |
"estimated": null, | |
"actual": null | |
}, | |
"gateDepartureTimes": { | |
"scheduled": 1545687000, | |
"estimated": null, | |
"actual": null | |
}, | |
"groundspeed": null, | |
"heading": null, | |
"historical": false, | |
"iataIdent": "YX6021", | |
"icon": "airliner", | |
"ident": "RPA6021", | |
"inboundFlight": { | |
"flightId": "RPA6022-1545460022-airline-0170:0", | |
"linkUrl": "/live/flight/id/RPA6022-1545460022-airline-0170%3a0" | |
}, | |
"internal": null, | |
"landingTimes": { | |
"scheduled": 1545691860, | |
"estimated": 1545691860, | |
"actual": null | |
}, | |
"links": { | |
}, | |
"myAlerts": { | |
"editAlert": "", | |
"advancedAlert": "/account/manage/alerts/0/add?m_ident=DAL6021;date_start=2018-12-24;date_end=2018-12-24" | |
}, | |
"myFlightAware": null, | |
"origin": { | |
"TZ": ":America/New_York", | |
"isValidAirportCode": true, | |
"altIdent": null, | |
"iata": "JFK", | |
"friendlyName": "John F Kennedy Intl", | |
"friendlyLocation": "New York, NY", | |
"coord": [ | |
-73.7787, | |
40.6399 | |
], | |
"icao": "KJFK", | |
"gate": "B47", | |
"terminal": "4", | |
"delays": null | |
}, | |
"poweredOff": null, | |
"poweredOn": null, | |
"redactedBlockedTail": null, | |
"redactedCallsign": null, | |
"redactedTail": true, | |
"remarks": null, | |
"resultUnknown": false, | |
"showSurfaceTimes": false, | |
"takeoffTimes": { | |
"scheduled": 1545687600, | |
"estimated": 1545687600, | |
"actual": null | |
}, | |
"taxiIn": null, | |
"taxiOut": null, | |
"thumbnail": { | |
"imageUrl": "https://e1.flightcdn.com/images/airline_logos/90p/RPA.png", | |
"linkUrl": "/live/fleet/RPA" | |
}, | |
"timestamp": null, | |
"track": null, | |
"updateType": "", | |
"waypoints": [ | |
[ | |
-73.78, | |
40.64 | |
] | |
], | |
"weather": null | |
} | |
} | |
} | |
-- FlightResultUnknown example -- | |
{ | |
"version": "2.9", | |
"summary": false, | |
"flights": { | |
"ACA8118-1425148009-airline-0033": { | |
"activityLog": { | |
"flights": [], | |
"additionalLogRowsAvailable": false | |
}, | |
"adhoc": false, | |
"adhocAvailable": false, | |
"aircraft": { | |
"type": "DH8C", | |
"tail": null, | |
"owner": null, | |
"ownerLocation": null, | |
"owner_type": null, | |
"canMessage": false, | |
"lifeguard": false, | |
"heavy": false, | |
"friendlyType": "de Havilland Dash 8-300 (twin-turboprop)" | |
}, | |
"aireonCandidate": false, | |
"airline": { | |
"fullName": "Air Canada", | |
"shortName": "Air Canada", | |
"icao": "ACA", | |
"iata": "AC", | |
"callsign": "Air Canada", | |
"url": "http://www.aircanada.com/" | |
}, | |
"altitude": null, | |
"altitudeChange": null, | |
"atcIdent": null, | |
"averageDelays": null, | |
"blocked": false, | |
"blockedForUser": false, | |
"blockMessage": null, | |
"cabinInfo": { | |
"text": null, | |
"links": null | |
}, | |
"cancelled": false, | |
"cockpitInformation": null, | |
"codeShare": { | |
"ident": "ACA8118", | |
"displayIdent": "ACA8118", | |
"iataIdent": "AC8118", | |
"airline": { | |
"fullName": "Air Canada", | |
"shortName": "Air Canada", | |
"icao": "ACA", | |
"iata": "AC", | |
"callsign": "Air Canada", | |
"url": "http://www.aircanada.com/" | |
}, | |
"friendlyIdent": "Air Canada 8118", | |
"thumbnail": { | |
"imageUrl": "https://e1.flightcdn.com/images/airline_logos/90p/ACA.png", | |
"linkUrl": "/live/fleet/ACA" | |
}, | |
"links": { | |
"permanent": "/live/flight/ACA8118/history/20150228/1830Z/KPDX/CYVR", | |
"staticMap": "/ajax/flight/map/ACA8118/20150228/1830Z/KPDX/CYVR/", | |
"trackLog": "/live/flight/ACA8118/history/20150228/1830Z/KPDX/CYVR/tracklog", | |
"flightHistory": "/live/flight/ACA8118/history", | |
"buyFlightHistory": "/live/flight/ACA8118/history/buy", | |
"reportInaccuracies": "/live/report/ACA8118/history/20150228/1830Z/KPDX/CYVR", | |
"facebook": "https://facebook.com/sharer.php?u=https://flightaware.com/live/flight/ACA8118/history/20150228/1830Z/KPDX/CYVR", | |
"twitter": "https://twitter.com/intent/tweet?url=https://flightaware.com/live/flight/ACA8118/history/20150228/1830Z/KPDX/CYVR&via=FlightAware" | |
} | |
}, | |
"coord": null, | |
"destination": { | |
"TZ": ":America/Vancouver", | |
"isValidAirportCode": true, | |
"altIdent": null, | |
"iata": "YVR", | |
"friendlyName": "Vancouver Int'l", | |
"friendlyLocation": "Vancouver, Canada", | |
"coord": [ | |
-123.1844, | |
49.1939 | |
], | |
"icao": "CYVR", | |
"gate": "E95", | |
"terminal": null, | |
"delays": null | |
}, | |
"displayIdent": "ACA8118", | |
"distance": { | |
"elapsed": 218, | |
"remaining": 0, | |
"actual": null | |
}, | |
"diverted": false, | |
"flightId": "ACA8118-1425148009-airline-0033", | |
"flightPlan": { | |
"speed": 202, | |
"altitude": null, | |
"route": "", | |
"directDistance": 218, | |
"plannedDistance": null, | |
"departure": 1425148200, | |
"ete": 3900, | |
"fuelBurn": { | |
"gallons": 360, | |
"pounds": 2400 | |
} | |
}, | |
"flightStatus": "fru", | |
"friendlyIdent": "Air Canada 8118", | |
"fruOverride": false, | |
"ga": false, | |
"gateArrivalTimes": { | |
"scheduled": 1425152700, | |
"estimated": 1425152280, | |
"actual": null | |
}, | |
"gateDepartureTimes": { | |
"scheduled": 1425148200, | |
"estimated": null, | |
"actual": 1425147840 | |
}, | |
"globalIdent": false, | |
"globalScheduledFBO": false, | |
"globalServices": {}, | |
"groundspeed": null, | |
"heading": null, | |
"historical": true, | |
"iataIdent": "AC8118", | |
"icon": "twin_large", | |
"ident": "ACA8118", | |
"inboundFlight": null, | |
"internal": null, | |
"landingTimes": { | |
"scheduled": 1425152100, | |
"estimated": 1425151680, | |
"actual": null | |
}, | |
"links": { | |
"permanent": "/live/flight/ACA8118/history/20150228/1830Z/KPDX/CYVR", | |
"staticMap": "/ajax/flight/map/ACA8118/20150228/1830Z/KPDX/CYVR/", | |
"trackLog": "/live/flight/ACA8118/history/20150228/1830Z/KPDX/CYVR/tracklog", | |
"flightHistory": "/live/flight/ACA8118/history", | |
"buyFlightHistory": "/live/flight/ACA8118/history/buy", | |
"reportInaccuracies": "/live/report/ACA8118/history/20150228/1830Z/KPDX/CYVR", | |
"facebook": "https://facebook.com/sharer.php?u=https://flightaware.com/live/flight/ACA8118/history/20150228/1830Z/KPDX/CYVR", | |
"twitter": "https://twitter.com/intent/tweet?url=https://flightaware.com/live/flight/ACA8118/history/20150228/1830Z/KPDX/CYVR&via=FlightAware" | |
}, | |
"myAlerts": { | |
"editAlert": "", | |
"advancedAlert": "/account/manage/alerts/0/add?m_ident=ACA8118;date_start=2015-02-28;date_end=2015-02-28" | |
}, | |
"myFlightAware": null, | |
"origin": { | |
"TZ": ":America/Los_Angeles", | |
"isValidAirportCode": true, | |
"altIdent": null, | |
"iata": "PDX", | |
"friendlyName": "Portland Intl", | |
"friendlyLocation": "Portland, OR", | |
"coord": [ | |
-122.5969, | |
45.5887 | |
], | |
"icao": "KPDX", | |
"gate": "E7", | |
"terminal": null, | |
"delays": null | |
}, | |
"poweredOff": null, | |
"poweredOn": null, | |
"redactedBlockedTail": null, | |
"redactedCallsign": null, | |
"redactedTail": true, | |
"relatedThumbnails": [ | |
{ | |
"thumbnail": "https://photos-e1.flightcdn.com/photos/retriever/4b098b4b26627daa9bab4caba8bcb945fe291347", | |
"target": "/photos/view/7623064-1b2bb0d2b8a5b775b3fa4f0b03e39fd148408c2f" | |
}, | |
{ | |
"thumbnail": "https://photos-e1.flightcdn.com/photos/retriever/cacb51eb50f9d3b0f6879e37b03a85cf9e387cba", | |
"target": "/photos/view/886540-9c121c79e1b702ec59ce0080d6a8b00e0bc765dc" | |
}, | |
{ | |
"thumbnail": "https://photos-e1.flightcdn.com/photos/retriever/03ee996763c152f783e604e9bac27fcd45eaca6d", | |
"target": "/photos/view/1944458-7c93a9b014607b30caaff444cdb427f9e62b698e" | |
}, | |
{ | |
"thumbnail": "https://photos-e1.flightcdn.com/photos/retriever/57d81ec86e6d1b238a7cea57aa247f7d2164999e", | |
"target": "/photos/view/495518-a194710ff48e9e98833b22e818802e85d94a55a4" | |
}, | |
{ | |
"thumbnail": "https://photos-e1.flightcdn.com/photos/retriever/d3314010bedc1c0fd260e8df7e070eb38d366919", | |
"target": "/photos/view/210344-babb486436db81de16cf50b7c4e36249a824a82b" | |
}, | |
{ | |
"thumbnail": "https://photos-e1.flightcdn.com/photos/retriever/997b2cbf8d9b01ca9d1d51865a01ff7caf4391ed", | |
"target": "/photos/view/495518-c12eb6d880438b7dded71cd5f01c7d3984e4c375" | |
}, | |
{ | |
"thumbnail": "https://photos-e1.flightcdn.com/photos/retriever/bf9a16cad8eaacaaa2ae247d1c067f2903d8d1cc", | |
"target": "/photos/view/495518-59d1905b73c0be23b0cbda1b7144f8ae787368d7" | |
}, | |
{ | |
"thumbnail": "https://photos-e1.flightcdn.com/photos/retriever/d8743bf3e194389a4359967628ad804306b12923", | |
"target": "/photos/view/3008590-e40c522614c9ac34b2828cb9cf05406444d235a7" | |
}, | |
{ | |
"thumbnail": "https://photos-e1.flightcdn.com/photos/retriever/3f658712776095afb2ec4c1572f60f205ec4eb6a", | |
"target": "/photos/view/3874346-94d96354c634a14cd269572409e09ff9006b13eb" | |
}, | |
{ | |
"thumbnail": "https://photos-e1.flightcdn.com/photos/retriever/cb1032275d03e0e9f69229532c4d3b2b7c90e82f", | |
"target": "/photos/view/495518-53363698421a5a754939a55891c8c2fed5ff522a" | |
}, | |
{ | |
"thumbnail": "https://photos-e1.flightcdn.com/photos/retriever/da1e46d7d4b4aa2b7c0d16ad5fa116c0bfee1f45", | |
"target": "/photos/view/495518-4b0565a09106ab7a89496af8c6ba91ce91e0b48c" | |
}, | |
{ | |
"thumbnail": "https://photos-e1.flightcdn.com/photos/retriever/5ecdaac69107e1fc33a99ffc4001ff8f174730ed", | |
"target": "/photos/view/1005405-dae6639f815e06b9119e086848edad01ec8490fb" | |
} | |
], | |
"remarks": null, | |
"resultUnknown": true, | |
"showSurfaceTimes": false, | |
"takeoffTimes": { | |
"scheduled": 1425148200, | |
"estimated": 1425148200, | |
"actual": 1425148200 | |
}, | |
"taxiIn": null, | |
"taxiOut": null, | |
"thumbnail": { | |
"imageUrl": "https://e1.flightcdn.com/images/airline_logos/90p/ACA.png", | |
"linkUrl": "/live/fleet/ACA" | |
}, | |
"timestamp": 1425152640, | |
"track": [ | |
{ | |
"timestamp": 1425148440, | |
"coord": [ | |
-122.6349, | |
45.8376 | |
], | |
"alt": 0, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
}, | |
{ | |
"timestamp": 1425148763, | |
"coord": [ | |
-122.6793, | |
46.1251 | |
], | |
"alt": null, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
}, | |
{ | |
"timestamp": 1425149086, | |
"coord": [ | |
-122.7242, | |
46.4125 | |
], | |
"alt": null, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
}, | |
{ | |
"timestamp": 1425149409, | |
"coord": [ | |
-122.7695, | |
46.7 | |
], | |
"alt": null, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
}, | |
{ | |
"timestamp": 1425149732, | |
"coord": [ | |
-122.8153, | |
46.9875 | |
], | |
"alt": null, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
}, | |
{ | |
"timestamp": 1425150055, | |
"coord": [ | |
-122.8616, | |
47.2749 | |
], | |
"alt": null, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
}, | |
{ | |
"timestamp": 1425150378, | |
"coord": [ | |
-122.9085, | |
47.5623 | |
], | |
"alt": null, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
}, | |
{ | |
"timestamp": 1425150702, | |
"coord": [ | |
-122.9558, | |
47.8497 | |
], | |
"alt": null, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
}, | |
{ | |
"timestamp": 1425151025, | |
"coord": [ | |
-123.0037, | |
48.1371 | |
], | |
"alt": null, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
}, | |
{ | |
"timestamp": 1425151348, | |
"coord": [ | |
-123.0521, | |
48.4244 | |
], | |
"alt": null, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
}, | |
{ | |
"timestamp": 1425151671, | |
"coord": [ | |
-123.101, | |
48.7118 | |
], | |
"alt": null, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
}, | |
{ | |
"timestamp": 1425151994, | |
"coord": [ | |
-123.1505, | |
48.9991 | |
], | |
"alt": null, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
}, | |
{ | |
"timestamp": 1425152640, | |
"coord": [ | |
-123.1844, | |
49.1939 | |
], | |
"alt": 0, | |
"gs": null, | |
"type": "TP", | |
"isolated": false | |
} | |
], | |
"updateType": "", | |
"waypoints": [], | |
"weather": null | |
} | |
} | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment