1 // Copyright (c) 2017 Matthew Brennan Jones <matthew.brennan.jones@gmail.com>
2 // Boost Software License - Version 1.0
3 // Get weather forecast with the D programming language
4 // https://github.com/workhorsy/d-weather-forecast
5 
6 /++
7 Get weather forecast with the D programming language. It first gets your longitude
8 and latitude using http://ipinfo.io. Then uses them to look up your
9 weather using http://forecast.weather.gov.
10 
11 Home page:
12 $(LINK https://github.com/workhorsy/d-weather-forecast)
13 
14 Version: 1.3.0
15 
16 License:
17 Boost Software License - Version 1.0
18 
19 Examples:
20 ----
21 import std.stdio : stdout, stderr;
22 import weather_forecast : getForecast, WeatherData;
23 
24 getForecast(delegate(WeatherData weather_data, Exception err) {
25 	if (err) {
26 		stderr.writefln("%s", err);
27 	} else {
28 		stdout.writefln("latitude: %s", weather_data.latitude);
29 		stdout.writefln("longitude: %s", weather_data.longitude);
30 		stdout.writefln("city: %s", weather_data.city);
31 		stdout.writefln("region: %s", weather_data.region);
32 		stdout.writefln("country: %s", weather_data.country);
33 		stdout.writefln("postal: %s", weather_data.postal);
34 		stdout.writefln("temperature: %s", weather_data.temperature);
35 		stdout.writefln("summary: %s", weather_data.summary);
36 	}
37 });
38 ----
39 +/
40 
41 module weather_forecast;
42 
43 
44 /++
45 Data gathered in WeatherData:
46 ----
47 struct WeatherData {
48 	string latitude;
49 	string longitude;
50 	string city;
51 	string region;
52 	string country;
53 	string postal;
54 	string temperature;
55 	string summary;
56 }
57 ----
58 +/
59 
60 struct WeatherData {
61 	string latitude;
62 	string longitude;
63 	string city;
64 	string region;
65 	string country;
66 	string postal;
67 	string temperature;
68 	string summary;
69 }
70 
71 /++
72 Returns the weather forecast using a callback.
73 
74 Params:
75  cb = The callback to fire when weather info has been downloaded. The callback
76 	sends the WeatherData data and an Exception if there was any problem.
77 +/
78 void getForecast(void delegate(WeatherData weather_data, Exception err) cb) {
79 	import std.stdio : stdout, stderr;
80 	import std.json : JSONValue, parseJSON;
81 	import std..string : chomp, format;
82 	import std.array : split;
83 	import std.conv : to;
84 	import ipinfo : getIpinfo, IpinfoData, httpGet;
85 
86 	WeatherData weather_data;
87 
88 	getIpinfo(delegate(IpinfoData ipinfo_data, Exception err) {
89 		if (err) {
90 			cb(WeatherData.init, err);
91 		} else {
92 			const string URL = "http://forecast.weather.gov/MapClick.php?lat=" ~ ipinfo_data.latitude ~ "&lon=" ~ ipinfo_data.longitude ~ "&FcstType=json";
93 
94 			httpGet(URL, delegate(int status, string response) {
95 				if (status != 200) {
96 					auto err = new Exception("Request for \"%s\" failed with status code: %s".format(URL, status));
97 					cb(WeatherData.init, err);
98 					return;
99 				}
100 
101 				try {
102 					JSONValue j = parseJSON(response);
103 
104 					weather_data.latitude = ipinfo_data.latitude;
105 					weather_data.longitude = ipinfo_data.longitude;
106 					weather_data.city = ipinfo_data.city;
107 					weather_data.region = ipinfo_data.region;
108 					weather_data.country = ipinfo_data.country;
109 					weather_data.postal = ipinfo_data.postal;
110 					weather_data.temperature = j["currentobservation"]["Temp"].str();
111 					weather_data.summary = j["data"]["weather"][0].str();
112 				} catch (Throwable) {
113 					auto err = new Exception("Failed to parse \"%s\" JSON response".format(URL));
114 					cb(WeatherData.init, err);
115 					return;
116 				}
117 
118 				cb(weather_data, null);
119 			});
120 		}
121 	});
122 }