From b98a1a4c9652291eda64ac06b071eeb27042ba53 Mon Sep 17 00:00:00 2001 From: Marco Colli Date: Mon, 28 Mar 2016 11:44:21 +0200 Subject: [PATCH] Initial commit --- LICENSE.txt | 21 +++++++++ README.md | 100 ++++++++++++++++++++++++++++++++++++++++ example.py | 28 +++++++++++ pushpad/__init__.py | 14 ++++++ pushpad/notification.py | 49 ++++++++++++++++++++ pushpad/pushpad.py | 24 ++++++++++ setup.py | 38 +++++++++++++++ 7 files changed, 274 insertions(+) create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 example.py create mode 100644 pushpad/__init__.py create mode 100644 pushpad/notification.py create mode 100644 pushpad/pushpad.py create mode 100644 setup.py diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..2fe6d89 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Pushpad (https://pushpad.xyz) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3cf9dd4 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# Pushpad: real push notifications for websites + +Add native push notifications to your web app using [Pushpad](https://pushpad.xyz). + +Features: + +- notifications are delivered even when the user is not on your website +- users don't need to install any app or plugin +- you can target specific users or send bulk notifications + +Currently push notifications work on the following browsers: + +- Chrome (Desktop and Android) +- Firefox (44+) +- Safari + +## Installation + +Use [pip](http://pip-installer.org/) or easy_install: + +```bash +pip install pushpad-python +``` + +## Getting started + +First you need to sign up to Pushpad and create a project there. + +Then set your authentication credentials and project: + +```python +import pushpad + +project = pushpad.Pushpad(auth_token='5374d7dfeffa2eb49965624ba7596a09', project_id=123) +``` + +`auth_token` can be found in the user account settings. + +`project_id` can be found in the project settings on Pushpad. A project is a list of subscriptions. + +## Collecting user subscriptions + +### Custom API + +Read the [docs](https://pushpad.xyz/docs#custom_api_docs). + +If you need to generate the HMAC signature for the `uid` you can use this helper: + +```python +project.signature_for(current_user_id) +``` + +### Simple API + +Let users subscribe to your push notifications: + +```python +'Subscribe anonymous to push notifications'.format( + url=project.path() +) + +'Subscribe current user to push notifications'.format( + url=project.path_for(current_user_id) +) +``` + +`current_user_id` is an identifier (e.g. primary key in the database) of the user currently logged in on your website. + +When a user clicks the link is sent to Pushpad, automatically asked to receive push notifications and redirected back to your website. + +## Sending notifications + +After you have collected the user subscriptions you can send them push notifications: + +```python +import pushpad + +project = pushpad.Pushpad(auth_token='5374d7dfeffa2eb49965624ba7596a09', project_id=123) +notification = pushpad.Notification( + project, + body="Hello world!", + title="Website Name", # optional, defaults to your project name + target_url="http://example.com" # optional, defaults to your project website +) + +# deliver to user +notification.deliver_to(user_id) +# deliver to users list +notification.deliver_to((user1_id, user2_id, user3_id)) +# deliver to everyone +notification.broadcast() +``` + +If no user with that id has subscribed to push notifications, that id is simply ignored. + +The methods above return an dict(): `res['scheduled']` contains the number of notifications that will be sent. For example if you call `notification.deliver_to(user)` but the user has never subscribed to push notifications the result will be `{'scheduled': 0}`. + +## License + +The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). diff --git a/example.py b/example.py new file mode 100644 index 0000000..5958246 --- /dev/null +++ b/example.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +import pushpad + +user1 = 'user1' +user2 = 'user2' +user3 = 'user3' +users = [user1, user2, user3] + +TOKEN='5374d7dfeffa2eb49965624ba7596a09' +PROJ_ID=123 + +project = pushpad.Pushpad(TOKEN, PROJ_ID) + +print("HMAC signature for the uid: %s is: %s" %(user1, project.signature_for(user1))) +print("Subscribe anonymous to push notifications: %s" %(project.path())) +print("Subscribe current user: %s to push notifications: %s" %(user1, project.path_for(user1))) + +notification = pushpad.Notification( + project, + body="Hello world!", + title="Website Name", + target_url="http://example.com" +) + +print("Send notification to user: %s\nResult: %s" % (user1, notification.deliver_to(user1))) +print("Send notification to users: %s\nResult: %s" % (users, notification.deliver_to(users))) +print("Send broadcast notification\nResult: %s" % notification.broadcast()) diff --git a/pushpad/__init__.py b/pushpad/__init__.py new file mode 100644 index 0000000..425c593 --- /dev/null +++ b/pushpad/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +from .pushpad import Pushpad +from .notification import Notification + +class PushpadBaseException(BaseException): + """ + Generic pushpad exception + """ + def __init__(self, *args, **kwargs): + BaseException.__init__(self, *args, **kwargs) + + +class NotificationDeliveryError(PushpadBaseException): + pass \ No newline at end of file diff --git a/pushpad/notification.py b/pushpad/notification.py new file mode 100644 index 0000000..3a2fd31 --- /dev/null +++ b/pushpad/notification.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +import requests +import pushpad + + +class Notification(object): + def __init__(self, project, body=None, title=None, target_url=None): + self._project = project + self._body = body + self._title = title + self._target_url = target_url + + def _req_headers(self): + return { + 'Authorization': 'Token token="%s"' % self._project.auth_token, + 'Content-Type': 'application/json;charset=UTF-8', + 'Accept': 'application/json', + } + + def _req_body(self, uids=[]): + res = { + 'notification': { + 'body': self._body, + 'title': self._title, + 'target_url': self._target_url, + } + } + if uids: + res.update({'uids': uids}) + return res + + def _deliver(self, req_body): + response = requests.post( + 'https://pushpad.xyz/projects/%s/notifications' % self._project.project_id, + headers=self._req_headers(), + json=req_body, + ) + if response.status_code != 201: + raise pushpad.NotificationDeliveryError('Response %s: %s' %(response.status_code, response.text)) + return response.json() + + def broadcast(self): + return self._deliver(self._req_body()) + + def deliver_to(self, uids): + return self._deliver( + req_body=self._req_body(uids) + ) \ No newline at end of file diff --git a/pushpad/pushpad.py b/pushpad/pushpad.py new file mode 100644 index 0000000..98ec74b --- /dev/null +++ b/pushpad/pushpad.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from hashlib import sha1 +import hmac + + +class Pushpad(object): + def __init__(self, auth_token, project_id): + self.auth_token = auth_token + self.project_id = project_id + + def signature_for(self, data): + return hmac.new(bytes(self.auth_token.encode()), data.encode(), sha1).hexdigest() + + def path(self): + return "https://pushpad.xyz/projects/{project_id}/subscription/edit".format( + project_id=self.project_id + ) + + def path_for(self, uid): + return "{project_path}?uid={uid}&uid_signature={uid_signature}".format( + project_path=self.path(), + uid=uid, + uid_signature=self.signature_for(uid) + ) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..924393d --- /dev/null +++ b/setup.py @@ -0,0 +1,38 @@ +"""A setuptools based setup module. + +See: +https://packaging.python.org/en/latest/distributing.html +""" + +from setuptools import setup, find_packages +from os import path + +here = path.abspath(path.dirname(__file__)) + + +setup( + name='pushpad', + version='0.1.0', + description='Pushpad: real push notifications for websites', + url='https://pushpad.xyz', + author='Pushpad', + author_email='support@pushpad.xyz', + license='MIT', + + classifiers=[ + 'Intended Audience :: Developers', + "Topic :: Software Development :: Libraries", + 'License :: OSI Approved :: MIT License', + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + ], + keywords='pushpad push notifications api', + packages=find_packages(exclude=['contrib', 'docs', 'tests*']), + install_requires=['requests'], + test_suite="tests", + extras_require={ + 'dev': ['check-manifest'], + 'test': ['coverage'], + }, +) \ No newline at end of file