├── .github └── workflows │ └── python-publish.yml ├── .gitignore ├── .travis.yml ├── CHANGES.txt ├── MANIFEST.in ├── README.md ├── README.rst ├── docs └── push │ └── push.md ├── examples ├── __init__.py ├── admin_example.py ├── batch_push_example.py ├── conf.py.sample ├── device_example.py ├── group_push_example.py ├── push_example.py ├── report_example.py ├── schedule_example.py └── zone_example.py ├── jpush ├── __init__.py ├── common.py ├── core.py ├── device │ ├── __init__.py │ ├── core.py │ └── entity.py ├── push │ ├── __init__.py │ ├── audience.py │ ├── core.py │ └── payload.py ├── report │ ├── __init__.py │ └── core.py └── schedule │ ├── __init__.py │ ├── core.py │ └── schedulepayload.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── conf.py.example ├── devices ├── test_devices.py └── test_entity.py ├── push ├── test_audience.py └── test_message.py ├── report └── test_report.py └── schedule └── test_schedule.py /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | jobs: 16 | deploy: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: '3.x' 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install build 30 | pip install wheel 31 | - name: Build package 32 | run: python -m build 33 | - name: Publish package 34 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 35 | with: 36 | user: __token__ 37 | password: ${{ secrets.PYPI_API_TOKEN }} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build/ 3 | dist/ 4 | jpush.egg-info/ 5 | .idea/ 6 | examples/conf.py 7 | /tests/conf.py 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | # - "3.2" 6 | # - "3.3" 7 | # - "3.4" 8 | - "3.5" 9 | - "3.6" 10 | 11 | # command to install dependencies 12 | install: 13 | - pip install . 14 | 15 | # command to run tests 16 | script: nosetests tests/* --verbosity=2 17 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | Version 3.1.0 2 | ----------- 3 | **Released 2015 Nov 9** 4 | Fix device api some bugs; 5 | 6 | 7 | Version 3.1.0 8 | ----------- 9 | **Released 2015 August 20** 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | include *.txt 3 | recursive-include examples * 4 | include CHANGES.txt 5 | include LICENSE.txt 6 | include README.rst 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JPush API Python Client 2 | 3 | ## 概述 4 | 这是 JPush REST API 的 Python 版本封装开发包,是由极光推送官方提供的,一般支持最新的 API 功能。 5 | 6 | 对应的 REST API 文档: 7 | 8 | ## 兼容版本 9 | + Python 2.7 10 | + Python 3 11 | 12 | ## 环境配置 13 | 14 | pip 方式: 15 | ``` 16 | sudo pip install jpush 17 | ``` 18 | easy_install 方式: 19 | ``` 20 | sudo easy_install jpush 21 | ``` 22 | 使用源码方式: 23 | ``` 24 | sudo python setup.py install 25 | ``` 26 | 27 | 28 | ## 代码样例 29 | 30 | > 代码样例在 jpush-api-python-client 中的 examples 文件夹中,[点击查看所有 examples ](https://github.com/jpush/jpush-api-python-client/tree/master/examples) 。 31 | 32 | > 以下片断来自项目代码里的文件:jpush-api-python-client 中的 examples/push_examples 目录下的 example_all.py 33 | 34 | > 这个样例演示了消息推送,日志设置,异常处理。 35 | 36 | ``` 37 | _jpush = jpush.JPush(app_key, master_secret) 38 | push = _jpush.create_push() 39 | # if you set the logging level to "DEBUG",it will show the debug logging. 40 | _jpush.set_logging("DEBUG") 41 | push.audience = jpush.all_ 42 | push.notification = jpush.notification(alert="hello python jpush api") 43 | push.platform = jpush.all_ 44 | try: 45 | response=push.send() 46 | except common.Unauthorized: 47 | raise common.Unauthorized("Unauthorized") 48 | except common.APIConnectionException: 49 | raise common.APIConnectionException("conn error") 50 | except common.JPushFailure: 51 | print ("JPushFailure") 52 | except: 53 | print ("Exception") 54 | ``` 55 | ## 日志说明 56 | logging level 默认的是 WARNING ,为了方便调试建议设置为 DEBUG 57 | 设置方法为: 58 | ``` 59 | _jpush.set_logging("DEBUG") 60 | ``` 61 | 62 | ## 异常说明 63 | 64 | + Unauthorized 65 | + AppKey,Master Secret 错误,验证失败必须改正。 66 | 67 | + APIConnectionException 68 | + 包含错误的信息:比如超时,无网络等情况。 69 | 70 | + JPushFailure 71 | + 请求出错,参考业务返回码。 72 | 73 | ## HTTP 状态码 74 | 75 | 参考文档: 76 | 77 | Push v3 API 状态码 参考文档:  78 | 79 | Report API 状态码 参考文档: 80 | 81 | Device API 状态码 参考文档: 82 | 83 | Push Schedule API 状态码 参考文档:  84 | 85 | [Release页面](https://github.com/jpush/jpush-api-python-client/releases) 有详细的版本发布记录与下载。 86 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | JPush API Python Client 3 | ======================= 4 | 5 | JPush's officially supported Python client library for accessing JPush APIs. 6 | 7 | JPush Rest API Documents: http://docs.jpush.io/server/rest_api_v3_push/ 8 | 9 | You can download the latest release file here: https://github.com/jpush/jpush-api-python-client/releases 10 | 11 | ------------ 12 | Installation 13 | ------------ 14 | To install jpush-api-python-client, simply: 15 | 16 | $ sudo pip install jpush 17 | 18 | or alternatively install via easy_install: 19 | 20 | $ sudo easy_install jpush 21 | 22 | 23 | or from source: 24 | 25 | $ sudo python setup.py install 26 | 27 | ------- 28 | Testing 29 | ------- 30 | For running the tests, you need the standard `unittest` module, shipped 31 | with Python. 32 | 33 | To run jpush-api-python-client tests, simply: 34 | 35 | $ nosetests tests/push tests/devices --verbosity=2 36 | 37 | -------- 38 | Examples 39 | -------- 40 | You can see more examples in https://github.com/jpush/jpush-api-python-client/blob/master/examples 41 | 42 | Simple iOS Push 43 | --------------- 44 | >>> import jpush as jpush 45 | >>> from conf import app_key, master_secret 46 | >>> _jpush = jpush.JPush(app_key, master_secret) 47 | >>> push = _jpush.create_push() 48 | >>> push.audience = jpush.all_ 49 | >>> ios_msg = jpush.ios(alert="Hello, IOS JPush!", badge="+1", sound="a.caf", extras={'k1':'v1'}) 50 | >>> android_msg = jpush.android(alert="Hello, android msg") 51 | >>> push.notification = jpush.notification(alert="Hello, JPush!", android=android_msg, ios=ios_msg) 52 | >>> push.options = {"time_to_live":86400, "sendno":12345,"apns_production":True} 53 | >>> push.platform = jpush.platform("ios") 54 | >>> push.send() 55 | 56 | 57 | Get taglist 58 | ----------------- 59 | >>> import jpush as jpush 60 | >>> from conf import app_key, master_secret 61 | >>> _jpush = jpush.JPush(app_key, master_secret) 62 | >>> device = _jpush.create_device() 63 | >>> device.get_taglist() 64 | 65 | -------- 66 | Questions 67 | -------- 68 | The best place to ask questions is our community site: 69 | http://community.jpush.cn/ 70 | -------------------------------------------------------------------------------- /docs/push/push.md: -------------------------------------------------------------------------------- 1 | ## 初始化 2 | 实例化JPush对象 3 | 4 | ``` 5 | _jpush = jpush.JPush(app_key, master_secret) 6 | ``` 7 | 8 | 参数说明 9 | 10 | > app_key https://www.jpush.cn/ 控制台获取 11 | 12 | > master_secret https://www.jpush.cn/ 控制台获取 13 | 14 | 返回值 15 | 16 | > JPush 实例 17 | 18 | ## Push api 19 | 20 | ### 初始化push对象 21 | 22 | ``` 23 | push = _jpush.create_push() 24 | ``` 25 | 参数说明 (无) 26 | 27 | 返回值 28 | 29 | > push 实例 30 | 31 | #### audience 设置 32 | 33 | ##### tag 设置 34 | ``` 35 | tag(*tags) 36 | ``` 37 | 38 | 参数说明 39 | 40 | tags 例如:tag("tag1", "tag2") 41 | 42 | 43 | 返回值 44 | > payload 字典 45 | 46 | ##### tag_and 设置 47 | ``` 48 | tag_and(*tag_ands) 49 | ``` 50 | 51 | 参数说明 52 | 53 | tags 例如:tag_and("tag1", "tag2") 54 | 55 | 返回值 56 | > payload 字典 57 | 58 | ##### tag_not 设置 59 | ``` 60 | tag_not(*tag_nots) 61 | ``` 62 | 63 | 参数说明 64 | 65 | tags 例如:tag_not("tag1", "tag2") 66 | 67 | 返回值 68 | > payload 字典 69 | 70 | ##### alias 设置 71 | ``` 72 | alias(*alias) 73 | ``` 74 | 75 | 参数说明 76 | 77 | alias 例如:alias("alias1", "alias2") 78 | 79 | 返回值 80 | > payload 字典 81 | 82 | ##### registration_id 设置 83 | ``` 84 | registration_id(*reg_ids) 85 | ``` 86 | 87 | 参数说明 88 | 89 | registration_id 例如:tag("registration_id1", "registration_id2") 90 | 91 | 返回值 92 | 93 | > payload 字典 94 | 95 | ##### 推送目标说明 96 | 97 | 推送设备对象,表示一条推送可以被推送到哪些设备列表。确认推送设备对象,JPush 提供了多种方式,比如:别名、标签、注册ID、分群、广播等。 98 | 99 | * all 100 | 101 | 如果要发广播(全部设备),则直接填写 “all”。 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 |
关键字含义类型说明备注
tagJSON Array标签数组。多个标签之间是 OR 的关系,即取并集。 用标签来进行大规模的设备属性、用户属性分群。 一次推送最多 20 个。
  • 有效的 tag 组成:字母(区分大小写)、数字、下划线、汉字。
  • 限制:每一个 tag 的长度限制为 40 字节。(判断长度需采用UTF-8编码)
tag_andJSON Array标签AND数组。多个标签之间是 AND 关系,即取交集。注册与 tag 区分。一次推送最多 20 个。
aliasJSON Array别名数组。多个别名之间是 OR 关系,即取并集。用别名来标识一个用户。一个设备只能绑定一个别名,但多个设备可以绑定同一个别名。一次推送最多 1000 个。
  • 有效的 alias 组成:字母(区分大小写)、数字、下划线、汉字。
  • 限制:每一个 alias 的长度限制为 40 字节。(判断长度需采用UTF-8编码)
registration_idJSON Array注册ID数组。多个注册ID之间是 OR 关系,即取并集。设备标识。一次推送最多 1000 个。
145 |
146 | 147 | 148 | #### notification 设置 149 | ``` 150 | notification(alert=None, ios=None, android=None, winphone=None) 151 | ``` 152 | 参数说明 153 | 154 | * alert 155 | 156 | > 通知的内容在各个平台上,都可能只有这一个最基本的属性 "alert"。 157 | 这个位置的 "alert" 属性(直接在 notification 对象下),是一个快捷定义,各平台的 alert 信息如果都一样,则可不定义。如果各平台有定义,则覆盖这里的定义。 158 | 159 | * ios 160 | 161 | > ios payload 字典 查看 [ios payload](https://github.com/jpush/jpush-api-python-client/blob/master/docs/push/push.md#ios-payload) 162 | 163 | * android 164 | 165 | > android payload 字典 查看 [android payload](https://github.com/jpush/jpush-api-python-client/blob/master/docs/push/push.md#android-payload) 166 | 167 | 168 | 返回值 169 | 170 | > notification payload 171 | 172 | ##### ios payload 173 | ``` 174 | ios(alert=None, badge='+1', sound=None, content_available=False, mutable_content=False, category=None, extras=None, sound_disable=False, thread_id=None): 175 | ``` 176 | 177 | 参数说明 178 |
179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 |
关键字类型选项含义说明
alertstring必填通知内容 193 | 这里指定内容将会覆盖上级统一指定的 alert 信息;内容为空则不展示到通知栏。支持字符串形式也支持官方定义的 alert payload 结构,在该结构中包含 title 和 subtitle 等官方支持的 key 194 |
soundstring可选通知提示声音 202 | 普通通知: string类型,如果无此字段,则此消息无声音提示;有此字段,如果找到了指定的声音就播放该声音,否则播放默认声音,如果此字段为空字符串,iOS 7 为默认声音,iOS 8 及以上系统为无声音。说明:JPush 官方 SDK 会默认填充声音字段,提供另外的方法关闭声音,详情查看各 SDK 的源码。 203 | 告警通知: JSON Object ,支持官方定义的 payload 结构,在该结构中包含 critical 、name 和 volume 等官方支持的 key 204 |
badgeint可选应用角标如果不填,表示不改变角标数字;否则把角标数字改为指定的数字;为 0 表示清除。JPush 官方 API Library(SDK) 会默认填充badge值为"+1",详情参考:badge +1
content-availableboolean可选推送唤醒推送的时候携带"content-available":true 说明是 Background Remote Notification,如果不携带此字段则是普通的Remote Notification。详情参考:Background Remote Notification
mutable-contentboolean可选通知扩展推送的时候携带 ”mutable-content":true 说明是支持iOS10的UNNotificationServiceExtension,如果不携带此字段则是普通的 Remote Notification。详情参考:UNNotificationServiceExtension
categorystring可选 IOS8才支持。设置APNs payload中的"category"字段值
thread-idstring可选通知分组ios 的远程通知通过该属性来对通知进行分组,同一个 thread-id 的通知归为一组。
extrasJSON Object可选扩展字段这里自定义 Key/value 信息,以供业务使用。
249 |
250 | 251 | 返回值 252 | 253 | > android payload 字典 254 | 255 | ##### android payload 256 | ``` 257 | android(alert, title=None, builder_id=None, extras=None, priority=None, category=None, style=None, alert_type=None, big_text=None, inbox=None, big_pic_path=None, large_icon=None, intent=None) 258 | ``` 259 | 参数说明 260 |
261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 |
关键字类型选项含义说明
alertstring必填通知内容这里指定了,则会覆盖上级统一指定的 alert 信息;内容可以为空字符串,则表示不展示到通知栏。
titlestring可选通知标题如果指定了,则通知里原来展示 App名称的地方,将展示成这个字段。
builder_idint可选通知栏样式IDAndroid SDK 可设置通知栏样式,这里根据样式 ID 来指定该使用哪套样式。
extrasJSON Object可选扩展字段这里自定义 JSON 格式的 Key/Value 信息,以供业务使用。
298 |
299 | 300 | 301 | 返回值 302 | 303 | > android payload 字典 304 | 305 | #### message 设置 306 | 307 | ``` 308 | message(msg_content, title=None, content_type=None, extras=None) 309 | ``` 310 | 311 | 参数说明 312 | 313 |
314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 |
关键字类型选项含义
msg_contentstring必填消息内容本身
titlestring可选消息标题
content_typestring可选消息内容类型
extrasJSON Object可选JSON 格式的可选参数
346 |
347 | 348 | 返回值 349 | 350 | >message payload 351 | 352 | #### smsmessage 设置 353 | 354 | ``` 355 | smsmessage(content,delay_time) 356 | ``` 357 | 358 | * 参数说明 359 | 360 |
361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 |
关键字类型选项示例
contentstring必填不能超过480个字符。"你好,JPush"为8个字符。超过67个字符的内容(含签名)会被拆分成多条短信下发。
delay_timeint必填单位为秒,不能超过24小时。设置为0,表示立即发送短信。该参数仅对android平台有效,iOS 和 Winphone平台则会立即发送短信
381 |
382 | 383 | * 返回值 384 | 385 | > smsmessage payload 386 | 387 | #### platform 设置 388 | 389 | ``` 390 | platform(*types) 391 | ``` 392 | 393 | * 参数说明 394 | 395 | JPush 当前支持 Android, iOS, Windows Phone 三个平台的推送。其关键字分别为:"android", "ios","winphone"。 396 | 397 | * 返回值 398 | 399 | > platform tuple 400 | 401 | #### options 设置 402 | 403 | ``` 404 | options(options) 405 | ``` 406 | 407 | * 参数说明 408 | 409 |
410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 |
关键字类型选项含义说明
sendnoint可选推送序号纯粹用来作为 API 调用标识,API 返回时被原样返回,以方便 API 调用方匹配请求与返回。
time_to_liveint可选离线消息保留时长(秒)推送当前用户不在线时,为该用户保留多长时间的离线消息,以便其上线时再次推送。默认 86400 (1 天),最长 10 天。设置为 0 表示不保留离线消息,只有推送当前在线的用户可以收到。
override_msg_idlong可选要覆盖的消息ID如果当前的推送要覆盖之前的一条推送,这里填写前一条推送的 msg_id 就会产生覆盖效果,即:1)该 msg_id 离线收到的消息是覆盖后的内容;2)即使该 msg_id Android 端用户已经收到,如果通知栏还未清除,则新的消息内容会覆盖之前这条通知;覆盖功能起作用的时限是:1 天。如果在覆盖指定时限内该 msg_id 不存在,则返回 1003 错误,提示不是一次有效的消息覆盖操作,当前的消息不会被推送。
apns_productionboolean可选APNs是否生产环境True 表示推送生产环境,False 表示要推送开发环境;如果不指定则为推送生产环境。JPush 官方 API LIbrary (SDK) 默认设置为推送 “开发环境”。
big_push_durationint可选定速推送时长(分钟)又名缓慢推送,把原本尽可能快的推送速度,降低下来,给定的n分钟内,均匀地向这次推送的目标用户推送。最大值为1400.未设置则不是定速推送。
454 |
455 | 456 | * 返回值 457 | 458 | > options 字典 459 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 4 | 5 | import jpush 6 | 7 | from .conf import app_key, master_secret, dev_key, dev_secret 8 | 9 | from . import device_example 10 | from . import push_example 11 | from . import report_example 12 | from . import schedule_example 13 | from . import group_push_example 14 | from . import admin_example 15 | from . import zone_example 16 | 17 | __all__ = [ 18 | device_example, 19 | push_example, 20 | report_example, 21 | schedule_example, 22 | group_push_example, 23 | admin_example, 24 | zone_example, 25 | ] 26 | -------------------------------------------------------------------------------- /examples/admin_example.py: -------------------------------------------------------------------------------- 1 | from . import jpush, dev_key, dev_secret 2 | 3 | admin = jpush.Admin(dev_key, dev_secret) 4 | admin.set_logging("DEBUG") 5 | 6 | def create_app(): 7 | response = admin.create_app('aaa', 'cn.jpush.app') 8 | return response 9 | 10 | def delete_app(app_key): 11 | response = admin.delete_app(app_key) 12 | return response 13 | -------------------------------------------------------------------------------- /examples/batch_push_example.py: -------------------------------------------------------------------------------- 1 | from conf import app_key, master_secret 2 | import jpush 3 | 4 | _jpush = jpush.JPush(app_key, master_secret) 5 | _jpush.set_logging("DEBUG") 6 | 7 | push = _jpush.create_push() 8 | single_payload_list = [ 9 | {'platform':'all', 'target':'regid1', 'notification':{'alert':'alert content'}}, 10 | {'platform':'all', 'target':'regid2', 'notification':{'alert':'alert content'}} 11 | ] 12 | response = push.batch_push_by_regid(single_payload_list) 13 | print(response) -------------------------------------------------------------------------------- /examples/conf.py.sample: -------------------------------------------------------------------------------- 1 | # please put your app_key and master_secret here 2 | app_key = u'xxxxxx' 3 | master_secret = u'xxxxxx' 4 | 5 | dev_key = u'xxxxxx' 6 | dev_secret = u'xxxxxx' 7 | -------------------------------------------------------------------------------- /examples/device_example.py: -------------------------------------------------------------------------------- 1 | from . import jpush, app_key, master_secret 2 | 3 | _jpush = jpush.JPush(app_key, master_secret) 4 | _jpush.set_logging("DEBUG") 5 | device = _jpush.create_device() 6 | 7 | def alias_user(): 8 | alias = "alias1" 9 | platform = "android,ios,hmos" 10 | device.get_aliasuser(alias, platform) 11 | 12 | def ctrl_tag(): 13 | reg_id = '090c1f59f89' 14 | entity = jpush.device_tag("") 15 | device.set_deviceinfo(reg_id, entity) 16 | 17 | def get_device(): 18 | reg_id = '090c1f59f89' 19 | device.get_deviceinfo(reg_id) 20 | 21 | def delete_alias(): 22 | alias = "alias1" 23 | platform = "android,ios,hmos" 24 | device.delete_alias(alias, platform) 25 | 26 | def delete_tag(): 27 | tag = "ddd" 28 | platform = "android,ios,hmos" 29 | device.delete_tag(tag, platform) 30 | 31 | def check_tag(): 32 | tag = "ddd" 33 | registration_id = '090c1f59f89' 34 | device.check_taguserexist(tag, registration_id) 35 | 36 | def tag_list(): 37 | device.get_taglist() 38 | 39 | def tag_update_user(): 40 | tag = "ddd" 41 | entity = jpush.device_regid(jpush.add("090c1f59f89")) 42 | device.update_tagusers(tag, entity) 43 | 44 | def update_device(): 45 | reg_id = '1507bfd3f7c466c355c' 46 | entity = jpush.device_tag(jpush.add("ddd", "tageee")) 47 | result=device.set_devicemobile(reg_id, entity) 48 | print (result.status_code) 49 | print (result.payload) 50 | 51 | def update_device_mobile(): 52 | reg_id = '1507bfd3f7c466c355c' 53 | entity = jpush.device_mobile("18588232140") 54 | device.set_devicemobile(reg_id, entity) 55 | 56 | def get_device_status(): 57 | reg_id = '1507bfd3f7c466c355c' 58 | device.get_device_status(reg_id) 59 | -------------------------------------------------------------------------------- /examples/group_push_example.py: -------------------------------------------------------------------------------- 1 | from . import jpush 2 | from jpush import common 3 | 4 | group_key = u'xxxxxx' 5 | group_secret = u'xxxxxx' 6 | 7 | group = jpush.GroupPush(group_key, group_secret) 8 | group.set_logging("DEBUG") 9 | 10 | def all(): 11 | push = group.create_push() 12 | push.audience = jpush.all_ 13 | push.notification = jpush.notification(alert="!hello python jpush api") 14 | push.platform = jpush.all_ 15 | try: 16 | response=push.send() 17 | except common.Unauthorized: 18 | raise common.Unauthorized("Unauthorized") 19 | except common.APIConnectionException: 20 | raise common.APIConnectionException("conn") 21 | except common.JPushFailure: 22 | print ("JPushFailure") 23 | except: 24 | print ("Exception") 25 | -------------------------------------------------------------------------------- /examples/push_example.py: -------------------------------------------------------------------------------- 1 | from conf import app_key, master_secret 2 | import jpush 3 | 4 | _jpush = jpush.JPush(app_key, master_secret) 5 | _jpush.set_logging("DEBUG") 6 | 7 | def alias(): 8 | push = _jpush.create_push() 9 | alias=["alias1", "alias2"] 10 | alias1={"alias": alias} 11 | print(alias1) 12 | push.audience = jpush.audience( 13 | jpush.tag("tag1", "tag2"), 14 | alias1 15 | ) 16 | 17 | push.notification = jpush.notification(alert="Hello world with audience!") 18 | push.platform = jpush.all_ 19 | print (push.payload) 20 | push.send() 21 | 22 | def all(): 23 | push = _jpush.create_push() 24 | push.audience = jpush.all_ 25 | push.notification = jpush.notification(alert="!hello python jpush api") 26 | push.platform = jpush.all_ 27 | try: 28 | response=push.send() 29 | except common.Unauthorized: 30 | raise common.Unauthorized("Unauthorized") 31 | except common.APIConnectionException: 32 | raise common.APIConnectionException("conn") 33 | except common.JPushFailure: 34 | print ("JPushFailure") 35 | except: 36 | print ("Exception") 37 | 38 | def audience(): 39 | push = _jpush.create_push() 40 | 41 | push.audience = jpush.audience( 42 | jpush.tag("tag1", "tag2"), 43 | jpush.alias("alias1", "alias2") 44 | ) 45 | 46 | 47 | push.notification = jpush.notification(alert="Hello world with audience!") 48 | push.platform = jpush.all_ 49 | print (push.payload) 50 | push.send() 51 | 52 | 53 | def notification(): 54 | push = _jpush.create_push() 55 | 56 | push.audience = jpush.all_ 57 | push.platform = jpush.all_ 58 | 59 | ios = jpush.ios(alert="Hello, IOS JPush!", sound="a.caf", extras={'k1':'v1'}) 60 | android = jpush.android(alert="Hello, Android msg", priority=1, style=1, alert_type=1,big_text='jjjjjjjjjj', extras={'k1':'v1'}) 61 | hmos = jpush.hmos(alert="Hello, HMOS JPush!", category="category", large_icon="large_icon", intent={"url":"action.system.home"}, extras={'k1':'v1'}, style="style", inbox="inbox") 62 | 63 | push.notification = jpush.notification(alert="Hello, JPush!", android=android, ios=ios, hmos=hmos) 64 | 65 | # pprint (push.payload) 66 | result = push.send() 67 | 68 | def options(): 69 | push = _jpush.create_push() 70 | push.audience = jpush.all_ 71 | push.notification = jpush.notification(alert="Hello, world!") 72 | push.platform = jpush.all_ 73 | push.options = {"time_to_live":86400, "sendno":12345,"apns_production":True} 74 | push.send() 75 | 76 | def platfrom_msg(): 77 | push = _jpush.create_push() 78 | push.audience = jpush.all_ 79 | ios_msg = jpush.ios(alert="Hello, IOS JPush!", badge="+1", sound="a.caf", extras={'k1':'v1'}) 80 | android_msg = jpush.android(alert="Hello, android msg") 81 | hmos_msg = jpush.hmos(alert="Hello, HMOS JPush msg") 82 | push.notification = jpush.notification(alert="Hello, JPush!", android=android_msg, ios=ios_msg, hmos=hmos_msg) 83 | push.message=jpush.message("content",extras={'k2':'v2','k3':'v3'}) 84 | push.platform = jpush.all_ 85 | push.send() 86 | 87 | 88 | def silent(): 89 | push = _jpush.create_push() 90 | push.audience = jpush.all_ 91 | ios_msg = jpush.ios(alert="Hello, IOS JPush!", badge="+1", extras={'k1':'v1'}, sound_disable=True) 92 | android_msg = jpush.android(alert="Hello, android msg") 93 | hmos_msg = jpush.hmos(alert="Hello, HMOS JPush msg") 94 | push.notification = jpush.notification(alert="Hello, JPush!", android=android_msg, ios=ios_msg, hmos=hmos_msg) 95 | push.platform = jpush.all_ 96 | push.send() 97 | 98 | 99 | def sms(): 100 | push = _jpush.create_push() 101 | push.audience = jpush.all_ 102 | push.notification = jpush.notification(alert="a sms message from python jpush api") 103 | push.platform = jpush.all_ 104 | push.smsmessage=jpush.smsmessage("a sms message from python jpush api",0) 105 | print (push.payload) 106 | push.send() 107 | 108 | def validate(): 109 | push = _jpush.create_push() 110 | push.audience = jpush.all_ 111 | push.notification = jpush.notification(alert="Hello, world!") 112 | push.platform = jpush.all_ 113 | push.send_validate() -------------------------------------------------------------------------------- /examples/report_example.py: -------------------------------------------------------------------------------- 1 | from conf import app_key, master_secret 2 | import jpush 3 | 4 | _jpush = jpush.JPush(app_key, master_secret) 5 | _jpush.set_logging("DEBUG") 6 | report=_jpush.create_report() 7 | 8 | def messages(): 9 | report.get_messages("3289406737") 10 | 11 | def messages_detail(): 12 | report.get_messages_detail("3289406737") 13 | 14 | def received(): 15 | report.get_received("3289406737") 16 | 17 | def received_detail(): 18 | report.get_received_detail("3289406737") 19 | 20 | def users(): 21 | report.get_users("DAY","2016-04-10","3") 22 | 23 | def status(): 24 | msgid = '3289406737' 25 | regid = 'xxx' 26 | report.get_status_message(int(msgid), [regid]) 27 | 28 | messages_detail() 29 | received_detail() -------------------------------------------------------------------------------- /examples/schedule_example.py: -------------------------------------------------------------------------------- 1 | from . import jpush, app_key, master_secret 2 | 3 | _jpush = jpush.JPush(app_key, master_secret) 4 | _jpush.set_logging("DEBUG") 5 | schedule = _jpush.create_schedule() 6 | 7 | def delete_schedule(): 8 | schedule.delete_schedule("e9c553d0-0850-11e6-b6d4-0021f652c102") 9 | 10 | def get_schedule(): 11 | schedule.get_schedule_by_id("e9c553d0-0850-11e6-b6d4-0021f652c102") 12 | 13 | def get_schedule_list(): 14 | schedule.get_schedule_list("1") 15 | 16 | def post_schedule(): 17 | push = _jpush.create_push() 18 | push.audience = jpush.all_ 19 | push.notification = jpush.notification(alert="Hello, world!") 20 | push.platform = jpush.all_ 21 | push=push.payload 22 | 23 | trigger=jpush.schedulepayload.trigger("2016-07-17 12:00:00") 24 | schedulepayload=jpush.schedulepayload.schedulepayload("name",True,trigger,push) 25 | result=schedule.post_schedule(schedulepayload) 26 | print (result.status_code) 27 | 28 | def put_schedule(): 29 | push = _jpush.create_push() 30 | push.audience = jpush.all_ 31 | push.notification = jpush.notification(alert="Hello, world!") 32 | push.platform = jpush.all_ 33 | push=push.payload 34 | 35 | trigger=jpush.schedulepayload.trigger("2016-05-17 12:00:00") 36 | schedulepayload=jpush.schedulepayload.schedulepayload("update a new name", True, trigger, push) 37 | schedule.put_schedule(schedulepayload,"17349f00-0852-11e6-91b1-0021f653c902") 38 | -------------------------------------------------------------------------------- /examples/zone_example.py: -------------------------------------------------------------------------------- 1 | from . import jpush, app_key, master_secret 2 | 3 | def default(): 4 | _jpush = jpush.JPush(app_key, master_secret) 5 | _jpush.set_logging("DEBUG") 6 | 7 | push = _jpush.create_push() 8 | push.audience = jpush.all_ 9 | push.notification = jpush.notification(alert="!hello python jpush api") 10 | push.platform = jpush.all_ 11 | try: 12 | response=push.send() 13 | except common.Unauthorized: 14 | raise common.Unauthorized("Unauthorized") 15 | except common.APIConnectionException: 16 | raise common.APIConnectionException("conn") 17 | except common.JPushFailure: 18 | print ("JPushFailure") 19 | except: 20 | print ("Exception") 21 | 22 | def bj(): 23 | _jpush = jpush.JPush(app_key, master_secret, zone = 'bj') 24 | _jpush.set_logging("DEBUG") 25 | 26 | push = _jpush.create_push() 27 | push.audience = jpush.all_ 28 | push.notification = jpush.notification(alert="!hello python jpush api") 29 | push.platform = jpush.all_ 30 | try: 31 | response=push.send() 32 | except common.Unauthorized: 33 | raise common.Unauthorized("Unauthorized") 34 | except common.APIConnectionException: 35 | raise common.APIConnectionException("conn") 36 | except common.JPushFailure: 37 | print ("JPushFailure") 38 | except: 39 | print ("Exception") 40 | -------------------------------------------------------------------------------- /jpush/__init__.py: -------------------------------------------------------------------------------- 1 | """Python package for using the JPush API""" 2 | from .core import JPush, GroupPush, Admin 3 | from .common import JPushFailure, Unauthorized 4 | 5 | from .push import ( 6 | Push, 7 | all_, 8 | tag, 9 | tag_and, 10 | tag_not, 11 | alias, 12 | registration_id, 13 | notification, 14 | ios, 15 | android, 16 | hmos, 17 | winphone, 18 | platform, 19 | audience, 20 | options, 21 | message, 22 | smsmessage, 23 | ) 24 | 25 | from .device import ( 26 | Device, 27 | add, 28 | remove, 29 | device_tag, 30 | device_alias, 31 | device_regid, 32 | device_mobile, 33 | ) 34 | 35 | from .report import ( 36 | Report, 37 | ReportResponse, 38 | ) 39 | 40 | from .schedule import ( 41 | Schedule, 42 | schedulepayload, 43 | ) 44 | 45 | __all__ = [ 46 | JPush, 47 | GroupPush, 48 | Admin, 49 | JPushFailure, 50 | Unauthorized, 51 | all_, 52 | Push, 53 | tag, 54 | tag_and, 55 | tag_not, 56 | alias, 57 | registration_id, 58 | notification, 59 | ios, 60 | android, 61 | hmos, 62 | winphone, 63 | message, 64 | smsmessage, 65 | platform, 66 | audience, 67 | options, 68 | Device, 69 | add, 70 | remove, 71 | device_tag, 72 | device_alias, 73 | device_regid, 74 | Report, 75 | ReportResponse, 76 | Schedule, 77 | schedulepayload, 78 | ] 79 | 80 | __version__ = '3.3.9' 81 | VERSION = tuple(map(int, __version__.split('.'))) 82 | 83 | # Silence urllib3 INFO logging by default 84 | 85 | import logging 86 | logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(logging.WARNING) 87 | -------------------------------------------------------------------------------- /jpush/common.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import requests 4 | 5 | ZONES = { 6 | 'DEFAULT': { 7 | 'PUSH': 'https://api.jpush.cn/v3/', 8 | 'REPORT': 'https://report.jpush.cn/v3/', 9 | 'DEVICE': 'https://device.jpush.cn/v3/devices/', 10 | 'ALIAS': 'https://device.jpush.cn/v3/aliases/', 11 | 'TAG': 'https://device.jpush.cn/v3/tags/', 12 | 'SCHEDULE': 'https://api.jpush.cn/v3/schedules/', 13 | 'ADMIN': 'https://admin.jpush.cn/v1/' 14 | }, 15 | 'BJ':{ 16 | 'PUSH': 'https://bjapi.push.jiguang.cn/v3/', 17 | 'REPORT': 'https://bjapi.push.jiguang.cn/v3/report/', 18 | 'DEVICE': 'https://bjapi.push.jiguang.cn/v3/device/', 19 | 'ALIAS': 'https://bjapi.push.jiguang.cn/v3/device/aliases/', 20 | 'TAG': 'https://bjapi.push.jiguang.cn/v3/device/tags/', 21 | 'SCHEDULE': 'https://bjapi.push.jiguang.cn/v3/push/schedules/', 22 | 'ADMIN': 'https://admin.jpush.cn/v1/' 23 | } 24 | } 25 | 26 | logger = logging.getLogger('jpush') 27 | 28 | def get_url(key, zone='default'): 29 | return ZONES[zone.upper()][key.upper()] 30 | 31 | class Unauthorized(Exception): 32 | """Raised when we get a 401 from the server""" 33 | def __init__(self, value): 34 | self.value = value 35 | 36 | def __str__(self): 37 | return repr(self.value) 38 | 39 | 40 | class JPushFailure(Exception): 41 | """Raised when we get an error response from the server. 42 | :param args: For backwards compatibility, ``*args`` includes the status and 43 | response body. 44 | """ 45 | 46 | error = None 47 | error_code = None 48 | details = None 49 | response = None 50 | 51 | def __init__(self, error, error_code, details, response, *args): 52 | self.error = error 53 | self.error_code = error_code 54 | self.details = details 55 | self.response = response 56 | # super(self, JPushFailure).__init__(*args) 57 | 58 | @classmethod 59 | def from_response(cls, response): 60 | """Instantiate a ValidationFailure from a Response object""" 61 | try: 62 | payload = response.json() 63 | error = payload.get('error') 64 | error_code = error.get('code') 65 | details = error.get('message') 66 | except ValueError: 67 | error = response.reason 68 | error_code = None 69 | details = response.content 70 | 71 | logger.error( 72 | "Request failed with status %d: '%s %s': %s", 73 | response.status_code, error_code, error, json.dumps(details)) 74 | 75 | return cls(error, error_code, details, response, response.status_code, response.content) 76 | 77 | def __str__(self): 78 | return repr(self.response.content) 79 | 80 | 81 | class APIConnectionException(Exception): 82 | def __init__(self, value): 83 | self.value = value 84 | # 修复celery的错误,参考https://github.com/celery/celery/issues/3623 85 | super(Exception, self).__init__(value) 86 | 87 | def __str__(self): 88 | return repr(self.value) 89 | 90 | 91 | class APIRequestException(Exception): 92 | def __init__(self, value): 93 | self.value = value 94 | super(Exception, self).__init__(value) 95 | 96 | def __str__(self): 97 | return repr(self.value) 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 | -------------------------------------------------------------------------------- /jpush/core.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import warnings 4 | 5 | import requests 6 | from . import common 7 | from .push import Push 8 | from .device import Device 9 | from .report import Report 10 | from .schedule import Schedule 11 | 12 | 13 | logger = logging.getLogger('jpush') 14 | 15 | 16 | class JPush(object): 17 | def __init__(self, key, secret, timeout=30, zone = 'default'): 18 | self.key = key 19 | self.secret = secret 20 | self.timeout = timeout 21 | self.zone = zone 22 | self.session = requests.Session() 23 | self.session.auth = (key, secret) 24 | 25 | def _request(self, method, body, url, content_type=None, version=None, params=None): 26 | headers = {} 27 | headers['user-agent'] = 'jpush-api-python-client' 28 | headers['connection'] = 'keep-alive' 29 | headers['content-type'] = 'application/json;charset:utf-8' 30 | 31 | logger.debug("Making %s request to %s. Headers:\n\t%s\nBody:\n\t%s", 32 | method, url, '\n\t'.join('%s: %s' % (key, value) for (key, value) in headers.items()), body) 33 | try: 34 | response = self.session.request(method, url, data=body, params=params, 35 | headers=headers, timeout=self.timeout) 36 | except requests.exceptions.ConnectTimeout: 37 | raise common.APIConnectionException("Connection to api.jpush.cn timed out.") 38 | except Exception: 39 | raise common.APIRequestException("Connection to api.jpush.cn error.") 40 | 41 | logger.debug("Received %s response. Headers:\n\t%s\nBody:\n\t%s", response.status_code, '\n\t'.join( 42 | '%s: %s' % (key, value) for (key, value) in response.headers.items()), response.content) 43 | 44 | if response.status_code == 401: 45 | raise common.Unauthorized("Please check your AppKey and Master Secret") 46 | elif not (200 <= response.status_code < 300): 47 | raise common.JPushFailure.from_response(response) 48 | return response 49 | 50 | def push(self, payload): 51 | """Push this payload to the specified recipients. 52 | 53 | Payload: a dictionary the contents to send, e.g.: 54 | {'aps': {'alert': 'Hello'}, 'android': {'alert': 'Hello'}} 55 | """ 56 | warnings.warn( 57 | "JPush.push() is deprecated. See documentation on upgrading.", 58 | DeprecationWarning) 59 | body = json.dumps(payload) 60 | url = common.get_url('push', self.zone) + 'push' 61 | self._request('POST', body, url, 'application/json', version=1) 62 | 63 | def set_logging(self, level): 64 | level_list= ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET"] 65 | if level in level_list: 66 | if(level == "CRITICAL"): 67 | logging.basicConfig(level=logging.CRITICAL) 68 | if (level == "ERROR"): 69 | logging.basicConfig(level=logging.ERROR) 70 | if (level == "WARNING"): 71 | logging.basicConfig(level=logging.WARNING) 72 | if (level == "INFO"): 73 | logging.basicConfig(level=logging.INFO) 74 | if (level == "DEBUG"): 75 | logging.basicConfig(level=logging.DEBUG) 76 | if (level == "NOTSET"): 77 | logging.basicConfig(level=logging.NOTSET) 78 | else: 79 | print ("set logging level failed ,the level is invalid.") 80 | 81 | def create_push(self): 82 | """Create a Push notification.""" 83 | return Push(self) 84 | 85 | def create_device(self): 86 | """Create a Device information.""" 87 | return Device(self) 88 | 89 | def create_report(self): 90 | """Create a Report.""" 91 | return Report(self) 92 | 93 | def create_schedule(self): 94 | """Create a Schedule.""" 95 | return Schedule(self) 96 | 97 | class GroupPush(JPush): 98 | 99 | def __init__(self, key, secret): 100 | JPush.__init__(self, 'group-' + key, secret) 101 | 102 | def create_push(self): 103 | """Create a Group Push notification.""" 104 | return Push(self, end_point = 'grouppush') 105 | 106 | class Admin(JPush): 107 | def __init__(self, key, secret): 108 | JPush.__init__(self, key, secret) 109 | 110 | def create_app(self, app_name, android_package, group_name=None): 111 | url = 'https://admin.jpush.cn/v1/app' 112 | entity = { 113 | 'app_name': app_name, 114 | 'android_package': android_package, 115 | 'group_name': group_name 116 | } 117 | body = json.dumps(entity) 118 | response = self._request('post', body, url, content_type=None, version=3) 119 | return AdminResponse(response) 120 | 121 | def delete_app(self, app_key): 122 | url = 'https://admin.jpush.cn/v1/app/' + app_key + '/delete' 123 | response = self._request('post', None, url, content_type=None, version=3) 124 | return AdminResponse(response) 125 | 126 | class AdminResponse(object): 127 | 128 | payload = None 129 | status_code = None 130 | 131 | def __init__(self, response): 132 | self.status_code = response.status_code 133 | if 0 != len(response.content): 134 | data = response.json() 135 | self.payload = data 136 | elif 200 == response.status_code: 137 | self.payload = "success" 138 | 139 | def get_status_code(self): 140 | return self.status_code 141 | 142 | def __str__(self): 143 | return "Admin response Payload: {0}".format(self.payload) 144 | -------------------------------------------------------------------------------- /jpush/device/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import Device 2 | 3 | from .entity import ( 4 | add, 5 | remove, 6 | device_tag, 7 | device_alias, 8 | device_regid, 9 | device_mobile, 10 | ) 11 | 12 | __all__ = [ 13 | Device, 14 | add, 15 | remove, 16 | device_tag, 17 | device_alias, 18 | device_regid, 19 | device_mobile, 20 | ] 21 | -------------------------------------------------------------------------------- /jpush/device/core.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from jpush import common 3 | import json 4 | 5 | 6 | class Device(object): 7 | """Device info query/update.. 8 | 9 | """ 10 | def __init__(self, jpush, zone = None): 11 | self._jpush = jpush 12 | self.entity = None 13 | self.zone = zone or jpush.zone 14 | 15 | def send(self, method, url, body = None, content_type=None, version=3, params = None): 16 | """Send the request 17 | 18 | """ 19 | response = self._jpush._request(method, body, url, content_type, version=3, params = params) 20 | return DeviceResponse(response) 21 | 22 | def get_taglist(self): 23 | """Get deviceinfo with registration id. 24 | """ 25 | url = common.get_url('tag', self.zone) 26 | info = self.send("GET", url) 27 | return info 28 | 29 | def get_deviceinfo(self, registration_id): 30 | """Get deviceinfo with registration id. 31 | """ 32 | url = common.get_url('device', self.zone) + registration_id 33 | info = self.send("GET", url) 34 | return info 35 | 36 | def set_deviceinfo(self, registration_id, entity): 37 | """Update deviceinfo with registration id. 38 | """ 39 | url = common.get_url('device', self.zone) + registration_id 40 | body = json.dumps(entity) 41 | info = self.send("POST", url, body) 42 | return info 43 | 44 | def set_devicemobile(self, registration_id, entity): 45 | """Update deviceinfo with registration id. 46 | """ 47 | url = common.get_url('device', self.zone) + registration_id 48 | body = json.dumps(entity) 49 | info = self.send("POST", url, body) 50 | return info 51 | 52 | def delete_tag(self, tag, platform=None): 53 | """Delete registration id tag. 54 | """ 55 | url = common.get_url('tag', self.zone) + tag 56 | params = { 'platform': platform } if platform else None 57 | info = self.send("DELETE", url, params = params) 58 | return info 59 | 60 | def update_tagusers(self, tag, entity): 61 | """Add/Remove specified tag users. 62 | """ 63 | url = common.get_url('tag', self.zone) + tag 64 | body = json.dumps(entity) 65 | info = self.send("POST", url, body) 66 | return info 67 | 68 | def check_taguserexist(self, tag, registration_id): 69 | """Check registration id whether in tag. 70 | """ 71 | url = common.get_url('tag', self.zone) + tag + "/registration_ids/" + registration_id 72 | info = self.send("GET", url) 73 | return info 74 | 75 | def delete_alias(self, alias, platform=None): 76 | """Delete appkey alias. 77 | """ 78 | url = common.get_url('alias', self.zone) + alias 79 | params = { 'platform': platform } if platform else None 80 | info = self.send("DELETE", url, params = params) 81 | return info 82 | 83 | def get_aliasuser(self, alias, platform=None): 84 | """Get appkey alias users. 85 | """ 86 | url = common.get_url('alias', self.zone) + alias 87 | params = { 'platform': platform } if platform else None 88 | info = self.send("GET", url, params = params) 89 | return info 90 | 91 | def get_device_status(self, reg_ids): 92 | """Get Online Status of User (VIP Exclusive Interface) 93 | """ 94 | url = common.get_url('device', self.zone) + 'status' 95 | 96 | if isinstance(reg_ids, str): 97 | reg_ids = [ reg_ids ] 98 | 99 | entity = { 'registration_ids': reg_ids } 100 | body = json.dumps(entity) 101 | info = self.send("POST", url, body) 102 | return info 103 | 104 | class DeviceResponse(object): 105 | """Response to a successful device request send. 106 | 107 | Right now this is a fairly simple wrapper around the json payload response, 108 | but making it an object gives us some flexibility to add functionality 109 | later. 110 | 111 | """ 112 | payload = None 113 | status_code = None 114 | 115 | def __init__(self, response): 116 | self.status_code = response.status_code 117 | if 0 != len(response.content): 118 | data = response.json() 119 | self.payload = data 120 | elif 200 == response.status_code: 121 | self.payload = "success" 122 | 123 | def get_status_code(self): 124 | return self.status_code 125 | 126 | def __str__(self): 127 | return "Device response Payload: {0}".format(self.payload) 128 | -------------------------------------------------------------------------------- /jpush/device/entity.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf8 -*- 3 | import sys 4 | 5 | if 2 != sys.version_info[0]: 6 | unicode = str 7 | 8 | def add(*types): 9 | """Select a (list of) to be added objects(s) 10 | 11 | >>> add("registrationid1", "registrationid2") 12 | {'add': ['registrationid1', 'registrationid2']} 13 | >>> add("tag1", "tag2") 14 | {'add': ['tag1', 'tag2']} 15 | >>> add("alias1", "alias2") 16 | {'add': ['alias1', 'alias2']} 17 | """ 18 | vadd = [v for v in types] 19 | return {"add": vadd} 20 | 21 | def remove(*types): 22 | """Select a (list of) to be removed objects(s) 23 | 24 | >>> remove("registrationid1", "registrationid2") 25 | {'remove': ['registrationid1', 'registrationid2']} 26 | >>> remove("tag1", "tag2") 27 | {'remove': ['tag1', 'tag2']} 28 | >>> remove("alias1", "alias2") 29 | {'remove': ['alias1', 'alias2']} 30 | """ 31 | vremove = [v for v in types] 32 | return {"remove": vremove} 33 | 34 | def device_tag(*types): 35 | """Get a tag object 36 | 37 | >>> device_tag("") 38 | {'tags': ''} 39 | >>> device_tag("tag1") 40 | {'tags': 'tag1'} 41 | >>> device_tag(add("tag1", "tag2"), remove("tag3", "tag4")) 42 | {'tags': {'add': ['tag1', 'tag2'], 'remove': ['tag3', 'tag4']}} 43 | """ 44 | tag = {} 45 | if 1 == len(types) and isinstance(types[0], (str, unicode)): 46 | tag["tags"] = types[0] 47 | return tag 48 | tag["tags"] = {} 49 | for t in types: 50 | for key in t: 51 | if key not in ('add', 'remove'): 52 | raise ValueError("Invalid tag '%s'" % t) 53 | tag["tags"][key] = t[key] 54 | return tag 55 | 56 | 57 | def device_mobile(device_mobile): 58 | mobile={} 59 | mobile["mobile"]=device_mobile 60 | return mobile 61 | 62 | 63 | def device_alias(*types): 64 | """Get an alias object 65 | 66 | >>> device_alias("") 67 | {'alias': ''} 68 | >>> device_alias("alias1") 69 | {'alias': 'alias1'} 70 | >>> device_alias(add("alias1", "alias2"), remove("alias3", "alias4")) 71 | {'alias': {'add': ['alias1', 'alias2'], 'remove': ['alias3', 'alias4']}} 72 | """ 73 | alias = {} 74 | if 1 == len(types) and isinstance(types[0], (str, unicode)): 75 | alias["alias"] = types[0] 76 | return alias 77 | alias["alias"] = {} 78 | for t in types: 79 | for key in t: 80 | if key not in ('add', 'remove'): 81 | raise ValueError("Invalid alias '%s'" % t) 82 | alias["alias"][key] = t[key] 83 | return alias 84 | 85 | 86 | def device_regid(*types): 87 | """Get a registration_id object 88 | 89 | >>> device_regid("") 90 | {'registration_ids': ''} 91 | >>> device_regid("registration_id1") 92 | {'registration_ids': 'registration_id1'} 93 | >>> device_regid(add("registration_id1", "registration_id2"), remove("registration_id3", "registration_id4")) 94 | {'registration_ids': {'add': ['registration_id1', 'registration_id2'], 'remove': ['registration_id3', 'registration_id4']}} 95 | """ 96 | registration_id = {} 97 | if 1 == len(types) and isinstance(types[0], (str, unicode)): 98 | registration_id["registration_ids"] = types[0] 99 | return registration_id 100 | registration_id["registration_ids"] = {} 101 | for t in types: 102 | for key in t: 103 | if key not in ('add', 'remove'): 104 | raise ValueError("Invalid registration_id '%s'" % t) 105 | registration_id["registration_ids"][key] = t[key] 106 | return registration_id 107 | 108 | if "__main__" == __name__: 109 | print (add("1", "2")) 110 | print (device_tag(add("a", "b"), remove('1', '2'))) 111 | -------------------------------------------------------------------------------- /jpush/push/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import Push 2 | 3 | from .audience import ( 4 | tag, 5 | tag_and, 6 | tag_not, 7 | alias, 8 | registration_id, 9 | segment, 10 | abtest 11 | ) 12 | 13 | from .payload import ( 14 | android, 15 | ios, 16 | winphone, 17 | hmos, 18 | platform, 19 | cid, 20 | notification, 21 | message, 22 | audience, 23 | options, 24 | smsmessage, 25 | ) 26 | 27 | # Common selector for audience & platform 28 | 29 | all_ = "all" 30 | """Select all, to do a broadcast. 31 | 32 | Used in both ``audience`` and ``platform``. 33 | """ 34 | 35 | __all__ = [ 36 | all_, 37 | Push, 38 | tag, 39 | tag_and, 40 | tag_not, 41 | alias, 42 | registration_id, 43 | segment, 44 | abtest, 45 | notification, 46 | message, 47 | platform, 48 | cid, 49 | audience, 50 | options, 51 | smsmessage, 52 | ] 53 | -------------------------------------------------------------------------------- /jpush/push/audience.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # Value selectors; aliases, tags, etc. 4 | 5 | def tag(*tags): 6 | """Select a (list of) tag(s).""" 7 | vtag = [t for t in tags] 8 | return {"tag": vtag} 9 | 10 | def tag_and(*tag_ands): 11 | """Select a (list of) tag_and(s).""" 12 | vtag_and = [t for t in tag_ands] 13 | return {"tag_and": vtag_and} 14 | 15 | def tag_not(*tag_nots): 16 | """Select a (list of) tag_not(s).""" 17 | vtag_not = [t for t in tag_nots] 18 | return {"tag_not": vtag_not} 19 | 20 | def alias(*alias): 21 | """Select a (list of) alias(es).""" 22 | valias = [t for t in alias] 23 | return {"alias": valias} 24 | 25 | def registration_id(*reg_ids): 26 | """Select a (list of) registration_id(s).""" 27 | vregistration_id = [t for t in reg_ids] 28 | return {"registration_id": vregistration_id} 29 | 30 | def segment(*segments): 31 | """Select a (list of) segment(s).""" 32 | vsegment = [t for t in segments] 33 | return {"segment": vsegment} 34 | 35 | def abtest(*abtests): 36 | """Select a (list of) abtest(s).""" 37 | vabtest = [t for t in abtests] 38 | return {"abtest": vabtest} 39 | -------------------------------------------------------------------------------- /jpush/push/core.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | from jpush import common 4 | 5 | logger = logging.getLogger('jpush') 6 | 7 | 8 | class Push(object): 9 | """A push notification. Set audience, message, etc, and send.""" 10 | 11 | def __init__(self, jpush, end_point = 'push', zone = None): 12 | self._jpush = jpush 13 | self.audience = None 14 | self.notification = None 15 | self.platform = None 16 | self.cid = None 17 | self.options = None 18 | self.message = None 19 | self.smsmessage=None 20 | self.end_point = end_point 21 | self.zone = zone or jpush.zone 22 | 23 | @property 24 | def payload(self): 25 | data = { 26 | "audience": self.audience, 27 | "platform": self.platform, 28 | } 29 | if (self.notification is None) and (self.message is None): 30 | raise ValueError("Notification and message cannot be both empty") 31 | if self.cid is not None: 32 | data['cid'] = self.cid 33 | if self.notification is not None: 34 | data['notification'] = self.notification 35 | if self.smsmessage is not None: 36 | data['sms_message'] = self.smsmessage 37 | if self.options is not None: 38 | data['options'] = self.options 39 | if self.message is not None: 40 | data['message'] = self.message 41 | return data 42 | 43 | def send(self): 44 | """Send the notification. 45 | 46 | :returns: :py:class:`PushResponse` object with ``push_ids`` and 47 | other response data. 48 | :raises JPushFailure: Request failed. 49 | :raises Unauthorized: Authentication failed. 50 | 51 | """ 52 | body = json.dumps(self.payload) 53 | url = common.get_url('push', self.zone) + self.end_point 54 | response = self._jpush._request('POST', body, url, 'application/json', version=3) 55 | return PushResponse(response) 56 | 57 | def send_validate(self): 58 | """Send the notification to validate. 59 | 60 | :returns: :py:class:`PushResponse` object with ``push_ids`` and 61 | other response data. 62 | :raises JPushFailure: Request failed. 63 | :raises Unauthorized: Authentication failed. 64 | 65 | """ 66 | body = json.dumps(self.payload) 67 | url = common.get_url('push', self.zone) + 'push/validate' 68 | 69 | response = self._jpush._request('POST', body, url, 'application/json', version=3) 70 | return PushResponse(response) 71 | 72 | def get_cid(self, count, type = None): 73 | body = None 74 | url = common.get_url('push', self.zone) + 'push/cid' 75 | 76 | params = { 77 | 'count': count, 78 | 'type': type 79 | } 80 | response = self._jpush._request('GET', body, url, 'application/json', version=3, params = params) 81 | return PushResponse(response) 82 | 83 | def batch_push_by_regid(self, single_payload_list): 84 | cid_response = self.get_cid(len(single_payload_list), 'push') 85 | cidlist = cid_response.payload['cidlist'] 86 | batch_payload = {"pushlist":{}} 87 | for index in range(len(single_payload_list)): 88 | batch_payload["pushlist"][cidlist[index]] = single_payload_list[index] 89 | body = json.dumps(batch_payload) 90 | url = common.get_url('push', self.zone) + 'push/batch/regid/single' 91 | response = self._jpush._request('POST', body, url, 'application/json', version=3) 92 | return PushResponse(response) 93 | 94 | def batch_push_by_alias(self, single_payload_list): 95 | cid_response = self.get_cid(len(single_payload_list), 'push') 96 | cidlist = cid_response.payload['cidlist'] 97 | batch_payload = {"pushlist":{}} 98 | for index in range(len(single_payload_list)): 99 | batch_payload["pushlist"][cidlist[index]] = single_payload_list[index] 100 | body = json.dumps(batch_payload) 101 | url = common.get_url('push', self.zone) + 'push/batch/alias/single' 102 | response = self._jpush._request('POST', body, url, 'application/json', version=3) 103 | return PushResponse(response) 104 | 105 | 106 | class PushResponse(object): 107 | """Response to a successful push notification send. 108 | 109 | Right now this is a fairly simple wrapper around the json payload response, 110 | but making it an object gives us some flexibility to add functionality 111 | later. 112 | 113 | """ 114 | payload = None 115 | status_code = None 116 | 117 | def __init__(self, response): 118 | self.status_code = response.status_code 119 | data = response.json() 120 | self.payload = data 121 | 122 | def get_status_code(self): 123 | return self.status_code 124 | 125 | def __str__(self): 126 | return "Response Payload: {0}".format(self.payload) 127 | -------------------------------------------------------------------------------- /jpush/push/payload.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | 4 | # Valid autobadge values: auto, +N, -N 5 | VALID_AUTOBADGE = re.compile(r'^(auto|[+-][\d]+)$') 6 | 7 | 8 | PY2 = sys.version_info[0] == 2 9 | 10 | if not PY2: 11 | string_types = (str,) 12 | else: 13 | string_types = (str, unicode) 14 | 15 | 16 | def notification(alert=None, ios=None, android=None, winphone=None,hmos=None): 17 | """Create a notification payload. 18 | 19 | :keyword alert: A simple text alert, applicable for all platforms. 20 | :keyword ios: An iOS platform override, as generated by :py:func:`ios`. 21 | :keyword android: An Android platform override, as generated by :py:func:`android`. 22 | :keyword winphone: A MPNS platform override, as generated by :py:func:`mpns`. 23 | :keyword hmos: A hmos platform override, as generated by :py:func:`hmos`. 24 | 25 | """ 26 | payload = {} 27 | if alert is not None: 28 | payload['alert'] = alert 29 | if ios is not None: 30 | payload['ios'] = ios 31 | if android is not None: 32 | payload['android'] = android 33 | if winphone is not None: 34 | payload['winphone'] = winphone 35 | if hmos is not None: 36 | payload['hmos'] = hmos 37 | if not payload: 38 | raise ValueError("Notification body may not be empty") 39 | return payload 40 | 41 | 42 | def ios(alert=None, badge='+1', sound=None, content_available=False, 43 | mutable_content=False, category=None, extras=None, sound_disable=False, thread_id=None): 44 | """iOS/APNS specific platform override payload. 45 | 46 | :keyword alert: iOS format alert, as either a string or dictionary. 47 | :keyword badge: An integer badge value or an *autobadge* string. 48 | :keyword sound: An string sound file to play. 49 | :keyword content_available: If True, pass on the content-available command 50 | for Newsstand iOS applications. 51 | :keyword extras: A set of key/value pairs to include in the push payload 52 | sent to the device. 53 | :keyword sound_disalbe: Disable sound to implement slient push. 54 | :keyword mutable_content: If True, enables modifying notification content in iOS service extension. 55 | :keyword category: String category for iOS notification action buttons. 56 | :keyword thread_id: String identifier to group related notifications in iOS. 57 | 58 | >>> ios(alert='Hello!', sound='cat.caf', 59 | ... extras={'articleid': '12345'}) 60 | {'sound': 'cat.caf', 'extras': {'articleid': '12345'}, 'alert': 'Hello!'} 61 | 62 | """ 63 | payload = {} 64 | if alert is not None: 65 | if not isinstance(alert, (string_types, dict)): 66 | raise ValueError("iOS alert must be a string or dictionary") 67 | payload['alert'] = alert 68 | if badge is not None: 69 | if not (isinstance(badge, str) or isinstance(badge, int)): 70 | raise ValueError("iOS badge must be an integer or string") 71 | if isinstance(badge, str) and not VALID_AUTOBADGE.match(badge): 72 | raise ValueError("Invalid iOS autobadge value") 73 | payload['badge'] = badge 74 | if not sound_disable: 75 | if sound is not None: 76 | payload['sound'] = sound 77 | else: 78 | payload['sound'] = 'default' 79 | if content_available: 80 | payload['content-available'] = 1 81 | if mutable_content: 82 | payload['mutable-content'] = 1 83 | if category: 84 | payload['category'] = category 85 | if thread_id: 86 | payload['thread-id'] = thread_id 87 | if extras is not None: 88 | payload['extras'] = extras 89 | return payload 90 | 91 | def android(alert, title=None, builder_id=None, extras=None, 92 | priority=None, category=None, style=None, alert_type=None, 93 | big_text=None, inbox=None, big_pic_path=None, large_icon=None, intent=None, channel_id=None): 94 | """Android specific platform override payload. 95 | 96 | :keyword alert: String alert text.If you set alert to a empty string,then the payload 97 | will not display on notification bar. 98 | more info:https://docs.jiguang.cn/jpush/server/push/rest_api_v3_push/#notification 99 | :keyword alert: String alert text. 100 | :keyword title: String 101 | :keyword builder_id: Integer 102 | :keyword extras: A set of key/value pairs to include in the push payload 103 | sent to the device. 104 | """ 105 | payload = {} 106 | if alert is not None: 107 | payload['alert'] = alert 108 | if title is not None: 109 | payload['title'] = title 110 | if builder_id is not None: 111 | payload['builder_id'] = builder_id 112 | if channel_id is not None: 113 | payload['channel_id'] = channel_id 114 | if priority is not None: 115 | payload['priority'] = priority 116 | if category is not None: 117 | payload['category'] = category 118 | if style is not None: 119 | payload['style'] = style 120 | if alert_type is not None: 121 | payload['alert_type'] = alert_type 122 | if big_text is not None: 123 | payload['big_text'] = big_text 124 | if inbox is not None: 125 | payload['inbox'] = inbox 126 | if big_pic_path is not None: 127 | payload['big_pic_path'] = big_pic_path 128 | if large_icon is not None: 129 | payload['large_icon'] = large_icon 130 | if intent is not None: 131 | payload['intent'] = intent 132 | if extras is not None: 133 | payload['extras'] = extras 134 | return payload 135 | 136 | 137 | def winphone(alert, title=None, _open_page=None, extras=None): 138 | """MPNS specific platform override payload. 139 | 140 | Must include exactly one of ``alert``, ``title``, ``_open_page``, or ``extras``. 141 | 142 | """ 143 | if len(list(filter(None, (alert, _open_page, title)))) != 1: 144 | raise ValueError("MPNS payload must have one notification type.") 145 | payload = {} 146 | if alert is not None: 147 | payload['alert'] = alert 148 | if title is not None: 149 | payload['title'] = title 150 | if _open_page is not None: 151 | payload['_open_page'] = _open_page 152 | if extras is not None: 153 | payload['extras'] = extras 154 | return payload 155 | 156 | def hmos(alert, title=None, category=None, large_icon=None, intent=None, extras=None, style=None, inbox=None): 157 | """Hmos specific platform override payload. 158 | more info:https://docs.jiguang.cn/jpush/server/push/rest_api_v3_push#hmos 159 | 160 | :keyword alert: String alert text. 161 | :keyword title: String 162 | :keyword category: String 163 | :keyword large_icon: String 164 | :keyword intent: A set of key/value pairs to include in the push payload 165 | :keyword extras: A set of key/value pairs to include in the push payload 166 | :keyword style: String 167 | :keyword inbox: String 168 | """ 169 | payload = {} 170 | if alert is not None: 171 | payload['alert'] = alert 172 | if title is not None: 173 | payload['title'] = title 174 | if category is not None: 175 | payload['category'] = category 176 | if large_icon is not None: 177 | payload['large_icon'] = large_icon 178 | if intent is not None: 179 | payload['intent'] = intent 180 | if extras is not None: 181 | payload['extras'] = extras 182 | if style is not None: 183 | payload['style'] = style 184 | if inbox is not None: 185 | payload['inbox'] = inbox 186 | return payload 187 | 188 | def message(msg_content, title=None, content_type=None, extras=None): 189 | """Inner-conn push message payload creation. 190 | 191 | :param msg_content: Required, string 192 | :param title: Optional, string 193 | :keyword content_type: Optional, MIME type of the body 194 | :keyword extras: Optional, dictionary of string values. 195 | 196 | """ 197 | payload = { 198 | 'msg_content': msg_content, 199 | } 200 | if title is not None: 201 | payload['title'] = title 202 | if content_type is not None: 203 | payload['content_type'] = content_type 204 | if extras is not None: 205 | payload['extras'] = extras 206 | return payload 207 | 208 | def smsmessage(delay_time, temp_id, temp_para = None, signid = None, active_filter = True): 209 | payload = {} 210 | payload["delay_time"]=delay_time 211 | payload["temp_id"]=temp_id 212 | if temp_para is not None: 213 | payload['temp_para'] = temp_para 214 | if signid is not None: 215 | payload['signid'] = signid 216 | if not active_filter: 217 | payload['active_filter'] = False 218 | return payload 219 | 220 | 221 | def cid(cid): 222 | payload = {} 223 | payload["cid"]=cid 224 | return payload 225 | 226 | def platform(*types): 227 | """Create a platform specifier. 228 | 229 | >>> platform('ios', 'winphone') 230 | ['ios', 'winphone'] 231 | >>> platform('ios', 'symbian') 232 | Traceback (most recent call last): 233 | ... 234 | ValueError: Invalid platform 'symbian' 235 | 236 | """ 237 | if len(types) == 1 and types[0] == 'all': 238 | return 'all' 239 | for t in types: 240 | if t not in ('ios', 'android', 'winphone', 'hmos'): 241 | raise ValueError("Invalid platform '%s'" % t) 242 | return [t for t in types] 243 | 244 | def options(options): 245 | """Create options object.""" 246 | return {"options": options} 247 | 248 | def audience(*types): 249 | """Select audience that match all of the given selectors. 250 | 251 | >>> audience(tag('sports'), tag_and('business')) 252 | {'audience': {'tag':['sports'], 'tag_and':['business']}} 253 | 254 | """ 255 | if 1 == len(types) and 'all' == types[0]: 256 | return "all" 257 | audience = {} 258 | for t in types: 259 | for key in t: 260 | if key not in ('tag', 'tag_and', 'tag_not', 'alias', 'registration_id', 'segment', 'abtest'): 261 | raise ValueError("Invalid audience '%s'" % t) 262 | audience[key] = t[key] 263 | return audience 264 | -------------------------------------------------------------------------------- /jpush/report/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import Report, ReportResponse 2 | 3 | __all__ = [ 4 | Report, 5 | ReportResponse, 6 | ] -------------------------------------------------------------------------------- /jpush/report/core.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from jpush import common 3 | 4 | logger = logging.getLogger('jpush') 5 | 6 | 7 | class Report(object): 8 | """JPush Report API V3""" 9 | def __init__(self, jpush, zone = None): 10 | self._jpush = jpush 11 | self.zone = zone or jpush.zone 12 | 13 | def send(self, method, url, body = None, content_type=None, version=3, params = None): 14 | """Send the request 15 | """ 16 | response = self._jpush._request(method, body,url,content_type,version=3, params = params) 17 | return ReportResponse(response) 18 | 19 | def get_received(self,msg_ids): 20 | url = common.get_url('report', self.zone) + 'received' 21 | params = { 'msg_ids': msg_ids } 22 | received = self.send("GET", url, params = params) 23 | return received 24 | 25 | def get_received_detail(self, msg_ids): 26 | url = common.get_url('report', self.zone) + 'received/detail' 27 | params = {'msg_ids': msg_ids} 28 | response = self.send("GET", url, params = params) 29 | return response 30 | 31 | def get_status_message(self, msg_id, reg_ids, date=None): 32 | import json 33 | url = common.get_url('report', self.zone) + 'status/message' 34 | if not isinstance(reg_ids, list): 35 | reg_ids = [reg_ids] 36 | body = { 37 | 'msg_id': msg_id, 38 | 'registration_ids': reg_ids 39 | } 40 | if date is not None: 41 | body['date'] = date 42 | body = json.dumps(body) 43 | sm = self.send("POST", url, body = body) 44 | return sm 45 | 46 | def get_messages(self, msg_ids): 47 | url = common.get_url('report', self.zone) + 'messages' 48 | params = { 'msg_ids': msg_ids } 49 | messages = self.send("GET", url, params = params) 50 | return messages 51 | 52 | def get_messages_detail(self, msg_ids): 53 | url = common.get_url('report', self.zone) + 'messages/detail' 54 | params = {'msg_ids': msg_ids} 55 | response = self.send("GET", url, params = params) 56 | return response 57 | 58 | def get_users(self, time_unit,start,duration): 59 | url = common.get_url('report', self.zone) + 'users' 60 | params = { 61 | 'time_unit': time_unit, 62 | 'start': start, 63 | 'duration': duration 64 | } 65 | users = self.send("GET", url, params = params) 66 | return users 67 | 68 | 69 | class ReportResponse(object): 70 | 71 | payload = None 72 | status_code = None 73 | 74 | def __init__(self, response): 75 | self.status_code = response.status_code 76 | if 0 != len(response.content): 77 | data = response.json() 78 | self.payload = data 79 | elif 200 == response.status_code: 80 | self.payload = "success" 81 | 82 | def get_status_code(self): 83 | return self.status_code 84 | 85 | def __str__(self): 86 | return "Report response Payload: {0}".format(self.payload) -------------------------------------------------------------------------------- /jpush/schedule/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import Schedule 2 | 3 | __all__ = [ 4 | Schedule, 5 | ] -------------------------------------------------------------------------------- /jpush/schedule/core.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | from jpush import common 4 | 5 | logger = logging.getLogger('jpush') 6 | 7 | 8 | class Schedule(object): 9 | """JPush Report API V3""" 10 | def __init__(self, jpush, zone = None): 11 | self._jpush = jpush 12 | self.zone = zone or jpush.zone 13 | 14 | def send(self, method, url, body = None, content_type=None, version=3, params = None): 15 | response = self._jpush._request(method, body, url, content_type, version=3, params = params) 16 | return ScheduleResponse(response) 17 | 18 | def post_schedule(self, schedulepayload): 19 | url = common.get_url('schedule', self.zone) 20 | body = json.dumps(schedulepayload) 21 | result = self.send("POST", url, body) 22 | return result 23 | 24 | def get_schedule_by_id(self, schedule_id): 25 | url = common.get_url('schedule', self.zone) + schedule_id 26 | result = self.send("GET", url) 27 | return result 28 | 29 | def get_schedule_list(self, page = 1): 30 | url = common.get_url('schedule', self.zone) 31 | params = { 'page': page } 32 | result = self.send("GET", url, params = params) 33 | return result 34 | 35 | def put_schedule(self, schedulepayload, schedule_id): 36 | url = common.get_url('schedule', self.zone) + schedule_id 37 | body = json.dumps(schedulepayload) 38 | result = self.send("PUT", url, body) 39 | return result 40 | 41 | def delete_schedule(self,schedule_id): 42 | url = common.get_url('schedule', self.zone) + schedule_id 43 | result = self.send("DELETE", url) 44 | return result 45 | 46 | def get_msg_ids(self, schedule_id): 47 | url = common.BASE_SCHEDULEURL + schedule_id + '/msg_ids' 48 | body = None 49 | result = self.send("GET", url, body) 50 | return result 51 | 52 | class ScheduleResponse(object): 53 | """Response to a successful device request send. 54 | 55 | Right now this is a fairly simple wrapper around the json payload response, 56 | but making it an object gives us some flexibility to add functionality 57 | later. 58 | 59 | """ 60 | payload = None 61 | status_code = None 62 | 63 | def __init__(self, response): 64 | self.status_code = response.status_code 65 | if 0 != len(response.content): 66 | data = response.json() 67 | self.payload = data 68 | elif 200 == response.status_code: 69 | self.payload = "success" 70 | 71 | def get_status_code(self): 72 | return self.status_code 73 | 74 | def __str__(self): 75 | return "Schedule response Payload: {0}".format(self.payload) 76 | -------------------------------------------------------------------------------- /jpush/schedule/schedulepayload.py: -------------------------------------------------------------------------------- 1 | import re 2 | from jpush import push 3 | 4 | def schedulepayload(name=None, enabled=None, trigger=None, push=None): 5 | schedulepayload = {} 6 | if name is not None: 7 | schedulepayload['name'] = name 8 | if enabled is not None: 9 | schedulepayload['enabled'] = enabled 10 | if trigger is not None: 11 | schedulepayload['trigger'] = trigger 12 | if push is not None: 13 | schedulepayload['push'] = push 14 | if not schedulepayload: 15 | raise ValueError("schedule payload may not be empty") 16 | return schedulepayload 17 | 18 | 19 | def trigger(time, start=None, end=None,time_unit=None,frequency=None,point=None): 20 | if(start==None and end==None and time_unit==None and frequency==None): 21 | trigger={} 22 | single={} 23 | single["time"]=time 24 | trigger["single"]=single 25 | return trigger 26 | else: 27 | trigger={} 28 | periodical={} 29 | if time is not None: 30 | periodical['time'] = time 31 | if start is not None: 32 | periodical['start'] = start 33 | if end is not None: 34 | periodical['end'] = end 35 | if time_unit is not None: 36 | periodical['time_unit'] = time_unit 37 | if frequency is not None: 38 | periodical['frequency'] = frequency 39 | if point is not None: 40 | periodical['point'] = point 41 | trigger["periodical"]=periodical 42 | return trigger 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | import ast 4 | try: 5 | from setuptools import setup 6 | except (ImportError): 7 | from distutils.core import setup 8 | 9 | _version_re = re.compile(r'__version__\s+=\s+(.*)') 10 | 11 | with open('jpush/__init__.py', 'rb') as f: 12 | version = str(ast.literal_eval(_version_re.search( 13 | f.read().decode('utf-8')).group(1))) 14 | 15 | setup( 16 | name='jpush', 17 | version=version, 18 | description='JPush\'s officially supported Python client library', 19 | keywords=('JPush', 'JPush API', 'Android Push', 'iOS Push', 'HMOS Push'), 20 | license='MIT License', 21 | long_description=open("README.rst", "r").read(), 22 | long_description_content_type="text/markdown", 23 | url='https://github.com/jpush/jpush-api-python-client', 24 | author='jpush', 25 | author_email='support@jpush.cn', 26 | 27 | packages=['jpush', 'jpush.push', 'jpush.device', 'jpush.report', 'jpush.schedule'], 28 | platforms='any', 29 | classifiers=[ 30 | 'Environment :: Console', 31 | 'Intended Audience :: Developers', 32 | 'License :: OSI Approved :: MIT License', 33 | 'Operating System :: OS Independent', 34 | 'Programming Language :: Python', 35 | ], 36 | 37 | install_requires=[ 38 | 'requests>=1.2', 39 | ], 40 | ) 41 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpush/jpush-api-python-client/12fc3561219dff8ad021182a8da37f08f0ff9c49/tests/__init__.py -------------------------------------------------------------------------------- /tests/conf.py.example: -------------------------------------------------------------------------------- 1 | # please put your app_key and master_secret here 2 | app_key = u'xxxxxx' 3 | master_secret = u'xxxxxx' 4 | -------------------------------------------------------------------------------- /tests/devices/test_devices.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tests.conf import app_key, master_secret 3 | from jpush import device 4 | from jpush import common 5 | import jpush as jpush 6 | 7 | 8 | _jpush = jpush.JPush(app_key, master_secret) 9 | device = _jpush.create_device() 10 | _jpush.set_logging("DEBUG") 11 | 12 | 13 | class TestEntity(unittest.TestCase): 14 | def test_create_device(self): 15 | reg_id = '1507bfd3f7c466c355c' 16 | entity = jpush.device_tag(jpush.add("ddd", "tageee")) 17 | result = device.set_devicemobile(reg_id, entity) 18 | self.assertEqual(result.status_code, 200) 19 | 20 | def test_aliasuser(self): 21 | alias = "alias1" 22 | platform = "android,ios,hmos" 23 | result = device.get_aliasuser(alias, platform) 24 | self.assertEqual(result.status_code, 200) 25 | 26 | def test_clear_tag(self): 27 | reg_id = '090c1f59f89' 28 | entity = jpush.device_tag("") 29 | try: 30 | device.set_deviceinfo(reg_id, entity) 31 | except common.JPushFailure: 32 | self.assertEqual(1, 1) 33 | except: 34 | self.assertEqual(1, 0) 35 | 36 | def test_get_device(self): 37 | reg_id = '090c1f59f89' 38 | try: 39 | device.get_deviceinfo(reg_id) 40 | except common.JPushFailure: 41 | self.assertEqual(1, 1) 42 | except: 43 | self.assertEqual(1, 0) 44 | 45 | def test_remove_alias(self): 46 | alias = "alias1" 47 | platform = "android,ios,hmos" 48 | result = device.delete_alias(alias, platform) 49 | self.assertEqual(result.status_code, 200) 50 | 51 | def test_remove_tags(self): 52 | tag = "ddd" 53 | platform = "android,ios,hmos" 54 | result = device.delete_tag(tag, platform) 55 | self.assertEqual(result.status_code, 200) 56 | 57 | def test_tag_list(self): 58 | result = device.get_taglist() 59 | self.assertEqual(result.status_code, 200) 60 | 61 | def test_set_device_mobile(self): 62 | reg_id = '1507bfd3f7c466c355c' 63 | entity = jpush.device_tag(jpush.add("ddd", "tageee")) 64 | result = device.set_devicemobile(reg_id, entity) 65 | self.assertEqual(result.status_code, 200) 66 | 67 | def test_device_mobile(self): 68 | reg_id = '1507bfd3f7c466c355c' 69 | entity = jpush.device_mobile("18588232140") 70 | result = device.set_devicemobile(reg_id, entity) 71 | self.assertEqual(result.status_code, 200) -------------------------------------------------------------------------------- /tests/devices/test_entity.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import jpush 3 | 4 | 5 | class TestEntity(unittest.TestCase): 6 | 7 | def test_basic_entity(self): 8 | entities = ( 9 | (jpush.add, 'tag1', {'add': ['tag1']}), 10 | (jpush.remove, 'tag1', {'remove': ['tag1']}), 11 | (jpush.device_tag, 'tag1', {'tags': 'tag1'}), 12 | (jpush.device_alias, 'alias1', {'alias': 'alias1'}), 13 | (jpush.device_regid, 'registration_id1', {'registration_ids': 'registration_id1'}), 14 | ) 15 | for entity, value, result in entities: 16 | self.assertEqual(entity(value), result) 17 | 18 | def test_compound_entity(self): 19 | self.assertEqual( 20 | jpush.device_tag(jpush.add("tag1", "tag2")), 21 | {'tags':{'add':['tag1', 'tag2']}}) 22 | 23 | self.assertEqual( 24 | jpush.device_tag(jpush.remove("tag1", "tag2")), 25 | {'tags':{'remove':['tag1', 'tag2']}}) 26 | 27 | self.assertEqual( 28 | jpush.device_alias(jpush.add("alias1", "alias2"), jpush.remove("alias3", "alias4")), 29 | {'alias':{'add':['alias1', 'alias2'], 'remove':['alias3', 'alias4']}}) 30 | 31 | self.assertEqual( 32 | jpush.device_regid(jpush.add("regid1", "regid2"), jpush.remove("regid3", "regid4")), 33 | {'registration_ids':{'add':['regid1', 'regid2'], 'remove':['regid3', 'regid4']}}) 34 | -------------------------------------------------------------------------------- /tests/push/test_audience.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import jpush as jpush 3 | from tests.conf import app_key, master_secret 4 | from jpush import common 5 | 6 | 7 | class TestAudience(unittest.TestCase): 8 | 9 | def test_basic_selectors(self): 10 | selectors = ( 11 | (jpush.tag, 'tag1', {'tag': ['tag1']}), 12 | (jpush.tag_and, 'tag1', {'tag_and': ['tag1']}), 13 | (jpush.alias, 'alias1', {'alias':['alias1']}), 14 | (jpush.registration_id, 'regid1', {'registration_id':['regid1']}), 15 | ) 16 | for selector, value, result in selectors: 17 | self.assertEqual(selector(value), result) 18 | 19 | def test_audience(self): 20 | _jpush = jpush.JPush(app_key, master_secret) 21 | 22 | push = _jpush.create_push() 23 | push.audience = jpush.audience( 24 | jpush.tag("tag1", "tag2"), 25 | jpush.alias("alias1", "alias2") 26 | ) 27 | push.notification = jpush.notification(alert="Hello world with audience!") 28 | push.platform = jpush.all_ 29 | try: 30 | response = push.send() 31 | 32 | self.assertEqual(response.status_code, 200) 33 | except common.Unauthorized as e: 34 | self.assertFalse(isinstance(e, common.Unauthorized)) 35 | raise common.Unauthorized("Unauthorized") 36 | except common.APIConnectionException as e: 37 | self.assertFalse(isinstance(e, common.APIConnectionException)) 38 | raise common.APIConnectionException("conn") 39 | except common.JPushFailure as e: 40 | self.assertFalse(isinstance(e, common.JPushFailure)) 41 | print ("JPushFailure") 42 | except: 43 | self.assertFalse(1) 44 | print ("Exception") 45 | -------------------------------------------------------------------------------- /tests/push/test_message.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import unittest 3 | import jpush as jpush 4 | from tests.conf import app_key, master_secret 5 | from jpush import common 6 | 7 | 8 | class TestMessage(unittest.TestCase): 9 | 10 | def test_simple_alert(self): 11 | self.assertEqual(jpush.notification(alert='中文'), {'alert':'中文'}) 12 | 13 | def test_ios(self): 14 | self.assertEqual( 15 | jpush.notification(ios=jpush.ios(alert="Hello", badge="+1", sound="a.caf", extras={'k1':'v1'})), 16 | {'ios': {'sound': 'a.caf', 'extras': {'k1': 'v1'}, 'badge': '+1', 'alert': 'Hello'}} 17 | ) 18 | 19 | def test_iossilent(self): 20 | self.assertEqual( 21 | jpush.notification(ios=jpush.ios(alert="Hello", badge="+1", extras={'k1':'v1'}, sound_disable=True)), 22 | {'ios': {'extras': {'k1': 'v1'}, 'badge': '+1', 'alert': 'Hello'}} 23 | ) 24 | 25 | def test_android(self): 26 | self.assertEqual( 27 | jpush.notification(android=jpush.android(alert="Hello", extras={'k2':'v2'})), 28 | {'android': {'extras': {'k2': 'v2'}, 'alert': 'Hello'}} 29 | ) 30 | 31 | def test_winphone(self): 32 | self.assertEqual( 33 | jpush.notification(winphone=jpush.winphone(alert="Hello", extras={'k3':'v3'})), 34 | {'winphone': {'extras': {'k3': 'v3'}, 'alert': 'Hello'}} 35 | ) 36 | 37 | def test_hmos(self): 38 | self.assertEqual( 39 | jpush.notification(hmos=jpush.hmos(alert="Hello", title="Title", extras={'k4':'v4'})), 40 | {'hmos': {'extras': {'k4': 'v4'}, 'alert': 'Hello', 'title': 'Title'}} 41 | ) 42 | 43 | def test_push(self): 44 | _jpush = jpush.JPush(app_key, master_secret) 45 | push = _jpush.create_push() 46 | push.audience = jpush.all_ 47 | push.notification = jpush.notification(alert="hello python jpush api") 48 | push.platform = jpush.all_ 49 | try: 50 | response = push.send() 51 | self.assertEqual(response.status_code, 200) 52 | except common.Unauthorized as e: 53 | self.assertFalse(isinstance(e, common.Unauthorized)) 54 | raise common.Unauthorized("Unauthorized") 55 | except common.APIConnectionException as e: 56 | self.assertFalse(isinstance(e, common.APIConnectionException)) 57 | raise common.APIConnectionException("conn") 58 | except common.JPushFailure as e: 59 | self.assertFalse(isinstance(e, common.JPushFailure)) 60 | print ("JPushFailure") 61 | except: 62 | self.assertFalse(1) 63 | print ("Exception") 64 | -------------------------------------------------------------------------------- /tests/report/test_report.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import jpush as jpush 3 | from tests.conf import app_key, master_secret 4 | from jpush import common 5 | 6 | _jpush = jpush.JPush(app_key, master_secret) 7 | schedule = _jpush.create_schedule() 8 | _jpush.set_logging("DEBUG") 9 | 10 | report=_jpush.create_report(); 11 | 12 | 13 | class TestEntity(unittest.TestCase): 14 | def test_messages(self): 15 | result = report.get_messages("3289406737") 16 | self.assertEqual(result.status_code, 200) 17 | 18 | def test_get_schedule_by_id(self): 19 | result = report.get_received("3289406737") 20 | self.assertEqual(result.status_code, 200) 21 | 22 | def test_get_schedule_by_invalid_id(self): 23 | try: 24 | result = report.get_users("DAY","2016-04-10","3") 25 | self.assertEqual(result.status_code, 200) 26 | except common.JPushFailure as e: 27 | self.assertIsInstance(e, common.JPushFailure) 28 | 29 | -------------------------------------------------------------------------------- /tests/schedule/test_schedule.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from jpush import schedule 3 | import jpush as jpush 4 | from jpush import common 5 | from tests.conf import app_key, master_secret 6 | import datetime 7 | 8 | _jpush = jpush.JPush(app_key, master_secret) 9 | schedule = _jpush.create_schedule() 10 | # _jpush.set_logging("DEBUG") 11 | 12 | push = _jpush.create_push() 13 | push.audience = jpush.all_ 14 | push.notification = jpush.notification(alert="Hello, world!") 15 | push.platform = jpush.all_ 16 | push=push.payload 17 | 18 | now = datetime.datetime.now() 19 | start_time = (now + datetime.timedelta(days=10)).strftime("%Y-%m-%d %H:%M:%S") 20 | end_time = (now +datetime.timedelta(days=20)).strftime("%Y-%m-%d %H:%M:%S") 21 | 22 | class TestEntity(unittest.TestCase): 23 | 24 | def test_post_schedule(self): 25 | trigger = jpush.schedulepayload.trigger(start_time) 26 | schedulepayload = jpush.schedulepayload.schedulepayload("name", True, trigger, push) 27 | result = schedule.post_schedule(schedulepayload) 28 | self.assertEqual(result.status_code, 200) 29 | 30 | def test_get_schedule_by_id(self): 31 | schedule_id = schedule.get_schedule_list("1").payload['schedules'][0]['schedule_id'] 32 | result = schedule.get_schedule_by_id(schedule_id) 33 | self.assertEqual(result.status_code, 200) 34 | 35 | def test_get_schedule_list(self): 36 | result = schedule.get_schedule_list("1") 37 | self.assertEqual(result.status_code, 200) 38 | 39 | # def test_put_schedule(self): 40 | # task = schedule.get_schedule_list("1").payload['schedules'][0] 41 | # schedule_id = task['schedule_id'] 42 | 43 | # trigger = jpush.schedulepayload.trigger(task['trigger']['single']['time']) 44 | # schedulepayload = jpush.schedulepayload.schedulepayload("update_a_new_name", True, trigger, push) 45 | # result = schedule.put_schedule(schedulepayload, schedule_id) 46 | # self.assertEqual(result.status_code, 200) 47 | --------------------------------------------------------------------------------