1 /** 2 Implemented in the D Programming Language 2016 by Laeeth Isharc and Kaleidic Associates Advisory UK Limited 3 MIT Licensed 4 Use at your own risk - this is not tested at all. 5 6 API for pushover notification API 7 https://pushover.net/ 8 */ 9 module symmetry.api.pushover; 10 11 12 import std.datetime: SysTime, DateTime; 13 import std.json: JSONValue; 14 import std.net.curl: HTTP; 15 16 17 immutable PushoverMessageSounds = [ 18 "pushover", 19 "bike", 20 "bugle", 21 "cashregister", 22 "classical", 23 "cosmic", 24 "falling", 25 "gamelan", 26 "incoming", 27 "intermission", 28 "magic", 29 "mechanical", 30 "pianobar", 31 "siren", 32 "spacealarm", 33 "tugboat", 34 "alien", 35 "climb", 36 "persistent", 37 "echo", 38 "updown", 39 "none" 40 ]; 41 42 43 string joinUrl(string url, string endpoint) 44 { 45 import std.exception: enforce; 46 enforce(url.length > 0, "broken url"); 47 if (url[$-1] == '/') 48 url = url[0..$-1]; 49 return url ~ "/" ~ endpoint; 50 } 51 52 53 struct PushoverAPI 54 { 55 string endpoint = "https://api.pushover.net/1/"; 56 string token; 57 string userKey = null; 58 59 this(string token) 60 { 61 this.token = token; 62 } 63 this(string token, string userKey) 64 { 65 this.token = token; 66 this.userKey = userKey; 67 } 68 } 69 70 enum PushoverMessagePriority 71 { 72 lowest = -2, 73 low = -1, 74 normal = 0, 75 high = 1, 76 emergency = 2, 77 } 78 79 80 struct PushoverMessage 81 { 82 import std.typecons: Nullable; 83 import std.datetime: SysTime; 84 85 string messageText = null; 86 string device = null; 87 string title = null; 88 string url = null; 89 string urlTitle = null; 90 Nullable!PushoverMessagePriority priority; 91 Nullable!SysTime timeStamp; 92 string sound = null; 93 94 this(string messageText) 95 { 96 this.messageText = messageText; 97 } 98 } 99 100 auto ref setMessage(ref PushoverMessage message, string messageText) 101 { 102 message.messageText = messageText; 103 return message; 104 } 105 106 auto ref setDevice(ref PushoverMessage message, string device) 107 { 108 message.device = device; 109 return message; 110 } 111 112 auto ref setTitle(ref PushoverMessage message, string title) 113 { 114 message.title = title; 115 return message; 116 } 117 118 auto ref setUrl(ref PushoverMessage message, string url) 119 { 120 message.url = url; 121 return message; 122 } 123 124 auto ref setUrlTitle(ref PushoverMessage message, string urlTitle) 125 { 126 message.urlTitle = urlTitle; 127 return message; 128 } 129 auto ref setPriority(ref PushoverMessage message, PushoverMessagePriority priority) 130 { 131 message.priority = priority; 132 return message; 133 } 134 135 auto ref setPriority(ref PushoverMessage message, int priority) 136 { 137 import std.conv: to; 138 message.priority = priority.to!PushoverMessagePriority; 139 return message; 140 } 141 142 auto ref setTimeStamp(ref PushoverMessage message, DateTime timeStamp) 143 { 144 message.timeStamp = cast(SysTime) timeStamp; 145 return message; 146 } 147 148 auto ref setTimeStamp(ref PushoverMessage message, SysTime timeStamp) 149 { 150 message.timeStamp = timeStamp; 151 return message; 152 } 153 154 auto ref setSound(ref PushoverMessage message, string sound) 155 { 156 message.sound = sound; 157 return message; 158 } 159 160 auto sendMessage(PushoverAPI api, PushoverMessage message, string user = null) 161 { 162 JSONValue params; 163 if (user is null) 164 params["user"] = api.userKey; 165 params["message"] = message.messageText; 166 if (message.device !is null) 167 params["device"] = message.device; 168 if (message.title !is null) 169 params["title"] = message.title; 170 if (message.url !is null) 171 params["url"] = message.url; 172 if (message.urlTitle !is null) 173 params["url_title"] = message.urlTitle; 174 if (!message.priority.isNull) 175 params["priority"] = message.priority.get; 176 if (!message.timeStamp.isNull) 177 params["time_stamp"] = message.timeStamp.get.toUnixTime; 178 if (message.sound !is null) 179 params["sound"] = message.sound; 180 return api.request("messages.json", HTTP.Method.post, params); 181 } 182 183 auto listGroupMembers(PushoverAPI api, string groupKey) 184 { 185 return api.request("groups/" ~ groupKey ~ ".json", HTTP.Method.get); 186 } 187 188 auto addUserToGroup(PushoverAPI api, string userKey, string groupKey, string device = null, string memo = null) 189 { 190 JSONValue params; 191 params["user"] = userKey; 192 if (device !is null) 193 params["device"] = device; 194 if (memo !is null) 195 params["memo"] = memo; 196 return api.request("groups/" ~ groupKey ~ "/add_user.json", HTTP.Method.post); 197 } 198 199 auto removeUserFromGroup(PushoverAPI api, string userKey, string groupKey) 200 { 201 JSONValue params; 202 params["user"] = userKey; 203 return api.request("groups/" ~ groupKey ~ "/delete_user.json", HTTP.Method.post); 204 } 205 206 auto disableUser(PushoverAPI api, string userKey, string groupKey) 207 { 208 JSONValue params; 209 params["user"] = userKey; 210 return api.request("groups/" ~ groupKey ~ "/disable_user.json", HTTP.Method.post); 211 } 212 213 auto enableUser(PushoverAPI api, string userKey, string groupKey) 214 { 215 JSONValue params; 216 params["user"] = userKey; 217 return api.request("groups/" ~ groupKey ~ "/enable_user.json", HTTP.Method.post); 218 } 219 220 auto renameGroup(PushoverAPI api, string oldName, string newName) 221 { 222 JSONValue params; 223 params["name"] = newName; 224 return api.request("groups/" ~ oldName ~ "/rename.json", HTTP.Method.post); 225 } 226 auto listSounds(PushoverAPI api) 227 { 228 return api.request("sounds.json", HTTP.Method.get); 229 } 230 231 auto validate(PushoverAPI api, string user, string device = null) 232 { 233 JSONValue params; 234 params["user"] = user; 235 if(device.length>0) 236 params["device"] = device; 237 return api.request("users/validate.json", HTTP.Method.post, params); 238 } 239 240 auto checkReceipt(PushoverAPI api, string receipt) 241 { 242 return api.request("receipts/" ~ receipt ~ ".json"); 243 } 244 245 auto cancelEmergencyDelivery(PushoverAPI api, string receipt) 246 { 247 return api.request("receipts/" ~ receipt ~ "/cancel.json"); 248 } 249 250 auto assignLicense(PushoverAPI api, string email = null, string os = null) 251 { 252 JSONValue params; 253 if (email !is null) 254 params["email"] = email; 255 if (os !is null) 256 params["os"] = os; 257 return api.request("licenses/assign.json"); 258 } 259 260 string stripQuotes(string s) 261 { 262 if (s.length < 2) 263 return s; 264 if (s[0] == '"') 265 s = s[1..$]; 266 if (s.length < 1) 267 return s; 268 if (s[$-1] == '"') 269 s = s[0..$-1]; 270 return s; 271 } 272 273 JSONValue request(PushoverAPI api, 274 string url, 275 HTTP.Method method = HTTP.Method.get, 276 JSONValue params = JSONValue(null)) 277 { 278 import std.array: appender; 279 import std.uri: encodeComponent; 280 import std.conv: to; 281 import std.algorithm: canFind; 282 import std.exception: enforce; 283 import std.stdio: writefln; 284 import std.json: parseJSON; 285 286 enforce(api.token.length > 0, "no token provided"); 287 auto paramsData = appender!string; 288 paramsData.put("token="); 289 paramsData.put(api.token.encodeComponent); 290 paramsData.put("&"); 291 if (!params.object.keys.canFind("user")) 292 { 293 paramsData.put("user="); 294 paramsData.put(api.userKey.encodeComponent); 295 paramsData.put("&"); 296 } 297 foreach(i, param; params.object.keys) 298 { 299 if (i > 0) 300 paramsData.put("&"); 301 paramsData.put(param.to!string.encodeComponent); 302 paramsData.put("="); 303 paramsData.put(params[param].toString.stripQuotes.encodeComponent); 304 } 305 306 debug 307 { 308 writefln("%s", params.toString); 309 writefln("%s", paramsData.data.to!string); 310 } 311 url = api.endpoint.joinUrl(url); 312 auto client = HTTP(url); 313 auto response = appender!(ubyte[]); 314 client.method = method; 315 client.setPostData(cast(void[])paramsData.data, "application/x-www-form-urlencoded"); 316 client.onReceive = (ubyte[] data) 317 { 318 response.put(data); 319 return data.length; 320 }; 321 client.perform(); // rely on curl to throw exceptions on 204, >=500 322 return parseJSON(cast(string)response.data); 323 } 324 325 version(StandAlone) 326 { 327 void main(string[] args) 328 { 329 import std.process : environment; 330 import std.stdio: writefln; 331 332 auto api = PushoverAPI(environment.get("PUSHOVER_API_TOKEN",""),environment.get("PUSHOVER_API_KEY","")); 333 PushoverMessage message; 334 message = message.setMessage("message text") 335 .setTitle("message title") 336 .setUrl("google.com") 337 .setUrlTitle("google") 338 .setPriority(PushoverMessagePriority.high) 339 .setTimeStamp(DateTime(2013, 1, 1)); 340 auto ret = api.sendMessage(message); 341 writefln("%s", ret["status"]); 342 writefln("%s", ret["request"]); 343 } 344 }