├── .gitignore ├── LICENSE.txt ├── README.md ├── pyjoin └── __init__.py ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Nolan Gilley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-join 2 | Python API for Join by Joao 3 | 4 | ### Actions 5 | Join actions should be provided as a dictionary. Each key is an action name, and each value is a list of the custom inputs. Use `None` for no custom input. For example: 6 | ``` 7 | {"Netflix": None, "Tweet": ["test tweet!"]} 8 | ``` 9 | 10 | ### To use the listener: 11 | 12 | ``` python 13 | import pyjoin 14 | 15 | def callback(data): 16 | print(data) 17 | 18 | api_key = "" 19 | l = pyjoin.Listener(name="server-test",port=5050, api_key=api_key) 20 | l.add_callback(callback) 21 | l.run() 22 | 23 | ``` 24 | 25 | #### Output: 26 | 27 | ``` python 28 | {u'json': u'{"push":{"location":false,"fromTasker":false,"toTasker":false,"find":false,"id":"89a694e8-d71b-440e-ad26-0a7034c4b972","deviceId":"0d2aa1c3c16b4e9e9251c2301f37641c","text":"hello"}}', u'type': u'GCMPush'} 29 | ``` 30 | -------------------------------------------------------------------------------- /pyjoin/__init__.py: -------------------------------------------------------------------------------- 1 | """Python API for using Join by joaoapps.""" 2 | import requests 3 | from flask import request, Response, Flask 4 | import os 5 | 6 | SEND_URL = "https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush?apikey=" 7 | LIST_URL = "https://joinjoaomgcd.appspot.com/_ah/api/registration/v1/listDevices?apikey=" 8 | REGISTER_URL = "https://joinjoaomgcd.appspot.com/_ah/api/registration/v1/registerDevice/" 9 | PUBLIC_IP_URL = "https://api.ipify.org" 10 | DEFAULT_PORT = "1820" 11 | 12 | 13 | class Action(object): 14 | 15 | def __init__(self, action): 16 | self.action = action 17 | self.response = Response(status=200, headers={"Content-Type": "text/html"}) 18 | 19 | def __call__(self, *args): 20 | data = request.json 21 | if self.action: 22 | self.action(data) 23 | return self.response 24 | 25 | class Listener: 26 | def __init__(self, name, port, api_key,public_ip=None): 27 | self.api_key = api_key 28 | self.name = name 29 | self.port = port 30 | if public_ip == None: 31 | self.public_ip = self.get_public_ip() 32 | else: 33 | self.public_ip = public_ip 34 | 35 | if port == None: 36 | self.port = DEFAULT_PORT 37 | else: 38 | self.port = port 39 | 40 | self.deviceID = self.get_device_id() 41 | self.app = Flask(self.name) 42 | 43 | def get_device_id(self): 44 | devices = get_devices(self.api_key) 45 | for d in devices: 46 | name, id = d 47 | if name == self.name: 48 | return id 49 | return self.register() 50 | 51 | def run(self): 52 | self.app.run(host="0.0.0.0", port=self.port, debug=False, use_reloader=False) 53 | 54 | def add_callback(self, handler=None): 55 | self.app.add_url_rule("/push", self.name, Action(handler),methods=["POST"]) 56 | 57 | def register(self): 58 | data = { 59 | "apikey": self.api_key, 60 | "regId": "{}:{}".format(self.public_ip,self.port), 61 | "deviceName": self.name, 62 | "deviceType": 13 63 | } 64 | r = requests.post(REGISTER_URL, data = data) 65 | if r.status_code!=200: 66 | raise Exception("Unable to register to join") 67 | return r.json().get("deviceId") 68 | 69 | def get_public_ip(self): 70 | r = requests.get(PUBLIC_IP_URL) 71 | if r.status_code !=200: 72 | raise Exception("Unable to get public IP") 73 | return r.text 74 | 75 | def get_devices(api_key): 76 | response = requests.get(LIST_URL + api_key).json() 77 | if response.get('success') and not response.get('userAuthError'): 78 | return [(r['deviceName'], r['deviceId']) for r in response['records']] 79 | return False 80 | 81 | def send_notification(api_key, text, device_id=None, device_ids=None, device_names=None, title=None, icon=None, smallicon=None, vibration=None, image=None, url=None, tts=None, tts_language=None, sound=None, notification_id=None, category=None, actions=None): 82 | if device_id is None and device_ids is None and device_names is None: return False 83 | req_url = SEND_URL + api_key + "&text=" + text 84 | if title: req_url += "&title=" + title 85 | if icon: req_url += "&icon=" + icon 86 | if image: req_url += "&image=" + image 87 | if smallicon: req_url += "&smallicon=" + smallicon 88 | if vibration: req_url += "&vibration=" + vibration 89 | if url: req_url += "&url=" + url 90 | if tts: req_url += "&say=" + tts 91 | if tts_language: req_url += "&language=" + tts_language 92 | if sound: req_url += "&sound=" + sound 93 | if notification_id: req_url += "¬ificationId=" + notification_id 94 | if category: req_url += "&category=" + category 95 | if device_id: req_url += "&deviceId=" + device_id 96 | if device_ids: req_url += "&deviceIds=" + device_ids 97 | if device_names: req_url += "&deviceNames=" + device_names 98 | if actions: 99 | action_strings = [] 100 | for action in actions: 101 | action_str = action 102 | if actions[action]: 103 | action_str += "=:=" + "=:=".join(actions[action]) 104 | action_strings.append(action_str) 105 | req_url += "&actions=" + "|||".join(action_strings) 106 | requests.get(req_url) 107 | 108 | def ring_device(api_key, device_id=None, device_ids=None, device_names=None): 109 | if device_id is None and device_ids is None and device_names is None: return False 110 | req_url = SEND_URL + api_key + "&find=true" 111 | if device_id: req_url += "&deviceId=" + device_id 112 | if device_ids: req_url += "&deviceIds=" + device_ids 113 | if device_names: req_url += "&deviceNames=" + device_names 114 | requests.get(req_url) 115 | 116 | def send_url(api_key, url, device_id=None, device_ids=None, device_names=None, title=None, text=None): 117 | if device_id is None and device_ids is None and device_names is None: return False 118 | req_url = SEND_URL + api_key + "&url=" + url 119 | if title: req_url += "&title=" + title 120 | req_url += "&text=" + text if text else "&text=" 121 | if device_id: req_url += "&deviceId=" + device_id 122 | if device_ids: req_url += "&deviceIds=" + device_ids 123 | if device_names: req_url += "&deviceNames=" + device_names 124 | requests.get(req_url) 125 | 126 | def set_wallpaper(api_key, url, device_id=None, device_ids=None, device_names=None): 127 | if device_id is None and device_ids is None and device_names is None: return False 128 | req_url = SEND_URL + api_key + "&wallpaper=" + url 129 | if device_id: req_url += "&deviceId=" + device_id 130 | if device_ids: req_url += "&deviceIds=" + device_ids 131 | if device_names: req_url += "&deviceNames=" + device_names 132 | requests.get(req_url) 133 | 134 | def send_file(api_key, url, device_id=None, device_ids=None, device_names=None, title=None, text=None): 135 | if device_id is None and device_ids is None and device_names is None: return False 136 | req_url = SEND_URL + api_key + "&file=" + url 137 | if title: req_url += "&title=" + title 138 | req_url += "&text=" + text if text else "&text=" 139 | if device_id: req_url += "&deviceId=" + device_id 140 | if device_ids: req_url += "&deviceIds=" + device_ids 141 | if device_names: req_url += "&deviceNames=" + device_names 142 | requests.get(req_url) 143 | 144 | def send_sms(api_key, sms_number, sms_text, device_id=None, device_ids=None, device_names=None): 145 | if device_id is None and device_ids is None and device_names is None: return False 146 | req_url = SEND_URL + api_key + "&smsnumber=" + sms_number + "&smstext=" + sms_text 147 | if device_id: req_url += "&deviceId=" + device_id 148 | if device_ids: req_url += "&deviceIds=" + device_ids 149 | if device_names: req_url += "&deviceNames=" + device_names 150 | requests.get(req_url) 151 | 152 | def set_mediavolume(api_key, mediavolume, device_id=None, device_ids=None, device_names=None): 153 | if device_id is None and device_ids is None and device_names is None: return False 154 | req_url = SEND_URL + api_key + "&mediaVolume=" + mediavolume 155 | if device_id: req_url += "&deviceId=" + device_id 156 | if device_ids: req_url += "&deviceIds=" + device_ids 157 | if device_names: req_url += "&deviceNames=" + device_names 158 | requests.get(req_url) 159 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | flask 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | try: 7 | from setuptools import setup 8 | except ImportError: 9 | from distutils.core import setup 10 | 11 | if sys.argv[-1] == 'publish': 12 | os.system('python setup.py sdist upload') 13 | sys.exit() 14 | 15 | license = """ 16 | MIT License 17 | Copyright (c) 2017 Nolan Gilley 18 | Permission is hereby granted, free of charge, to any person obtaining a copy 19 | of this software and associated documentation files (the "Software"), to deal 20 | in the Software without restriction, including without limitation the rights 21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | copies of the Software, and to permit persons to whom the Software is 23 | furnished to do so, subject to the following conditions: 24 | The above copyright notice and this permission notice shall be included in all 25 | copies or substantial portions of the Software. 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | """ 34 | 35 | setup(name='python-join-api', 36 | version='0.0.9', 37 | description='Python API for interacting with Join by joaoapps.', 38 | url='https://github.com/nkgilley/python-join-api', 39 | author='Nolan Gilley', 40 | author_email='nkgilley@gmail.com', 41 | license='MIT', 42 | install_requires=['requests>=2.0','flask>=1.1.2'], 43 | packages=['pyjoin'], 44 | zip_safe=True) 45 | --------------------------------------------------------------------------------