Module bluevia
[hide private]
[frames] | no frames]

Source Code for Module bluevia

   1  #  
   2  # The MIT license 
   3  # 
   4  # Copyright (C) 2011 by Bernhard Walter ( @bernhard42 ) 
   5  #  
   6  # Permission is hereby granted, free of charge, to any person obtaining a copy 
   7  # of this software and associated documentation files (the "Software"), to deal 
   8  # in the Software without restriction, including without limitation the rights 
   9  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
  10  # copies of the Software, and to permit persons to whom the Software is 
  11  # furnished to do so, subject to the following conditions: 
  12   
  13  # The above copyright notice and this permission notice shall be included in 
  14  # all copies or substantial portions of the Software. 
  15   
  16  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
  17  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
  18  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
  19  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
  20  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
  21  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
  22  # THE SOFTWARE. 
  23  # 
  24   
  25  # 
  26  # Version 01.08.2011 
  27  # 
  28   
  29  import oauth2 as oauth 
  30  import httplib2, pickle, os, types, time, urllib, simplejson, uuid 
  31  from types import * 
  32  from bluevia_helpers import _parseAdResponse 
  33  from bluevia_helpers import _encodeMultipart 
  34  from bluevia_helpers import _decodeMultipart 
  35   
  36  # # # # # # # # # # # # # # # #  
  37  # base Class 
  38  # # # # # # # # # # # # # # # # 
  39   
40 -class BlueVia():
41 """ 42 The BlueVia base class. All other BlueVia classes are inherited from this class BlueVia. 43 44 Mainly Stores consumer and access_token, provides the generic _signAndSend(...) a debug() method 45 46 HOWTO USE 47 ========= 48 49 oAuth routines 50 -------------- 51 52 >>> o = bluevia.BlueViaOauth('<secret>', '<key>') 53 >>> o.fetch_request_token() 54 55 Returns the oAuth URL for user authorization 56 57 Successful authorization returns an oAuth verifier 58 59 >>> o.fetch_access_token("<verifier>") 60 >>> o.saveAccessToken("newtok.pkl") 61 62 63 SMS outbound routines 64 --------------------- 65 66 >>> s = bluevia.BlueViaOutboundSms() 67 >>> s.loadAccessToken("newtok.pkl") 68 >>> s.sendSMS([myMobileNumber], "Hallo Welt") 69 70 Returns the delivery URL 71 72 >>> s.deliveryStatus("<deliveryURL>") 73 74 SMS receive routines 75 -------------------- 76 77 For Sandbox testing use an app that can both send SMS and receive SMS with keyword "BlueViaRocks" 78 79 1) Send a fake SMS 80 81 >>> s = bluevia.BlueViaOutboundSms() 82 >>> s.loadAccessToken("smsmo.pkl") 83 >>> s.sendSMS(["445480605"], "SANDBlueViaRocks so much!") 84 85 2) Receive SMS with App 86 87 >>> i = bluevia.BlueViaInboundSMS() 88 >>> i.loadAccessToken("smsmo.pkl") 89 >>> i.receiveSMS("445480605") # UK shortcode 90 91 For live testing use a mobile from the developer (e.g. the one owning the application) 92 93 1) Send "TESTBlueViaRocks so much live!" to 445480605 94 95 2) Retrieve from Test System 96 97 >>> i = bluevia.BlueViaInboundSMS("") # set sandbox parameter to "" makes test calls 98 >>> i.loadAccessToken("smsmo.pkl") 99 >>> i.receiveSMS("445480605") 100 101 Location routines 102 ----------------- 103 104 >>> l = bluevia.BlueViaLocation() 105 >>> l.loadAccessToken("newtok.pkl") 106 >>> l.locateTerminal(): 107 108 User context routines 109 --------------------- 110 111 >>> u = bluevia.BlueViaUserContext() 112 >>> u.loadAccessToken("newtok.pkl") 113 >>> u.getInfo(): 114 115 Advertising routines 116 -------------------- 117 118 >>> a = bluevia.BlueViaAds("<adspace Id>") 119 >>> a.loadAccessToken("newtok.pkl") 120 >>> a.getAd_3l(keywordList = ["sport"]) 121 122 Payment routines 123 ---------------- 124 125 >>> p = bluevia.BlueViaPayment('<secret>', '<key>') 126 >>> p.fetch_request_token(<amount>, <currency>, <serviceId>, <serviceName>) 127 >>> p.fetch_access_token(<verifier>) 128 >>> p.savePaymentInfo("payment.pkl") # optional, token valid for 48 h 129 >>> p.loadPaymentInfo("payment.pkl") # optional 130 >>> p.issuePayment() 131 >>> p.checkPayment(<transactionId>) 132 """ 133 134 access_token = None 135 consumer = None 136 realm = None 137 environment = "" 138 version = None 139 debugFlag = False 140 141 http = httplib2.Http() 142 143
144 - def _signAndSend(self, requestUrl, method, token, parameters={}, body="", \ 145 extraHeaders={}, is_form_encoded = False):
146 """ 147 Generic method to call an oAuth authorized API in BlueVia including oAuth signature. 148 149 @param requestUrl: (string): The BlueVia URL 150 @param method: (string): HTTP method, "GET" or "POST" 151 @param token: (oauth.Token): Usually the Access Token. During oAuth Dance None or Request Token 152 153 @param parameters: (dict): Necessary call paramters, e.g. version, alt. Default: None 154 @param body: (string): Body of the HTTP call. Default: "" 155 @param extraHeaders: (dict): Some calls need extra headers, e.g. {"Content-Type":"application/json"}. Default: None 156 @param is_form_encoded: (boolean): If True parameters are send as form encoded HTTP body. DEFAULT: False 157 158 @return: (tuple): (HTTP response, HTTP response data) 159 """ 160 161 req = oauth.Request.from_consumer_and_token(self.consumer, token, method, requestUrl, \ 162 parameters, body, is_form_encoded) 163 req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), self.consumer, token) 164 165 headers = req.to_header(realm=self.realm) 166 if parameters.has_key("xoauth_apiName"): 167 headers['Authorization'] += ', xoauth_apiName="%s"' % parameters.get("xoauth_apiName") 168 169 if extraHeaders: 170 headers.update(extraHeaders) 171 172 if is_form_encoded: 173 # get version and alt parameter only 174 params = [p for p in parameters.items() if p[0] in ["version","alt"]] 175 else: 176 # remove oauth_ parameters like oauth_callback 177 params = [p for p in parameters.items() if p and p[0][:6] != "oauth_"] 178 179 query = None 180 if params: 181 query = "&".join(["%s=%s" % (p[0], p[1]) for p in params]) 182 if query: 183 requestUrl += "?" + query 184 185 if self.debugFlag: self._debug(requestUrl, query, headers, body, token, req) 186 187 response, content = self.http.request(requestUrl, method, body, headers) 188 189 if self.debugFlag: 190 print('response["status"] = %s' % response["status"]) 191 print('content =\n %s' % content) 192 193 return response, content
194 195
196 - def loadAccessToken(self, path):
197 """ 198 Load Consumer Credentials and Access Token from disk (pickle file). 199 200 @note: 201 Unencrypted storage. Use only for testing! 202 203 @param path: (string): Path to pickle file 204 205 @return: (boolean): True if successfully loaded 206 """ 207 208 if os.path.exists(path): 209 fd = open(path, "r") 210 self.consumer, self.access_token = pickle.load(fd) 211 fd.close() 212 return True 213 else: 214 return False
215 216
217 - def setConsumer(self, consumer):
218 """ 219 Set the Consumer credentials. 220 221 @param consumer: (oauth.Token): The consumer credentials as provided by getCosumer in class BlueViaOauth 222 """ 223 224 self.consumer = consumer
225 226
227 - def setAccessToken(self, access_token):
228 """ 229 Set the Access Token. 230 231 @param access_token: (oauth.Token): The oAuth access token as provided by getAccessToken in class BlueViaOauth 232 """ 233 234 self.access_token = access_token
235 236
237 - def hasCredentials(self):
238 """ 239 Check availability of access token 240 """ 241 242 return (self.consumer != None) and (self.access_token != None)
243 244
245 - def setDebug(self, dbgFlag):
246 """ 247 Set or unset the debug flag 248 249 @param dbgFlag: (boolean): If True debug information will be printed to stdout 250 """ 251 252 self.debugFlag = dbgFlag
253 254
255 - def _debug(self, requestUrl, query, headers, body, token, req):
256 """ 257 Prints aut anything relevant for oAuth debugging: URL, method, body, headers, signature base string, ... 258 259 @note: Internal method 260 """ 261 262 print("\nurl = " + requestUrl) 263 if not query: query = "" 264 print("\nquery = " + query) 265 print("\nhead = " + simplejson.dumps(headers, indent = 2).replace(", ", ",\n")) 266 try: 267 bstr = simplejson.dumps(simplejson.loads(body), indent = 2) 268 except: 269 bstr = body 270 print("\nbody = " + bstr) 271 sm = oauth.SignatureMethod_HMAC_SHA1() 272 key, base = sm.signing_base(req, self.consumer, token) 273 print("\noAuth signature components") 274 print("\nbase = " + base) 275 print("\nkey = " + key)
276 277 278 # # # # # # # # # # # # # # # # 279 # oAuth Class 280 # # # # # # # # # # # # # # # # 281
282 -class BlueViaOauth(BlueVia):
283 """ 284 This class provides the methods for the oAuth Dance. 285 It supports Out Of Band authorization as defined in oAuth 1.0a 286 """ 287
288 - def __init__(self, consumer_key, consumer_secret, realm="BlueVia"):
289 """ 290 Initialize the BlueViaOauth object 291 292 @param consumer_key: (string): Key of the Consumer Credentials 293 @param consumer_secret: (string): Secret of the Consumer Credentials 294 295 @param realm: (string): Realm string. Default: "BlueVia" 296 """ 297 298 self.realm = realm 299 self.consumer = oauth.Consumer(consumer_key, consumer_secret) 300 self.request_token_url = 'https://api.bluevia.com/services/REST/Oauth/getRequestToken' 301 self.access_token_url = 'https://api.bluevia.com/services/REST/Oauth/getAccessToken' 302 self.authorization_url = 'https://connect.bluevia.com/authorise' 303 self.request_token = None
304 305
306 - def fetch_request_token(self, callback="oob"):
307 """ 308 First call of the oAuth Dance. Provide the Consumer Credential and request the Request Token 309 310 @param callback: (string): The callback URL or "oob". Default: "oob" 311 312 @return: (tuple): (HTTP status, authorization URL). HTTP status == "200" for success 313 """ 314 315 316 response, content = self._signAndSend(self.request_token_url, "POST", None, parameters={"oauth_callback":callback}) 317 if response["status"] == '200': 318 self.request_token = oauth.Token.from_string(content) 319 return int(response["status"]), "%s?oauth_token=%s" % (self.authorization_url, self.request_token.key) 320 else: 321 return int(response["status"]), content
322 323
324 - def fetch_access_token(self, verifier):
325 """ 326 The final step of the oAuth Dance. Exchange the Request Token with the Access Token 327 328 @param verifier: (string): The oAuth verifier of the successful user authorization 329 330 @return: (string): HTTP status == "200" for success 331 """ 332 333 assert type(verifier) is StringType and verifier!= "", "Oauth 'verifier' must be a non empty string" 334 335 self.request_token.set_verifier(verifier) 336 response, content = self._signAndSend(self.access_token_url, "POST", self.request_token, parameters={}) 337 if response["status"] == '200': 338 self.access_token = oauth.Token.from_string(content) 339 return int(response["status"]) 340 else: 341 return int(response["status"]), content
342 343
344 - def saveAccessToken(self, path):
345 """ 346 Save the Access Token. 347 348 Note: Unencrypted storage. Use only during development 349 350 @param path: (string): Path to file on disk (pickle file) 351 """ 352 353 assert type(path) is StringType and path!= "", "'path' must be a non empty string" 354 355 fd = open(path, "w") 356 pickle.dump((self.consumer, self.access_token), fd) 357 fd.close()
358 359
360 - def getConsumer(self):
361 """ 362 Retrieve the Consumer Credentials 363 """ 364 365 return self.consumer
366 367
368 - def getAccessToken(self):
369 """ 370 Retrieve the Access Token 371 """ 372 373 return self.access_token
374 375 376 # # # # # # # # # # # # # # # # 377 # Outbound SMS Class 378 # # # # # # # # # # # # # # # # 379
380 -class BlueViaOutboundSms(BlueVia):
381 """ 382 The BlueVia class for sending and tracking SMS. 383 """ 384
385 - def __init__(self, sandbox = "_Sandbox", realm = "BlueVia", version="v1"):
386 """ 387 Initialize the BlueViaOutboundSms object 388 389 @param sandbox: (string): Indicates whether testing should be done in Sandbox mode. Use "" for real network access; Default: "_Sandbox" 390 @param realm: (string): Realm string; Default: "BlueVia" 391 @param version: (string): BlueVia API version; Default: "v1" 392 """ 393 394 self.environment = sandbox 395 self.realm = realm 396 self.version = version 397 self.outbound_sms_url = "https://api.bluevia.com/services/REST/SMS%s/outbound/requests"
398 399
400 - def sendSMS(self, addresses, message):
401 """ 402 Send SMS via BlueVia to one or more recipients 403 404 @param addresses: (array): An array of mobile numbers (string) in the form "44 (for UK) 7764735478" (Mobile number without first zero and no spaces) 405 @param message: (string): A maximum 160 char string containing the SMS message 406 407 @return: (tuple): (HTTP status, deliveryURL). HTTP status == "201" for success. Use deliverURL in method deliverStatus. 408 """ 409 410 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 411 assert (type(addresses) is ListType and addresses!=[]) or \ 412 (type(addresses) is StringType and addresses!="") , "'addresses' must be a non empty string or a non empty array of strings" 413 assert type(message) is StringType and message!= "", "'message' must be a non empty string" 414 415 if type(addresses) == types.StringType: 416 addresses = [addresses] 417 addrlist = ",".join(['{"phoneNumber":"%s"}' % a for a in addresses]) 418 if len(addresses) > 1: addrlist = '[%s]' % addrlist 419 420 body = '{"smsText": {"address": %s,"message": "%s", "originAddress": {"alias": "%s"}}}' \ 421 % (addrlist, message, self.access_token.key) 422 423 parameters = {"version":self.version, "alt":"json"} 424 425 response, content = self._signAndSend(self.outbound_sms_url % self.environment, "POST", self.access_token, \ 426 parameters=parameters, body=body, \ 427 extraHeaders={"Content-Type":"application/json;charset=UTF8"}) 428 if response["status"] == '201': 429 return int(response["status"]), response["location"] 430 else: 431 return int(response["status"]), content
432 433
434 - def deliveryStatus(self, deliveryURL):
435 """ 436 Track the delivery of a BlueVia SMS 437 438 @param deliveryURL: (string): deliveryURL provided by sendSMS method 439 440 @return: (tuple): (HTTP status, (dict) deliveryReceipt). HTTP status == "200" for success. 441 """ 442 443 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 444 assert type(deliveryURL) is StringType and deliveryURL!= "", "'deliveryURL' must be a non empty string" 445 446 parameters = {"version":self.version, "alt":"json"} 447 response, content = self._signAndSend(deliveryURL, "GET", self.access_token, parameters=parameters) 448 if response["status"] == '200': 449 return int(response["status"]), simplejson.loads(content)["smsDeliveryStatus"] 450 else: 451 return int(response["status"]), content
452 453 454 # # # # # # # # # # # # # # # # 455 # Inbound SMS Class 456 # # # # # # # # # # # # # # # # 457
458 -class BlueViaInboundSMS(BlueVia):
459 """ 460 The BlueVia class for receiving SMS. 461 """ 462
463 - def __init__(self, sandbox = "_Sandbox", realm = "BlueVia", version="v1"):
464 """ 465 Initialize the BlueViaInboundSMS object 466 467 @param sandbox: (string): Indicates whether testing should be done in Sandbox mode. Use "" for real network access; Default: "_Sandbox", 468 @param realm: (string): Realm string; Default: "BlueVia" 469 @param version: (string): BlueVia API version; Default: "v1" 470 """ 471 472 self.environment = sandbox 473 self.realm = realm 474 self.version = version 475 self.inbound_sms_url = "https://api.bluevia.com/services/REST/SMS%s/inbound/%s/messages" 476 self.inbound_notification_url = "https://api.bluevia.com/services/REST/SMS%s/inbound/subscriptions"
477
478 - def receiveSMS(self, shortcode):
479 """ 480 Receive all SMS sent to the shortcode with the Keyword defined during BlueVia App generation 481 482 @param shortcode: (string): SMS shortcode including country code without "+", e.g. "44" 483 484 @return: (tuple): (HTTP status, (dict) receivedSMS). HTTP status == "200" for success. 485 """ 486 487 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 488 assert type(shortcode) is StringType and shortcode!= "", "'shortcode' must be a non empty string" 489 490 url = self.inbound_sms_url % (self.environment, shortcode) 491 parameters = {"version":self.version, "alt":"json"} 492 response, content = self._signAndSend(url, "GET", self.access_token, parameters=parameters) 493 if response["status"] == '200': 494 return int(response["status"]), simplejson.loads(content)['receivedSMS'] 495 else: 496 return int(response["status"]), content
497 498
499 - def subscribeNotifications(self, shortcode, keyword, endpoint, correlator):
500 """ 501 Subscribe to Receive SMS notifications 502 503 @param shortcode: (string): SMS shortcode including country code without "+", e.g. "44" 504 @param keyword: (string): The registered SMS Keyword of the application 505 @param endpoint: (string): The url to which BlueVia shall post the relevant SMS 506 @param correlator: (string): The correlator allows to identify the subscription 507 508 @return: (tuple): (HTTP status, unsubscribeURL). HTTP status == "201" for success. Use unsubscribeURL in method unsubscribeNotifications. 509 """ 510 511 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 512 assert type(shortcode) is StringType and shortcode!= "", "'shortcode' must be a non empty string" 513 514 url = self.inbound_notification_url % (self.environment) 515 516 body = '{"smsNotification": {\ 517 "reference": { "correlator": "%s", "endpoint": "%s"}, \ 518 "destinationAddress": {"phoneNumber": "%s"}, \ 519 "criteria": "%s" }}}' % (correlator, endpoint, shortcode, keyword) 520 521 parameters = {"version":self.version, "alt":"json"} 522 523 response, content = self._signAndSend(url, "POST", self.access_token, \ 524 parameters=parameters, body=body, \ 525 extraHeaders={"Content-Type":"application/json;charset=UTF8"}) 526 if response["status"] == '201': 527 return int(response["status"]), response["location"] 528 else: 529 return int(response["status"]), content
530
531 - def unsubscribeNotifications(self, url):
532 """ 533 Unsubscribe to Receive SMS notifications 534 535 @param url: (string): The unsubscribe URL returned by the subscribeNotifications method 536 537 @return: (tuple): (HTTP status, unsubscribeURL). HTTP status == "204" for success. 538 """ 539 540 parameters = {"version":self.version} 541 response, content = self._signAndSend(url, "DELETE", self.access_token, parameters=parameters) 542 543 return int(response["status"]), content
544 545 # # # # # # # # # # # # # # # # 546 # Outbound MMS Class 547 # # # # # # # # # # # # # # # # 548
549 -class BlueViaOutboundMms(BlueVia):
550 """ 551 The BlueVia class for sending and tracking MMS. 552 """ 553
554 - def __init__(self, sandbox = "_Sandbox", realm = "BlueVia", version="v1"):
555 """ 556 Initialize the BlueViaOutboundMms object 557 558 @param sandbox: (string): Indicates whether testing should be done in Sandbox mode. Use "" for real network access; Default: "_Sandbox", 559 @param realm: (string): Realm string; Default: "BlueVia" 560 @param version: (string): BlueVia API version; Default: "v1" 561 """ 562 563 self.environment = sandbox 564 self.realm = realm 565 self.version = version 566 self.outbound_mms_url = "https://api.bluevia.com/services/REST/MMS%s/outbound/requests"
567 568
569 - def sendMMS(self, addresses, subject, messages, attachments):
570 """ 571 Send MMS via BlueVia to one or more recipients 572 573 @param addresses: (array): An array of mobile numbers in the form "44 (for UK) 7764735478" (Mobile number without first zero and no spaces) 574 @param subject: (string): MMS subject text 575 @param messages: (array): An array containing the messages (string) 576 @param attachments: (array): An array of paths to files (string) to be sent with MMS 577 578 @return: (tuple): (HTTP status, deliveryURL). HTTP status == "201" for success. Use deliverURL in method deliverStatus. 579 """ 580 581 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 582 assert (type(addresses) is ListType and addresses!=[]) or \ 583 (type(addresses) is StringType and addresses!="") , "'addresses' must be a non empty string or a non empty array of strings" 584 assert type(subject) is StringType and subject!= "", "'subject' must be a non empty string" 585 assert type(messages) is ListType and messages!=[], "'messages' must be a non empty array of strings" 586 assert type(attachments) is ListType and attachments!=[], "'attachments' must be a non empty array of strings" 587 588 if type(addresses) == types.StringType: 589 addresses = [addresses] 590 addrlist = ",".join(['{"phoneNumber":"%s"}' % a for a in addresses]) 591 if len(addresses) > 1: addrlist = '[%s]' % addrlist 592 593 root = '{"message": {"address": %s,"subject": "%s", "originAddress": {"alias": "%s"}}}' \ 594 % (addrlist, subject, self.access_token.key) 595 596 body, headers = _encodeMultipart(root, messages, dict([[x,x] for x in attachments])) 597 598 parameters={"version":self.version, "alt":"json"} 599 600 response, content = self._signAndSend(self.outbound_mms_url % self.environment, "POST", self.access_token, \ 601 parameters=parameters, body=body, extraHeaders=headers) 602 if response["status"] == '201': 603 return int(response["status"]), response["location"] 604 else: 605 return int(response["status"]), content
606 607
608 - def deliveryStatus(self, deliveryURL):
609 """ 610 Track the delivery of a BlueVia MMS 611 612 @param deliveryURL: (string): deliveryURL provided by sendMMS method 613 614 @return: (tuple): (HTTP status, (dict) deliveryReceipt). HTTP status == "200" for success. 615 """ 616 617 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 618 assert type(deliveryURL) is StringType and deliveryURL!= "", "'deliveryURL' must be a non empty string" 619 620 parameters = {"version":self.version, "alt":"json"} 621 response, content = self._signAndSend(deliveryURL, "GET", self.access_token, parameters=parameters) 622 if response["status"] == '200': 623 return int(response["status"]), simplejson.loads(content)["messageDeliveryStatus"] 624 else: 625 return int(response["status"]), content
626 627 628 # # # # # # # # # # # # # # # # 629 # Inbound MMS Class 630 # # # # # # # # # # # # # # # # 631
632 -class BlueViaInboundMMS(BlueVia):
633 """ 634 The BlueVia class for receiving MMS and retrieving MMS attachments. 635 """ 636
637 - def __init__(self, sandbox = "_Sandbox", realm = "BlueVia", version="v1"):
638 """ 639 Initialize the BlueViaInboundMMS object 640 641 @param sandbox: (string): Indicates whether testing should be done in Sandbox mode. Use "" for real network access; Default: "_Sandbox" 642 @param realm: (string): Realm string; Default: "BlueVia" 643 @param version: (string): BlueVia API version; Default: "v1" 644 """ 645 646 self.environment = sandbox 647 self.realm = realm 648 self.version = version 649 self.inbound_mms_url = "https://api.bluevia.com/services/REST/MMS%s/inbound/%s/messages" 650 self.attachments_mms_url = "https://api.bluevia.com/services/REST/MMS%s/inbound/%s/messages/%s"
651 652
653 - def receiveMMS(self, shortcode):
654 """ 655 Receive all MMS sent to the shortcode with the Keyword defined during BlueVia App generation 656 657 This method returns the message ids necessary to retrieve the MMS attachments 658 659 @param shortcode: (string): MMS shortcode including country code without "+", e.g. "44" 660 661 @return: (tuple): (HTTP status, (dict) receivedSMS). HTTP status == "200" for success. 662 """ 663 664 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 665 assert type(shortcode) is StringType and shortcode!= "", "'shortcode' must be a non empty string" 666 667 url = self.inbound_mms_url % (self.environment, shortcode) 668 parameters = {"version":self.version, "alt":"json"} 669 response, content = self._signAndSend(url, "GET", self.access_token, parameters=parameters) 670 if response["status"] == '200': 671 return int(response["status"]), simplejson.loads(content)['receivedMessages'] 672 else: 673 return int(response["status"]), content
674 675
676 - def retrieveAttachments(self, shortcode, messageId):
677 """ 678 Retrueve the MMS attachments 679 680 @param shortcode: (string): MMS shortcode including country code without "+", e.g. "44" 681 @param messageId: (string): The message Id recieved by receiveMMS method 682 683 @return: (tuple): (HTTP status, ((int) count, (string) folder)). HTTP status == "200" for success. 684 Writes <count> decoded files into <folder> 685 """ 686 687 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 688 assert type(shortcode) is StringType and shortcode!= "", "'shortcode' must be a non empty string" 689 assert type(messageId) is StringType and messageId!= "", "'messageId' must be a non empty string" 690 691 url = self.attachments_mms_url % (self.environment, shortcode, messageId) 692 parameters = {"version":self.version, "alt":"json"} 693 response, content = self._signAndSend(url, "GET", self.access_token, parameters=parameters) 694 695 if response["status"] == '200': 696 return int(response["status"]), _decodeMultipart(messageId, content) 697 else: 698 return int(response["status"]), content
699 700 701 702 # # # # # # # # # # # # # # # # 703 # User Context Class 704 # # # # # # # # # # # # # # # # 705
706 -class BlueViaUserContext(BlueVia):
707 """ 708 The BlueVia class for retrieving information about user and terminal. 709 """ 710
711 - def __init__(self, sandbox = "_Sandbox", realm = "BlueVia", version="v1"):
712 """ 713 Initialize the BlueViaUserContext object 714 715 @param sandbox: (string): Indicates whether testing should be done in Sandbox mode. Use "" for real network access; Default: "_Sandbox" 716 @param realm: (string): Realm string; Default: "BlueVia" 717 @param version: (string): BlueVia API version; Default: "v1" 718 """ 719 720 self.environment = sandbox 721 self.realm = realm 722 self.version = version 723 self.user_context_url = "https://api.bluevia.com/services/REST/Directory%s/alias:%s/UserInfo%s"
724 725
726 - def _getInfo(self, infoType, resultKey):
727 """ 728 Internal method. 729 """ 730 731 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 732 733 url = self.user_context_url % (self.environment, self.access_token.key, infoType) 734 parameters = {"version":self.version, "alt":"json"} 735 response, content = self._signAndSend(url, "GET", self.access_token, parameters=parameters) 736 if response["status"] == '200': 737 return int(response["status"]), simplejson.loads(content)[resultKey] 738 else: 739 return int(response["status"]), content
740 741
742 - def getUserInfo(self):
743 """ 744 Retrieve all available info about the user. 745 Aggregates getPersonalInfo, getProfileInfo, getAccessInfo, getTerminalInfo 746 747 @return: (tuple): (HTTP status, (dict) userInfo). HTTP status == "200" for success. 748 """ 749 750 return self._getInfo("", "userInfo")
751 752
753 - def getPersonalInfo(self):
754 """ 755 Retrieve personal info about the user (depending on country, see www.bluevia.com) 756 757 @return: (tuple): (HTTP status, (dict) userPersonalInfo). HTTP status == "200" for success. 758 """ 759 760 return self._getInfo("/UserPersonalInfo", "userPersonalInfo")
761 762
763 - def getProfileInfo(self):
764 """ 765 Retrieve info about the user's profile (depending on country, see www.bluevia.com) 766 767 @return: (tuple): (HTTP status, (dict) userProfileInfo). HTTP status == "200" for success. 768 """ 769 770 return self._getInfo("/UserProfile", "userProfile")
771 772
773 - def getAccessInfo(self):
774 """ 775 Retrieve info about the user's access type (depending on country, see www.bluevia.com) 776 777 @return: (tuple): (HTTP status, (dict) userAccessInfo). HTTP status == "200" for success. 778 """ 779 780 return self._getInfo("/UserAccessInfo", "userAccessInfo")
781 782
783 - def getTerminalInfo(self):
784 """ 785 Retrieve info about the user's terminal (depending on country, see www.bluevia.com) 786 787 @return: (tuple): (HTTP status, (dict) userTerminalInfo). HTTP status == "200" for success. 788 """ 789 790 return self._getInfo("/UserTerminalInfo", "userTerminalInfo")
791 792 793 # # # # # # # # # # # # # # # # 794 # Location Class 795 # # # # # # # # # # # # # # # # 796
797 -class BlueViaLocation(BlueVia):
798 """ 799 The BlueVia class for retrieving user's terminal cell location. 800 """ 801
802 - def __init__(self, sandbox = "_Sandbox", realm = "BlueVia", version="v1"):
803 """ 804 Initialize the BlueViaLocation object 805 806 @param sandbox: (string): Indicates whether testing should be done in Sandbox mode. Use "" for real network access; Default: "_Sandbox" 807 @param realm: (string): Realm string; Default: "BlueVia" 808 @param version: (string): BlueVia API version; Default: "v1" 809 """ 810 811 self.environment = sandbox 812 self.realm = realm 813 self.version = version 814 self.location_url = "https://api.bluevia.com/services/REST/Location%s/TerminalLocation"
815 816
817 - def locateTerminal(self, accuracy=None):
818 """ 819 Retrieve the cell location of user's terminal (mobile device) including accuracy information 820 821 Parameter: 822 @param accuracy: (int): Provide neccessary accurracy. Returns an error if accuracy requirements are not met. 823 824 @return: (tuple): (HTTP status, (dict) locationData). HTTP status == "200" for success. 825 """ 826 827 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 828 829 url = self.location_url % (self.environment) 830 parameters = {"version":self.version, "alt":"json", "locatedParty":"alias:" + self.access_token.key } 831 if accuracy: 832 parameters["acceptableAccuracy"] = str(accuracy) 833 response, content = self._signAndSend(url, "GET", self.access_token, parameters=parameters) 834 if response["status"] == '200': 835 return int(response["status"]), simplejson.loads(content) 836 else: 837 return int(response["status"]), content
838 839 840 841 # # # # # # # # # # # # # # # # 842 # Advertising Class 843 # # # # # # # # # # # # # # # # 844
845 -class BlueViaAds(BlueVia):
846 """ 847 The BlueVia class for receiving BlueVia ads. 848 """ 849
850 - def __init__(self, adspaceId, sandbox = "_Sandbox", realm = "BlueVia", version="v1"):
851 """ 852 Initialize the BlueViaAds object 853 854 @param adspaceId: The BlueVia ad space id provide during BlueVia app generation 855 @param sandbox: (string): Indicates whether testing should be done in Sandbox mode. Use "" for real network access; Default: "_Sandbox" 856 @param realm: (string): Realm string; Default: "BlueVia" 857 @param version: (string): BlueVia API version; Default: "v1" 858 """ 859 860 assert type(adspaceId) is StringType and adspaceId!= "", "'adspaceId' must be a non empty string" 861 862 self.environment = sandbox 863 self.realm = realm 864 self.version = version 865 self.adspaceId = adspaceId 866 self.simple_ads_url = "https://api.bluevia.com/services/REST/Advertising%s/simple/requests"
867 868
869 - def getAd_2l(self, country, targetUserId=None, textAd=False, userAgent='none', keywordList=None, protectionPolicy=1):
870 """ 871 Get ads without user authorization (2-legged oAuth) 872 873 @param country: (string): User's country in standard abbreviation, e.g. "UK" 874 875 @param targetUserId: (string): Unique id to avoid users to be presented with the same Ad several times. Default: None, 876 @param textAd: (string): If true requestes text ads, else image ads; efault is False 877 @param userAgent: (string): Allows BlueVia advertising service to return the more appropiate size. Default: 'none' (and not python None) 878 @param keywordList: (array): Get an ad related to some topics (e.g. 'computer|laptop'). Default: None, 879 @param protectionPolicy: (int): 1: Low, moderately explicit content 2:Safe, not rated content 3:High, explicit content. Default: 1 880 881 @return: (tuple): (HTTP status, (dict) advertisingData). HTTP status == "201" for success. 882 """ 883 884 assert type(country) is StringType and country!= "", "'country' must be a non empty string" 885 886 return self._getAd(None, country, targetUserId, textAd, userAgent, keywordList, protectionPolicy)
887 888
889 - def getAd_3l(self, textAd=False, userAgent='none', keywordList=None, protectionPolicy=1):
890 """ 891 Get ads without user authorization (2-legged oAuth) 892 893 @param textAd: (string): If true requestes text ads, else image ads; efault is False 894 @param userAgent: (string): Allows BlueVia advertising service to return the more appropiate size. Default: 'none' (and not python None) 895 @param keywordList: (array): Get an ad related to some topics (e.g. 'computer|laptop'). Default: None, 896 @param protectionPolicy: (int): 1: Low, moderately explicit content 2:Safe, not rated content 3:High, explicit content. Default: 1 897 898 @return: (tuple): (HTTP status, (dict) advertisingData). HTTP status == "201" for success. 899 """ 900 901 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 902 903 return self._getAd(self.access_token, None, None, textAd, userAgent, keywordList, protectionPolicy)
904 905
906 - def _getAd(self, token, country, targetUserId, textAd, userAgent, keywordList, protectionPolicy):
907 """ 908 Internal method 909 """ 910 911 parameters = {} 912 parameters["ad_request_id"] = str(uuid.uuid4()) + time.asctime() 913 if textAd: 914 parameters["ad_presentation"] = '0104' 915 else: 916 parameters["ad_presentation"] = '0101' 917 if country: 918 parameters["country"] = country 919 if targetUserId: 920 parameters["target_user_id"] = targetUserId 921 922 parameters["ad_space"] = self.adspaceId 923 parameters["user_agent"] = userAgent 924 if keywordList: 925 parameters["keywords"] = "|".join(keywordList) 926 parameters["protection_policy"] = protectionPolicy 927 928 # exclude version parameter from body 929 body = urllib.urlencode(parameters) 930 931 parameters["version"] = self.version 932 933 response, content = self._signAndSend(self.simple_ads_url % self.environment, "POST", token, \ 934 parameters=parameters, body=body, is_form_encoded=True, \ 935 extraHeaders={"Content-Type":"application/x-www-form-urlencoded;charset=UTF8"}) 936 937 if response["status"] == '201': 938 return (int(response["status"]), _parseAdResponse(content)) 939 else: 940 return response, content
941 942 # # # # # # # # # # # # # # # # 943 # Payment Class 944 # # # # # # # # # # # # # # # # 945
946 -class BlueViaPayment(BlueViaOauth):
947 """ 948 The BlueVia class for payments. 949 """ 950
951 - def __init__(self, consumer_key, consumer_secret, sandbox = "_Sandbox", realm = "BlueVia", version="v1"):
952 """ 953 Initialize the BlueViaPayment object 954 955 @param consumer_key: (string): Key of the Consumer Credentials 956 @param consumer_secret: (string): Secret of the Consumer Credentials 957 @param sandbox: (string): Indicates whether testing should be done in Sandbox mode. Use "" for real network access; Default: "_Sandbox" 958 @param realm: (string): Realm string; Default: "BlueVia" 959 @param version: (string): BlueVia API version; Default: "v1" 960 """ 961 962 BlueViaOauth.__init__(self, consumer_key, consumer_secret) 963 self.environment = sandbox 964 self.realm = realm 965 self.version = version 966 self.payment_url = "https://api.bluevia.com/services/RPC/Payment%s/payment" 967 self.payment_status_url = "https://api.bluevia.com/services/RPC/Payment%s/getPaymentStatus" 968 self.payment_cancel_url = "https://api.bluevia.com/services/RPC/Payment%s/cancelAuthorization"
969
970 - def fetch_request_token(self, amount, currency, serviceId, serviceName, callback="oob"):
971 """ 972 First call of the Payment oAuth Dance. Provide the Consumer Credential and request the one time Request Token 973 (Override of BlueViaOauth fetch_request_token method) 974 975 @param amount: (string): Price in the form 125 for 1.25 976 @param currency: (string): Currency, e.g. "EUR", "GBP" 977 @param serviceId: (string): Product identifier provided by our Mobile Payments Partner (sandbox: free choice) 978 @param serviceName: (string): Product name as registered at our Mobile Payments Partner (sandbox: free choice) 979 @param callback: (string): The callback URL or "oob". Default: "oob" 980 981 @return: (tuple): (HTTP status, authorization URL). HTTP status == "200" for success 982 """ 983 984 assert type(amount) is IntType and amount > 0, "'amount' must be an Integer > 0" 985 assert type(currency) is StringType and currency!= "", "'currency' must be a non empty string" 986 assert type(serviceId) is StringType and serviceId!= "", "'serviceId' must be a non empty string" 987 assert type(serviceName) is StringType and serviceName!= "", "'serviceName' must be a non empty string" 988 989 self.amount = amount 990 self.currency = currency 991 self.serviceId = serviceId 992 self.serviceName = serviceName 993 self.correlator = str(uuid.uuid4()) 994 995 paymentInfo = {"paymentInfo.currency":currency, "paymentInfo.amount":amount} 996 serviceInfo = {"serviceInfo.name":serviceName, "serviceInfo.serviceID":serviceId} 997 998 parameters={"oauth_callback":callback, "xoauth_apiName":"%s%s" % ("Payment",self.environment)} 999 parameters.update(paymentInfo) 1000 parameters.update(serviceInfo) 1001 1002 body = "%s&%s" % (urllib.urlencode(paymentInfo), urllib.urlencode(serviceInfo)) 1003 body = body.replace("+", "%20") 1004 1005 response, content = self._signAndSend(self.request_token_url, "POST", None, \ 1006 parameters=parameters, \ 1007 body=body, is_form_encoded=True, \ 1008 extraHeaders={"Content-Type":"application/x-www-form-urlencoded;charset=UTF8"}) 1009 if response["status"] == '200': 1010 self.request_token = oauth.Token.from_string(content) 1011 return int(response["status"]), "%s?oauth_token=%s" % (self.authorization_url, self.request_token.key) 1012 else: 1013 return int(response["status"]), content
1014 1015
1016 - def savePaymentInfo(self, path):
1017 """ 1018 Save Access Token and payment info (amount, currency, serviceId, serviceName, correlator) 1019 Will be valid for 48h only! 1020 1021 Note: Unencrypted storage. Use only during development 1022 1023 @param path: (string): Path to file on disk (pickle file) 1024 """ 1025 1026 assert type(path) is StringType and path!= "", "'path' must be a non empty string" 1027 1028 fd = open(path, "w") 1029 data = (self.consumer, self.access_token, self.amount, self.currency, \ 1030 self.serviceId, self.serviceName, self.correlator) 1031 pickle.dump(data, fd) 1032 fd.close()
1033 1034
1035 - def loadPaymentInfo(self, path):
1036 """ 1037 Load Access Token and payment info (amount, currency, serviceId, serviceName, correlator) 1038 1039 @param path: (string): Path to file on disk (pickle file) 1040 """ 1041 1042 assert type(path) is StringType and path!= "", "'path' must be a non empty string" 1043 1044 if os.path.exists(path): 1045 fd = open(path, "r") 1046 self.consumer, self.access_token, self.amount, self.currency, \ 1047 self.serviceId, self.serviceName, self.correlator = pickle.load(fd) 1048 fd.close() 1049 return True 1050 else: 1051 return False
1052 1053
1054 - def issuePayment(self):
1055 """ 1056 Issue the actual payment with amount, currency, serviceId, serviceName given by fetch_request_token method 1057 1058 @return: (tuple): (HTTP status, (dict) paymentStatus). HTTP status == "200" for success. 1059 """ 1060 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 1061 assert type(self.amount) is IntType and self.amount > 0, "'amount' must be an Integer > 0" 1062 assert type(self.currency) is StringType and self.currency!= "", "'currency' must be a non empty string" 1063 1064 p = {"methodCall": { 1065 "id": self.correlator, 1066 "version": self.version, 1067 "method": "PAYMENT", 1068 "params": { "paymentParams": {"paymentInfo": { "amount": str(self.amount), 1069 "currency": self.currency }, 1070 "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ") } } 1071 }} 1072 body = simplejson.dumps(p) 1073 1074 response, content = self._signAndSend(self.payment_url % self.environment, "POST", self.access_token, \ 1075 parameters={}, body=body, \ 1076 extraHeaders={"Content-Type":"application/json;charset=UTF8"}) 1077 if response["status"] == '200': 1078 return int(response["status"]), simplejson.loads(content) 1079 else: 1080 return int(response["status"]), content
1081 1082
1083 - def checkPayment(self, transactionId):
1084 """ 1085 Check the Payment status (polling) 1086 1087 @param transactionId: (string): Transaction Id provided by issuePayment method 1088 1089 @return: (tuple): (HTTP status, (dict) paymentStatus). HTTP status == "200" for success. 1090 """ 1091 1092 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 1093 assert type(transactionId) is StringType and transactionId!= "", "'transactionId' must be a non empty string" 1094 1095 p = {"methodCall": { 1096 "id": self.correlator, 1097 "version": self.version, 1098 "method": "GET_PAYMENT_STATUS", 1099 "params": { "getPaymentStatusParams": { "transactionId": transactionId} } 1100 }} 1101 1102 body = simplejson.dumps(p) 1103 1104 response, content = self._signAndSend(self.payment_status_url % self.environment, "POST", self.access_token, \ 1105 parameters={}, body=body, \ 1106 extraHeaders={"Content-Type":"application/json;charset=UTF8"}) 1107 1108 if response["status"] == '200': 1109 return int(response["status"]), simplejson.loads(content) 1110 else: 1111 return int(response["status"]), content
1112 1113
1114 - def cancelPayment(self, correlator):
1115 """ 1116 Check the Payment status (polling) 1117 1118 @param correlator: (string): correlator provided by issuePayment method 1119 1120 @return: (tuple): (HTTP status, (dict) paymentStatus). HTTP status == "200" for success. 1121 """ 1122 1123 assert self.hasCredentials(), "load oAuth credentials first or execute oAuth Dance" 1124 assert type(correlator) is StringType and correlator!= "", "'correlator' must be a non empty string" 1125 1126 p = {"methodCall": { 1127 "id": self.correlator, 1128 "version": self.version, 1129 "method": "CANCEL_AUTHORIZATION" 1130 }} 1131 1132 body = simplejson.dumps(p) 1133 1134 response, content = self._signAndSend(self.payment_cancel_url % self.environment, "POST", self.access_token, \ 1135 parameters={}, body=body, \ 1136 extraHeaders={"Content-Type":"application/json;charset=UTF8"}) 1137 1138 if response["status"] == '200': 1139 return int(response["status"]), simplejson.loads(content) 1140 else: 1141 return int(response["status"]), content
1142