1 /** 2 Pushover Dlang API by Laeeth Isharc and Kaleidic Associates Advisory Limited 3 2016, 2017 4 5 Beta bindings for https://pushover.net/api notification API 6 7 Generated documentation http://pushover.code.kaleidic.io 8 9 "Pushover uses a simple, versioned REST API to receive messages from your application and send them to devices running device clients. 10 To simplify the user registration process and usage of our API, there are no complicated out-of-band authentication mechanisms or 11 per-call signing libraries required, such as OAuth. HTTP libraries available in just about every language, or even from the command line, 12 can be used without any custom modules or extra dependencies needed. See our FAQ for examples in different programming languages." 13 14 Boost-licensed. Use at your peril. 15 */ 16 /** 17 Example: 18 --- 19 import kaleidic.api.pushover; 20 import std.datetime:Clock; 21 22 enum applicationKey = "set me".ApplicationToken; 23 enum targetUserKey = "set me".UserKey; 24 enum groupKey = "set me".GroupKey; 25 enum targetUserMemo ="memo field here"; 26 27 auto api=PushoverAPI(applicationToken); 28 writefln("validate target user: %s",api.validate(targetUserKey)); 29 writeln("result of adding target user to group:", 30 api.addUserToGroup( 31 targetUserKey, 32 groupKey, 33 null.DeviceName, 34 targetUserMemo) 35 ); 36 PushoverMessage message; 37 38 message=message.setMessage("as the CNBC anchor said, is buying GS here like D&G on sale?") 39 .setTitle("Kaleidic Market Alert - GS") 40 .setUrl("kaleidic.io") 41 .setUrlTitle("GS chart") 42 .setPriority(PushoverMessagePriority.high) 43 .setTimeStamp(Clock.currTime()); 44 writefln("%s",message); 45 auto ret=api.sendMessage(message,targetUserKey); 46 writefln("message status: %s",ret["status"]); 47 writefln("message request: %s",ret["request"]); 48 --- 49 */ 50 51 module kaleidic.api.pushover; 52 import std.stdio; 53 import std.json; 54 import std.net.curl; 55 import std.exception:Exception,enforce,assumeUnique; 56 import std.conv:to; 57 import std.algorithm:countUntil,map,each; 58 import std.traits:EnumMembers; 59 import std.array:array,appender; 60 import std.format:format; 61 import std.variant:Algebraic; 62 import std.typecons:Nullable; 63 import std.datetime:SysTime,DateTime; 64 65 /// 66 static this() 67 { 68 PushoverMessageSounds=[ "pushover", 69 "bike", 70 "bugle", 71 "cashregister", 72 "classical", 73 "cosmic", 74 "falling", 75 "gamelan", 76 "incoming", 77 "intermission", 78 "magic", 79 "mechanical", 80 "pianobar", 81 "siren", 82 "spacealarm", 83 "tugboat", 84 "alien", 85 "climb", 86 "persistent", 87 "echo", 88 "updown", 89 "none" ]; 90 91 } 92 93 /// 94 string joinUrl(string url, string endpoint) 95 { 96 enforce(url.length>0, "broken url"); 97 if (url[$-1]=='/') 98 url=url[0..$-1]; 99 return url~"/"~endpoint; 100 } 101 102 /// 103 struct ApplicationKey 104 { 105 string key; 106 alias key this; 107 } 108 109 /// 110 struct UserKey 111 { 112 string key; 113 alias key this; 114 } 115 116 /// 117 struct GroupKey 118 { 119 string key; 120 alias key this; 121 } 122 123 /// 124 struct APIToken 125 { 126 string token; 127 alias token this; 128 } 129 130 /// 131 struct DeviceName 132 { 133 string name; 134 alias name this; 135 } 136 137 /// 138 struct PushoverAPI 139 { 140 string endpoint = "https://api.pushover.net/1/"; 141 APIToken token; 142 UserKey userKey=null.UserKey; 143 144 this(APIToken token) 145 { 146 this.token=token; 147 } 148 this(APIToken token, UserKey userKey) 149 { 150 this.token=token; 151 this.userKey=userKey; 152 } 153 } 154 155 /// 156 enum PushoverMessagePriority 157 { 158 lowest=-2, 159 low=-1, 160 normal=0, 161 high=1, 162 emergency=2, 163 } 164 165 /// 166 string[] PushoverMessageSounds; 167 168 /// 169 struct PushoverMessage 170 { 171 string messageText=null; 172 DeviceName device=null.DeviceName; 173 string title=null; 174 string url=null; 175 string urlTitle=null; 176 Nullable!PushoverMessagePriority priority; 177 Nullable!SysTime timeStamp; 178 string sound=null; 179 180 this(string messageText) 181 { 182 this.messageText=messageText; 183 } 184 } 185 186 /// 187 auto ref setMessage(ref PushoverMessage message, string messageText) 188 { 189 message.messageText=messageText; 190 return message; 191 } 192 193 /// 194 auto ref setDevice(ref PushoverMessage message, DeviceName device) 195 { 196 message.device=device.name; 197 return message; 198 } 199 200 /// 201 auto ref setTitle(ref PushoverMessage message, string title) 202 { 203 message.title=title; 204 return message; 205 } 206 207 /// 208 auto ref setUrl(ref PushoverMessage message, string url) 209 { 210 message.url=url; 211 return message; 212 } 213 214 /// 215 auto ref setUrlTitle(ref PushoverMessage message, string urlTitle) 216 { 217 message.urlTitle=urlTitle; 218 return message; 219 } 220 221 /// 222 auto ref setPriority(ref PushoverMessage message, PushoverMessagePriority priority) 223 { 224 message.priority=priority; 225 return message; 226 } 227 228 /// 229 auto ref setPriority(ref PushoverMessage message, int priority) 230 { 231 message.priority=priority.to!PushoverMessagePriority; 232 return message; 233 } 234 235 /// 236 auto ref setTimeStamp(ref PushoverMessage message, DateTime timeStamp) 237 { 238 message.timeStamp=cast(SysTime) timeStamp; 239 return message; 240 } 241 242 /// 243 auto ref setTimeStamp(ref PushoverMessage message, SysTime timeStamp) 244 { 245 message.timeStamp=timeStamp; 246 return message; 247 } 248 249 /// 250 auto ref setSound(ref PushoverMessage message, string sound) 251 { 252 message.sound=sound; 253 return message; 254 } 255 256 /// 257 auto sendMessage(PushoverAPI api, PushoverMessage message, UserKey user=null.UserKey) 258 { 259 JSONValue params = ["message": message.messageText]; 260 if (user.key.length==0) 261 { 262 enforce(api.userKey.key.length>0,"PushOverAPI.sendMessage - you must specify either a user in the sendMessage call or in the API constructor"); 263 params["user"]=api.userKey.key; 264 } 265 else 266 { 267 params["user"] = user.key; 268 } 269 if (message.device.name !is null) 270 params["device"]=message.device.name; 271 if (message.title !is null) 272 params["title"] = message.title; 273 if (message.url !is null) 274 params["url"] = message.url; 275 if (message.urlTitle !is null) 276 params["url_title"] = message.urlTitle; 277 if (!message.priority.isNull) 278 params["priority"] = message.priority; 279 if (!message.timeStamp.isNull) 280 params["time_stamp"] = message.timeStamp.toUnixTime; 281 if (message.sound !is null) 282 params["sound"] = message.sound; 283 return api.request("messages.json", HTTP.Method.post,params); 284 } 285 286 /// 287 auto listGroupMembers(PushoverAPI api, GroupKey groupKey) 288 { 289 return api.request("groups/"~groupKey~".json",HTTP.Method.get); 290 } 291 292 293 /// 294 auto addUserToGroup(PushoverAPI api, UserKey userKey, GroupKey groupKey, DeviceName device=null.DeviceName, string memo=null) 295 { 296 import std.uri:encodeComponent; 297 JSONValue params; 298 params["user"] = userKey.key; 299 if (device.name !is null) 300 params["device"] = device.name; 301 if (memo !is null) 302 params["memo"] = memo; 303 return api.request("groups/"~groupKey.key.encodeComponent~"/add_user.json",HTTP.Method.post,params); 304 } 305 306 /// 307 auto removeUserFromGroup(PushoverAPI api, UserKey userKey, GroupKey groupKey) 308 { 309 import std.uri:encodeComponent; 310 JSONValue params; 311 params["user"]=userKey; 312 return api.request("groups/"~groupKey.key.encodeComponent~"/delete_user.json",HTTP.Method.post,params); 313 } 314 315 /// 316 auto disableUser(PushoverAPI api, UserKey userKey, GroupKey groupKey) 317 { 318 import std.uri:encodeComponent; 319 JSONValue params; 320 params["user"]=userKey; 321 return api.request("groups/"~groupKey.key.encodeComponent~"/disable_user.json",HTTP.Method.post,params); 322 } 323 324 /// 325 auto enableUser(PushoverAPI api, UserKey userKey, GroupKey groupKey) 326 { 327 import std.uri:encodeComponent; 328 JSONValue params =[ "user": userKey.key ]; 329 return api.request("groups/"~groupKey.key.encodeComponent~"/enable_user.json",HTTP.Method.post,params); 330 } 331 332 /// 333 auto renameGroup(PushoverAPI api, string oldName, string newName) 334 { 335 import std.uri:encodeComponent; 336 JSONValue params; 337 params["name"]=newName; 338 return api.request("groups/"~oldName.encodeComponent~"/rename.json",HTTP.Method.post,params); 339 } 340 341 /// 342 auto listSounds(PushoverAPI api) 343 { 344 return api.request("sounds.json",HTTP.Method.get); 345 } 346 347 /// 348 auto validate(PushoverAPI api, UserKey user, DeviceName device=null.DeviceName) 349 { 350 JSONValue params; 351 params["user"]=user.key; 352 if(device.length>0) 353 params["device"]=device.name; 354 return api.request("users/validate.json",HTTP.Method.post,params); 355 } 356 357 /// 358 auto checkReceipt(PushoverAPI api, string receipt) 359 { 360 import std.uri:encodeComponent; 361 return api.request("receipts/"~receipt.encodeComponent~".json"); 362 } 363 364 /// 365 auto cancelEmergencyDelivery(PushoverAPI api, string receipt) 366 { 367 import std.uri:encodeComponent; 368 return api.request("receipts/"~receipt.encodeComponent~"/cancel.json"); 369 } 370 371 /// 372 auto assignLicense(PushoverAPI api, string email=null, string os=null) 373 { 374 JSONValue params; 375 if (email !is null) 376 params["email"]=email; 377 if (os !is null) 378 params["os"]=os; 379 return api.request("licenses/assign.json"); 380 } 381 382 /// 383 string stripQuotes(string s) 384 { 385 if (s.length<2) 386 return s; 387 if (s[0]=='"') 388 s=s[1..$]; 389 if (s.length<1) 390 return s; 391 if (s[$-1]=='"') 392 s=s[0..$-1]; 393 return s; 394 } 395 396 /// 397 string fixUrl(string s) // sorry - not sure why urls are being escaped - kludge for now 398 { 399 import std.string:replace; 400 return s.replace("\\/","/"); 401 } 402 403 /// 404 auto request(PushoverAPI api, string url, HTTP.Method method=HTTP.Method.get, JSONValue params=JSONValue(null)) 405 { 406 import std.array:appender; 407 import std.uri:encodeComponent; 408 import std.conv:to; 409 import std.algorithm:canFind; 410 enforce(api.token.length>0,"no token provided"); 411 auto paramsData=appender!string; 412 paramsData.put("token="); 413 paramsData.put(api.token.encodeComponent); 414 paramsData.put("&"); 415 if (params.type != JSON_TYPE.OBJECT) 416 { 417 params=["user": api.userKey.key]; 418 } 419 else if (!params.object.keys.canFind("user")) 420 { 421 params["user"]=api.userKey.key; 422 } 423 424 425 if (params.type == JSON_TYPE.OBJECT) 426 { 427 foreach(i,param;params.object.keys) 428 { 429 if (i>0) 430 paramsData.put("&"); 431 paramsData.put(param.to!string.encodeComponent); 432 paramsData.put("="); 433 paramsData.put(param.to!string=="url"?params[param].toString.stripQuotes.fixUrl:params[param].toString.stripQuotes.encodeComponent); 434 } 435 } 436 debug 437 { 438 writefln("request: %s",paramsData.data); 439 } 440 url=api.endpoint.joinUrl(url); 441 auto client=HTTP(url); 442 auto response=appender!(ubyte[]); 443 client.method=method; 444 client.setPostData(cast(void[])paramsData.data,"application/x-www-form-urlencoded"); 445 client.onReceive = (ubyte[] data) 446 { 447 response.put(data); 448 return data.length; 449 }; 450 client.perform(); // rely on curl to throw exceptions on 204, >=500 451 debug writeln(cast(string)response.data); 452 return parseJSON(cast(string)response.data); 453 } 454 455 456 457 /// 458 unittest 459 { 460 import std.datetime:Clock; 461 462 enum applicationKey = "set me".ApplicationToken; 463 enum targetUserKey = "set me".UserKey; 464 enum groupKey = "set me".GroupKey; 465 enum targetUserMemo ="memo field here"; 466 467 auto api=PushoverAPI(applicationToken); 468 writefln("validate target user: %s",api.validate(targetUserKey)); 469 writeln("result of adding target user to group:",api.addUserToGroup(targetUserKey,groupKey,null.DeviceName,targetUserMemo)); 470 PushoverMessage message; 471 472 message=message.setMessage("as the CNBC anchor said, is buying GS here like D&G on sale?") 473 .setTitle("Kaleidic Market Alert - GS") 474 .setUrl("kaleidic.io") 475 .setUrlTitle("GS chart") 476 .setPriority(PushoverMessagePriority.high) 477 .setTimeStamp(Clock.currTime()); 478 writefln("%s",message); 479 auto ret=api.sendMessage(message,targetUserKey); 480 writefln("message status: %s",ret["status"]); 481 writefln("message request: %s",ret["request"]); 482 } 483