├── .gitignore ├── LICENSE ├── Pipfile ├── README.md ├── requirements.txt ├── runtests.py ├── sample ├── measure_speeds.py ├── speedify_default.py ├── speedify_export.py ├── speedify_import.py ├── speedify_login.py ├── stat_callbacks_sample.py └── teams_export.py ├── speedify.py ├── speedifysettings.py ├── speedifyutil.py ├── tests ├── test_speedify.py └── test_speedifysettings.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.lock 3 | *.log 4 | sample/*.json 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | pip-wheel-metadata/ 29 | share/python-wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # celery beat schedule file 99 | celerybeat-schedule 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Connectify, inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | url = "https://pypi.python.org/simple" 4 | verify_ssl = true 5 | name = "pypi" 6 | 7 | 8 | [packages] 9 | 10 | 11 | [dev-packages] 12 | 13 | 14 | 15 | [requires] 16 | 17 | python_version = "3.6" 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Speedify for Python 2 | ======================= 3 | 4 | Lets you control [Speedify](https://speedify.com), the bonding VPN, from Python. Nearly everything available to the user interface is available via this library. 5 | 6 | This library exposes all of the functionality from the [Speedify CLI](https://support.speedify.com/article/285-speedify-command-line-interface). 7 | 8 | Tested on Windows, macOS, Ubuntu, Raspbian. 9 | 10 | Automatically looks for `speedify_cli` in a number of standard locations. 11 | 12 | You can force it to use a particular location by either setting the environment variable `SPEEDIFY_CLI` or by calling `speedify.set_cli()`. In either case it takes the full path of the `speedify_cli` executable. 13 | 14 | Please see the documentation on our [CLI](https://support.speedify.com/article/285-speedify-cli) for more information on the commands and options available. 15 | 16 | ## Examples 17 | 18 | Put Speedify in speed mode with UDP transport 19 | ```python 20 | import speedify 21 | speedify.mode("speed") 22 | speedify.transport("udp") 23 | ``` 24 | 25 | Alternatively: 26 | ```python 27 | from speedifysettings import apply_setting 28 | #Put Speedify in speed mode with UDP transport 29 | apply_setting("mode", "speed") 30 | apply_setting("transport", "udp") 31 | ``` 32 | 33 | Apply multiple settings at once, and print current settings: 34 | ```python 35 | from speedifysettings import apply_speedify_settings, get_speedify_settings 36 | 37 | speedify_settings = '''{"connectmethod" : "closest","encryption" : true, "jumbo" : true, 38 | "mode" : "speed", "privacy_killswitch":false, "privacy_dnsleak": true, 39 | "startupconnect": true, "transport":"auto","overflow_threshold": 30.0, 40 | "adapter_priority_ethernet" : "always","adapter_priority_wifi" : "always", 41 | "adapter_priority_cellular" : "secondary", "adapter_datalimit_daily_all" : 0, 42 | "adapter_datalimit_monthly_all" : 0, "adapter_ratelimit" : {"download_bps": 0, "upload_bps": 0}, 43 | }''' 44 | 45 | #Apply settings 46 | apply_speedify_settings(speedify_settings) 47 | #Print current settings 48 | print(get_speedify_settings()) 49 | ``` 50 | 51 | `privacy_killswitch` and `privacy_dnsleak` are only supported on Windows. 52 | 53 | The example settings above contain all of the possible settings. 54 | 55 | ## Changelog 56 | 57 | ### Release 12.5.x 58 | 59 | Added 60 | - `dns(str)` 61 | - `streamtest()` 62 | - `directory(str)` 63 | - `gateway(str)` 64 | - `esni(bool)` 65 | - `headercompression(bool)` 66 | - `privacy(str, bool)` 67 | - `daemon(str)` 68 | - `login_auto()` 69 | - `login_oauth(token)` 70 | - `streamingbypass_domains_add(str)` 71 | - `streamingbypass_domains_rem(str)` 72 | - `streamingbypass_domains_set(str)` 73 | - `streamingbypass_ipv4_add(str)` 74 | - `streamingbypass_ipv4_rem(str)` 75 | - `streamingbypass_ipv4_set(str)` 76 | - `streamingbypass_ipv6_add(str)` 77 | - `streamingbypass_ipv6_rem(str)` 78 | - `streamingbypass_ipv6_set(str)` 79 | - `streamingbypass_ports_add(str)` 80 | - `streamingbypass_ports_rem(str)` 81 | - `streamingbypass_ports_set(str)` 82 | - `adapter_overratelimit(str, int)` 83 | - `adapter_dailylimit_boost(str, int)` 84 | - `show_servers()` 85 | - `show_settings()` 86 | - `show_privacy()` 87 | - `show_adapters()` 88 | - `show_currentserver()` 89 | - `show_user()` 90 | - `show_directory()` 91 | - `show_connectmethod()` 92 | - `show_streamingbypass()` 93 | - `show_disconnect()` 94 | - `show_streaming()` 95 | - `show_speedtest()` 96 | 97 | Changed 98 | - `adapter_encryption(str, str) -> adapter_encryption(str, str or bool)` 99 | - `encryption(str) -> encryption(str or bool)` 100 | - `mode(str = "speed") -> mode(str)` 101 | 102 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python_version >= '3.6' 2 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | loader = unittest.TestLoader() 5 | start_dir = os.path.join(os.path.curdir, "tests") 6 | suite = loader.discover(start_dir) 7 | 8 | runner = unittest.TextTestRunner() 9 | runner.run(suite) 10 | -------------------------------------------------------------------------------- /sample/measure_speeds.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | import speedify 5 | from speedify import State 6 | from speedifysettings import apply_setting 7 | import speedifyutil 8 | import time 9 | import math 10 | import json 11 | import logging 12 | 13 | # what properties do we want to test: 14 | possible_attributes = ["jumbo", "transport", "encryption", "privacy_killswitch", "mode"] 15 | 16 | """ 17 | find and apply the best set of speedify settings for you, by trying 18 | every possible combination, and test speed on each and every combo. 19 | 20 | The settings in possible attributes are available to be tested by passing the 21 | setting through the command line 22 | ex: python measure_speeds.py jumbo transport 23 | """ 24 | 25 | logging.basicConfig( 26 | handlers=[logging.FileHandler("test.log"), logging.StreamHandler(sys.stdout)], 27 | format="%(asctime)s\t%(levelname)s\t%(module)s\t%(message)s", 28 | level=logging.DEBUG, 29 | ) 30 | 31 | 32 | def apply_value(name, value): 33 | if name == "transport": 34 | transport = "tcp" if value else "udp" 35 | speedify.transport(transport) 36 | elif name == "mode": 37 | mode = "redundant" if value else "speed" 38 | speedify.mode(mode) 39 | else: 40 | apply_setting(name, value) 41 | 42 | 43 | def print_all_attr(attrlist, attrvalues): 44 | # should turn into json and dump 45 | settingsmap = {} 46 | for att, value in zip(attrlist, attrvalues): 47 | if str(att) == "transport": 48 | attval = "tcp" if value else "udp" 49 | elif str(att) == "redundant": 50 | attval = "redundant" if value else "speed" 51 | else: 52 | attval = value 53 | settingsmap[att] = attval 54 | logging.info(json.dumps(settingsmap)) 55 | 56 | 57 | def print_attr(attribute, value): 58 | if str(attribute) == "transport": 59 | attval = "tcp" if value else "udp" 60 | elif str(attribute) == "redundant": 61 | attval = "redundant" if value else "speed" 62 | else: 63 | attval = value 64 | logging.info(str(attribute) + " = " + str(attval)) 65 | 66 | 67 | def set_all_attr(attrlist, attrvalues): 68 | for att, value in zip(attrlist, attrvalues): 69 | apply_value(att, value) 70 | 71 | 72 | def sizeof_fmt(num, suffix="B"): 73 | for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: 74 | if abs(num) < 1000.0: 75 | return "%3.1f%s%s" % (num, unit, suffix) 76 | num /= 1000.0 77 | return "%.1f%s%s" % (num, "Yi", suffix) 78 | 79 | 80 | def main(): 81 | if len(sys.argv) > 1: 82 | # arguments specified on command line 83 | attributes = [] 84 | for arg in sys.argv: 85 | if arg in possible_attributes: 86 | attributes.append(arg) 87 | else: 88 | logging.info("Need to pass in list of attributes to test") 89 | logging.info("Possible attributes: " + str(possible_attributes)) 90 | sys.exit(1) 91 | 92 | logging.info("Testing with attributes: " + str(attributes)) 93 | 94 | rounds = math.pow(2, len(attributes)) 95 | 96 | # find the fastest server 97 | connect = speedify.connect_closest() 98 | # pull its name so we can connect directly from now on and no risk Getting 99 | # a different server 100 | server = connect["tag"] 101 | 102 | logging.info("Testing using server: " + server) 103 | 104 | # best results yet 105 | best_download = -1 106 | best_download_attributes = [] 107 | best_upload = -1 108 | best_upload_attributes = [] 109 | failed = False 110 | 111 | i = 0 112 | logging.info("== START ==") 113 | while i < rounds: 114 | logging.info("Loop: " + str(i)) 115 | attributecount = 0 116 | atval = False 117 | # list of True|Falses same length as the list of attributes to be tested 118 | current_attributes = [] 119 | # builds a logical table, first attribute alternates, second switches every 120 | # two, third attribute, every 4, so that every possible combination gets tried and tested 121 | for x in attributes: 122 | attributecount = attributecount + 1 123 | demonin = math.pow(2, attributecount) 124 | atval = True if i % demonin < (demonin / 2) else False 125 | current_attributes.append(atval) 126 | set_all_attr(attributes, current_attributes) 127 | print_all_attr(attributes, current_attributes) 128 | i = i + 1 129 | speedify.connect(server) 130 | state = speedify.show_state() 131 | if state != State.CONNECTED: 132 | time.time() 133 | logging.error("Did not connect!") 134 | failed = True 135 | break 136 | trcattempts = 0 137 | trc = False 138 | while trcattempts < 30: 139 | # lets make sure internet is working before proceeding 140 | # typically takes a dozen times through this loop before 141 | # we start seeing it really work. think that's just windows. 142 | try: 143 | if speedifyutil.using_speedify(): 144 | trc = True 145 | break 146 | except FileNotFoundError: 147 | sys.exit(1) 148 | trcattempts = trcattempts + 1 149 | # internet can take a bit, give it some time 150 | time.sleep(0.2) 151 | if not trc: 152 | logging.warning("Speedify not providing internet!") 153 | continue 154 | speedresult = speedify.speedtest() 155 | if speedresult["status"] != "complete": 156 | logging.warning("Speedtest did not complete!") 157 | else: 158 | for result in speedresult["connectionResults"]: 159 | if result["adapterID"] == "speedify": 160 | down = int(result["downloadBps"]) 161 | up = int(result["uploadBps"]) 162 | 163 | logging.info("download speed - " + str(sizeof_fmt(down)) + "bps") 164 | if down > best_download: 165 | logging.info(" best download yet!") 166 | best_download = down 167 | best_download_attributes = list(current_attributes) 168 | logging.info("upload speed - " + str(sizeof_fmt(up)) + "bps") 169 | if up > best_upload: 170 | logging.info(" best upload yet!") 171 | best_upload = up 172 | best_upload_attributes = list(current_attributes) 173 | speedify.disconnect() 174 | 175 | if not failed: 176 | logging.info("== DONE ==") 177 | logging.info("best download : " + str(sizeof_fmt(best_download)) + "bps") 178 | print_all_attr(attributes, best_download_attributes) 179 | logging.info("best upload : " + str(sizeof_fmt(best_upload)) + "bps") 180 | print_all_attr(attributes, best_upload_attributes) 181 | 182 | logging.info("Applying best download values") 183 | set_all_attr(attributes, best_download_attributes) 184 | else: 185 | logging.error("== FAILED ==") 186 | sys.exit(1) 187 | 188 | 189 | if __name__ == "__main__": 190 | main() 191 | -------------------------------------------------------------------------------- /sample/speedify_default.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | import speedifysettings 5 | import logging 6 | 7 | logging.basicConfig( 8 | handlers=[logging.FileHandler("test.log"), logging.StreamHandler(sys.stdout)], 9 | format="%(asctime)s\t%(levelname)s\t%(module)s\t%(message)s", 10 | level=logging.INFO, 11 | ) 12 | 13 | # Get the default settings, and apply them to just put us in a known state 14 | success = speedifysettings.apply_speedify_settings(speedifysettings.speedify_defaults) 15 | if success: 16 | logging.info("Default settings applied successfully") 17 | sys.exit(0) 18 | else: 19 | logging.info("Failed to apply default settings") 20 | sys.exit(1) 21 | -------------------------------------------------------------------------------- /sample/speedify_export.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | import speedifysettings 5 | import logging 6 | 7 | # Write out the current speedify settings to std out 8 | logging.basicConfig( 9 | handlers=[logging.FileHandler("test.log"), logging.StreamHandler(sys.stdout)], 10 | format="%(asctime)s\t%(levelname)s\t%(module)s\t%(message)s", 11 | level=logging.INFO, 12 | ) 13 | 14 | currentsettings = speedifysettings.get_speedify_settings_as_json_string() 15 | print(currentsettings) 16 | -------------------------------------------------------------------------------- /sample/speedify_import.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | import speedifysettings 5 | import logging 6 | import fileinput 7 | 8 | # takes a settings json and applies the settings. Either name on command line or piped in a stdin. 9 | logging.basicConfig( 10 | handlers=[logging.FileHandler("test.log"), logging.StreamHandler(sys.stdout)], 11 | format="%(asctime)s\t%(levelname)s\t%(module)s\t%(message)s", 12 | level=logging.INFO, 13 | ) 14 | 15 | for line in fileinput.input(): 16 | speedifysettings.apply_speedify_settings(line) 17 | -------------------------------------------------------------------------------- /sample/speedify_login.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | import speedify 5 | from speedify import State, SpeedifyError, Priority 6 | 7 | """ 8 | This sample connects to the closest server and configures some speedify settings 9 | """ 10 | 11 | if len(sys.argv) > 2: 12 | user = sys.argv[1] 13 | password = sys.argv[2] 14 | 15 | state = speedify.show_state() 16 | print("Speedify's state is " + str(state)) 17 | if state == State.LOGGED_OUT: 18 | try: 19 | speedify.login(user, password) 20 | except Exception: 21 | print("Error could not login!") 22 | sys.exit(1) 23 | 24 | try: 25 | if speedify.show_state() != State.LOGGED_IN: 26 | # get to LOGGED_IN state 27 | speedify.disconnect() 28 | state = speedify.show_state() 29 | print("Speedify's state is " + str(state)) 30 | except SpeedifyError: 31 | pass 32 | 33 | try: 34 | print("Configuring settings") 35 | # make sure encryption is on 36 | speedify.encryption(True) 37 | # we do not want to connect everytime we login, just when we say 38 | speedify.startupconnect(False) 39 | # make our default connect(), connect to the closest server. 40 | speedify.connectmethod("closest") 41 | print("Settings configured") 42 | except SpeedifyError as se: 43 | print("Error setting settings " + se.message) 44 | sys.exit(1) 45 | 46 | 47 | try: 48 | print("connecting to the closest server") 49 | serverInfo = speedify.connect_closest() 50 | print("Server country: " + serverInfo["country"]) 51 | print("Server city: " + serverInfo["city"]) 52 | print("Server num: " + str(serverInfo["num"])) 53 | print("connected!") 54 | 55 | adapters = speedify.show_adapters() 56 | for adapter in adapters: 57 | if adapter["type"] == "Wi-Fi": 58 | print("Found a Wi-Fi card: " + str(adapter["description"])) 59 | guid = adapter["adapterID"] 60 | print("adapterID: " + str(guid)) 61 | newadapters = speedify.adapter_priority(guid, Priority.ALWAYS) 62 | print("Priority: " + adapter["priority"]) 63 | 64 | sys.exit(0) 65 | except SpeedifyError as se: 66 | print("problems connecting " + se.message) 67 | sys.exit(1) 68 | -------------------------------------------------------------------------------- /sample/stat_callbacks_sample.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | import speedify 5 | import logging 6 | 7 | logging.basicConfig( 8 | handlers=[logging.FileHandler("test.log"), logging.StreamHandler(sys.stdout)], 9 | format="%(asctime)s\t%(levelname)s\t%(module)s\t%(message)s", 10 | level=logging.DEBUG, 11 | ) 12 | 13 | """ 14 | stats_callback sample: log whenever Wi-Fi SSID or speedify state changes 15 | """ 16 | 17 | 18 | class speedify_callback: 19 | def __init__(self): 20 | self.last_ssid = "" 21 | self.last_state = None 22 | 23 | def __call__(self, callback_input): 24 | if callback_input[0] == "adapters": 25 | self.adapter_callback(callback_input) 26 | elif callback_input[0] == "state": 27 | self.state_callback(callback_input) 28 | 29 | def adapter_callback(self, callback_input): 30 | networklist = callback_input[1] 31 | for network in networklist: 32 | if (network["type"] == "Wi-Fi") and (network["state"] == "connected"): 33 | if "connectedNetworkName" in network: 34 | ssid = network["connectedNetworkName"] 35 | if not self.last_ssid == ssid: 36 | logging.info("SSID changed to " + ssid) 37 | self.last_ssid = ssid 38 | 39 | def state_callback(self, callback_input): 40 | state_obj = callback_input[1] 41 | if "state" in state_obj: 42 | new_state = state_obj["state"] 43 | if new_state != self.last_state: 44 | logging.info("State changed to " + new_state) 45 | self.last_state = new_state 46 | 47 | 48 | speedify_callback = speedify_callback() 49 | speedify.stats_callback(0, speedify_callback) 50 | -------------------------------------------------------------------------------- /sample/teams_export.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | import speedify 5 | import json 6 | import os 7 | 8 | from speedify import Priority 9 | from speedify import SpeedifyError 10 | import logging 11 | 12 | # Write out the current speedify settings to std out 13 | # In format suitable for use with the Speedify for Teams API. 14 | # if the first command line argument is "lock" then all the settings will be generated marked as locked 15 | 16 | logging.basicConfig( 17 | handlers=[logging.FileHandler("test.log"), logging.StreamHandler(sys.stdout)], 18 | format="%(asctime)s\t%(levelname)s\t%(module)s\t%(message)s", 19 | level=logging.INFO, 20 | ) 21 | 22 | adaptertypes = ["Wi-Fi", "Cellular", "Ethernet", "Unknown"] 23 | 24 | 25 | def get_team_settings_as_json_string(locked=False, exportadapters=True): 26 | """ 27 | Returns the current speedify settings as a JSON string 28 | 29 | :returns: str -- JSON string of speedify settings 30 | """ 31 | return json.dumps(get_team_settings(locked, exportadapters)) 32 | 33 | 34 | def get_team_settings(locked, exportadapters): 35 | """ 36 | Returns the current speedify settings as a dict 37 | 38 | :returns: dict -- dict of speedify settings 39 | """ 40 | # The tree where we build our export 41 | settingsExport = {} 42 | 43 | try: 44 | currentsettings = speedify.show_settings() 45 | if exportadapters: 46 | adapters = speedify.show_adapters() 47 | # TODO: perConnectionEncryptionSettings 48 | # perConnectionEncryptionSettings = {} 49 | # settingsExport["perConnectionEncryptionSettings"] = perConnectionEncryptionSettings; 50 | prioritiesExport = { 51 | "Wi-Fi": {"value": "always", "locked": locked}, 52 | "Ethernet": {"value": "always", "locked": locked}, 53 | "Unknown": {"value": "always", "locked": locked}, 54 | "Cellular": {"value": "secondary", "locked": locked}, 55 | } 56 | settingsExport["priorities"] = prioritiesExport 57 | ratelimitExport = { 58 | "Wi-Fi": {"value": 0, "locked": locked}, 59 | "Ethernet": {"value": 0, "locked": locked}, 60 | "Unknown": {"value": 0, "locked": locked}, 61 | "Cellular": {"value": 0, "locked": locked}, 62 | } 63 | settingsExport["rateLimit"] = ratelimitExport 64 | monthlyLimitExport = { 65 | "Wi-Fi": { 66 | "value": {"monthlyLimit": 0, "monthlyResetDay": 0}, 67 | "locked": locked, 68 | }, 69 | "Ethernet": { 70 | "value": {"monthlyLimit": 0, "monthlyResetDay": 0}, 71 | "locked": locked, 72 | }, 73 | "Unknown": { 74 | "value": {"monthlyLimit": 0, "monthlyResetDay": 0}, 75 | "locked": locked, 76 | }, 77 | "Cellular": { 78 | "value": {"monthlyLimit": 0, "monthlyResetDay": 0}, 79 | "locked": locked, 80 | }, 81 | } 82 | dailyLimitExport = { 83 | "Wi-Fi": {"value": 0, "locked": locked}, 84 | "Ethernet": {"value": 0, "locked": locked}, 85 | "Unknown": {"value": 0, "locked": locked}, 86 | "Cellular": {"value": 0, "locked": locked}, 87 | } 88 | overlimitRateLimitExport = { 89 | "Wi-Fi": {"value": 0, "locked": locked}, 90 | "Ethernet": {"value": 0, "locked": locked}, 91 | "Unknown": {"value": 0, "locked": locked}, 92 | "Cellular": {"value": 0, "locked": locked}, 93 | } 94 | settingsExport["overlimitRateLimit"] = overlimitRateLimitExport 95 | encyptdefault = currentsettings["encrypted"] 96 | perConnectionEncryptionSettingExport = { 97 | "Wi-Fi": {"value": encyptdefault, "locked": locked}, 98 | "Ethernet": {"value": encyptdefault, "locked": locked}, 99 | "Unknown": {"value": encyptdefault, "locked": locked}, 100 | "Cellular": {"value": encyptdefault, "locked": locked}, 101 | } 102 | 103 | perConnectionEncryptionSettings = currentsettings[ 104 | "perConnectionEncryptionSettings" 105 | ] 106 | anyperconnection = False 107 | anylimits = False 108 | 109 | for adapter in adapters: 110 | logging.debug("Adapter is :" + str(adapter)) 111 | adaptertype = adapter["type"] 112 | # everything is keyed on adapter type, if you have 113 | # more than one adapter with same type, one of them 114 | # will get overwritten by the other. 115 | for perConnectionEncryptionSetting in perConnectionEncryptionSettings: 116 | # the per connection encryption is tucked into settings instead of adapters, 117 | # but you still need to look in adapters to find the adaptertype 118 | if ( 119 | perConnectionEncryptionSetting["adapterID"] 120 | == adapter["adapterID"] 121 | ): 122 | encryptExport = {} 123 | encryptExport["value"] = perConnectionEncryptionSetting[ 124 | "encrypted" 125 | ] 126 | encryptExport["locked"] = locked 127 | anyperconnection = True 128 | perConnectionEncryptionSettingExport[ 129 | adaptertype 130 | ] = encryptExport 131 | 132 | ratelimitExport[adaptertype] = {} 133 | ratelimitExport[adaptertype]["value"] = adapter["rateLimit"] 134 | ratelimitExport[adaptertype]["locked"] = locked 135 | prioritiesExport[adaptertype] = {} 136 | prioritiesExport[adaptertype]["value"] = adapter["priority"] 137 | prioritiesExport[adaptertype]["locked"] = locked 138 | 139 | if "dataUsage" in adapter: 140 | limits = adapter["dataUsage"] 141 | anylimits = True 142 | monthlyLimitExport[adaptertype] = {} 143 | monthlyLimitExport[adaptertype]["value"] = {} 144 | monthlyLimitExport[adaptertype]["value"]["monthlyLimit"] = limits[ 145 | "usageMonthlyLimit" 146 | ] 147 | monthlyLimitExport[adaptertype]["value"][ 148 | "monthlyResetDay" 149 | ] = limits["usageMonthlyResetDay"] 150 | monthlyLimitExport[adaptertype]["locked"] = locked 151 | dailyLimitExport[adaptertype] = {} 152 | dailyLimitExport[adaptertype]["value"] = limits["usageDailyLimit"] 153 | dailyLimitExport[adaptertype]["locked"] = locked 154 | overlimitRateLimitExport[adaptertype] = {} 155 | overlimitRateLimitExport[adaptertype]["value"] = limits[ 156 | "overlimitRatelimit" 157 | ] 158 | overlimitRateLimitExport[adaptertype]["locked"] = locked 159 | # add optional sections only if they have something 160 | if anyperconnection: 161 | settingsExport[ 162 | "perConnectionEncryptionSetting" 163 | ] = perConnectionEncryptionSettingExport 164 | if anylimits: 165 | settingsExport["monthlyLimit"] = monthlyLimitExport 166 | settingsExport["dailyLimit"] = dailyLimitExport 167 | # done with adapters 168 | logging.debug("Settings are:" + str(currentsettings)) 169 | settingsExport["encrypted"] = {} 170 | settingsExport["encrypted"]["value"] = currentsettings["encrypted"] 171 | settingsExport["encrypted"]["locked"] = locked 172 | settingsExport["jumboPackets"] = {} 173 | settingsExport["jumboPackets"]["value"] = currentsettings["jumboPackets"] 174 | settingsExport["jumboPackets"]["locked"] = locked 175 | settingsExport["transportMode"] = {} 176 | settingsExport["transportMode"]["value"] = currentsettings["transportMode"] 177 | settingsExport["transportMode"]["locked"] = locked 178 | settingsExport["startupConnect"] = {} 179 | settingsExport["startupConnect"]["value"] = currentsettings["startupConnect"] 180 | settingsExport["startupConnect"]["locked"] = locked 181 | settingsExport["bondingMode"] = {} 182 | settingsExport["bondingMode"]["value"] = currentsettings["bondingMode"] 183 | settingsExport["bondingMode"]["locked"] = locked 184 | settingsExport["overflowThreshold"] = {} 185 | settingsExport["overflowThreshold"]["value"] = currentsettings[ 186 | "overflowThreshold" 187 | ] 188 | settingsExport["overflowThreshold"]["locked"] = locked 189 | settingsExport["packetAggregation"] = {} 190 | settingsExport["packetAggregation"]["value"] = currentsettings[ 191 | "packetAggregation" 192 | ] 193 | settingsExport["packetAggregation"]["locked"] = locked 194 | settingsExport["allowChaChaEncryption"] = {} 195 | settingsExport["allowChaChaEncryption"]["value"] = currentsettings[ 196 | "allowChaChaEncryption" 197 | ] 198 | settingsExport["allowChaChaEncryption"]["locked"] = locked 199 | 200 | connectmethodsettings = speedify.show_connectmethod() 201 | settingsExport["connectMethod"] = {} 202 | settingsExport["connectMethod"]["value"] = speedify.connectmethod_as_string( 203 | connectmethodsettings 204 | ) 205 | settingsExport["connectMethod"]["locked"] = locked 206 | 207 | if "forwardedPorts" in currentsettings: 208 | forwardedPortsExport = {} 209 | forwardedPortsExport["locked"] = locked 210 | forwardedPortsExport["value"] = [] 211 | forwardedPortSettings = currentsettings["forwardedPorts"] 212 | for portInfo in forwardedPortSettings: 213 | newPortExport = {} 214 | newPortExport["port"] = portInfo["port"] 215 | newPortExport["proto"] = portInfo["protocol"] 216 | forwardedPortsExport["value"].append(newPortExport) 217 | settingsExport["forwardedPorts"] = forwardedPortsExport 218 | 219 | privacysettings = speedify.show_privacy() 220 | 221 | if "dnsLeak" in privacysettings: 222 | settingsExport["dnsleak"] = {} 223 | settingsExport["dnsleak"]["value"] = privacysettings["dnsleak"] 224 | settingsExport["dnsleak"]["locked"] = locked 225 | if "killswitch" in privacysettings: 226 | settingsExport["killswitch"] = {} 227 | settingsExport["killswitch"]["value"] = privacysettings["killswitch"] 228 | settingsExport["killswitch"]["locked"] = locked 229 | if "dnsAddresses" in privacysettings: 230 | settingsExport["dnsAddresses"] = {} 231 | settingsExport["dnsAddresses"]["locked"] = locked 232 | settingsExport["dnsAddresses"]["value"] = [] 233 | for dnsserver in privacysettings["dnsAddresses"]: 234 | settingsExport["dnsAddresses"]["value"].append(dnsserver) 235 | settingsExport["crashReports"] = {} 236 | settingsExport["crashReports"]["value"] = privacysettings["crashReports"] 237 | settingsExport["crashReports"]["locked"] = locked 238 | 239 | except SpeedifyError as se: 240 | logging.error("Speedify error on getTeamSetting:" + str(se)) 241 | 242 | jsonExport = {} 243 | jsonExport["settings"] = settingsExport 244 | return jsonExport 245 | 246 | 247 | lockoutput = False 248 | exportAdapters = True 249 | if len(sys.argv) > 1: 250 | if "lock" in sys.argv: 251 | lockoutput = True 252 | currentsettings = get_team_settings_as_json_string( 253 | locked=lockoutput, exportadapters=exportAdapters 254 | ) 255 | print(currentsettings) 256 | -------------------------------------------------------------------------------- /speedify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # Uses Python 3.7 3 | 4 | import json 5 | import logging 6 | import subprocess 7 | import os 8 | from enum import Enum 9 | from functools import wraps 10 | from utils import use_shell 11 | 12 | logger = logging.getLogger(__name__) 13 | logger.setLevel(logging.INFO) 14 | 15 | """ 16 | .. module:: speedify 17 | :synopsis: Contains speedify cli wrapper functions 18 | """ 19 | 20 | 21 | class State(Enum): 22 | """Enum of speedify states.""" 23 | 24 | LOGGED_OUT = 0 25 | LOGGING_IN = 1 26 | LOGGED_IN = 2 27 | AUTO_CONNECTING = 3 28 | CONNECTING = 4 29 | DISCONNECTING = 5 30 | CONNECTED = 6 31 | OVERLIMIT = 7 32 | UNKNOWN = 8 33 | 34 | 35 | class Priority(Enum): 36 | """Enum of speedify connection priorities.""" 37 | 38 | ALWAYS = "always" 39 | BACKUP = "backup" 40 | SECONDARY = "secondary" 41 | NEVER = "never" 42 | 43 | 44 | class SpeedifyError(Exception): 45 | """Generic error thrown by library.""" 46 | 47 | def __init__(self, message): 48 | self.message = message 49 | 50 | 51 | class SpeedifyAPIError(SpeedifyError): 52 | """Error thrown if speedify gave a bad json response.""" 53 | 54 | def __init__(self, error_code, error_type, error_message): 55 | self.error_code = error_code 56 | self.error_type = error_type 57 | self.error_message = error_message 58 | self.message = error_message 59 | 60 | 61 | _cli_path = None 62 | 63 | 64 | def set_cli(new_cli_path): 65 | """Change the path to the cli after importing the module. 66 | The path defaults to the cli's default install location. 67 | 68 | :param new_cli_path: Full path to speedify_cli. 69 | :type new_cli_path: str 70 | """ 71 | global _cli_path 72 | _cli_path = new_cli_path 73 | 74 | 75 | def get_cli(): 76 | """ 77 | :returns: str -- The full path to the speedify cli. 78 | """ 79 | global _cli_path 80 | if (_cli_path == None) or (_cli_path == ""): 81 | _cli_path = _find_cli() 82 | return _cli_path 83 | 84 | 85 | def find_state_for_string(mystate): 86 | return State[str(mystate).upper().strip()] 87 | 88 | 89 | def exception_wrapper(argument): 90 | def decorator(function): 91 | @wraps(function) 92 | def wrapper(*args, **kwargs): 93 | try: 94 | result = function(*args, **kwargs) 95 | return result 96 | except SpeedifyError as err: 97 | logger.error(argument + ": " + err.message) 98 | raise err 99 | 100 | return wrapper 101 | 102 | return decorator 103 | 104 | 105 | # Functions for controlling Speedify State 106 | 107 | 108 | @exception_wrapper("Failed to connect") 109 | def connect(server: str = ""): 110 | """ 111 | connect(server="") 112 | Tell Speedify to connect. Returns serverInformation if success, raises Speedify if unsuccessful. 113 | See show_servers() for the list of servers available. 114 | 115 | Example: 116 | connect() 117 | connect("us nyc 11") # server numbers may change, use show_servers() 118 | 119 | :param server: Server to connect to. 120 | :type server: str 121 | :returns: dict -- :ref:`JSON currentserver ` from speedify. 122 | """ 123 | args = ["connect"] + server.split() 124 | return _run_speedify_cmd(args) 125 | 126 | 127 | def connect_closest(): 128 | """Connects to the closest server 129 | 130 | :returns: dict -- :ref:`JSON currentserver ` from speedify. 131 | """ 132 | return connect("closest") 133 | 134 | 135 | def connect_public(): 136 | """Connects to the closest public server 137 | 138 | :returns: dict -- :ref:`JSON currentserver ` from speedify. 139 | """ 140 | return connect("public") 141 | 142 | 143 | def connect_private(): 144 | """Connects to the closest private server 145 | 146 | :returns: dict -- :ref:`JSON currentserver ` from speedify. 147 | """ 148 | return connect("private") 149 | 150 | 151 | def connect_p2p(): 152 | """Connects to a server that allows p2p traffic 153 | 154 | :returns: dict -- :ref:`JSON currentserver ` from speedify. 155 | """ 156 | return connect("p2p") 157 | 158 | 159 | def connect_country(country: str = "us"): 160 | """Connects to a server via the 2 letter country code 161 | See show_servers() for the list of servers available. 162 | 163 | :param country: 2 letter country code. 164 | :type country: str 165 | :returns: dict -- :ref:`JSON currentserver ` from speedify. 166 | """ 167 | return connect(country) 168 | 169 | 170 | def connect_last(): 171 | """Connects to the last server 172 | 173 | :returns: dict -- :ref:`JSON currentserver ` from speedify. 174 | """ 175 | return connect("last") 176 | 177 | 178 | @exception_wrapper("Disconnect failed") 179 | def disconnect(): 180 | """ 181 | disconnect() 182 | Disconnects. Waits for disconnect to complete 183 | 184 | :returns: bool -- TRUE if disconnect succeeded 185 | """ 186 | _run_speedify_cmd(["disconnect"]) 187 | return True 188 | 189 | 190 | @exception_wrapper("Failed to set connect method") 191 | def connectmethod(method, country="us", city=None, num=None): 192 | """ 193 | connectmethod(method, country="us", city=None, num=None) 194 | Sets the default connectmethod of -- 195 | closest 196 | public 197 | private 198 | p2p 199 | country (in which case country is required) 200 | 201 | :param method: The connect method. 202 | :type method: str 203 | :param country: 2 letter country code. 204 | :type country: str 205 | :param city: The (optional) city the server is located. 206 | :type city: str 207 | :param num: The (optional) server number. 208 | :type num: int 209 | :returns: dict -- :ref:`JSON connectmethod ` from speedify. 210 | """ 211 | args = ["connectmethod"] 212 | if method == "dedicated": 213 | method = "private" 214 | if method == "country": 215 | args.append(country) 216 | if city != None: 217 | args.append(city) 218 | if num != None: 219 | args.append(num) 220 | elif method: 221 | args.append(method) 222 | resultjson = _run_speedify_cmd(args) 223 | return resultjson 224 | 225 | 226 | def connectmethod_as_string(connectMethodObject, hypens=True): 227 | """takes the JSON returned by show_connectmethod and turns it into a string 228 | either with -s for places that want us-nova-2 type strings, or with spaces 229 | for passing to command line of connectmethod, "us nova 2", for example 230 | """ 231 | sep = " " 232 | if hypens: 233 | sep = "-" 234 | ret = connectMethodObject["connectMethod"] 235 | if ret == "country": 236 | ret = str(connectMethodObject["country"]) 237 | if connectMethodObject["city"]: 238 | ret = ret + sep + str(connectMethodObject["city"]) 239 | if connectMethodObject["num"]: 240 | ret = ret + sep + str(connectMethodObject["num"]) 241 | return ret 242 | 243 | 244 | @exception_wrapper("Failed to login") 245 | def login(user, password): 246 | """ 247 | login(user, password) 248 | Login. Returns a State. State object will hold 249 | information on the success of this command. 250 | 251 | :param user: username 252 | :type user: str 253 | :param password: password 254 | :type password: str 255 | :returns: speedify.State -- The speedify state enum. 256 | """ 257 | args = ["login", user, password] 258 | resultjson = _run_speedify_cmd(args) 259 | return find_state_for_string(resultjson["state"]) 260 | 261 | 262 | @exception_wrapper("Failed to login") 263 | def login_auto(): 264 | """ 265 | login() 266 | Attempt to login automatically. 267 | Returns a state indicating success category. 268 | 269 | :returns: speedify.State -- The speedify state enum. 270 | """ 271 | resultjson = _run_speedify_cmd(["login", "auto"]) 272 | return find_state_for_string(resultjson["state"]) 273 | 274 | 275 | @exception_wrapper("Failed to login") 276 | def login_oauth(token: str): 277 | """ 278 | login() 279 | Attempt to login via an oauth token. 280 | Returns a state indicating success category. 281 | 282 | :param token: The oauth token. 283 | :tyope token: str 284 | :returns: speedify.State -- The speedify state enum. 285 | """ 286 | resultjson = _run_speedify_cmd("login", "oauth", token) 287 | return find_state_for_string(resultjson["state"]) 288 | 289 | 290 | @exception_wrapper("Failed to logout") 291 | def logout(): 292 | """ 293 | logout() 294 | logout. returns the state, desc=LOGGED_OUT is a success 295 | 296 | :returns: speedify.State -- The speedify state enum. 297 | """ 298 | jret = _run_speedify_cmd(["logout"]) 299 | return find_state_for_string(jret["state"]) 300 | 301 | 302 | def esni(is_on: bool = True): 303 | """ 304 | esni(is_on) 305 | Turn esni functionality on or off. 306 | 307 | :param is_on: Whether esni should be on... or not. 308 | :type is_on: bool 309 | """ 310 | if is_on is True: 311 | is_on = "on" 312 | elif is_on is False: 313 | is_on = "off" 314 | else: 315 | raise ValueError("is_on neither True nor False") 316 | return _run_speedify_cmd(["esni", is_on]) 317 | 318 | 319 | def headercompression(is_on: bool = True): 320 | """ 321 | headercompression(is_on) 322 | Turn header compression on or off. 323 | 324 | :param is_on: Whether headercompression should be on... or not. 325 | :type is_on: bool 326 | """ 327 | if is_on is True: 328 | is_on = "on" 329 | elif is_on is False: 330 | is_on = "off" 331 | else: 332 | raise ValueError("is_on neither True nor False") 333 | return _run_speedify_cmd(["headercompression", is_on]) 334 | 335 | 336 | def gateway(uri: str): 337 | """ 338 | gateway(uri) 339 | 340 | Set the gateway uri. 341 | 342 | :param uri: The gateway uri. 343 | :type uri: str 344 | """ 345 | return _run_speedify_cmd(["gateway", uri]) 346 | 347 | 348 | def daemon(method: str): 349 | """ 350 | daemon(method) 351 | Call `method` on the daemon. Only "exit" is supported. 352 | 353 | :param method: The method to call. Only "exit" is available. 354 | """ 355 | return _run_speedify_cmd(["daemon", method]) 356 | 357 | 358 | # 359 | # Getter functions 360 | # 361 | 362 | 363 | @exception_wrapper("Failed to show server list") 364 | def show_servers(): 365 | """ 366 | show_servers() 367 | Returns all the servers, public and private 368 | 369 | :returns: dict -- :ref:`JSON server list ` from speedify. 370 | """ 371 | return _run_speedify_cmd(["show", "servers"]) 372 | 373 | 374 | @exception_wrapper("Failed to show privacy") 375 | def show_privacy(): 376 | """ 377 | show_privacy() 378 | Returns privacy settings 379 | 380 | :returns: dict -- dict -- :ref:`JSON privacy ` from speedify. 381 | """ 382 | return _run_speedify_cmd(["show", "privacy"]) 383 | 384 | 385 | @exception_wrapper("Failed to show settings") 386 | def show_settings(): 387 | """ 388 | show_settings() 389 | Returns current settings 390 | 391 | :returns: dict -- dict -- :ref:`JSON settings ` from speedify. 392 | """ 393 | return _run_speedify_cmd(["show", "settings"]) 394 | 395 | 396 | @exception_wrapper("Failed to show adapters") 397 | def show_adapters(): 398 | """ 399 | show_adapters() 400 | Returns current adapters 401 | 402 | :returns: dict -- dict -- :ref:`JSON list of adapters ` from speedify. 403 | """ 404 | return _run_speedify_cmd(["show", "adapters"]) 405 | 406 | 407 | @exception_wrapper("Failed to show directory") 408 | def show_directory(): 409 | """ 410 | show_directory() 411 | Returns current directory service 412 | 413 | :returns: dict -- dict -- :ref:`JSON object for the current directory service ` from speedify. 414 | """ 415 | return _run_speedify_cmd(["show", "directory"]) 416 | 417 | 418 | @exception_wrapper("Failed to do captiveportal_check") 419 | def captiveportal_check(): 420 | """ 421 | captiveportal_check() 422 | Returns adapters which are currently blocked by a captive portal 423 | 424 | :returns: dict -- dict -- :ref:`JSON list of adapters behind captive portal` from speedify. 425 | """ 426 | return _run_speedify_cmd(["captiveportal", "check"]) 427 | 428 | 429 | @exception_wrapper("Failed to do captiveportal_login") 430 | def captiveportal_login(proxy: bool = True, adapterID: str = None): 431 | """ 432 | captiveportal_login() 433 | Starts or stops the local proxy intercepting traffic on ports 52,80,433, for 434 | filling in a captive portal. If the user interface is running, once this is 435 | turned on, it will launch a captive portal browser. If it's not, then it's 436 | up to you to launch a browser pointing at an http website to get to the 437 | portal page. 438 | 439 | :param proxy: Whether the local proxy should intercept captive portal traffic 440 | :type priority: boolean 441 | :param adapterID: The interface adapterID 442 | :type adapterID: str 443 | 444 | :returns: dict -- dict -- :ref:`JSON list of adapters behind captive portal` from speedify. 445 | """ 446 | args = ["captiveportal", "login"] 447 | startproxy = True 448 | if proxy == "on": 449 | args.append("on") 450 | elif proxy == "off": 451 | startproxy = False 452 | args.append("off") 453 | elif proxy: 454 | args.append("on") 455 | else: 456 | startproxy = False 457 | args.append("off") 458 | 459 | if adapterID and startproxy: 460 | args.append(adapterID) 461 | return _run_speedify_cmd(args) 462 | 463 | 464 | @exception_wrapper("Failed to show connectmethod") 465 | def show_connectmethod(): 466 | """ 467 | show_connectmethod() 468 | Returns the current state of the 'connectmethod' setting. 469 | 470 | :returns dict -- :ref:`JSON connectmethod `. 471 | """ 472 | return _run_speedify_cmd(["show", "connectmethod"]) 473 | 474 | 475 | @exception_wrapper("Failed to show streamingbypass") 476 | def show_streamingbypass(): 477 | """ 478 | show_streamingbypass() 479 | Returns the current state of the 'streamingbypass' setting. 480 | 481 | :returns dict -- :ref:`JSON streamingbypass `. 482 | """ 483 | return _run_speedify_cmd(["show", "streamingbypass"]) 484 | 485 | 486 | @exception_wrapper("Failed to show disconnect") 487 | def show_disconnect(): 488 | """ 489 | show_disconnect() 490 | Returns the last reason for a disconnection. 491 | 492 | :returns dict -- :ref:`JSON disconnect `. 493 | """ 494 | return _run_speedify_cmd(["show", "disconnect"]) 495 | 496 | 497 | @exception_wrapper("Failed to show streaming") 498 | def show_streaming(): 499 | """ 500 | show_streaming() 501 | Returns the current state of the 'streaming' setting. 502 | 503 | :returns dict -- :ref:`JSON streaming `. 504 | """ 505 | return _run_speedify_cmd(["show", "streaming"]) 506 | 507 | 508 | @exception_wrapper("Failed to show speedtest") 509 | def show_speedtest(): 510 | """ 511 | show_speedtest() 512 | Returns the current results of 'speedtest'. 513 | 514 | :returns dict -- :ref:`JSON speedtest `. 515 | """ 516 | return _run_speedify_cmd(["show", "speedtest"]) 517 | 518 | 519 | @exception_wrapper("Failed to get current server") 520 | def show_currentserver(): 521 | """ 522 | show_currentserver() 523 | Returns current server 524 | 525 | :returns: dict -- :ref:`JSON currentserver ` from speedify. 526 | """ 527 | return _run_speedify_cmd(["show", "currentserver"]) 528 | 529 | 530 | @exception_wrapper("Failed to get current user") 531 | def show_user(): 532 | """ 533 | show_user() 534 | Returns current user 535 | 536 | :returns: dict -- :ref:`JSON response ` from speedify. 537 | """ 538 | return _run_speedify_cmd(["show", "user"]) 539 | 540 | 541 | @exception_wrapper("Failed to show connect method") 542 | def show_connectmethod(): 543 | """ 544 | show_connectmethod() 545 | Returns the connectmethod related settings 546 | 547 | :returns: :ref:`JSON response ` from speedify. 548 | """ 549 | return _run_speedify_cmd(["show", "connectmethod"]) 550 | 551 | 552 | @exception_wrapper("getting state") 553 | def show_state(): 554 | """ 555 | show_state() 556 | Returns the current state of Speedify (CONNECTED, CONNECTING, etc.) 557 | 558 | :returns: speedify.State -- The speedify state enum. 559 | """ 560 | resultjson = _run_speedify_cmd(["state"]) 561 | return find_state_for_string(resultjson["state"]) 562 | 563 | 564 | @exception_wrapper("Failed to get version") 565 | def show_version(): 566 | """ 567 | show_version() 568 | Returns speedify version 569 | 570 | :returns: dict -- :ref:`JSON version ` from speedify. 571 | """ 572 | return _run_speedify_cmd(["version"]) 573 | 574 | 575 | @exception_wrapper("Failed to get stats") 576 | def safebrowsing_stats(): 577 | args = ["safebrowsing", "stats"] 578 | return _run_speedify_cmd(args) 579 | 580 | 581 | # 582 | # Setter functions 583 | # 584 | 585 | 586 | @exception_wrapper("Failed to set directory server") 587 | def directory(domain: str = ""): 588 | """ 589 | directory(domain) 590 | 591 | Uses the given domain as the directory server. 592 | 593 | :param domain: The domain of the directory server. 594 | :type operation: str 595 | """ 596 | return _run_speedify_cmd(["directory", domain]) 597 | 598 | 599 | @exception_wrapper("Failed to set DNS") 600 | def dns(ip_addr: str = ""): 601 | """ 602 | dns(ip_addr) 603 | 604 | Uses the given IP address for as the DNS server. 605 | 606 | Example: 607 | dns("8.8.8.8") 608 | 609 | :param ip_addr: The IP address of the DNS server. 610 | :type operation: str 611 | """ 612 | return _run_speedify_cmd(["dns", ip_addr]) 613 | 614 | 615 | @exception_wrapper("Failed to add streaming bypass") 616 | def streaming_domains_add(domains: str): 617 | """ 618 | streaming_domains_add(domains) 619 | 620 | Add the streaming hint for some domains. 621 | 622 | Example: 623 | streaming_domains_add("example.com google.com") 624 | 625 | :param domains: The domains to add the streaming hint for. 626 | :type domains: str 627 | """ 628 | return _run_speedify_cmd(["streaming", "domains", "add", domains]) 629 | 630 | 631 | @exception_wrapper("Failed to remove streaming bypass") 632 | def streaming_domains_rem(domains: str): 633 | """ 634 | streaming_domains_rem(domains) 635 | 636 | Remove the streaming hint for some domains. 637 | 638 | Example: 639 | streaming_domains_rem("example.com google.com") 640 | 641 | :param domains: The domains to remove the streaming hint from. 642 | :type domains: str 643 | """ 644 | return _run_speedify_cmd(["streaming", "domains", "rem", domains]) 645 | 646 | 647 | @exception_wrapper("Failed to set streaming bypass") 648 | def streaming_domains_set(domains: str): 649 | """ 650 | streaming_domains_set(domains) 651 | 652 | Set the streaming hint for some domains. 653 | 654 | Example: 655 | streaming_domains_set("example.com google.com") 656 | 657 | :param domains: The domains to set the streaming hint on. 658 | :type domains: str 659 | """ 660 | return _run_speedify_cmd(["streaming", "domains", "set", domains]) 661 | 662 | 663 | @exception_wrapper("Failed to add streaming flag") 664 | def streaming_ipv4_add(ipv4_addrs: str): 665 | """ 666 | streaming_ipv4_add(ipv4_addrs) 667 | 668 | Add the streaming flag for some ipv4 address(es). 669 | 670 | Example: 671 | streaming_ipv4_add( 672 | "68.80.59.53 55.38.18.29" 673 | ) 674 | 675 | :param ipv4: The ipv4 adress(es) to add the streaming flag to. 676 | Example: 677 | "68.80.59.53 55.38.18.29" 678 | "68.80.59.53" 679 | :type ipv4_addrs: str 680 | """ 681 | return _run_speedify_cmd(["streaming", "ipv4", "add", ipv4_addrs]) 682 | 683 | 684 | @exception_wrapper("Failed to remove streaming flag") 685 | def streaming_ipv4_rem(ipv4_addrs: str): 686 | """ 687 | streaming_ipv4_rem(ipv4_addrs) 688 | 689 | Remove the streaming flag from some ipv4 adress(es). 690 | 691 | Example: 692 | streaming_ipv4_rem( 693 | "68.80.59.53 55.38.18.29" 694 | ) 695 | 696 | :param ipv4: The ipv4 adress(es) to remove the streaming flag from. 697 | Example: 698 | "68.80.59.53 55.38.18.29" 699 | "68.80.59.53" 700 | :type ipv4_addrs: str 701 | """ 702 | return _run_speedify_cmd(["streaming", "ipv4", "rem", ipv4_addrs]) 703 | 704 | 705 | @exception_wrapper("Failed to set streaming flag") 706 | def streaming_ipv4_set(ipv4_addrs: str): 707 | """ 708 | streaming_ipv4_set(ipv4_addrs) 709 | 710 | Set the streaming flag on some ipv4 adress(es). 711 | 712 | Example: 713 | streaming_ipv4_set( 714 | "68.80.59.53 55.38.18.29" 715 | ) 716 | 717 | :param ipv4: The ipv4 adress(es) to set the streaming flag on. 718 | Example: 719 | "68.80.59.53 55.38.18.29" 720 | "68.80.59.53" 721 | :type ipv4_addrs: str 722 | """ 723 | return _run_speedify_cmd(["streaming", "ipv4", "set", ipv4_addrs]) 724 | 725 | 726 | @exception_wrapper("Failed to add streaming flag") 727 | def streaming_ipv6_add(ipv6_addrs: str): 728 | """ 729 | streaming_ipv6_add(ipv6_addrs) 730 | 731 | Add the streaming flag for some ipv6 address(es). 732 | 733 | Example: 734 | streaming_ipv6_add( 735 | "2001:db8:1234:ffff:ffff:ffff:ffff:0f0f 2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 736 | ) 737 | 738 | :param ipv6: The ipv6 adress(es) to add the streaming flag to. 739 | Example: 740 | "2001:db8:1234:ffff:ffff:ffff:ffff:0f0f 2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 741 | "2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 742 | :type ipv6_addrs: str 743 | """ 744 | return _run_speedify_cmd(["streaming", "ipv6", "add", ipv6_addrs]) 745 | 746 | 747 | @exception_wrapper("Failed to remove streaming flag") 748 | def streaming_ipv6_rem(ipv6_addrs: str): 749 | """ 750 | streaming_ipv6_rem(ipv6_addrs) 751 | 752 | Remove the streaming flag from some ipv6 adress(es). 753 | 754 | Example: 755 | streaming_ipv6_rem( 756 | "2001:db8:1234:ffff:ffff:ffff:ffff:0f0f 2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 757 | ) 758 | 759 | :param ipv6: The ipv6 adress(es) to remove the streaming flag from. 760 | Example: 761 | "2001:db8:1234:ffff:ffff:ffff:ffff:0f0f 2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 762 | "2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 763 | :type ipv6_addrs: str 764 | """ 765 | return _run_speedify_cmd(["streaming", "ipv6", "rem", ipv6_addrs]) 766 | 767 | 768 | @exception_wrapper("Failed to set streaming flag") 769 | def streaming_ipv6_set(ipv6_addrs: str): 770 | """ 771 | streaming_ipv6_set(ipv6_addrs) 772 | 773 | Set the streaming flag on some ipv6 adress(es). 774 | 775 | Example: 776 | streaming_ipv6_set( 777 | "2001:db8:1234:ffff:ffff:ffff:ffff:0f0f 2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 778 | ) 779 | 780 | :param ipv6: The ipv6 adress(es) to set the streaming flag on. 781 | Example: 782 | "2001:db8:1234:ffff:ffff:ffff:ffff:0f0f 2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 783 | "2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 784 | :type ipv6_addrs: str 785 | """ 786 | return _run_speedify_cmd(["streaming", "ipv6", "set", ipv6_addrs]) 787 | 788 | 789 | @exception_wrapper("Failed to add streaming flag") 790 | def streaming_ports_add(ports: str): 791 | """ 792 | streaming_ports_add(ports) 793 | 794 | Add the streaming flag for some port(s). 795 | 796 | Example: 797 | streaming_ports_add( 798 | "9999/tcp" 799 | ) 800 | 801 | :param ports: The port(s) to add the streaming flag to. 802 | Example: 803 | "9999/tcp" 804 | "1500-2000/udp" 805 | Form: 806 | "/" 807 | "-/" 808 | :type ports: str 809 | """ 810 | return _run_speedify_cmd(["streaming", "ports", "add", ports]) 811 | 812 | 813 | @exception_wrapper("Failed to remove streaming flag") 814 | def streaming_ports_rem(ports: str): 815 | """ 816 | streaming_ports_rem(ports) 817 | 818 | Remove the streaming flag from some port(s). 819 | 820 | Example: 821 | streaming_ports_rem( 822 | "9999/tcp" 823 | ) 824 | 825 | :param ports: The port(s) to remove the streaming flag from. 826 | Example: 827 | "9999/tcp" 828 | "1500-2000/udp" 829 | Form: 830 | "/" 831 | "-/" 832 | :type ports: str 833 | """ 834 | return _run_speedify_cmd(["streaming", "ports", "rem", ports]) 835 | 836 | 837 | @exception_wrapper("Failed to set streaming flag") 838 | def streaming_ports_set(ports: str): 839 | """ 840 | streaming_ports_set(ports) 841 | 842 | Set the streaming flag on some port(s). 843 | 844 | Example: 845 | streaming_ports_set( 846 | "9999/tcp" 847 | ) 848 | 849 | :param ports: The port(s) to set the streaming flag on. 850 | Example: 851 | "9999/tcp" 852 | "1500-2000/udp" 853 | Form: 854 | "/" 855 | "-/" 856 | :type ports: str 857 | """ 858 | return _run_speedify_cmd(["streaming", "ports", "set", ports]) 859 | 860 | 861 | @exception_wrapper("Failed to add streaming bypass") 862 | def streamingbypass_domains_add(domains: str): 863 | """ 864 | streamingbypass_domains_add(domains) 865 | 866 | Add a streaming bypass for some domain(s). 867 | 868 | Example: 869 | streamingbypass_domains_add( 870 | "example.com google.com" 871 | ) 872 | 873 | :param domains: The domain(s) to add a streaming bypass to. 874 | Example: 875 | "example.com google.com" 876 | "google.com" 877 | :type domains: str 878 | """ 879 | return _run_speedify_cmd(["streamingbypass", "domains", "add", domains]) 880 | 881 | 882 | @exception_wrapper("Failed to remove streaming bypass") 883 | def streamingbypass_domains_rem(domains: str): 884 | """ 885 | streamingbypass_domains_rem(domains) 886 | 887 | Remove a streaming bypass from some domain(s). 888 | 889 | Example: 890 | streamingbypass_domains_rem( 891 | "example.com google.com" 892 | ) 893 | 894 | :param domains: The domain(s) to remove the streaming bypass from. 895 | Example: 896 | "example.com google.com" 897 | "google.com" 898 | :type domains: str 899 | """ 900 | return _run_speedify_cmd(["streamingbypass", "domains", "rem", domains]) 901 | 902 | 903 | @exception_wrapper("Failed to set streaming bypass") 904 | def streamingbypass_domains_set(domains: str): 905 | """ 906 | streamingbypass_domains_set(domains) 907 | 908 | Set a streaming bypass on some domain(s). 909 | 910 | Example: 911 | streamingbypass_domains_set( 912 | "example.com google.com" 913 | ) 914 | 915 | :param domains: The domain(s) to set the streaming bypass on. 916 | Example: 917 | "example.com google.com" 918 | "google.com" 919 | :type domains: str 920 | """ 921 | return _run_speedify_cmd(["streamingbypass", "domains", "set", domains]) 922 | 923 | 924 | @exception_wrapper("Failed to add streaming bypass") 925 | def streamingbypass_ipv4_add(ipv4_addrs: str): 926 | """ 927 | streamingbypass_ipv4_add(ipv4_addrs) 928 | 929 | Add a streaming bypass for some ipv4 address(es). 930 | 931 | Example: 932 | streamingbypass_ipv4_add( 933 | "68.80.59.53 55.38.18.29" 934 | ) 935 | 936 | :param ipv4_addrs: The ipv4 address(es) to add a streaming bypass to. 937 | Example: 938 | "68.80.59.53 55.38.18.29" 939 | "55.38.18.29" 940 | :type ipv4_addrs: str 941 | """ 942 | return _run_speedify_cmd(["streamingbypass", "ipv4", "add", ipv4_addrs]) 943 | 944 | 945 | @exception_wrapper("Failed to remove streaming bypass") 946 | def streamingbypass_ipv4_rem(ipv4_addrs: str): 947 | """ 948 | streamingbypass_ipv4_rem(ipv4_addrs) 949 | 950 | Remove a streaming bypass from some ipv4 address(es). 951 | 952 | Example: 953 | streamingbypass_ipv4_rem( 954 | "68.80.59.53 55.38.18.29" 955 | ) 956 | 957 | :param ipv4_addrs: The ipv4 address(es) to remove the streaming bypass from. 958 | Example: 959 | "68.80.59.53 55.38.18.29" 960 | "55.38.18.29" 961 | :type ipv4_addrs: str 962 | """ 963 | return _run_speedify_cmd(["streamingbypass", "ipv4", "rem", ipv4_addrs]) 964 | 965 | 966 | @exception_wrapper("Failed to set streaming bypass") 967 | def streamingbypass_ipv4_set(ipv4_addrs: str): 968 | """ 969 | streamingbypass_ipv4_set(ipv4_addrs) 970 | 971 | Set a streaming bypass on some ipv4 address(es). 972 | 973 | Example: 974 | streamingbypass_ipv4_set( 975 | "68.80.59.53 55.38.18.29" 976 | ) 977 | 978 | :param ipv4_addrs: The ipv4 address(es) to set the streaming bypass on. 979 | Example: 980 | "68.80.59.53 55.38.18.29" 981 | "55.38.18.29" 982 | :type ipv4_addrs: str 983 | """ 984 | return _run_speedify_cmd(["streamingbypass", "ipv4", "set", ipv4_addrs]) 985 | 986 | 987 | @exception_wrapper("Failed to add streaming bypass") 988 | def streamingbypass_ipv6_add(ipv6_addrs: str): 989 | """ 990 | streamingbypass_ipv6_add(ipv6_addrs) 991 | 992 | Add a streaming bypass for some ipv6 address(es). 993 | 994 | Example: 995 | streamingbypass_ipv6_add( 996 | "2001:db8:1234:ffff:ffff:ffff:ffff:0f0f 2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 997 | ) 998 | 999 | :param ipv6_addrs: The ipv6 address(es) to add a streaming bypass to. 1000 | Example: 1001 | "2001:db8:1234:ffff:ffff:ffff:ffff:0f0f 2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 1002 | "2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 1003 | :type ipv6_addrs: str 1004 | """ 1005 | return _run_speedify_cmd(["streamingbypass", "ipv6", "add", ipv6_addrs]) 1006 | 1007 | 1008 | @exception_wrapper("Failed to remove streaming bypass") 1009 | def streamingbypass_ipv6_rem(ipv6_addrs: str): 1010 | """ 1011 | streamingbypass_ipv6_rem(ipv6_addrs) 1012 | 1013 | Remove a streaming bypass from some ipv6 address(es). 1014 | 1015 | Example: 1016 | streamingbypass_ipv6_rem( 1017 | "2001:db8:1234:ffff:ffff:ffff:ffff:0f0f 2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 1018 | ) 1019 | 1020 | :param ipv6_addrs: The ipv6 address(es) to remove the streaming bypass from. 1021 | Example: 1022 | "2001:db8:1234:ffff:ffff:ffff:ffff:0f0f 2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 1023 | "2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 1024 | :type ipv6_addrs: str 1025 | """ 1026 | return _run_speedify_cmd(["streamingbypass", "ipv6", "rem", ipv6_addrs]) 1027 | 1028 | 1029 | @exception_wrapper("Failed to set streaming bypass") 1030 | def streamingbypass_ipv6_set(ipv6_addrs: str): 1031 | """ 1032 | streamingbypass_ipv6_set(ipv6_addrs) 1033 | 1034 | Set a streaming bypass on some ipv6 address(es). 1035 | 1036 | Example: 1037 | streamingbypass_ipv6_set( 1038 | "2001:db8:1234:ffff:ffff:ffff:ffff:0f0f 2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 1039 | ) 1040 | 1041 | :param ipv6_addrs: The ipv6 address(es) to set the streaming bypass on. 1042 | Example: 1043 | "2001:db8:1234:ffff:ffff:ffff:ffff:0f0f 2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 1044 | "2001:db8:1234:ffff:ffff:ffff:ffff:ffff" 1045 | :type ipv6_addrs: str 1046 | """ 1047 | return _run_speedify_cmd(["streamingbypass", "ipv6", "set", ipv6_addrs]) 1048 | 1049 | 1050 | @exception_wrapper("Failed to add streaming bypass") 1051 | def streamingbypass_ports_add(ports: str): 1052 | """ 1053 | streamingbypass_ports_add(ports) 1054 | 1055 | Add a streaming bypass for some port(s). 1056 | 1057 | Example: 1058 | streamingbypass_ports_add("9999/tcp") 1059 | 1060 | :param ports: The ports to add a streaming bypass to. 1061 | Must be of one of these forms: 1062 | "/" 1063 | "-/" 1064 | :type ports: str 1065 | """ 1066 | return _run_speedify_cmd(["streamingbypass", "ports", "add", ports]) 1067 | 1068 | 1069 | @exception_wrapper("Failed to rem streaming bypass") 1070 | def streamingbypass_ports_rem(ports: str): 1071 | """ 1072 | streamingbypass_ports_rem(ports) 1073 | 1074 | Remove a streaming bypass for some port(s). 1075 | 1076 | Example: 1077 | streamingbypass_ports_rem("9999/tcp") 1078 | 1079 | :param ports: The ports to remove a streaming bypass from. 1080 | Must be of one of these forms: 1081 | "/" 1082 | "-/" 1083 | :type ports: str 1084 | """ 1085 | return _run_speedify_cmd(["streamingbypass", "ports", "rem", ports]) 1086 | 1087 | 1088 | @exception_wrapper("Failed to set streaming bypass") 1089 | def streamingbypass_ports_set(ports: str): 1090 | """ 1091 | streamingbypass_ports_set(ports) 1092 | 1093 | Set a streaming bypass for some port(s). 1094 | 1095 | Example: 1096 | streamingbypass_ports_set("9999/tcp") 1097 | 1098 | :param ports: The ports to set a streaming bypass on. 1099 | Must be of one of these forms: 1100 | "/" 1101 | "-/" 1102 | :type ports: str 1103 | """ 1104 | return _run_speedify_cmd(["streamingbypass", "ports", "set", ports]) 1105 | 1106 | 1107 | @exception_wrapper("Failed to set streaming bypass") 1108 | def streamingbypass_service(service_name: str, is_on: bool): 1109 | """ 1110 | streamingbypass_service(service_name, is_on) 1111 | 1112 | Set the streaming bypass, on or off, for some pre-defined service. 1113 | 1114 | Example: 1115 | streamingbypass_service("Netflix", True) 1116 | 1117 | :param service_name: The service to modify. 1118 | :type service_name: str 1119 | :param is_on: Whether to bypass the service... or not. 1120 | :type is_on: bool 1121 | """ 1122 | if is_on is True: 1123 | is_on = "on" 1124 | elif is_on is False: 1125 | is_on = "off" 1126 | return _run_speedify_cmd(["streamingbypass", "service", service_name, is_on]) 1127 | 1128 | 1129 | @exception_wrapper("Failed to set adapter encryption") 1130 | def adapter_overratelimit(adapterID: str, bps: int): 1131 | """ 1132 | adapter_overratelimit(adapterID: str, bps) 1133 | 1134 | Sets the rate limit, in bps, on adapterID. 1135 | (show_adapters is where you find the adapterIDs). 1136 | 1137 | :param adapterID: The interface adapterID 1138 | :type adapterID: str 1139 | :param bps: Speed, in bps, to limit the adapter to. 1140 | :type bps: int 1141 | :returns: dict -- :ref:`JSON adapter response ` from speedify. 1142 | """ 1143 | return _run_speedify_cmd(["adapter", "overlimitratelimit", adapterID, str(bps)]) 1144 | 1145 | 1146 | @exception_wrapper("Failed to set adapter priority") 1147 | def adapter_priority(adapterID: str, priority=Priority.ALWAYS): 1148 | """ 1149 | adapter_priority(adapterID: str, priority=Priority.ALWAYS) 1150 | Sets the priority on the adapter whose adapterID is provided (show_adapters is where you find the adapterIDs) 1151 | 1152 | :param adapterID: The interface adapterID 1153 | :type adapterID: str 1154 | :param priority: The speedify priority 1155 | :type priority: speedify.Priority 1156 | :returns: dict -- :ref:`JSON adapter response ` from speedify. 1157 | """ 1158 | args = ["adapter", "priority"] 1159 | args.append(str(adapterID)) 1160 | args.append((str(priority.value))) 1161 | resultjson = _run_speedify_cmd(args) 1162 | return resultjson 1163 | 1164 | 1165 | @exception_wrapper("Failed to set adapter encryption") 1166 | def adapter_encryption(adapterID: str, should_encrypt): 1167 | """ 1168 | adapter_encryption(adapterID: str, should_encrypt) 1169 | 1170 | Example: 1171 | adapter_encryption("something", True) 1172 | adapter_encryption("something", "off") 1173 | 1174 | Sets the encryption on the adapter whose adapterID is provided 1175 | (show_adapters is where you find the adapterIDs). 1176 | 1177 | Note that any time the main encryption() function is called, 1178 | all the per adapter encryption settings are immediately reset. 1179 | 1180 | :param adapterID: The interface adapterID 1181 | :type adapterID: str 1182 | :param should_encrypt: Whether to encrypt 1183 | :type should_encrypt: bool | str 1184 | :returns: dict -- :ref:`JSON adapter response ` from speedify. 1185 | """ 1186 | if should_encrypt is True: 1187 | should_encrypt = "on" 1188 | elif should_encrypt is False: 1189 | should_encrypt = "off" 1190 | should_encrypt = str(should_encrypt) 1191 | return _run_speedify_cmd(["adapter", "encryption", adapterID, should_encrypt]) 1192 | 1193 | 1194 | @exception_wrapper("Failed to set adapter ratelimit") 1195 | def adapter_ratelimit(adapterID: str, download_bps: int = 0, upload_bps: int = 0): 1196 | """ 1197 | adapter_ratelimit(adapterID: str, download_bps: int = 0, upload_bps: int = 0) 1198 | Sets the ratelimit in bps on the adapter whose adapterID is provided 1199 | (show_adapters is where you find the adapterIDs) 1200 | 1201 | :param adapterID: The interface adapterID 1202 | :type adapterID: str 1203 | :param upload_bps: The upload ratelimit in bps 1204 | :type upload_bps: int 1205 | :param download_bps: The download ratelimit in bps 1206 | :type download_bps: int 1207 | :returns: dict -- :ref:`JSON adapter response ` from speedify. 1208 | """ 1209 | return _run_speedify_cmd(["adapter", "ratelimit", adapterID, str(download_bps), str(upload_bps)]) 1210 | 1211 | 1212 | @exception_wrapper("Failed to set adapter daily limit") 1213 | def adapter_datalimit_daily(adapterID: str, limit: int = 0): 1214 | """ 1215 | adapter_datalimit_daily(adapterID, limit: int = 0) 1216 | Sets the daily usage limit in bytes on the adapter whose adapterID is provided 1217 | (show_adapters is where you find the adapterIDs) 1218 | 1219 | :param adapterID: The interface adapterID 1220 | :type adapterID: str 1221 | :param limit: The daily usage limit, in bytes 1222 | :type limit: int 1223 | :returns: dict -- :ref:`JSON adapter response ` from speedify 1224 | """ 1225 | return _run_speedify_cmd(["adapter", "datalimit", "daily", adapterID, str(limit)]) 1226 | 1227 | 1228 | @exception_wrapper("Failed to set adapter daily boost") 1229 | def adapter_datalimit_dailyboost(adapterID: str, boost: int = 0): 1230 | """ 1231 | adapter_datalimit_dailyboost(adapterID, boost: int = 0) 1232 | 1233 | Gives some additional daily data, in bytes, 1234 | to the adapter whose adapterID is provided. 1235 | 1236 | Show_adapters is where you find the adapterIDs. 1237 | 1238 | :param adapterID: The interface adapterID 1239 | :type adapterID: str 1240 | :param boost: Some additional bytes to give to the daily limit. 1241 | :type boost: int 1242 | :returns: dict -- :ref:`JSON adapter response ` from speedify 1243 | """ 1244 | return _run_speedify_cmd(["adapter", "datalimit", "dailyboost", str(boost)]) 1245 | 1246 | 1247 | @exception_wrapper("Failed to set adapter monthly limit") 1248 | def adapter_datalimit_monthly(adapterID: str, limit: int = 0, reset_day: int = 0): 1249 | """ 1250 | adapter_datalimit_monthly(adapterID: str, limit: int = 0, reset_day: int = 0) 1251 | Sets the monthly usage limit in bytes on the adapter whose adapterID is provided 1252 | (show_adapters is where you find the adapterIDs) 1253 | 1254 | :param adapterID: The interface adapterID 1255 | :type adapterID: str 1256 | :param limit: The monthly usage limit, in bytes 1257 | :type limit: int 1258 | :param reset_day: The day of the month to reset monthly usage (0-31) 1259 | :type reset_Day: int 1260 | :returns: dict -- :ref:`JSON adapter response ` from speedify. 1261 | """ 1262 | args = ["adapter", "datalimit", "monthly", adapterID, str(limit), str(reset_day)] 1263 | return _run_speedify_cmd(args) 1264 | 1265 | 1266 | @exception_wrapper("Failed to reset adapter usage") 1267 | def adapter_resetusage(adapterID: str): 1268 | """ 1269 | adapter_resetusage(adapterID) 1270 | Resets all the stats on this adapter back to 0. Starts both daily and monthly limits over, if set. 1271 | 1272 | :param adapterID: The interface adapterID 1273 | :type adapterID: str 1274 | :returns: dict -- :ref:`JSON adapter response ` from speedify. 1275 | """ 1276 | return _run_speedify_cmd(["adapter", "resetusage", adapterID]) 1277 | 1278 | 1279 | @exception_wrapper("Failed to set forwarded ports") 1280 | def ports(tcpports: list = [], udpports: list = []): 1281 | """ 1282 | ports(tcpports=[], udpports=[]) 1283 | sets port forwarding. call with no arguments to unset all port forwarding 1284 | 1285 | :param tcpports: List of tcp ports to forward on 1286 | :type tcpport: list 1287 | :param udpports: List of udp ports to forward on 1288 | :type udpport: list 1289 | :returns: dict -- :ref:`JSON settings ` from speedify 1290 | """ 1291 | args = ["ports"] 1292 | if tcpports is not None: 1293 | for port in tcpports: 1294 | args.append(str(port) + "/tcp") 1295 | if udpports is not None: 1296 | for port in udpports: 1297 | args.append(str(port) + "/udp") 1298 | 1299 | resultjson = _run_speedify_cmd(args) 1300 | return resultjson 1301 | 1302 | 1303 | @exception_wrapper("Failed to change modes") 1304 | def mode(mode: str): 1305 | """ 1306 | mode(mode="speed") 1307 | 1308 | Uses one of 'redundant', 'speed' or 'streaming' operation modes. 1309 | 1310 | :param mode: One of: 1311 | "redundant" 1312 | "speed" 1313 | "streaming" 1314 | :type mode: str 1315 | :returns: dict -- :ref:`JSON settings ` from speedify 1316 | """ 1317 | return _run_speedify_cmd(["mode", mode]) 1318 | 1319 | 1320 | @exception_wrapper("Failed to set encryption") 1321 | def encryption(should_encrypt=True): 1322 | """ 1323 | encryption(encrypt = True) 1324 | Sets encryption on or off. 1325 | 1326 | :param encrypt: Encrypted on or off 1327 | :type encrypt: bool 1328 | :returns: dict -- :ref:`JSON settings ` from speedify 1329 | """ 1330 | if should_encrypt is True: 1331 | should_encrypt = "on" 1332 | elif should_encrypt is False: 1333 | should_encrypt = "off" 1334 | resultjson = _run_speedify_cmd(["encryption", should_encrypt]) 1335 | return resultjson 1336 | 1337 | 1338 | @exception_wrapper("Failed to set jumbo") 1339 | def jumbo(mode: bool = True): 1340 | """ 1341 | jumbo(mode = True) 1342 | Sets jumbo MTU mode on or off. 1343 | 1344 | :param mode: Jumbo MTU on or off 1345 | :type mode: bool 1346 | :returns: dict -- :ref:`JSON settings ` from speedify 1347 | """ 1348 | args = ["jumbo"] 1349 | if mode == "on": 1350 | args.append("on") 1351 | elif mode == "off": 1352 | args.append("off") 1353 | elif mode is True: 1354 | args.append("on") 1355 | elif mode is False: 1356 | args.append("off") 1357 | else: 1358 | # probably invalid, but we'll let speedify tell us THAT 1359 | args.append(mode) 1360 | 1361 | resultjson = _run_speedify_cmd(args) 1362 | return resultjson 1363 | 1364 | 1365 | @exception_wrapper("Failed to set packetaggregation") 1366 | def packetaggregation(is_on: bool = True): 1367 | """ 1368 | packetaggregation(is_on = True) 1369 | Sets packetaggregation mode on or off. 1370 | 1371 | :param is_on: Whether packet aggregation is on... or off. 1372 | :type is_on: bool 1373 | :returns: dict -- :ref:`JSON settings ` from speedify 1374 | """ 1375 | if is_on is True: 1376 | is_on = "on" 1377 | elif is_on is False: 1378 | is_on = "off" 1379 | return _run_speedify_cmd(["packetaggr", is_on]) 1380 | 1381 | 1382 | @exception_wrapper("Failed to set killswitch") 1383 | def killswitch(killswitch: bool = False): 1384 | """ 1385 | killswitch(killswitch = False) 1386 | sets killswitch on or off. (Windows only) 1387 | 1388 | :param killswitch: killswitch on or off 1389 | :type killswitch: bool 1390 | :returns: dict -- :ref:`JSON privacy response ` from speedify 1391 | """ 1392 | args = ["privacy", "killswitch"] 1393 | args.append("on") if killswitch else args.append("off") 1394 | resultjson = _run_speedify_cmd(args) 1395 | return resultjson 1396 | 1397 | 1398 | @exception_wrapper("Failed to set overflow") 1399 | def overflow(speed_in_mbps: float = 30.0): 1400 | """ 1401 | overflow(speed_in_mbps = 30.0) 1402 | sets overflow threshold. 1403 | 1404 | :param speed_in_mbps: Overflow threshold in mbps 1405 | :type speed_in_mbps: float 1406 | :returns: dict -- :ref:`JSON settings ` from speedify 1407 | """ 1408 | args = ["overflow"] 1409 | args.append(str(speed_in_mbps)) 1410 | resultjson = _run_speedify_cmd(args) 1411 | return resultjson 1412 | 1413 | 1414 | @exception_wrapper("Failed to set dnsleak") 1415 | def dnsleak(leak: bool = False): 1416 | """ 1417 | dnsleak(leak = False) 1418 | sets dnsleak on or off. (Windows only) 1419 | 1420 | :param dnsleak: dnsleak on or off 1421 | :type dnsleak: bool 1422 | :returns: dict -- :ref:`JSON privacy response ` from speedify 1423 | """ 1424 | args = ["privacy", "dnsleak"] 1425 | args.append("on") if leak else args.append("off") 1426 | resultjson = _run_speedify_cmd(args) 1427 | return resultjson 1428 | 1429 | 1430 | @exception_wrapper("Failed to set startupconnect") 1431 | def startupconnect(is_on: bool = True): 1432 | """ 1433 | startupconnect(is_on) 1434 | 1435 | Sets whether or not to automatically connect on login. 1436 | 1437 | :param is_on: Sets connect on startup on/off 1438 | :type is_on: bool 1439 | :returns: dict -- :ref:`JSON settings ` from speedify 1440 | """ 1441 | if is_on is True: 1442 | is_on = "on" 1443 | elif is_on is False: 1444 | is_on = "off" 1445 | else: 1446 | raise ValueError("is_on neither True nor False") 1447 | return _run_speedify_cmd(["startupconnect", is_on]) 1448 | 1449 | 1450 | @exception_wrapper("Failed to set route default") 1451 | def routedefault(is_default: bool = True): 1452 | """ 1453 | routedefault(is_default=True) 1454 | sets whether Speedify should take the default route to the internet. 1455 | defaults to True, only make it False if you're planning to set up 1456 | routing rules, like IP Tables, yourself.. 1457 | 1458 | :param connect: Sets routedefault on/off 1459 | :type connect: bool 1460 | :returns: dict -- :ref:`JSON settings ` from speedify 1461 | """ 1462 | if is_default is True: 1463 | is_default = "on" 1464 | elif is_default is False: 1465 | is_default = "off" 1466 | else: 1467 | raise ValueError("is_on neither True nor False") 1468 | return _run_speedify_cmd(["route", "default", is_default]) 1469 | 1470 | 1471 | @exception_wrapper("Failed to run streamtest") 1472 | def streamtest(): 1473 | """ 1474 | streamtest() 1475 | 1476 | Runs stream test. 1477 | Returns final results. 1478 | Will take around 30 seconds. 1479 | 1480 | :returns: dict -- :ref:`JSON streamtest ` from speedify 1481 | """ 1482 | return _run_speedify_cmd(["speedtest"], cmdtimeout=600) 1483 | 1484 | 1485 | @exception_wrapper("Failed to run speedtest") 1486 | def speedtest(): 1487 | """ 1488 | speedtest() 1489 | 1490 | Runs speed test. 1491 | Returns final results. 1492 | Will take around 30 seconds. 1493 | 1494 | :returns: dict -- :ref:`JSON speedtest ` from speedify 1495 | """ 1496 | jret = _run_speedify_cmd(["speedtest"], cmdtimeout=600) 1497 | return jret 1498 | 1499 | 1500 | @exception_wrapper("Failed to set transport") 1501 | def transport(transport: str = "auto"): 1502 | """ 1503 | transport(transport='auto') 1504 | Sets the transport mode (auto/tcp/multi-tcp/udp/https). 1505 | 1506 | :param transport: Sets the transport to one of 1507 | "auto" 1508 | "udp" 1509 | "tcp" 1510 | "multi-tcp" 1511 | "https" 1512 | :type transport: str 1513 | :returns: dict -- :ref:`JSON settings ` from speedify 1514 | """ 1515 | args = ["transport", transport] 1516 | resultjson = _run_speedify_cmd(args) 1517 | return resultjson 1518 | 1519 | 1520 | @exception_wrapper("Failed getting stats") 1521 | def stats(time: int = 1): 1522 | """ 1523 | stats(time=1) 1524 | calls stats returns a list of all the parsed json objects it gets back 1525 | 1526 | :param time: How long to run the stats command. 1527 | :type time: int 1528 | :returns: list -- list JSON stat responses from speedify. 1529 | """ 1530 | if time == 0: 1531 | logger.error("stats cannot be run with 0, would never return") 1532 | raise SpeedifyError("Stats cannot be run with 0") 1533 | if time == 1: 1534 | # fix for bug where passing in 1 returns nothing. 1535 | time = 2 1536 | 1537 | class list_callback: 1538 | def __init__(self): 1539 | self.result_list = list() 1540 | 1541 | def __call__(self, input): 1542 | self.result_list.append(input) 1543 | 1544 | list_callback = list_callback() 1545 | stats_callback(time, list_callback) 1546 | return list_callback.result_list 1547 | 1548 | 1549 | def stats_callback(time: int, callback): 1550 | """ 1551 | stats_callback(time, callback) 1552 | calls stats, and callback supplied function with each line of output. 0 is forever 1553 | 1554 | :param time: How long to run the stats command. 1555 | :type time: int 1556 | :param callback: Callback function 1557 | :type callback: function 1558 | """ 1559 | args = ["stats", str(time)] 1560 | cmd = [get_cli()] + args 1561 | 1562 | _run_long_command(cmd, callback) 1563 | 1564 | 1565 | @exception_wrapper("Failed to initialize safe browsing") 1566 | def safebrowsing_initialize(settings: str): 1567 | args = ["safebrowsing", "initialize", settings] 1568 | return _run_speedify_cmd(args) 1569 | 1570 | 1571 | @exception_wrapper("Failed to configure safe browsing") 1572 | def safebrowsing_configure(settings: str): 1573 | args = ["safebrowsing", "config", settings] 1574 | return _run_speedify_cmd(args) 1575 | 1576 | 1577 | @exception_wrapper("Failed to enable safe browsing") 1578 | def safebrowsing_enable(enable: bool): 1579 | args = ["safebrowsing", "enable"] 1580 | args.append("on") if enable else args.append("off") 1581 | return _run_speedify_cmd(args) 1582 | 1583 | 1584 | @exception_wrapper("Failed getting safebrowsing error") 1585 | def safebrowsing_error(time: int = 1): 1586 | if time == 0: 1587 | logger.error("safebrowsing error cannot be run with 0, would never return") 1588 | raise SpeedifyError( 1589 | "safebrowsing error cannot be run with 0, would never return" 1590 | ) 1591 | 1592 | class list_callback: 1593 | def __init__(self): 1594 | self.result_list = list() 1595 | 1596 | def __call__(self, input): 1597 | self.result_list.append(input) 1598 | 1599 | list_callback = list_callback() 1600 | safebrowsing_error_callback(time, list_callback) 1601 | return list_callback.result_list 1602 | 1603 | 1604 | def safebrowsing_error_callback(time: int, callback): 1605 | args = ["safebrowsing", "errors", str(time)] 1606 | cmd = [get_cli()] + args 1607 | 1608 | _run_long_command(cmd, callback) 1609 | 1610 | 1611 | # 1612 | # Internal functions 1613 | # 1614 | 1615 | 1616 | def _run_speedify_cmd(args, cmdtimeout: int = 60): 1617 | "passes list of args to speedify command line returns the objects pulled from the json" 1618 | resultstr = "" 1619 | try: 1620 | cmd = [get_cli()] + args 1621 | result = subprocess.run( 1622 | cmd, 1623 | stdout=subprocess.PIPE, 1624 | stderr=subprocess.PIPE, 1625 | shell=use_shell(), 1626 | check=True, 1627 | timeout=cmdtimeout, 1628 | ) 1629 | resultstr = result.stdout.decode("utf-8").strip() 1630 | sep = os.linesep * 2 1631 | records = resultstr.split(sep) 1632 | reclen = len(records) 1633 | if reclen > 0: 1634 | return json.loads(records[-1]) 1635 | logger.error("command " + args[0] + " had NO records") 1636 | raise SpeedifyError("No output from command " + args[0]) 1637 | except subprocess.TimeoutExpired: 1638 | logger.error("Command timed out") 1639 | raise SpeedifyError("Command timed out: " + args[0]) 1640 | except ValueError: 1641 | logger.error("Running cmd, bad json: (" + resultstr + ")") 1642 | raise SpeedifyError("Invalid output from CLI") 1643 | except subprocess.CalledProcessError as cpe: 1644 | # TODO: errors can be json now 1645 | out = cpe.stderr.decode("utf-8").strip() 1646 | if not out: 1647 | out = cpe.stdout.decode("utf-8").strip() 1648 | returncode = cpe.returncode 1649 | errorKind = "Unknown" 1650 | if returncode == 1: 1651 | errorKind = "Speedify API" 1652 | elif returncode == 2: 1653 | errorKind = "Invalid Parameter" 1654 | elif returncode == 3: 1655 | errorKind = "Missing Parameter" 1656 | elif returncode == 4: 1657 | errorKind = "Unknown Parameter" 1658 | # whole usage message here, no help 1659 | raise SpeedifyError(errorKind) 1660 | 1661 | newerror = None 1662 | if returncode == 1: 1663 | try: 1664 | job = json.loads(out) 1665 | if "errorCode" in job: 1666 | # json error! came from the speedify daemon 1667 | newerror = SpeedifyAPIError( 1668 | job["errorCode"], job["errorType"], job["errorMessage"] 1669 | ) 1670 | except ValueError: 1671 | logger.error("Could not parse Speedify API Error: " + out) 1672 | newerror = SpeedifyError(errorKind + ": Could not parse error message") 1673 | else: 1674 | lastline = [i for i in out.split("\n") if i][-1] 1675 | if lastline: 1676 | newerror = SpeedifyError(str(lastline)) 1677 | else: 1678 | newerror = SpeedifyError(errorKind + ": " + str("Unknown error")) 1679 | 1680 | if newerror: 1681 | raise newerror 1682 | else: 1683 | # treat the plain text as an error, common for valid command, with invalud arguments 1684 | logger.error("runSpeedifyCmd CPE : " + out) 1685 | raise SpeedifyError(errorKind + ": " + str(": " + out)) 1686 | 1687 | 1688 | # 1689 | # Callbacks 1690 | # 1691 | 1692 | 1693 | # The normal _run_speedify_cmd runs the command and waits for the final output. 1694 | # these versions keep running, calling you back as json objects are emitted. useful 1695 | # for stats and for a verbose speedtest, otherwise, stick with the non-callback versions 1696 | # 1697 | 1698 | 1699 | @exception_wrapper("SpeedifyError in longRunCommand") 1700 | def _run_long_command(cmdarray, callback): 1701 | "callback is a function you provide, passed parsed json objects" 1702 | outputbuffer = "" 1703 | 1704 | with subprocess.Popen(cmdarray, stdout=subprocess.PIPE) as proc: 1705 | for line in proc.stdout: 1706 | line = line.decode("utf-8").strip() 1707 | if line: 1708 | outputbuffer += str(line) 1709 | else: 1710 | if outputbuffer: 1711 | _do_callback(callback, outputbuffer) 1712 | outputbuffer = "" 1713 | else: 1714 | outputbuffer = "" 1715 | 1716 | if outputbuffer: 1717 | _do_callback(callback, outputbuffer) 1718 | 1719 | 1720 | def _do_callback(callback, message): 1721 | "parsing string as json, calls callback function with result" 1722 | jsonret = "" 1723 | try: 1724 | if message: 1725 | jsonret = json.loads(message) 1726 | except SpeedifyError as e: 1727 | logger.debug("problem parsing json: " + str(e)) 1728 | if jsonret: 1729 | try: 1730 | callback(jsonret) 1731 | except SpeedifyError as e: 1732 | logger.warning("problem callback: " + str(e)) 1733 | 1734 | 1735 | # Default cli search locations 1736 | def _find_cli(): 1737 | """Finds the path for the CLI""" 1738 | if "SPEEDIFY_CLI" in os.environ: 1739 | possible = os.environ["SPEEDIFY_CLI"] 1740 | if possible: 1741 | if os.path.isfile(possible): 1742 | logging.debug("Using cli from SPEEDIFY_CLI of (" + possible + ")") 1743 | return possible 1744 | else: 1745 | logging.warning( 1746 | 'SPEEDIFY_CLI specified a nonexistant path to cli: "' 1747 | + possible 1748 | + '"' 1749 | ) 1750 | possible_paths = [ 1751 | "/Applications/Speedify.app/Contents/Resources/speedify_cli", 1752 | "c://program files (x86)//speedify//speedify_cli.exe", 1753 | "c://program files//speedify//speedify_cli.exe", 1754 | "/usr/share/speedify/speedify_cli", 1755 | ] 1756 | for pp in possible_paths: 1757 | if os.path.isfile(pp): 1758 | logging.debug("Using cli of (" + pp + ")") 1759 | return pp 1760 | 1761 | logging.error("Could not find speedify_cli!") 1762 | raise SpeedifyError("Speedify CLI not found") 1763 | -------------------------------------------------------------------------------- /speedifysettings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # Uses Python 3.7 3 | 4 | import speedify 5 | import json 6 | import logging 7 | import os 8 | 9 | from speedify import Priority 10 | from speedify import SpeedifyError 11 | 12 | """ 13 | .. module:: speedifysettings 14 | :synopsis: Contains speedify cli convenience functions 15 | """ 16 | 17 | 18 | # for convenience, here's a JSON that resets everything to normal 19 | speedify_defaults = ( 20 | """{"connectmethod" : "closest","encryption" : true, "jumbo" : true, 21 | "mode" : "speed", 22 | "startupconnect": true, "packet_aggregation": true, "transport":"auto","overflow_threshold": 30.0, 23 | "adapter_priority_ethernet" : "always","adapter_priority_wifi" : "always", 24 | "adapter_priority_cellular" : "secondary", "adapter_datalimit_daily_all" : 0, 25 | "adapter_datalimit_monthly_all" : 0, 26 | "adapter_ratelimit": {"upload_bps": 0, "download_bps": 0}, 27 | "route_default": true 28 | """ 29 | + ( 30 | ', "privacy_killswitch":false, "privacy_dnsleak": true,' 31 | if os.name == "nt" 32 | else "" 33 | ) 34 | + """ 35 | }""" 36 | ) 37 | 38 | 39 | def apply_setting(setting, value): 40 | """ 41 | Sets the setting to the value given 42 | 43 | :param setting: The speedify setting to set. 44 | :type setting: str 45 | :param value: The value to set the setting to. 46 | :type value: str/int 47 | """ 48 | success = True 49 | try: 50 | adapterguids = [] 51 | logging.debug("setting: " + str(setting) + ", value:" + str(value)) 52 | if setting.startswith("adapter_"): 53 | setting_split = setting.split("_") 54 | adaptertype = setting_split[-1] 55 | adapterguids = _find_adapterids(adaptertype) 56 | _apply_setting_to_adapters(setting, value, adapterguids) 57 | elif setting == "connectmethod": 58 | speedify.connectmethod(value) 59 | elif setting == "directory": 60 | speedify.directory(value) 61 | elif setting == "encryption": 62 | speedify.encryption(value) 63 | elif setting == "packet_aggregation": 64 | speedify.packetaggregation(value) 65 | elif setting == "jumbo": 66 | speedify.jumbo(value) 67 | # dnsleak and killswitch not available on all platforms 68 | elif setting == "privacy_dnsleak": 69 | if os.name == "nt": 70 | speedify.dnsleak(value) 71 | else: 72 | logging.info("dnsleak not supported on this platform") 73 | elif setting == "privacy_killswitch": 74 | if os.name == "nt": 75 | speedify.killswitch(value) 76 | else: 77 | logging.info("killswitch not supported on this platform") 78 | elif setting == "mode": 79 | speedify.mode(value) 80 | elif setting == "overflow_threshold": 81 | speedify.overflow(float(value)) 82 | elif setting == "route_default": 83 | speedify.routedefault(value) 84 | elif setting == "startupconnect": 85 | speedify.startupconnect(value) 86 | elif setting == "transport": 87 | speedify.transport(value) 88 | else: 89 | logging.warning("unknown setting " + str(setting)) 90 | success = False 91 | except SpeedifyError as se: 92 | logging.error( 93 | "Speedify error on setting:" 94 | + str(setting) 95 | + " value:" 96 | + str(value) 97 | + ", exception:" 98 | + str(se) 99 | ) 100 | success = False 101 | return success 102 | 103 | 104 | def apply_speedify_settings(newsettings): 105 | """Takes a string or parsed json of the settings, and applies them. 106 | 107 | :param newsettings: The JSON of speedify settings to set. May be a string or a dict 108 | :type setting: dict/str 109 | :returns: bool -- Returns True if all settings applied, False if ANY fail 110 | """ 111 | # possible future optimization, use show_ to pull current settings, and only change settings that changed. 112 | filesuccess = True 113 | try: 114 | body = {} 115 | 116 | if isinstance(newsettings, str): 117 | body = json.loads(newsettings) 118 | else: 119 | body = newsettings 120 | for cmd in body: 121 | value = body[cmd] 122 | filesuccess = filesuccess and apply_setting(cmd, value) 123 | except Exception as e: 124 | logging.error("Failed to apply file:" + str(e)) 125 | filesuccess = False 126 | return filesuccess 127 | 128 | 129 | def get_speedify_settings_as_json_string(): 130 | """ 131 | Returns the current speedify settings as a JSON string 132 | 133 | :returns: str -- JSON string of speedify settings 134 | """ 135 | return json.dumps(get_speedify_settings()) 136 | 137 | 138 | def get_speedify_settings(): 139 | """ 140 | Returns the current speedify settings as a dict 141 | 142 | :returns: dict -- dict of speedify settings 143 | """ 144 | settings = {} 145 | # pulls out the current settings... couple flaws: 146 | # can't get the privacy settings without changing them first, CAN get overflow_threshold 147 | # but the other functions can't actually set that. 148 | try: 149 | adapters = speedify.show_adapters() 150 | for adapter in adapters: 151 | logging.debug("Adapter is :" + str(adapter)) 152 | adaptername = adapter["name"] 153 | settings["adapter_ratelimit_" + adaptername] = { 154 | "upload_bps": adapter["rateLimit"]["uploadBps"], 155 | "download_bps": adapter["rateLimit"]["downloadBps"], 156 | } 157 | settings["adapter_priority_" + adaptername] = adapter["priority"] 158 | if "dataUsage" in adapter: 159 | limits = adapter["dataUsage"] 160 | if limits: 161 | if limits["usageMonthlyLimit"]: 162 | settings["adapter_datalimit_monthly_" + adaptername] = limits[ 163 | "usageMonthlyLimit" 164 | ] 165 | if limits["usageDailyLimit"]: 166 | settings["adapter_datalimit_daily_" + adaptername] = limits[ 167 | "usageDailyLimit" 168 | ] 169 | 170 | currentsettings = speedify.show_settings() 171 | logging.debug("Settings are:" + str(currentsettings)) 172 | settings["encryption"] = currentsettings["encrypted"] 173 | settings["jumbo"] = currentsettings["jumboPackets"] 174 | settings["transport"] = currentsettings["transportMode"] 175 | settings["startupconnect"] = currentsettings["startupConnect"] 176 | settings["mode"] = currentsettings["bondingMode"] 177 | settings["overflow_threshold"] = currentsettings["overflowThreshold"] 178 | settings["packet_aggregation"] = currentsettings["packetAggregation"] 179 | settings["route_default"] = currentsettings["enableDefaultRoute"] 180 | # TODO: can no longer get connectmethod back out! 181 | connectmethodsettings = speedify.show_connectmethod() 182 | settings["connectmethod"] = connectmethodsettings["connectMethod"] 183 | 184 | user = speedify.show_user() 185 | logging.debug("User is:" + str(user)) 186 | privacysettings = speedify.show_privacy() 187 | if "dnsleak" in privacysettings: 188 | settings["privacy_dnsleak"] = privacysettings["dnsleak"] 189 | if "killswitch" in privacysettings: 190 | settings["privacy_killswitch"] = privacysettings["killswitch"] 191 | 192 | except SpeedifyError as se: 193 | logging.error("Speedify error on getSpeedfiySetting:" + str(se)) 194 | 195 | return settings 196 | 197 | 198 | def _find_adapterids(adaptertype="wifi"): 199 | # gives you a list of Guids which match the string you pass in. could be a type "wifi", "ethernet", 200 | # a name like "Ethernet 2" or "en0", or the GUID of an adapter. 201 | adapterGuids = [] 202 | isGuid = False 203 | isAll = False 204 | adaptertype = adaptertype.lower() 205 | if adaptertype == "wifi": 206 | # mdm takes "wifi", cli calls it "Wi-Fi", no biggie just fix 207 | adaptertype = "wi-fi" 208 | if adaptertype.startswith("{"): 209 | # it's a guid! 210 | isGuid = True 211 | guid = adaptertype.lower() 212 | if adaptertype == "all": 213 | # applies to every adapter! Note that there's no guarantee on order here. 214 | # so if you have a "_cellular" and an "_all" it's random which setting 215 | # the cellular will have at the end. Would make sense to apply them in order 216 | # but we're just tossing them in a dictionary. 217 | isAll = True 218 | 219 | adapters = speedify.show_adapters() 220 | for adapter in adapters: 221 | guid = None 222 | if isAll: 223 | adapterGuids.append(str(adapter["adapterID"])) 224 | elif not isGuid: 225 | logging.debug("adapter type: " + str(adapter["type"])) 226 | if adapter["type"].lower() == adaptertype: 227 | logging.debug( 228 | "Found by type: " 229 | + str(adapter["description"]) 230 | + " guid " 231 | + str(adapter["adapterID"]) 232 | ) 233 | adapterGuids.append(str(adapter["adapterID"])) 234 | elif adapter["name"].lower() == adaptertype: 235 | logging.debug( 236 | "Found by name" 237 | + str(adapter["description"]) 238 | + " guid " 239 | + str(adapter["adapterID"]) 240 | ) 241 | adapterGuids.append(str(adapter["adapterID"])) 242 | else: 243 | if adapter["adapterID"].lower() == guid: 244 | logging.debug( 245 | "Found by guid, " 246 | + str(adapter["description"]) 247 | + " guid " 248 | + str(adapter["adapterID"]) 249 | ) 250 | adapterGuids.append(str(adapter["adapterID"])) 251 | return adapterGuids 252 | 253 | 254 | def _apply_setting_to_adapters(setting, value, adapterguids): 255 | # applies one setting to an list of adapters, specified via guids 256 | if setting.startswith("adapter_datalimit_daily"): 257 | for guid in adapterguids: 258 | speedify.adapter_datalimit_daily(guid, value) 259 | elif setting.startswith("adapter_datalimit_monthly"): 260 | for guid in adapterguids: 261 | speedify.adapter_datalimit_monthly(guid, value) 262 | elif setting.startswith("adapter_priority"): 263 | try: 264 | for guid in adapterguids: 265 | speedify.adapter_priority(guid, Priority[str(value).upper()]) 266 | except KeyError as keyerr: 267 | print("no such priority: " + str(value) + str(keyerr)) 268 | raise 269 | elif setting.startswith("adapter_ratelimit"): 270 | for guid in adapterguids: 271 | speedify.adapter_ratelimit(guid, value["download_bps"], value["upload_bps"]) 272 | -------------------------------------------------------------------------------- /speedifyutil.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | import speedify 4 | from speedify import State 5 | from utils import use_shell 6 | import platform 7 | import logging 8 | 9 | 10 | def confirm_state_speedify(state=State.LOGGED_IN): 11 | "Confirms with a True|False whether speedify is in state you pass in" 12 | desc = speedify.show_state() 13 | if state == desc: 14 | return True 15 | else: 16 | logging.error("confirmStateSpeedify Failed command results: " + str(desc)) 17 | return False 18 | 19 | 20 | def list_servers_speedify(public=True, private=False, excludeTest=True): 21 | "Returns flattened array of servers, excludes any with test in name" 22 | try: 23 | serverlist = [] 24 | jret = speedify.show_servers() 25 | if public: 26 | if "public" in jret: 27 | for server in jret["public"]: 28 | serverlist.append(server["tag"]) 29 | 30 | if private: 31 | if "private" in jret: 32 | for server in jret["private"]: 33 | serverlist.append(server["tag"]) 34 | if excludeTest: 35 | # servers with "test"in the name are bad news 36 | serverlist2 = [x for x in serverlist if "-test" not in x] 37 | serverlist = serverlist2 38 | return serverlist 39 | except speedify.SpeedifyError as err: 40 | logging.error("Failed to get server list: " + err.message) 41 | return "ERROR" 42 | 43 | 44 | def using_speedify(destination="8.8.8.8"): 45 | "Checks that the internet gateway really is a speed server" 46 | tracert = ["traceroute", "-m", "2", destination] 47 | if platform.system() == "Windows": 48 | tracert = ["tracert", "-h", "1", "-d", destination] 49 | elif platform.system() == "Linux": 50 | tracert = ["mtr", "-m", "2", "-c", "2", "--report", destination] 51 | try: 52 | result = subprocess.run(tracert, stdout=subprocess.PIPE, shell=use_shell()) 53 | except FileNotFoundError as e: 54 | logging.error(e) 55 | raise e 56 | resultstr = result.stdout.decode("utf-8") 57 | if "10.202.0.1" in resultstr: 58 | return True 59 | else: 60 | # print(resultstr) 61 | return False 62 | -------------------------------------------------------------------------------- /tests/test_speedify.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | 5 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 6 | sys.path.append("../") 7 | 8 | import speedify 9 | from speedify import State, Priority, SpeedifyError, SpeedifyAPIError 10 | import speedifysettings 11 | import speedifyutil 12 | import logging 13 | import unittest 14 | import time 15 | import random 16 | 17 | logging.basicConfig( 18 | handlers=[logging.FileHandler("test.log"), logging.StreamHandler(sys.stdout)], 19 | format="%(asctime)s\t%(levelname)s\t%(module)s\t%(message)s", 20 | level=logging.INFO, 21 | ) 22 | 23 | # Test the speedify library 24 | # assumes you're logged in 25 | 26 | 27 | def server_countries() -> set[str]: 28 | all_known_servers = speedify.show_servers()["private"] + speedify.show_servers()["public"] 29 | return {s["country"] for s in all_known_servers} 30 | 31 | 32 | class TestSpeedify(unittest.TestCase): 33 | # Note doesn't test login/logout. but then we have to deal with credentials being stored. 34 | 35 | def setUp(self): 36 | self.assertFalse(speedify.show_state() == State.LOGGED_OUT) 37 | speedify.encryption(True) 38 | speedify.transport("auto") 39 | speedify.jumbo(True) 40 | speedify.packetaggregation(True) 41 | speedify.routedefault(True) 42 | speedify.connectmethod("closest") 43 | speedify.disconnect() 44 | self.assertFalse(speedify.show_state() == State.LOGGED_OUT) 45 | 46 | def test_dns(self): 47 | logging.debug("\n\nTesting dns...") 48 | ips = ["8.8.8.8", ""] 49 | for ip in ips: 50 | self.assertEqual(speedify.dns(ip)["dnsAddresses"], [ip] if ip != "" else []) 51 | 52 | def test_streamtest(self): 53 | logging.debug("\n\nTesting streamtest...") 54 | if speedify.show_state() is not State.CONNECTED: 55 | speedify.connect("closest") 56 | self.assertEqual(speedify.streamtest()[0]["isError"], False) 57 | 58 | def test_directory(self): 59 | logging.debug("\n\nTesting directory settings...") 60 | result = speedify.show_directory()["domain"] 61 | is_prod = result == "" 62 | is_dev = re.search(r"devdirectory.*", result) 63 | self.assertTrue(is_prod or is_dev) 64 | 65 | def test_show(self): 66 | logging.debug("\n\nTesting show keys...") 67 | show_functions = [ 68 | speedify.show_servers, 69 | speedify.show_settings, 70 | speedify.show_privacy, 71 | speedify.show_adapters, 72 | speedify.show_currentserver, 73 | speedify.show_user, 74 | speedify.show_directory, 75 | speedify.show_connectmethod, 76 | speedify.show_streamingbypass, 77 | speedify.show_disconnect, 78 | speedify.show_streaming, 79 | speedify.show_speedtest, 80 | ] 81 | for f in show_functions: 82 | self.assertTrue(f() != "" and not None) 83 | 84 | def test_headercompression(self): 85 | logging.debug("\n\nTesting header compression settings...") 86 | for b in [False, True]: 87 | self.assertEqual(speedify.headercompression(b)["headerCompression"], b) 88 | 89 | def test_streamingbypass_domains(self): 90 | logging.debug("\n\nTesting streaming bypass for domains...") 91 | ip = "11.11.11.11" 92 | mode = { 93 | "on_add": {"op": speedify.streamingbypass_domains_add, "val": True}, 94 | "on_rem": {"op": speedify.streamingbypass_domains_rem, "val": False}, 95 | } 96 | for m in mode.keys(): 97 | self.assertEqual( 98 | ip in mode[m]["op"](ip)["domains"], 99 | mode[m]["val"], 100 | ) 101 | 102 | def test_streamingbypass_ports(self): 103 | logging.debug("\n\nTesting streaming bypass for ports...") 104 | 105 | def result_of(d): 106 | try: 107 | return d["ports"][0]["port"] 108 | except IndexError: 109 | return False 110 | 111 | port_num = "9999" 112 | mode = { 113 | "on_add": {"op": speedify.streamingbypass_ports_add, "val": True}, 114 | "on_rem": {"op": speedify.streamingbypass_ports_rem, "val": False}, 115 | } 116 | for m in mode.keys(): 117 | self.assertEqual( 118 | int(port_num) == result_of(mode[m]["op"](port_num + "/tcp")), 119 | mode[m]["val"], 120 | ) 121 | 122 | def test_streamingbypass_ipv4(self): 123 | logging.debug("\n\nTesting streaming bypass for ipv4 addresses...") 124 | ip = "68.80.59.53" 125 | mode = { 126 | "on_add": {"op": speedify.streamingbypass_ipv4_add, "val": True}, 127 | "on_rem": {"op": speedify.streamingbypass_ipv4_rem, "val": False}, 128 | } 129 | for m in mode.keys(): 130 | self.assertEqual( 131 | ip in mode[m]["op"](ip)["ipv4"], 132 | mode[m]["val"], 133 | ) 134 | 135 | def test_streamingbypass_ipv6(self): 136 | logging.debug("\n\nTesting streaming bypass for ipv6 addresses...") 137 | ip = "2001:db8:1234:ffff:ffff:ffff:ffff:f0f" 138 | mode = { 139 | "on_add": {"op": speedify.streamingbypass_ipv6_add, "val": True}, 140 | "on_rem": {"op": speedify.streamingbypass_ipv6_rem, "val": False}, 141 | } 142 | for m in mode.keys(): 143 | self.assertEqual( 144 | ip in mode[m]["op"](ip)["ipv6"], 145 | mode[m]["val"], 146 | ) 147 | 148 | def test_streamingbypass_service(self): 149 | logging.debug("\n\nTesting streaming bypass for services...") 150 | # I think these are still ok to test with. 151 | # If the get out of date: 152 | # speedify_cli show streamingbypass | grep title | sed -E 's/.*: (.*)/\1,/g' 153 | valid_service_names = [ 154 | "Netflix", 155 | "Disney+", 156 | "HBO", 157 | "Hulu", 158 | "Peacock", 159 | "Amazon Prime", 160 | "Youtube TV", 161 | "Ring", 162 | "VoLTE", 163 | "Reliance Jio", 164 | "Microsoft Your Phone", 165 | "Spectrum TV & Mobile", 166 | "Showtime", 167 | "Visual Voice Mail", 168 | "Android Auto", 169 | "Tubi", 170 | "Hotstar", 171 | "RCS Messaging", 172 | "Ubisoft Connect", 173 | "Apple Updates", 174 | ] 175 | for s in valid_service_names: 176 | for b in [False, True]: 177 | for i in speedify.streamingbypass_service(s, b)["services"]: 178 | if i["title"] == s: 179 | self.assertTrue(i["enabled"] is b) 180 | 181 | def test_adapter_overratelimit(self): 182 | logging.debug("\n\nTesting overratelimit...") 183 | 184 | def getrl(d): 185 | return d[0]["dataUsage"]["overlimitRatelimit"] 186 | 187 | for l in [getrl(speedify.show_adapters()), 2000000]: 188 | self.assertEqual( 189 | getrl( 190 | speedify.adapter_overratelimit( 191 | speedify.show_adapters()[0]["adapterID"], l 192 | ) 193 | ), 194 | l, 195 | ) 196 | 197 | def test_connect(self): 198 | logging.debug("\n\nTesting connect...") 199 | serverinfo = speedify.connect_closest() 200 | state = speedify.show_state() 201 | self.assertEqual(state, State.CONNECTED) 202 | self.assertIn("tag", serverinfo) 203 | self.assertIn("country", serverinfo) 204 | 205 | def test_connect_country(self): 206 | logging.debug("\n\nTesting connect country...") 207 | country_sample = random.sample(list(server_countries()), 3) 208 | for country in country_sample: 209 | serverinfo = speedify.connect_country(country) 210 | state = speedify.show_state() 211 | self.assertEqual(state, State.CONNECTED) 212 | self.assertIn("tag", serverinfo) 213 | self.assertIn("country", serverinfo) 214 | self.assertEqual(serverinfo["country"], country) 215 | new_serverinfo = speedify.show_currentserver() 216 | self.assertEqual(new_serverinfo["country"], country) 217 | 218 | def test_transport(self): 219 | logging.debug("\n\nTesting transport...") 220 | mysettings = speedify.transport("https") 221 | speedify.connect() 222 | mysettings = speedify.show_settings() 223 | self.assertEqual(mysettings["transportMode"], "https") 224 | # to make sure runtime changed, could check stats and look for connectionstats : connections[] : protocol 225 | mysettings = speedify.transport("tcp") 226 | self.assertEqual(mysettings["transportMode"], "tcp") 227 | speedify.connect() 228 | mysettings = speedify.show_settings() 229 | self.assertEqual(mysettings["transportMode"], "tcp") 230 | 231 | def test_bad_country(self): 232 | logging.debug("\n\nTesting bad country...") 233 | logging.debug("[Testing error handling, ignore next few errors]") 234 | state = speedify.show_state() 235 | self.assertEqual(state, State.LOGGED_IN) 236 | logging.debug("connecting to bad country") 237 | with self.assertRaises(SpeedifyAPIError): 238 | speedify.connect_country("pp") 239 | logging.debug("after connecting to bad country") 240 | state = speedify.show_state() 241 | self.assertEqual(state, State.LOGGED_IN) 242 | logging.debug("Done testing error handling") 243 | 244 | def test_disconnect(self): 245 | logging.debug("\n\nTesting disconnect...") 246 | speedify.connect_closest() 247 | state = speedify.show_state() 248 | self.assertEqual(state, State.CONNECTED) 249 | speedify.disconnect() 250 | state = speedify.show_state() 251 | self.assertEqual(state, speedify.State.LOGGED_IN) 252 | 253 | def test_connectmethod(self): 254 | logging.debug("\n\nTesting connectmethod...") 255 | speedify.connect_closest() 256 | country = random.choice(list(server_countries())) 257 | speedify.connectmethod("private", country) 258 | # pull settings from speedify to be sure they really set 259 | cm_settings = speedify.show_connectmethod() 260 | self.assertEqual(cm_settings["connectMethod"], "private") 261 | # country is ignored on 262 | self.assertEqual(cm_settings["country"], "") 263 | self.assertEqual(cm_settings["num"], 0) 264 | self.assertEqual(cm_settings["city"], "") 265 | speedify.connectmethod("p2p") 266 | cm_settings = speedify.show_connectmethod() 267 | self.assertEqual(cm_settings["connectMethod"], "p2p") 268 | self.assertEqual(cm_settings["country"], "") 269 | self.assertEqual(cm_settings["num"], 0) 270 | self.assertEqual(cm_settings["city"], "") 271 | country_sample = random.sample(list(server_countries()), 3) 272 | for country in country_sample: 273 | retval = speedify.connectmethod("country", country=country) 274 | cm_settings = speedify.show_connectmethod() 275 | self.assertEqual(cm_settings["connectMethod"], "country") 276 | self.assertEqual(cm_settings["country"], country) 277 | # the settings were returned by the actual connectmethod call, 278 | # and should be exactly the same 279 | self.assertEqual(cm_settings["connectMethod"], retval["connectMethod"]) 280 | self.assertEqual(cm_settings["country"], retval["country"]) 281 | self.assertEqual(cm_settings["num"], retval["num"]) 282 | self.assertEqual(cm_settings["city"], retval["city"]) 283 | speedify.connectmethod("closest") 284 | cm_settings = speedify.show_connectmethod() 285 | self.assertEqual(cm_settings["connectMethod"], "closest") 286 | self.assertEqual(cm_settings["country"], "") 287 | self.assertEqual(cm_settings["num"], 0) 288 | self.assertEqual(cm_settings["city"], "") 289 | 290 | def test_version(self): 291 | logging.debug("\n\nTesting version...") 292 | version = speedify.show_version() 293 | self.assertIn("maj", version) 294 | # expect at least Speedify 8.0 295 | self.assertGreater(version["maj"], 7) 296 | self.assertIn("min", version) 297 | self.assertIn("bug", version) 298 | self.assertIn("build", version) 299 | 300 | def test_settings(self): 301 | logging.debug("\n\nTesting settings...") 302 | # test some basic settings 303 | speedify.packetaggregation(False) 304 | speedify.jumbo(False) 305 | my_settings = speedify.show_settings() 306 | self.assertFalse(my_settings["packetAggregation"]) 307 | self.assertFalse(my_settings["jumboPackets"]) 308 | speedify.packetaggregation(True) 309 | speedify.jumbo(True) 310 | my_settings = speedify.show_settings() 311 | self.assertTrue(my_settings["packetAggregation"]) 312 | self.assertTrue(my_settings["jumboPackets"]) 313 | 314 | def test_badarguments(self): 315 | logging.debug("\n\nTesting bad arguments...") 316 | # reaching into private methods to force some errors to be sure they're handled 317 | try: 318 | goterror = False 319 | # invalid command 320 | speedify._run_speedify_cmd(["invalidcommand"]) 321 | except speedify.SpeedifyError as sapie: 322 | self.assertTrue("Unknown Parameter" in sapie.message) 323 | goterror = True 324 | self.assertTrue(goterror) 325 | try: 326 | # valid command, missing required argument 327 | goterror = False 328 | speedify._run_speedify_cmd(["overflow"]) 329 | except speedify.SpeedifyError as sapie: 330 | self.assertTrue("Missing parameters" in sapie.message) 331 | goterror = True 332 | self.assertTrue(goterror) 333 | try: 334 | goterror = False 335 | # valid command, invalid argument 336 | speedify._run_speedify_cmd(["overflow", "bob"]) 337 | except speedify.SpeedifyError as sapie: 338 | self.assertTrue("Invalid parameters" in sapie.message) 339 | goterror = True 340 | self.assertTrue(goterror) 341 | 342 | def test_privacy(self): 343 | logging.debug("\n\nTesting privacy...") 344 | if os.name == "nt": 345 | # the windows only calls 346 | speedify.killswitch(True) 347 | privacy_settings = speedify.show_privacy() 348 | self.assertTrue(privacy_settings["killswitch"]) 349 | speedify.killswitch(False) 350 | privacy_settings = speedify.show_privacy() 351 | self.assertFalse(privacy_settings["killswitch"]) 352 | else: 353 | # shouldn't be there if we're not windows 354 | with self.assertRaises(SpeedifyError): 355 | logging.disable(logging.ERROR) 356 | speedify.killswitch(True) 357 | logging.disable(logging.NOTSET) 358 | 359 | def test_routedefault(self): 360 | logging.debug("\n\nTesting route default...") 361 | speedify.connect() 362 | if not speedifyutil.using_speedify(): 363 | time.sleep(3) 364 | self.assertTrue(speedifyutil.using_speedify()) 365 | speedify.routedefault(False) 366 | self.assertFalse(speedify.show_settings()["enableDefaultRoute"]) 367 | time.sleep(1) 368 | if speedifyutil.using_speedify(): 369 | # try twice in case it takes a moment to settle 370 | time.sleep(1) 371 | self.assertFalse(speedifyutil.using_speedify()) 372 | speedify.routedefault(True) 373 | # for whatever reason getting the route back takes longer than giving it up 374 | self.assertTrue(speedify.show_settings()["enableDefaultRoute"]) 375 | time.sleep(2) 376 | if not speedifyutil.using_speedify(): 377 | # try twice in case it takes a moment to settle 378 | time.sleep(2) 379 | self.assertTrue(speedifyutil.using_speedify()) 380 | 381 | def test_serverlist(self): 382 | logging.debug("\n\nTesting server list...") 383 | # also tests connecting to one server 384 | server_list = speedify.show_servers() 385 | self.assertIn("public", server_list) 386 | public_list = server_list["public"] 387 | server_info = public_list[0] 388 | self.assertIn("tag", server_info) 389 | self.assertIn("country", server_info) 390 | self.assertIn("city", server_info) 391 | self.assertIn("num", server_info) 392 | self.assertFalse(server_info["isPrivate"]) 393 | new_server = speedify.connect( 394 | server_info["country"] 395 | + " " 396 | + server_info["city"] 397 | + " " 398 | + str(server_info["num"]) 399 | ) 400 | self.assertEqual(server_info["tag"], new_server["tag"]) 401 | self.assertEqual(server_info["country"], new_server["country"]) 402 | self.assertEqual(server_info["city"], new_server["city"]) 403 | self.assertEqual(server_info["num"], new_server["num"]) 404 | 405 | def test_stats(self): 406 | logging.debug("\n\nTesting stats...") 407 | speedify.connect_closest() 408 | report_list = speedify.stats(2) 409 | self.assertTrue(report_list) # Check for non empty list 410 | reports = [item[0] for item in report_list] 411 | self.assertIn("adapters", reports) # Check for at least one adapters report 412 | 413 | def test_adapters(self): 414 | logging.debug("\n\nTesting adapters...") 415 | adapters = speedify.show_adapters() 416 | self.assertTrue(adapters) 417 | adapterIDs = [adapter["adapterID"] for adapter in adapters] 418 | self._set_and_test_adapter_list(adapterIDs, Priority.BACKUP, 10000000) 419 | self._set_and_test_adapter_list(adapterIDs, Priority.ALWAYS, 0) 420 | 421 | def test_encryption(self): 422 | logging.debug("\n\nTesting encryption...") 423 | adapters = speedify.show_adapters() 424 | self.assertTrue(adapters) 425 | # just grab first adapter for testing 426 | adapterID = [adapter["adapterID"] for adapter in adapters][0] 427 | speedify.adapter_encryption(adapterID, False) 428 | mysettings = speedify.show_settings() 429 | perConnectionEncryptionEnabled = mysettings["perConnectionEncryptionEnabled"] 430 | self.assertTrue(perConnectionEncryptionEnabled) 431 | encrypted = mysettings["encrypted"] 432 | perConnectionEncryptionSettings = mysettings["perConnectionEncryptionSettings"] 433 | firstadapter = perConnectionEncryptionSettings[0] 434 | self.assertEqual(firstadapter["adapterID"], adapterID) 435 | self.assertEqual(firstadapter["encrypted"], False) 436 | # main thing should still be encrypted just not our one adapter 437 | self.assertTrue(encrypted) 438 | speedify.encryption(False) 439 | # this should both turn off encryption and wipe the custom settings 440 | mysettings = speedify.show_settings() 441 | perConnectionEncryptionEnabled = mysettings["perConnectionEncryptionEnabled"] 442 | self.assertFalse(perConnectionEncryptionEnabled) 443 | encrypted = mysettings["encrypted"] 444 | self.assertFalse(encrypted) 445 | # now let's test with only the adapter being encrypted 446 | speedify.adapter_encryption(adapterID, True) 447 | mysettings = speedify.show_settings() 448 | perConnectionEncryptionEnabled = mysettings["perConnectionEncryptionEnabled"] 449 | self.assertTrue(perConnectionEncryptionEnabled) 450 | encrypted = mysettings["encrypted"] 451 | perConnectionEncryptionSettings = mysettings["perConnectionEncryptionSettings"] 452 | firstadapter = perConnectionEncryptionSettings[0] 453 | self.assertEqual(firstadapter["adapterID"], adapterID) 454 | self.assertEqual(firstadapter["encrypted"], True) 455 | speedify.encryption(True) 456 | # this should both turn on encryption and wipe the custom settings 457 | mysettings = speedify.show_settings() 458 | perConnectionEncryptionEnabled = mysettings["perConnectionEncryptionEnabled"] 459 | self.assertFalse(perConnectionEncryptionEnabled) 460 | encrypted = mysettings["encrypted"] 461 | self.assertTrue(encrypted) 462 | 463 | def _set_and_test_adapter_list(self, adapterIDs, priority, limit): 464 | for adapterID in adapterIDs: 465 | speedify.adapter_priority(adapterID, priority) 466 | speedify.adapter_ratelimit(adapterID, limit, limit) 467 | speedify.adapter_datalimit_daily(adapterID, limit) 468 | speedify.adapter_datalimit_monthly(adapterID, limit, 0) 469 | updated_adapters = speedify.show_adapters() 470 | priorities = [adapter["priority"] for adapter in updated_adapters] 471 | rate_limits = [adapter["rateLimit"] for adapter in updated_adapters] 472 | daily_limits = [ 473 | adapter["dataUsage"]["usageDailyLimit"] for adapter in updated_adapters 474 | ] 475 | monthly_limits = [ 476 | adapter["dataUsage"]["usageMonthlyLimit"] for adapter in updated_adapters 477 | ] 478 | for set_priority, rate_limit, daily_limit, monthly_limit in zip( 479 | priorities, rate_limits, daily_limits, monthly_limits 480 | ): 481 | # Disconnected adapters speedify is aware of will have an unchangable priority never 482 | if set_priority != Priority.NEVER.value: 483 | self.assertEqual(set_priority, priority.value) 484 | self.assertEqual(rate_limit["uploadBps"], limit) 485 | self.assertEqual(rate_limit["downloadBps"], limit) 486 | self.assertEqual(daily_limit, limit) 487 | self.assertEqual(monthly_limit, limit) 488 | 489 | 490 | if __name__ == "__main__": 491 | speedifysettings.apply_speedify_settings(speedifysettings.speedify_defaults) 492 | unittest.main() 493 | speedifysettings.apply_speedify_settings(speedifysettings.speedify_defaults) 494 | -------------------------------------------------------------------------------- /tests/test_speedifysettings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 5 | sys.path.append("../") 6 | 7 | import speedify 8 | from speedify import State 9 | import speedifysettings 10 | import logging 11 | import unittest 12 | 13 | logging.basicConfig( 14 | handlers=[logging.FileHandler("test.log"), logging.StreamHandler(sys.stdout)], 15 | format="%(asctime)s\t%(levelname)s\t%(module)s\t%(message)s", 16 | level=logging.INFO, 17 | ) 18 | 19 | # Test the speedifysettings library 20 | 21 | 22 | class TestSpeedifySettings(unittest.TestCase): 23 | def setUp(self): 24 | self.assertFalse(speedify.show_state() == State.LOGGED_OUT) 25 | speedify.encryption(True) 26 | speedify.transport("auto") 27 | speedify.jumbo(True) 28 | speedify.packetaggregation(True) 29 | speedify.routedefault(True) 30 | self.assertFalse(speedify.show_state() == State.LOGGED_OUT) 31 | 32 | def test_reset(self): 33 | logging.debug("\n\nTesting reset...") 34 | # read settings 35 | currentsettings = speedifysettings.get_speedify_settings() 36 | # write them back 37 | self.assertTrue(speedifysettings.apply_speedify_settings(currentsettings)) 38 | 39 | def test_set_defaults(self): 40 | logging.debug("\n\nTesting setting defaults...") 41 | speedify.encryption(False) 42 | speedify.transport("tcp") 43 | self.assertTrue( 44 | speedifysettings.apply_speedify_settings(speedifysettings.speedify_defaults) 45 | ) 46 | settings = speedify.show_settings() 47 | self.assertTrue(settings["encrypted"]) 48 | self.assertTrue(settings["jumboPackets"]) 49 | self.assertEqual(settings["transportMode"], "auto") 50 | 51 | def test_read_settings(self): 52 | logging.debug("\n\nTesting reading defaults...") 53 | speedify.encryption(False) 54 | speedify.transport("tcp") 55 | speedify.packetaggregation(False) 56 | mysettings = speedifysettings.get_speedify_settings() 57 | self.assertIn("encryption", mysettings) 58 | self.assertFalse(mysettings["encryption"]) 59 | self.assertFalse(mysettings["packet_aggregation"]) 60 | self.assertIn("transport", mysettings) 61 | self.assertEqual("tcp", mysettings["transport"]) 62 | self.assertIn("jumbo", mysettings) 63 | self.assertTrue(mysettings["jumbo"]) 64 | 65 | def test_set_json(self): 66 | logging.debug("\n\nTesting setting json...") 67 | # lets use a settings string to apply it back 68 | json_string = '{"encryption" : false, "jumbo" : false, "packet_aggregation":false,"transport":"tcp","adapter_priority_wifi" : "backup", "route_default": false}' 69 | self.assertTrue(speedifysettings.apply_speedify_settings(json_string)) 70 | settings = speedify.show_settings() 71 | self.assertFalse(settings["encrypted"]) 72 | self.assertFalse(settings["jumboPackets"]) 73 | self.assertFalse(settings["packetAggregation"]) 74 | self.assertFalse(settings["enableDefaultRoute"]) 75 | self.assertEqual(settings["transportMode"], "tcp") 76 | 77 | def test_bad_json(self): 78 | logging.debug("\n\nTesting bad json...") 79 | # bad setting 80 | logging.disable(logging.ERROR) 81 | json_string = '{"encryption_nonexistant" : true}' 82 | self.assertFalse(speedifysettings.apply_speedify_settings(json_string)) 83 | # wrong data type on boolean 84 | json_string = '{ "jumbo" :"bob", "transport":"auto"}' 85 | self.assertFalse(speedifysettings.apply_speedify_settings(json_string)) 86 | 87 | # nonexistent Priority 88 | json_string = '{"adapter_priority_all" : "frank"}' 89 | self.assertFalse(speedifysettings.apply_speedify_settings(json_string)) 90 | 91 | logging.disable(logging.NOTSET) 92 | 93 | 94 | if __name__ == "__main__": 95 | unittest.main() 96 | speedifysettings.apply_speedify_settings(speedifysettings.speedify_defaults) 97 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # Uses Python 3.7 3 | import logging 4 | import platform 5 | import socket 6 | 7 | # Fastest answer from https://stackoverflow.com/questions/3764291/checking-network-connection 8 | def ping_internet(host="8.8.8.8", port=53, timeout=3): 9 | """ 10 | Host: 8.8.8.8 (google-public-dns-a.google.com) 11 | OpenPort: 53/tcp 12 | Service: domain (DNS/TCP) 13 | """ 14 | try: 15 | socket.setdefaulttimeout(timeout) 16 | socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) 17 | return True 18 | except socket.error as ex: 19 | logging.warning(ex.message) 20 | return False 21 | 22 | 23 | def use_shell(): 24 | 25 | if platform.system().lower() == "darwin": 26 | return False 27 | if platform.system().lower() == "linux": 28 | return False 29 | else: 30 | return True 31 | --------------------------------------------------------------------------------