├── .gitignore ├── .pre-commit-config.yaml ├── README.md ├── poetry.lock ├── pyproject.toml ├── rslapi.py ├── rslctl └── rslctl.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/ambv/black 3 | rev: stable 4 | hooks: 5 | - id: black 6 | language_version: python3.8 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `rslsync-api-client` 2 | 3 | This is an experimental client for controlling Resilio Sync. 4 | It is based on a reverse-engineering of the API used for the Resilio Sync webapp. 5 | 6 | # Features 7 | 8 | * Select and unselect files for sparse checkout 9 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.3" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "main" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "bacon" 11 | version = "0.2.0" 12 | description = "Bacon Game Engine" 13 | category = "main" 14 | optional = false 15 | python-versions = "*" 16 | 17 | [[package]] 18 | name = "beautifulsoup4" 19 | version = "4.8.0" 20 | description = "Screen-scraping library" 21 | category = "main" 22 | optional = false 23 | python-versions = "*" 24 | 25 | [package.dependencies] 26 | soupsieve = ">=1.2" 27 | 28 | [package.extras] 29 | html5lib = ["html5lib"] 30 | lxml = ["lxml"] 31 | 32 | [[package]] 33 | name = "bs4" 34 | version = "0.0.1" 35 | description = "Dummy package for Beautiful Soup" 36 | category = "main" 37 | optional = false 38 | python-versions = "*" 39 | 40 | [package.dependencies] 41 | beautifulsoup4 = "*" 42 | 43 | [[package]] 44 | name = "certifi" 45 | version = "2019.6.16" 46 | description = "Python package for providing Mozilla's CA Bundle." 47 | category = "main" 48 | optional = false 49 | python-versions = "*" 50 | 51 | [[package]] 52 | name = "cfgv" 53 | version = "3.0.0" 54 | description = "Validate configuration and produce human readable error messages." 55 | category = "main" 56 | optional = false 57 | python-versions = ">=3.6" 58 | 59 | [[package]] 60 | name = "chardet" 61 | version = "3.0.4" 62 | description = "Universal encoding detector for Python 2 and 3" 63 | category = "main" 64 | optional = false 65 | python-versions = "*" 66 | 67 | [[package]] 68 | name = "distlib" 69 | version = "0.3.0" 70 | description = "Distribution utilities" 71 | category = "main" 72 | optional = false 73 | python-versions = "*" 74 | 75 | [package.dependencies] 76 | bacon = "<=0.2" 77 | 78 | [[package]] 79 | name = "filelock" 80 | version = "3.0.12" 81 | description = "A platform independent file lock." 82 | category = "main" 83 | optional = false 84 | python-versions = "*" 85 | 86 | [[package]] 87 | name = "identify" 88 | version = "1.4.11" 89 | description = "File identification library for Python" 90 | category = "main" 91 | optional = false 92 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 93 | 94 | [package.extras] 95 | license = ["editdistance"] 96 | 97 | [[package]] 98 | name = "idna" 99 | version = "2.8" 100 | description = "Internationalized Domain Names in Applications (IDNA)" 101 | category = "main" 102 | optional = false 103 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 104 | 105 | [[package]] 106 | name = "importlib-metadata" 107 | version = "1.5.0" 108 | description = "Read metadata from Python packages" 109 | category = "main" 110 | optional = false 111 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 112 | 113 | [package.dependencies] 114 | zipp = ">=0.5" 115 | 116 | [package.extras] 117 | docs = ["sphinx", "rst.linker"] 118 | testing = ["packaging", "importlib-resources"] 119 | 120 | [[package]] 121 | name = "lxml" 122 | version = "4.6.5" 123 | description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." 124 | category = "main" 125 | optional = false 126 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" 127 | 128 | [package.extras] 129 | cssselect = ["cssselect (>=0.7)"] 130 | html5 = ["html5lib"] 131 | htmlsoup = ["beautifulsoup4"] 132 | source = ["Cython (>=0.29.7)"] 133 | 134 | [[package]] 135 | name = "nodeenv" 136 | version = "1.3.5" 137 | description = "Node.js virtual environment builder" 138 | category = "main" 139 | optional = false 140 | python-versions = "*" 141 | 142 | [[package]] 143 | name = "pre-commit" 144 | version = "2.1.0" 145 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 146 | category = "main" 147 | optional = false 148 | python-versions = ">=3.6" 149 | 150 | [package.dependencies] 151 | cfgv = ">=2.0.0" 152 | identify = ">=1.0.0" 153 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 154 | nodeenv = ">=0.11.1" 155 | pyyaml = ">=5.1" 156 | toml = "*" 157 | virtualenv = ">=15.2" 158 | 159 | [[package]] 160 | name = "pyyaml" 161 | version = "5.4" 162 | description = "YAML parser and emitter for Python" 163 | category = "main" 164 | optional = false 165 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 166 | 167 | [[package]] 168 | name = "requests" 169 | version = "2.22.0" 170 | description = "Python HTTP for Humans." 171 | category = "main" 172 | optional = false 173 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 174 | 175 | [package.dependencies] 176 | certifi = ">=2017.4.17" 177 | chardet = ">=3.0.2,<3.1.0" 178 | idna = ">=2.5,<2.9" 179 | urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" 180 | 181 | [package.extras] 182 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] 183 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 184 | 185 | [[package]] 186 | name = "six" 187 | version = "1.14.0" 188 | description = "Python 2 and 3 compatibility utilities" 189 | category = "main" 190 | optional = false 191 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 192 | 193 | [[package]] 194 | name = "soupsieve" 195 | version = "1.9.3" 196 | description = "A modern CSS selector implementation for Beautiful Soup." 197 | category = "main" 198 | optional = false 199 | python-versions = "*" 200 | 201 | [[package]] 202 | name = "toml" 203 | version = "0.10.0" 204 | description = "Python Library for Tom's Obvious, Minimal Language" 205 | category = "main" 206 | optional = false 207 | python-versions = "*" 208 | 209 | [[package]] 210 | name = "urllib3" 211 | version = "1.25.3" 212 | description = "HTTP library with thread-safe connection pooling, file post, and more." 213 | category = "main" 214 | optional = false 215 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" 216 | 217 | [package.extras] 218 | brotli = ["brotlipy (>=0.6.0)"] 219 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 220 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 221 | 222 | [[package]] 223 | name = "virtualenv" 224 | version = "20.0.4" 225 | description = "Virtual Python Environment builder" 226 | category = "main" 227 | optional = false 228 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 229 | 230 | [package.dependencies] 231 | appdirs = ">=1.4.3,<2" 232 | distlib = ">=0.3.0,<1" 233 | filelock = ">=3.0.0,<4" 234 | importlib-metadata = {version = ">=0.12,<2", markers = "python_version < \"3.8\""} 235 | six = ">=1.9.0,<2" 236 | 237 | [package.extras] 238 | docs = ["sphinx (>=2.0.0,<3)", "sphinx-argparse (>=0.2.5,<1)", "sphinx-rtd-theme (>=0.4.3,<1)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2,<1)"] 239 | testing = ["pytest (>=4.0.0,<6)", "coverage (>=4.5.1,<6)", "pytest-mock (>=2.0.0,<3)", "pytest-env (>=0.6.2,<1)", "packaging (>=20.0)", "xonsh (>=0.9.13,<1)"] 240 | 241 | [[package]] 242 | name = "zipp" 243 | version = "3.0.0" 244 | description = "Backport of pathlib-compatible object wrapper for zip files" 245 | category = "main" 246 | optional = false 247 | python-versions = ">=3.6" 248 | 249 | [package.extras] 250 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 251 | testing = ["jaraco.itertools", "func-timeout"] 252 | 253 | [metadata] 254 | lock-version = "1.1" 255 | python-versions = "^3.7" 256 | content-hash = "2788e6f31f28cd02884f42315020443495eaf4445d0deab987e06e186e51eea9" 257 | 258 | [metadata.files] 259 | appdirs = [ 260 | {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, 261 | {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, 262 | ] 263 | bacon = [ 264 | {file = "bacon-0.2.0.zip", hash = "sha256:62bd99edd7a3d3e8fddbc7cd7e164313fc01cad6e84c4a010026aadc3129cc0f"}, 265 | ] 266 | beautifulsoup4 = [ 267 | {file = "beautifulsoup4-4.8.0-py2-none-any.whl", hash = "sha256:05668158c7b85b791c5abde53e50265e16f98ad601c402ba44d70f96c4159612"}, 268 | {file = "beautifulsoup4-4.8.0-py3-none-any.whl", hash = "sha256:f040590be10520f2ea4c2ae8c3dae441c7cfff5308ec9d58a0ec0c1b8f81d469"}, 269 | {file = "beautifulsoup4-4.8.0.tar.gz", hash = "sha256:25288c9e176f354bf277c0a10aa96c782a6a18a17122dba2e8cec4a97e03343b"}, 270 | ] 271 | bs4 = [ 272 | {file = "bs4-0.0.1.tar.gz", hash = "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"}, 273 | ] 274 | certifi = [ 275 | {file = "certifi-2019.6.16-py2.py3-none-any.whl", hash = "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939"}, 276 | {file = "certifi-2019.6.16.tar.gz", hash = "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"}, 277 | ] 278 | cfgv = [ 279 | {file = "cfgv-3.0.0-py2.py3-none-any.whl", hash = "sha256:f22b426ed59cd2ab2b54ff96608d846c33dfb8766a67f0b4a6ce130ce244414f"}, 280 | {file = "cfgv-3.0.0.tar.gz", hash = "sha256:04b093b14ddf9fd4d17c53ebfd55582d27b76ed30050193c14e560770c5360eb"}, 281 | ] 282 | chardet = [ 283 | {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, 284 | {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, 285 | ] 286 | distlib = [ 287 | {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, 288 | ] 289 | filelock = [ 290 | {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, 291 | {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, 292 | ] 293 | identify = [ 294 | {file = "identify-1.4.11-py2.py3-none-any.whl", hash = "sha256:1222b648251bdcb8deb240b294f450fbf704c7984e08baa92507e4ea10b436d5"}, 295 | {file = "identify-1.4.11.tar.gz", hash = "sha256:d824ebe21f38325c771c41b08a95a761db1982f1fc0eee37c6c97df3f1636b96"}, 296 | ] 297 | idna = [ 298 | {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, 299 | {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, 300 | ] 301 | importlib-metadata = [ 302 | {file = "importlib_metadata-1.5.0-py2.py3-none-any.whl", hash = "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b"}, 303 | {file = "importlib_metadata-1.5.0.tar.gz", hash = "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302"}, 304 | ] 305 | lxml = [ 306 | {file = "lxml-4.6.5-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:abcf7daa5ebcc89328326254f6dd6d566adb483d4d00178892afd386ab389de2"}, 307 | {file = "lxml-4.6.5-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3884476a90d415be79adfa4e0e393048630d0d5bcd5757c4c07d8b4b00a1096b"}, 308 | {file = "lxml-4.6.5-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:add017c5bd6b9ec3a5f09248396b6ee2ce61c5621f087eb2269c813cd8813808"}, 309 | {file = "lxml-4.6.5-cp27-cp27m-win32.whl", hash = "sha256:a702005e447d712375433ed0499cb6e1503fadd6c96a47f51d707b4d37b76d3c"}, 310 | {file = "lxml-4.6.5-cp27-cp27m-win_amd64.whl", hash = "sha256:da07c7e7fc9a3f40446b78c54dbba8bfd5c9100dfecb21b65bfe3f57844f5e71"}, 311 | {file = "lxml-4.6.5-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a708c291900c40a7ecf23f1d2384ed0bc0604e24094dd13417c7e7f8f7a50d93"}, 312 | {file = "lxml-4.6.5-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f33d8efb42e4fc2b31b3b4527940b25cdebb3026fb56a80c1c1c11a4271d2352"}, 313 | {file = "lxml-4.6.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:f6befb83bca720b71d6bd6326a3b26e9496ae6649e26585de024890fe50f49b8"}, 314 | {file = "lxml-4.6.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:59d77bfa3bea13caee95bc0d3f1c518b15049b97dd61ea8b3d71ce677a67f808"}, 315 | {file = "lxml-4.6.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:68a851176c931e2b3de6214347b767451243eeed3bea34c172127bbb5bf6c210"}, 316 | {file = "lxml-4.6.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a7790a273225b0c46e5f859c1327f0f659896cc72eaa537d23aa3ad9ff2a1cc1"}, 317 | {file = "lxml-4.6.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6548fc551de15f310dd0564751d9dc3d405278d45ea9b2b369ed1eccf142e1f5"}, 318 | {file = "lxml-4.6.5-cp310-cp310-win32.whl", hash = "sha256:dc8a0dbb2a10ae8bb609584f5c504789f0f3d0d81840da4849102ec84289f952"}, 319 | {file = "lxml-4.6.5-cp310-cp310-win_amd64.whl", hash = "sha256:1ccbfe5d17835db906f2bab6f15b34194db1a5b07929cba3cf45a96dbfbfefc0"}, 320 | {file = "lxml-4.6.5-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca9a40497f7e97a2a961c04fa8a6f23d790b0521350a8b455759d786b0bcb203"}, 321 | {file = "lxml-4.6.5-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e5b4b0d9440046ead3bd425eb2b852499241ee0cef1ae151038e4f87ede888c4"}, 322 | {file = "lxml-4.6.5-cp35-cp35m-win32.whl", hash = "sha256:87f8f7df70b90fbe7b49969f07b347e3f978f8bd1046bb8ecae659921869202b"}, 323 | {file = "lxml-4.6.5-cp35-cp35m-win_amd64.whl", hash = "sha256:ce52aad32ec6e46d1a91ff8b8014a91538800dd533914bfc4a82f5018d971408"}, 324 | {file = "lxml-4.6.5-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:8021eeff7fabde21b9858ed058a8250ad230cede91764d598c2466b0ba70db8b"}, 325 | {file = "lxml-4.6.5-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:cab343b265e38d4e00649cbbad9278b734c5715f9bcbb72c85a1f99b1a58e19a"}, 326 | {file = "lxml-4.6.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3534d7c468c044f6aef3c0aff541db2826986a29ea73f2ca831f5d5284d9b570"}, 327 | {file = "lxml-4.6.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdb98f4c9e8a1735efddfaa995b0c96559792da15d56b76428bdfc29f77c4cdb"}, 328 | {file = "lxml-4.6.5-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:5ea121cb66d7e5cb396b4c3ca90471252b94e01809805cfe3e4e44be2db3a99c"}, 329 | {file = "lxml-4.6.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:121fc6f71c692b49af6c963b84ab7084402624ffbe605287da362f8af0668ea3"}, 330 | {file = "lxml-4.6.5-cp36-cp36m-win32.whl", hash = "sha256:1a2a7659b8eb93c6daee350a0d844994d49245a0f6c05c747f619386fb90ba04"}, 331 | {file = "lxml-4.6.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2f77556266a8fe5428b8759fbfc4bd70be1d1d9c9b25d2a414f6a0c0b0f09120"}, 332 | {file = "lxml-4.6.5-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:558485218ee06458643b929765ac1eb04519ca3d1e2dcc288517de864c747c33"}, 333 | {file = "lxml-4.6.5-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ba0006799f21d83c3717fe20e2707a10bbc296475155aadf4f5850f6659b96b9"}, 334 | {file = "lxml-4.6.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:916d457ad84e05b7db52700bad0a15c56e0c3000dcaf1263b2fb7a56fe148996"}, 335 | {file = "lxml-4.6.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c580c2a61d8297a6e47f4d01f066517dbb019be98032880d19ece7f337a9401d"}, 336 | {file = "lxml-4.6.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a21b78af7e2e13bec6bea12fc33bc05730197674f3e5402ce214d07026ccfebd"}, 337 | {file = "lxml-4.6.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:46515773570a33eae13e451c8fcf440222ef24bd3b26f40774dd0bd8b6db15b2"}, 338 | {file = "lxml-4.6.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:124f09614f999551ac65e5b9875981ce4b66ac4b8e2ba9284572f741935df3d9"}, 339 | {file = "lxml-4.6.5-cp37-cp37m-win32.whl", hash = "sha256:b4015baed99d046c760f09a4c59d234d8f398a454380c3cf0b859aba97136090"}, 340 | {file = "lxml-4.6.5-cp37-cp37m-win_amd64.whl", hash = "sha256:12ae2339d32a2b15010972e1e2467345b7bf962e155671239fba74c229564b7f"}, 341 | {file = "lxml-4.6.5-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:76b6c296e4f7a1a8a128aec42d128646897f9ae9a700ef6839cdc9b3900db9b5"}, 342 | {file = "lxml-4.6.5-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:534032a5ceb34bba1da193b7d386ac575127cc39338379f39a164b10d97ade89"}, 343 | {file = "lxml-4.6.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:60aeb14ff9022d2687ef98ce55f6342944c40d00916452bb90899a191802137a"}, 344 | {file = "lxml-4.6.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9801bcd52ac9c795a7d81ea67471a42cffe532e46cfb750cd5713befc5c019c0"}, 345 | {file = "lxml-4.6.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b95fb7e6f9c2f53db88f4642231fc2b8907d854e614710996a96f1f32018d5c"}, 346 | {file = "lxml-4.6.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:642eb4cabd997c9b949a994f9643cd8ae00cf4ca8c5cd9c273962296fadf1c44"}, 347 | {file = "lxml-4.6.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af4139172ff0263d269abdcc641e944c9de4b5d660894a3ec7e9f9db63b56ac9"}, 348 | {file = "lxml-4.6.5-cp38-cp38-win32.whl", hash = "sha256:57cf05466917e08f90e323f025b96f493f92c0344694f5702579ab4b7e2eb10d"}, 349 | {file = "lxml-4.6.5-cp38-cp38-win_amd64.whl", hash = "sha256:4f415624cf8b065796649a5e4621773dc5c9ea574a944c76a7f8a6d3d2906b41"}, 350 | {file = "lxml-4.6.5-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7679bb6e4d9a3978a46ab19a3560e8d2b7265ef3c88152e7fdc130d649789887"}, 351 | {file = "lxml-4.6.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c34234a1bc9e466c104372af74d11a9f98338a3f72fae22b80485171a64e0144"}, 352 | {file = "lxml-4.6.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4b9390bf973e3907d967b75be199cf1978ca8443183cf1e78ad80ad8be9cf242"}, 353 | {file = "lxml-4.6.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fcc849b28f584ed1dbf277291ded5c32bb3476a37032df4a1d523b55faa5f944"}, 354 | {file = "lxml-4.6.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:46f21f2600d001af10e847df9eb3b832e8a439f696c04891bcb8a8cedd859af9"}, 355 | {file = "lxml-4.6.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:99cf827f5a783038eb313beee6533dddb8bdb086d7269c5c144c1c952d142ace"}, 356 | {file = "lxml-4.6.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:925174cafb0f1179a7fd38da90302555d7445e34c9ece68019e53c946be7f542"}, 357 | {file = "lxml-4.6.5-cp39-cp39-win32.whl", hash = "sha256:12d8d6fe3ddef629ac1349fa89a638b296a34b6529573f5055d1cb4e5245f73b"}, 358 | {file = "lxml-4.6.5-cp39-cp39-win_amd64.whl", hash = "sha256:a52e8f317336a44836475e9c802f51c2dc38d612eaa76532cb1d17690338b63b"}, 359 | {file = "lxml-4.6.5-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:11ae552a78612620afd15625be9f1b82e3cc2e634f90d6b11709b10a100cba59"}, 360 | {file = "lxml-4.6.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:473701599665d874919d05bb33b56180447b3a9da8d52d6d9799f381ce23f95c"}, 361 | {file = "lxml-4.6.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7f00cc64b49d2ef19ddae898a3def9dd8fda9c3d27c8a174c2889ee757918e71"}, 362 | {file = "lxml-4.6.5-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:73e8614258404b2689a26cb5d002512b8bc4dfa18aca86382f68f959aee9b0c8"}, 363 | {file = "lxml-4.6.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ff44de36772b05c2eb74f2b4b6d1ae29b8f41ed5506310ce1258d44826ee38c1"}, 364 | {file = "lxml-4.6.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5d5254c815c186744c8f922e2ce861a2bdeabc06520b4b30b2f7d9767791ce6e"}, 365 | {file = "lxml-4.6.5.tar.gz", hash = "sha256:6e84edecc3a82f90d44ddee2ee2a2630d4994b8471816e226d2b771cda7ac4ca"}, 366 | ] 367 | nodeenv = [ 368 | {file = "nodeenv-1.3.5-py2.py3-none-any.whl", hash = "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"}, 369 | {file = "nodeenv-1.3.5.tar.gz", hash = "sha256:7389d06a7ea50c80ca51eda1b185db7b9ec38af1304d12d8b8299d6218486e91"}, 370 | ] 371 | pre-commit = [ 372 | {file = "pre_commit-2.1.0-py2.py3-none-any.whl", hash = "sha256:5387b53bb84ad9abc9b0845775dddd4e3243fd64cdcddaa6db28d3da6fbf06c2"}, 373 | {file = "pre_commit-2.1.0.tar.gz", hash = "sha256:5295fb6d652a6c5e0b4636cd2c73183efdf253d45b657ce7367183134e806fe1"}, 374 | ] 375 | pyyaml = [ 376 | {file = "PyYAML-5.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f7a21e3d99aa3095ef0553e7ceba36fb693998fbb1226f1392ce33681047465f"}, 377 | {file = "PyYAML-5.4-cp27-cp27m-win32.whl", hash = "sha256:52bf0930903818e600ae6c2901f748bc4869c0c406056f679ab9614e5d21a166"}, 378 | {file = "PyYAML-5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:a36a48a51e5471513a5aea920cdad84cbd56d70a5057cca3499a637496ea379c"}, 379 | {file = "PyYAML-5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:5e7ac4e0e79a53451dc2814f6876c2fa6f71452de1498bbe29c0b54b69a986f4"}, 380 | {file = "PyYAML-5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc552b6434b90d9dbed6a4f13339625dc466fd82597119897e9489c953acbc22"}, 381 | {file = "PyYAML-5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0dc9f2eb2e3c97640928dec63fd8dc1dd91e6b6ed236bd5ac00332b99b5c2ff9"}, 382 | {file = "PyYAML-5.4-cp36-cp36m-win32.whl", hash = "sha256:5a3f345acff76cad4aa9cb171ee76c590f37394186325d53d1aa25318b0d4a09"}, 383 | {file = "PyYAML-5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:f3790156c606299ff499ec44db422f66f05a7363b39eb9d5b064f17bd7d7c47b"}, 384 | {file = "PyYAML-5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:124fd7c7bc1e95b1eafc60825f2daf67c73ce7b33f1194731240d24b0d1bf628"}, 385 | {file = "PyYAML-5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8b818b6c5a920cbe4203b5a6b14256f0e5244338244560da89b7b0f1313ea4b6"}, 386 | {file = "PyYAML-5.4-cp37-cp37m-win32.whl", hash = "sha256:737bd70e454a284d456aa1fa71a0b429dd527bcbf52c5c33f7c8eee81ac16b89"}, 387 | {file = "PyYAML-5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:7242790ab6c20316b8e7bb545be48d7ed36e26bbe279fd56f2c4a12510e60b4b"}, 388 | {file = "PyYAML-5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cc547d3ead3754712223abb7b403f0a184e4c3eae18c9bb7fd15adef1597cc4b"}, 389 | {file = "PyYAML-5.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8635d53223b1f561b081ff4adecb828fd484b8efffe542edcfdff471997f7c39"}, 390 | {file = "PyYAML-5.4-cp38-cp38-win32.whl", hash = "sha256:26fcb33776857f4072601502d93e1a619f166c9c00befb52826e7b774efaa9db"}, 391 | {file = "PyYAML-5.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2243dd033fd02c01212ad5c601dafb44fbb293065f430b0d3dbf03f3254d615"}, 392 | {file = "PyYAML-5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:31ba07c54ef4a897758563e3a0fcc60077698df10180abe4b8165d9895c00ebf"}, 393 | {file = "PyYAML-5.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:02c78d77281d8f8d07a255e57abdbf43b02257f59f50cc6b636937d68efa5dd0"}, 394 | {file = "PyYAML-5.4-cp39-cp39-win32.whl", hash = "sha256:fdc6b2cb4b19e431994f25a9160695cc59a4e861710cc6fc97161c5e845fc579"}, 395 | {file = "PyYAML-5.4-cp39-cp39-win_amd64.whl", hash = "sha256:8bf38641b4713d77da19e91f8b5296b832e4db87338d6aeffe422d42f1ca896d"}, 396 | {file = "PyYAML-5.4.tar.gz", hash = "sha256:3c49e39ac034fd64fd576d63bb4db53cda89b362768a67f07749d55f128ac18a"}, 397 | ] 398 | requests = [ 399 | {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, 400 | {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, 401 | ] 402 | six = [ 403 | {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, 404 | {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, 405 | ] 406 | soupsieve = [ 407 | {file = "soupsieve-1.9.3-py2.py3-none-any.whl", hash = "sha256:a5a6166b4767725fd52ae55fee8c8b6137d9a51e9f1edea461a062a759160118"}, 408 | {file = "soupsieve-1.9.3.tar.gz", hash = "sha256:8662843366b8d8779dec4e2f921bebec9afd856a5ff2e82cd419acc5054a1a92"}, 409 | ] 410 | toml = [ 411 | {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, 412 | {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, 413 | {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, 414 | ] 415 | urllib3 = [ 416 | {file = "urllib3-1.25.3-py2.py3-none-any.whl", hash = "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1"}, 417 | {file = "urllib3-1.25.3.tar.gz", hash = "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"}, 418 | ] 419 | virtualenv = [ 420 | {file = "virtualenv-20.0.4-py2.py3-none-any.whl", hash = "sha256:de2cbdd5926c48d7b84e0300dea9e8f276f61d186e8e49223d71d91250fbaebd"}, 421 | {file = "virtualenv-20.0.4.tar.gz", hash = "sha256:08f3623597ce73b85d6854fb26608a6f39ee9d055c81178dc6583803797f8994"}, 422 | ] 423 | zipp = [ 424 | {file = "zipp-3.0.0-py3-none-any.whl", hash = "sha256:12248a63bbdf7548f89cb4c7cda4681e537031eda29c02ea29674bc6854460c2"}, 425 | {file = "zipp-3.0.0.tar.gz", hash = "sha256:7c0f8e91abc0dc07a5068f315c52cb30c66bfbc581e5b50704c8a2f6ebae794a"}, 426 | ] 427 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "resilio-sync-cli" 3 | version = "0.1.0" 4 | description = "A command line interface for Resilio Sync" 5 | authors = ["PythonNut "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.7" 10 | requests = "^2.22" 11 | lxml = "^4.6" 12 | bs4 = "^0.0.1" 13 | appdirs = "^1.4" 14 | pre-commit = "^2.1.0" 15 | 16 | [tool.poetry.dev-dependencies] 17 | 18 | [build-system] 19 | requires = ["poetry>=0.12"] 20 | build-backend = "poetry.masonry.api" 21 | -------------------------------------------------------------------------------- /rslapi.py: -------------------------------------------------------------------------------- 1 | import getpass 2 | import requests 3 | import urllib.parse 4 | from datetime import datetime 5 | import bs4 6 | from pathlib import Path 7 | 8 | requests.packages.urllib3.disable_warnings() 9 | 10 | 11 | class ResilioSyncFolder(object): 12 | def __init__(self, folder_obj): 13 | self.data = folder_obj 14 | 15 | # first the straightforward copies 16 | self.name = folder_obj["name"] 17 | self.folderid = folder_obj["folderid"] 18 | 19 | # now for more complicated things 20 | self.path = None 21 | if "path" in folder_obj: 22 | self.path = Path(folder_obj["path"]).resolve() 23 | 24 | self.synclevel = folder_obj["synclevel"] 25 | self.selected = self.synclevel > 0 26 | 27 | def __repr__(self): 28 | return f'' 29 | 30 | 31 | class ResilioSyncClient(object): 32 | def __init__(self, host, port, username, password): 33 | self.host, self.port = host, port 34 | self.api_url = f"http://{self.host}:{self.port}" 35 | self.username, self.password = username, password 36 | 37 | self.session = requests.Session() 38 | self.session.auth = (self.username, self.password) 39 | self.session.timeout = 1 40 | self.session.verify = False 41 | 42 | self.token = self.get_token() 43 | 44 | def format_timestamp(self): 45 | dt = datetime.utcnow() - datetime(1970, 1, 1) 46 | ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 47 | return int(ms) 48 | 49 | def normalize_path(self, path): 50 | return Path(path).absolute().resolve() 51 | 52 | def get_token(self): 53 | token_url = urllib.parse.urljoin(self.api_url, "gui/token.html") 54 | response = self.session.get( 55 | token_url, params={"t": self.format_timestamp()}, timeout=5 56 | ) 57 | 58 | soup = bs4.BeautifulSoup(response.content, "lxml") 59 | token_divs = soup.select("#token") 60 | token = token_divs[0].decode_contents() 61 | return token 62 | 63 | def get_generic(self, params): 64 | response = self.session.get( 65 | urllib.parse.urljoin(self.api_url, "gui/"), 66 | params={"token": self.token, **params, "t": self.format_timestamp()}, 67 | timeout=5, 68 | ) 69 | 70 | return response.json() 71 | 72 | def get_folders(self): 73 | json = self.get_generic({"action": "getsyncfolders", "discovery": 1}) 74 | assert json["status"] == 200 75 | return [ResilioSyncFolder(obj) for obj in json["folders"]] 76 | 77 | def get_folder_by_path(self, rel_path): 78 | abs_path = self.normalize_path(rel_path) 79 | folders = self.get_folders() 80 | containing_folders = [] 81 | for folder in folders: 82 | if not folder.selected: 83 | continue 84 | if folder.path == abs_path or folder.path in abs_path.parents: 85 | containing_folders.append(folder) 86 | 87 | assert len(containing_folders) <= 1 88 | if not containing_folders: 89 | raise ValueError("Path is not contained in a synced folder.") 90 | 91 | return containing_folders[0] 92 | 93 | def file_exists(self, folder, path): 94 | assert folder.selected 95 | path = Path(path) 96 | if not path.is_absolute(): 97 | path = folder.path / path 98 | 99 | abs_path = self.normalize_path(path) 100 | base_path = Path(".") 101 | 102 | while True: 103 | path_str = str(base_path) 104 | if base_path == Path("."): 105 | path_str = "" 106 | 107 | result = self.get_generic( 108 | { 109 | "action": "getfileslist", 110 | "folderid": folder.folderid, 111 | "path": path_str, 112 | } 113 | ) 114 | 115 | assert result["status"] == 200 116 | files = result["value"]["files"] 117 | for f in files: 118 | test_path = folder.path / base_path / f["name"] 119 | if test_path == abs_path: 120 | return True 121 | 122 | if test_path in abs_path.parents: 123 | base_path /= f["name"] 124 | break 125 | else: 126 | return False 127 | 128 | def set_sync_status(self, rel_path, sync=True): 129 | # First, track down the parent folder, if it exists 130 | abs_path = self.normalize_path(rel_path) 131 | folder = self.get_folder_by_path(abs_path) 132 | child_rel_path = abs_path.relative_to(folder.path) 133 | 134 | # Now, check if the folder has our file 135 | if not self.file_exists(folder, child_rel_path): 136 | raise ValueError("Path does not exist in the folder!") 137 | 138 | self.get_generic( 139 | { 140 | "action": "setfilemode", 141 | "folderid": folder.folderid, 142 | "path": child_rel_path, 143 | "selected": "true" if sync else "false", 144 | "removefromall": "false", 145 | } 146 | ) 147 | -------------------------------------------------------------------------------- /rslctl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import os 4 | import subprocess 5 | from pathlib import Path 6 | 7 | subprocess.run( 8 | ["poetry", "run", "python", "rslctl.py", *sys.argv[1:]], 9 | cwd=Path(__file__).resolve().parent, 10 | env=dict(os.environ, CWD=str(Path.cwd())), 11 | ) 12 | -------------------------------------------------------------------------------- /rslctl.py: -------------------------------------------------------------------------------- 1 | import appdirs 2 | import argparse 3 | import os 4 | 5 | import base64 6 | import shelve 7 | import getpass 8 | import json 9 | 10 | from pathlib import Path 11 | import rslapi 12 | 13 | 14 | def main(args): 15 | conf_root = Path(appdirs.user_config_dir("rslctl", "PythonNut")) 16 | conf_file_path = conf_root / "config.json" 17 | conf_root.mkdir(exist_ok=True) 18 | 19 | cwd = Path(os.environ["CWD"]) 20 | 21 | if not conf_file_path.exists(): 22 | host = input("host [localhost]: ") or "localhost" 23 | port = input("port [8888]: ") or 8888 24 | username = input("username: ") 25 | password = base64.b64encode( 26 | getpass.getpass("password: ").encode("utf-8") 27 | ).decode("utf-8") 28 | with open(conf_file_path, "w") as f: 29 | json.dump( 30 | { 31 | "host": host, 32 | "port": port, 33 | "username": username, 34 | "password": password, 35 | }, 36 | f, 37 | ) 38 | 39 | with open(conf_file_path) as f: 40 | config = json.load(f) 41 | 42 | api = rslapi.ResilioSyncClient( 43 | host=config["host"], 44 | port=config["port"], 45 | username=config["username"], 46 | password=base64.b64decode(config["password"]).decode("utf-8"), 47 | ) 48 | 49 | if args.parser == "parser_select": 50 | for f in args.file: 51 | api.set_sync_status(cwd / f, True) 52 | elif args.parser == "parser_unselect": 53 | for f in args.file: 54 | api.set_sync_status(cwd / f, False) 55 | 56 | 57 | if __name__ == "__main__": 58 | parser = argparse.ArgumentParser(description="Control Resilio Sync.") 59 | subparsers = parser.add_subparsers(help="subcommands") 60 | 61 | parser_select = subparsers.add_parser("select", help="select a file") 62 | parser_select.add_argument("file", type=str, nargs="+", help="file to select") 63 | parser_select.set_defaults(parser="parser_select") 64 | 65 | parser_unselect = subparsers.add_parser("unselect", help="unselect a file") 66 | parser_unselect.add_argument("file", type=str, nargs="+", help="file to select") 67 | parser_unselect.set_defaults(parser="parser_unselect") 68 | 69 | args = parser.parse_args() 70 | main(args) 71 | --------------------------------------------------------------------------------