-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.js
219 lines (185 loc) · 6.58 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
// Grab the two libraries we use for getting data and sending messages
const request = require('request-promise-native');
const twilio = require('twilio');
// Get all the environment variables up front.
const darkskyApiKey = process.env.DARKSKY_KEY;
const darkskyLatLong = process.env.DARKSKY_LATLONG;
const twilioAccount = process.env.TWILIO_ACCOUNT;
const twilioToken = process.env.TWILIO_TOKEN;
const twilioFrom = process.env.TWILIO_FROM;
const twilioTo = process.env.TWILIO_TO;
// If any of them are not set, exit with an error now.
if (
!darkskyApiKey ||
!darkskyLatLong ||
!twilioAccount ||
!twilioToken ||
!twilioFrom ||
!twilioTo
) {
// Log the error.
console.error(
'All of the DARKSKY_KEY, DARKSKY_LATLONG, TWILIO_ACCOUNT, TWILIO_TOKEN, ' +
'TWILIO_FROM, TWILIO_TO environment variables must be set before ' +
'running the program.'
);
// Exit with a failure errorlevel.
process.exit(1);
}
// Thes are the icons we're going to use to represent darksky's machine icons
// in text messages.
const icons = {
'clear-day': '☀️',
'clear-night': '🌘',
rain: '🌧',
snow: '❄️',
sleet: 'e',
wind: '💨',
fog: '🌫',
cloudy: '☁️',
'partly-cloudy-day': '⛅️',
'partly-cloudy-night': '⛅️',
};
// These are some hardcoded endpoint variables that set up the forecast call.
const forecastEndpoint = 'https://api.darksky.net/forecast';
const queryParams = 'units=uk2&exclude=currently,minutely,alerts,flags';
// Calculate the offest required for daylight savings. All of the times are
// returned in +00:00 time, so we need to see what today's current offset away
// from that is.
const offset = new Date(Date.now()).getTimezoneOffset() * 60 * 1000;
// Given a parsed daily object from the JSON string returned by DarkSky, turn
// it into a textual summary.
const generateDailySummary = daily => {
// Get darksky's own summary to start with.
const text = daily.summary.toLowerCase();
// Get the day's temperature range.
const min = Math.floor(daily.temperatureMin);
const max = Math.floor(daily.temperatureMax);
// Get the day's 'feels-like' range.
const feelMin = Math.floor(daily.apparentTemperatureMin);
const feelMax = Math.floor(daily.apparentTemperatureMax);
// Get the chance of rain, or whatever.
const precipChance = Math.floor(daily.precipProbability * 100.0);
// Get the forecasted falling wet stuff.
let precipKind = daily.precipType;
// Check that they've told us what it is.
if (!precipKind) {
// Normally if there's 0% chance of anything, a sensible default is rain.
precipKind = 'rain';
}
// Get the sunrise and sunset times and apply the BST/DST offest to them.
const sunrise = new Date(
parseInt(daily.sunriseTime) * 1000 - offset
).toLocaleTimeString('en-GB');
const sunset = new Date(
parseInt(daily.sunsetTime) * 1000 - offset
).toLocaleTimeString('en-GB');
// Return one massive big string concatenated results message.
return (
'Good Morning! Your weather forcecast for today is ' +
text +
' The temperature will be ' +
min +
'–' +
max +
'°c, which will feel like ' +
feelMin +
'–' +
feelMax +
"°c. There's a " +
precipChance +
'% chance of ' +
precipKind +
" and today's sunrise is at " +
sunrise +
' with sunset due at ' +
sunset +
'.'
);
};
// Given a parsed hourly object from the JSON string returned by DarkSky, turn
// it into a textual summary.
const generateHourlySummary = hourly => {
// This is bad/dark magic. We're asking for a localeTime...
const time = new Date(
parseInt(hourly.time) * 1000 - offset
).toLocaleTimeString('en-GB');
// ...and then doing string splitting to make a human readable short-time.
const timeOfForecast = time.split(':')[0] + ' ' + time.split(' ')[1];
// Darksky give us the forecast to decimal places - whole numbers will do.
const forecastTemperature = Math.floor(hourly.temperature);
// Get the emoji version of darksky's icons
const forecastConditions = icons[hourly.icon];
// Return one massive big string concatenated results message.
return (
timeOfForecast + ' : ' + forecastTemperature + '°c ' + forecastConditions
);
};
// Make the Darksky API call and process the results into the body of our text
// message.
const generateSummary = async () => {
// Grab the API call's result as stringified JSON.
const result = await request(
forecastEndpoint +
'/' +
darkskyApiKey +
'/' +
darkskyLatLong +
'?' +
queryParams
);
// Turn the string into a JSON object.
const forecast = JSON.parse(result);
// Grab a summarised version of the first day in the results.
const dailySummary = generateDailySummary(forecast.daily.data[0]);
// Start the whole forecast off with that summary.
let summary = dailySummary + '\n';
// Get the forecasts for the next 10 hours.
for (let hourly of forecast.hourly.data.slice(0, 10)) {
// Turn each of those objects into summaries.
const hourlySummary = generateHourlySummary(hourly);
// Append them to the running summary variable.
summary = summary + '\n' + hourlySummary;
}
// Return back the biggest blog of emoji filled text you've ever seen.
return summary;
};
// Use Twilio to send a text message.
const sendMessage = async summary => {
// Create the client connection.
const client = twilio(twilioAccount, twilioToken);
// Create a message object and wait for it to send.
const message = await client.messages.create({
body: summary,
from: twilioFrom,
to: twilioTo,
});
// Return the response object for consumption.
return message;
};
// Our main function - it has to be inside a function and not bare in the file
// as we're using async/await calling.
const main = async () => {
// The API calls and the results parsing could die, so we should wrap them
// in a try/catch block.
try {
// Ask Darksky for a forecast and return a textual summary.
const summary = await generateSummary();
// Use Twilio to send it as an SMS.
const message = await sendMessage(summary);
// Check for error state in the message repsonse
if (message.errorCode != null || message.errorMessage != null) {
// If there is, log the error.
console.error('Error code : ' + message.errorCode);
console.error('Error message : ' + message.errorMessage);
// And exit with an errorlevel.
process.exit(2);
}
} catch (err) {
// If any errors occur report them to the user.
console.error(err);
}
};
// Run our main function - it uses async and await to avoid callback or
// promise hell.
main();