32 Commits

Author SHA1 Message Date
Rai
45485d4dbf [docs] update registration URL, see https://github.com/subutux/rmapy/issues/28 (#33) 2022-12-06 08:54:11 +01:00
Stijn Van Campenhout
8916261afd bump version 2021-07-23 10:36:50 +02:00
Stijn Van Campenhout
081db1ddc2 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	rmapy/const.py
2021-07-23 10:35:59 +02:00
Stijn Van Campenhout
5c78598271 fix: auth url. See #25 2021-07-23 10:34:17 +02:00
Stijn Van Campenhout
42430b0eb8 🚀 bump version to 0.3.0 2021-07-11 11:38:42 +02:00
Samy Abidib
934e270c6d feat: Add highlight support (#22)
* Add support for downloading highlights
* Add to docs
* Respond to feedback
* Add doc line
* Bug and doc fix

 Thanks @sabidib!
2021-07-11 11:37:20 +02:00
Stijn Van Campenhout
8b120202af fix: fix: CVE-2021-33503 2021-07-11 11:33:54 +02:00
Stijn Van Campenhout
5595ade375 fix: CVE-2021-33503 2021-07-11 11:26:30 +02:00
Stijn Van Campenhout
18865083d7 Merge pull request #21
ZipDocument class vars should be instance vars
2021-05-30 13:27:32 +02:00
Stijn Van Campenhout
10a27bcdb2 Bump version 2021-05-30 15:01:13 +02:00
Stijn Van Campenhout
011cb2f221 Merge pull request #24
Updated the API URL to new domain name
2021-05-30 12:56:16 +02:00
Dan Corne
7e2a3033e0 Updated the API URL to new domain name
This has changed recently and the old domain no longer works.
2021-05-20 19:20:04 +01:00
Samy Abidib
fbc75858d7 ZipDocument class vars should be instance vars 2021-05-11 11:53:32 -04:00
Stijn Van Campenhout
50fef896d3 bump version 2021-03-21 11:14:22 +01:00
dependabot[bot]
a0965d58b3 Bump jinja2 from 2.10.1 to 2.11.3 (#16)
Bumps [jinja2](https://github.com/pallets/jinja) from 2.10.1 to 2.11.3.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/master/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/2.10.1...2.11.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-21 11:06:17 +01:00
Reece Mathews
544bd11dd1 Ignore missing files when used with Remarkable 2 (#14)
* Ignore missing thumbnails during dump

* Ignore missing metadata file during load
2021-03-21 11:05:31 +01:00
Stijn Van Campenhout
b5f0367dbb Updated version number 2021-01-30 18:02:43 +01:00
Stijn Van Campenhout
bc36d7b7c6 Updated to latest release 2021-01-30 17:50:49 +01:00
Adeel Khan
15d8090e79 doc: change refresh to renew (#11) 2021-01-30 14:46:21 +01:00
Jacob Kaplan-Moss
8bb29e3bd7 fix(download): Ignore missing thumbnail files (#9)
* Ignore missing thumbnail files

* Change logger name
2021-01-30 14:35:32 +01:00
Stijn Van Campenhout
29d113f3af Added release workflow 2020-11-13 08:16:08 +01:00
ruebeckscube
433eb8e722 Change Collection.items to instance variable (#8)
fix: Change Collection.items to instance variable
2020-11-13 08:10:39 +01:00
Stijn Van Campenhout
7ceac42fdb Merge branch 'bsdz-patch-1' 2020-11-12 16:31:17 +01:00
Stijn Van Campenhout
f5e4972775 Merge branch 'patch-1' of https://github.com/bsdz/rmapy into bsdz-patch-1 2020-11-12 16:30:37 +01:00
Stijn Van Campenhout
cca7ea05fd Merge pull request #4 from jodergrosse/correction_quickstart_doc
Closes #3
2020-11-11 18:13:57 +01:00
Stijn Van Campenhout
48239c9b9f Merge pull request #2 from jodergrosse/bugfix/register_new_device
fix: #1
2020-11-11 18:11:49 +01:00
Blair Azzopardi
3554640693 Correct typo "Succes" -> "Success" 2020-06-20 23:54:38 +01:00
Johannes Gross
f20916bd34 replace wrong function name 2020-03-07 22:18:55 +01:00
Johannes Gross
7d70d58f6e fix:
body was passed to register_device as argument 'data'
2020-03-07 22:05:52 +01:00
Stijn Van Campenhout
506b34d40a Code cleanup & Refactoring 2019-12-26 15:16:30 +01:00
Stijn Van Campenhout
3f0e0e36a2 Rename rmapi rmapy 2019-12-19 14:25:26 +01:00
Stijn Van Campenhout
00dc50550f Rename rmapi rmapy 2019-12-19 13:42:26 +01:00
23 changed files with 511 additions and 398 deletions

31
.github/workflows/python-publish.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
# This workflows will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
name: Upload Python Package
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*

View File

@@ -9,6 +9,7 @@ verify_ssl = true
requests = "*" requests = "*"
pyaml = "*" pyaml = "*"
sphinx = "*" sphinx = "*"
urllib3 = ">=1.26.5"
[requires] [requires]
python_version = "3.7" python_version = "3.7"

268
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "880b0f4dc28652bfa8674e7ebeaae37eb25ec47d136a03791bcef5b6f7a5518d" "sha256": "f7fb5bcb7b1ad271a4b43ccd956c97f6d6a530869b7d9b8454aaf4defcadf09f"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -23,227 +23,253 @@
], ],
"version": "==0.7.12" "version": "==0.7.12"
}, },
"attrs": {
"hashes": [
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
],
"version": "==19.1.0"
},
"babel": { "babel": {
"hashes": [ "hashes": [
"sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9",
"sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28" "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"
], ],
"version": "==2.7.0" "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.9.1"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
"sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
], ],
"version": "==2019.9.11" "version": "==2021.5.30"
}, },
"chardet": { "chardet": {
"hashes": [ "hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
], ],
"version": "==3.0.4" "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0"
}, },
"docutils": { "docutils": {
"hashes": [ "hashes": [
"sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125",
"sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"
"sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"
], ],
"version": "==0.15.2" "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.17.1"
}, },
"idna": { "idna": {
"hashes": [ "hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
], ],
"version": "==2.8" "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10"
}, },
"imagesize": { "imagesize": {
"hashes": [ "hashes": [
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
], ],
"version": "==1.1.0" "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.2.0"
}, },
"jinja2": { "jinja2": {
"hashes": [ "hashes": [
"sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4",
"sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"
], ],
"version": "==2.10.1" "markers": "python_version >= '3.6'",
"version": "==3.0.1"
}, },
"markupsafe": { "markupsafe": {
"hashes": [ "hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
"sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
"sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
"sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
"sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
], ],
"version": "==1.1.1" "markers": "python_version >= '3.6'",
"version": "==2.0.1"
}, },
"packaging": { "packaging": {
"hashes": [ "hashes": [
"sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
"sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
], ],
"version": "==19.1" "markers": "python_version >= '3.6'",
"version": "==21.0"
}, },
"pyaml": { "pyaml": {
"hashes": [ "hashes": [
"sha256:a2dcbc4a8bb00b541efd1c5a064d93474d4f41ded1484fbb08bec9d236523931", "sha256:29a5c2a68660a799103d6949167bd6c7953d031449d08802386372de1db6ad71",
"sha256:c79ae98ececda136a034115ca178ee8bf3aa7df236c488c2f55d12f177b88f1e" "sha256:67081749a82b72c45e5f7f812ee3a14a03b3f5c25ff36ec3b290514f8c4c4b99"
], ],
"index": "pypi", "index": "pypi",
"version": "==19.4.1" "version": "==20.4.0"
}, },
"pygments": { "pygments": {
"hashes": [ "hashes": [
"sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
"sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
], ],
"version": "==2.4.2" "markers": "python_version >= '3.5'",
"version": "==2.9.0"
}, },
"pyparsing": { "pyparsing": {
"hashes": [ "hashes": [
"sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
], ],
"version": "==2.4.2" "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
}, },
"pytz": { "pytz": {
"hashes": [ "hashes": [
"sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
"sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
], ],
"version": "==2019.2" "version": "==2021.1"
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
"sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696",
"sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393",
"sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77",
"sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922",
"sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5",
"sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8",
"sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10",
"sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc",
"sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
"sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
"sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
"sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8" "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
"sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
"sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
"sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
], ],
"version": "==5.1.2" "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==5.4.1"
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.22.0" "version": "==2.25.1"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
],
"version": "==1.12.0"
}, },
"snowballstemmer": { "snowballstemmer": {
"hashes": [ "hashes": [
"sha256:713e53b79cbcf97bc5245a06080a33d54a77e7cce2f789c835a143bcdb5c033e" "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2",
"sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"
], ],
"version": "==1.9.1" "version": "==2.1.0"
}, },
"sphinx": { "sphinx": {
"hashes": [ "hashes": [
"sha256:0d586b0f8c2fc3cc6559c5e8fd6124628110514fda0e5d7c82e682d749d2e845", "sha256:5747f3c855028076fcff1e4df5e75e07c836f0ac11f7df886747231092cfe4ad",
"sha256:839a3ed6f6b092bb60f492024489cc9e6991360fb9f52ed6361acd510d261069" "sha256:dff357e6a208eb7edb2002714733ac21a9fe597e73609ff417ab8cf0c6b4fbb8"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.2.0" "version": "==4.0.3"
}, },
"sphinxcontrib-applehelp": { "sphinxcontrib-applehelp": {
"hashes": [ "hashes": [
"sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
"sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d" "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
], ],
"version": "==1.0.1" "markers": "python_version >= '3.5'",
"version": "==1.0.2"
}, },
"sphinxcontrib-devhelp": { "sphinxcontrib-devhelp": {
"hashes": [ "hashes": [
"sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
"sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981" "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
], ],
"version": "==1.0.1" "markers": "python_version >= '3.5'",
"version": "==1.0.2"
}, },
"sphinxcontrib-htmlhelp": { "sphinxcontrib-htmlhelp": {
"hashes": [ "hashes": [
"sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07",
"sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7" "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"
], ],
"version": "==1.0.2" "markers": "python_version >= '3.6'",
"version": "==2.0.0"
}, },
"sphinxcontrib-jsmath": { "sphinxcontrib-jsmath": {
"hashes": [ "hashes": [
"sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
"sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
], ],
"markers": "python_version >= '3.5'",
"version": "==1.0.1" "version": "==1.0.1"
}, },
"sphinxcontrib-qthelp": { "sphinxcontrib-qthelp": {
"hashes": [ "hashes": [
"sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
"sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f" "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
], ],
"version": "==1.0.2" "markers": "python_version >= '3.5'",
"version": "==1.0.3"
}, },
"sphinxcontrib-serializinghtml": { "sphinxcontrib-serializinghtml": {
"hashes": [ "hashes": [
"sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd",
"sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768" "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"
], ],
"version": "==1.1.3" "markers": "python_version >= '3.5'",
"version": "==1.1.5"
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
"sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
], ],
"version": "==1.25.3" "index": "pypi",
"version": "==1.26.6"
} }
}, },
"develop": {} "develop": {}

View File

@@ -1,4 +1,4 @@
# rMapi # rMapy
This is an (unofficial) Remarkable Cloud API Client written in Python. This is an (unofficial) Remarkable Cloud API Client written in Python.
@@ -14,4 +14,3 @@ This is an (unofficial) Remarkable Cloud API Client written in Python.
* ❎ cli interface * ❎ cli interface
* ❎ export pdf with annotations * ❎ export pdf with annotations

View File

@@ -18,7 +18,7 @@ sys.path.insert(0, os.path.abspath('../..'))
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = 'rmapi' project = 'rmapy'
copyright = '2019, Stijn Van Campenhout' copyright = '2019, Stijn Van Campenhout'
author = 'Stijn Van Campenhout' author = 'Stijn Van Campenhout'

View File

@@ -1,9 +1,9 @@
.. rmapi documentation master file, created by .. rmapy documentation master file, created by
sphinx-quickstart on Tue Sep 17 19:24:29 2019. sphinx-quickstart on Tue Sep 17 19:24:29 2019.
You can adapt this file completely to your liking, but it should at least You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive. contain the root `toctree` directive.
Welcome to rmapi's documentation! Welcome to rmapy's documentation!
================================= =================================
This is an (unofficial) Remarkable Cloud API Client written in Python. This is an (unofficial) Remarkable Cloud API Client written in Python.
@@ -50,9 +50,9 @@ API Documentation
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
rmapi rmapy
.. automodule:: rmapi .. automodule:: rmapy
Indices and tables Indices and tables

View File

@@ -1,11 +1,11 @@
Pip Pip
=== ===
Like any other package, you can install rmapi using pip: Like any other package, you can install rmapy using pip:
.. code-block:: bash .. code-block:: bash
pip install rmapi pip install rmapy

View File

@@ -1,7 +1,7 @@
rmapi rmapy
===== =====
.. toctree:: .. toctree::
:maxdepth: 4 :maxdepth: 4
rmapi rmapy

View File

@@ -1,4 +1,4 @@
quickstart quick start
========== ==========
If you previously used the go package `rmapi`_ ,the keys for authorization If you previously used the go package `rmapi`_ ,the keys for authorization
@@ -7,12 +7,12 @@ are re-used because we use the same storage location & format.
If not, you'll need to register the client as a new device on `my remarkable`_. If not, you'll need to register the client as a new device on `my remarkable`_.
.. _my remarkable: https://my.remarkable.com/connect/remarkable .. _my remarkable: https://my.remarkable.com/device/desktop/connect
.. _rmapi: https://github.com/juruen/rmapi .. _rmapi: https://github.com/juruen/rmapi
Registering the API CLient Registering the API Client
~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
Registering the device is easy. Go to `my remarkable`_ to register a new device Registering the device is easy. Go to `my remarkable`_ to register a new device
@@ -22,20 +22,21 @@ and use the code you see on the webpage
:linenos: :linenos:
from rmapi.api import Client from rmapy.api import Client
rmapi = Client() rmapy = Client()
# Shoud return False # Should return False
rmapi.is_authenticated() rmapy.is_auth()
# This registers the client as a new device. The received device token is # This registers the client as a new device. The received device token is
# stored in the users directory in the file ~/.rmapi, the same as with the # stored in the users directory in the file ~/.rmapi, the same as with the
# go rmapi client. # go rmapi client.
rmapi.register_device("fkgzzklrs") # Get a token at https://my.remarkable.com/device/desktop/connect.
# It's always a good idea to refresh the user token everytime you start rmapy.register_device("fkgzzklrs")
# It's always a good idea to renew the user token every time you start
# a new session. # a new session.
rmapi.refresh_token() rmapy.renew_token()
# Shoud return True # Should return True
rmapi.is_authenticated() rmapy.is_auth()
Working with items Working with items
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@@ -50,21 +51,21 @@ We can list the items in the Cloud
.. code-block:: python .. code-block:: python
:linenos: :linenos:
>>> from rmapi.api import Client >>> from rmapy.api import Client
>>> rmapi = Client() >>> rmapy = Client()
>>> rmapi.renew_token() >>> rmapy.renew_token()
True True
>>> collection = rmapi.get_meta_items() >>> collection = rmapy.get_meta_items()
>>> collection >>> collection
<rmapi.collections.Collection object at 0x7fa1982d7e90> <rmapy.collections.Collection object at 0x7fa1982d7e90>
>>> len(collection) >>> len(collection)
181 181
>>> # Count the amount of documents >>> # Count the amount of documents
... from rmapi.document import Document ... from rmapy.document import Document
>>> len([f for f in collection if isinstance(f, Document)]) >>> len([f for f in collection if isinstance(f, Document)])
139 139
>>> # Count the amount of folders >>> # Count the amount of folders
... from rmapi.folder import Folder ... from rmapy.folder import Folder
>>> len([f for f in collection if isinstance(f, Folder)]) >>> len([f for f in collection if isinstance(f, Folder)])
42 42
@@ -74,7 +75,7 @@ DocumentType
```````````` ````````````
A DocumentType is a document. This can be a pdf, epub or notebook. A DocumentType is a document. This can be a pdf, epub or notebook.
These types are represented by the object :class:`rmapi.document.Document` These types are represented by the object :class:`rmapy.document.Document`
Changing the metadata is easy Changing the metadata is easy
@@ -83,28 +84,28 @@ Changing the metadata is easy
:linenos: :linenos:
>>> from rmapi.api import Client >>> from rmapy.api import Client
>>> rmapi = Client() >>> rmapy = Client()
>>> rmapi.renew_token() >>> rmapy.renew_token()
True True
>>> collection = rmapi.get_meta_items() >>> collection = rmapy.get_meta_items()
>>> doc = [ d for d in collection if d.VissibleName == 'ModernC'][0] >>> doc = [ d for d in collection if d.VissibleName == 'ModernC'][0]
>>> doc >>> doc
<rmapi.document.Document a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3> <rmapy.document.Document a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3>
>>> doc.to_dict() >>> doc.to_dict()
{'ID': 'a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3', 'Version': 1, 'Message': '', 'Succes': True, 'BlobURLGet': '', 'BlobURLGetExpires': '0001-01-01T00:00:00Z', 'BlobURLPut': '', 'BlobURLPutExpires': '', 'ModifiedClient': '2019-09-18T20:12:07.206206Z', 'Type': 'DocumentType', 'VissibleName': 'ModernC', 'CurrentPage': 0, 'Bookmarked': False, 'Parent': ''} {'ID': 'a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3', 'Version': 1, 'Message': '', 'Succes': True, 'BlobURLGet': '', 'BlobURLGetExpires': '0001-01-01T00:00:00Z', 'BlobURLPut': '', 'BlobURLPutExpires': '', 'ModifiedClient': '2019-09-18T20:12:07.206206Z', 'Type': 'DocumentType', 'VissibleName': 'ModernC', 'CurrentPage': 0, 'Bookmarked': False, 'Parent': ''}
>>> doc.VissibleName = "Modern C: The book of wisdom" >>> doc.VissibleName = "Modern C: The book of wisdom"
>>> # push the changes back to the Remarkable Cloud >>> # push the changes back to the Remarkable Cloud
... rmapi.update_metadata(doc) ... rmapy.update_metadata(doc)
True True
>>> collection = rmapi.get_meta_items() >>> collection = rmapy.get_meta_items()
>>> doc = [ d for d in docs if d.VissibleName == 'ModernC'][0] >>> doc = [ d for d in docs if d.VissibleName == 'ModernC'][0]
Traceback (most recent call last): Traceback (most recent call last):
File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <module>
IndexError: list index out of range IndexError: list index out of range
>>> doc = [ d for d in docs if d.VissibleName == 'Modern C: The book of wisdom'][0] >>> doc = [ d for d in docs if d.VissibleName == 'Modern C: The book of wisdom'][0]
>>> doc >>> doc
<rmapi.document.Document a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3> <rmapy.document.Document a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3>
>>> doc.to_dict() >>> doc.to_dict()
{'ID': 'a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3', 'Version': 1, 'Message': '', 'Succes': True, 'BlobURLGet': '', 'BlobURLGetExpires': '0001-01-01T00:00:00Z', 'BlobURLPut': '', 'BlobURLPutExpires': '', 'ModifiedClient': '2019-09-18T20:12:07.206206Z', 'Type': 'DocumentType', 'VissibleName': 'Modern C: The book of wisdom', 'CurrentPage': 0, 'Bookmarked': False, 'Parent': ''} {'ID': 'a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3', 'Version': 1, 'Message': '', 'Succes': True, 'BlobURLGet': '', 'BlobURLGetExpires': '0001-01-01T00:00:00Z', 'BlobURLPut': '', 'BlobURLPutExpires': '', 'ModifiedClient': '2019-09-18T20:12:07.206206Z', 'Type': 'DocumentType', 'VissibleName': 'Modern C: The book of wisdom', 'CurrentPage': 0, 'Bookmarked': False, 'Parent': ''}
@@ -114,7 +115,7 @@ CollectionType
A CollectionType is a Folder. A CollectionType is a Folder.
These types are represented by the object :class:`rmapi.folder.Folder` These types are represented by the object :class:`rmapy.folder.Folder`
Working with folders is easy! Working with folders is easy!
@@ -122,42 +123,42 @@ Working with folders is easy!
:linenos: :linenos:
>>> from rmapi.api import Client >>> from rmapy.api import Client
>>> rmapi = Client() >>> rmapy = Client()
>>> rmapi.renew_token() >>> rmapy.renew_token()
True True
>>> collection = rmapi.get_meta_items() >>> collection = rmapy.get_meta_items()
>>> collection >>> collection
<rmapi.collections.Collection object at 0x7fc4718e1ed0> <rmapy.collections.Collection object at 0x7fc4718e1ed0>
>>> from rmapi.folder import Folder >>> from rmapy.folder import Folder
>>> # Get all the folders. Note that the fs of Remarkable is flat in the cloud >>> # Get all the folders. Note that the fs of Remarkable is flat in the cloud
... folders = [ f for f in collection if isinstance(f, Folder) ] ... folders = [ f for f in collection if isinstance(f, Folder) ]
>>> folders >>> folders
[<rmapi.folder.Folder 028400f5-b258-4563-bf5d-9a47c314668c>, <rmapi.folder.Folder 06a36729-f91e-47da-b334-dc088c1e73d2>, ...] [<rmapy.folder.Folder 028400f5-b258-4563-bf5d-9a47c314668c>, <rmapy.folder.Folder 06a36729-f91e-47da-b334-dc088c1e73d2>, ...]
>>> # Get the root folders >>> # Get the root folders
... root = [ f for f in folders if f.Parent == "" ] ... root = [ f for f in folders if f.Parent == "" ]
>>> root >>> root
[<rmapi.folder.Folder 028400f5-b258-4563-bf5d-9a47c314668c>, <rmapi.folder.Folder 5005a085-d7ee-4867-8859-4cd90dee0d62>, ...] [<rmapy.folder.Folder 028400f5-b258-4563-bf5d-9a47c314668c>, <rmapy.folder.Folder 5005a085-d7ee-4867-8859-4cd90dee0d62>, ...]
>>> # Create a new folder >>> # Create a new folder
... new_folder = Folder("New Folder") ... new_folder = Folder("New Folder")
>>> new_folder >>> new_folder
<rmapi.folder.Folder 579df08d-7ee4-4f30-9994-887e6341cae3> <rmapy.folder.Folder 579df08d-7ee4-4f30-9994-887e6341cae3>
>>> rmapi.create_folder(new_folder) >>> rmapy.create_folder(new_folder)
True True
>>> # verify >>> # verify
... [ f for f in rmapi.get_meta_items() if f.VissibleName == "New Folder" ] ... [ f for f in rmapy.get_meta_items() if f.VissibleName == "New Folder" ]
[<rmapi.folder.Folder 579df08d-7ee4-4f30-9994-887e6341cae3>] [<rmapy.folder.Folder 579df08d-7ee4-4f30-9994-887e6341cae3>]
>>> [ f for f in rmapi.get_meta_items() if f.VissibleName == "New Folder" ][0].ID == new_folder.ID >>> [ f for f in rmapy.get_meta_items() if f.VissibleName == "New Folder" ][0].ID == new_folder.ID
True True
>>> # Move a document in a folder >>> # Move a document in a folder
... doc = rmapi.get_doc("a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3") ... doc = rmapy.get_doc("a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3")
>>> doc >>> doc
<rmapi.document.Document a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3> <rmapy.document.Document a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3>
>>> doc.Parent = new_folder.ID >>> doc.Parent = new_folder.ID
>>> # Submit the changes >>> # Submit the changes
... rmapi.update_metadata(doc) ... rmapy.update_metadata(doc)
True True
>>> doc = rmapi.get_doc("a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3") >>> doc = rmapy.get_doc("a969fcd6-64b0-4f71-b1ce-d9533ec4a2a3")
>>> doc.Parent == new_folder.ID >>> doc.Parent == new_folder.ID
True True
@@ -175,10 +176,13 @@ Here is the content of an archive retried on the tablet as example:
* 384326f5-133e-49c8-82ff-30aa19f3cfa40.pdf * 384326f5-133e-49c8-82ff-30aa19f3cfa40.pdf
* 384327f5-133e-49c8-82ff-30aa19f3cfa40.pagedata * 384327f5-133e-49c8-82ff-30aa19f3cfa40.pagedata
* 384327f5-133e-49c8-82ff-30aa19f3cfa40.thumbnails/0.jpg * 384327f5-133e-49c8-82ff-30aa19f3cfa40.thumbnails/0.jpg
* 384327f5-133e-49c8-82ff-30aa19f3cfa40.highlights/9b75d8df-1d06-4c59-8f3e-4cf69aa96cd9.json
As the .zip file from remarkable is simply a normal .zip file As the .zip file from remarkable is simply a normal .zip file
containing specific file formats. containing specific file formats.
Highlights are stored in the `{uuid}.highlights/` folder.
You can find some help about the format at the following URL: You can find some help about the format at the following URL:
https://remarkablewiki.com/tech/filesystem https://remarkablewiki.com/tech/filesystem
@@ -193,14 +197,14 @@ the remarkable file format:
:linenos: :linenos:
>>> from rmapi.document import ZipDocument >>> from rmapy.document import ZipDocument
>>> from rmapi.api import Client >>> from rmapy.api import Client
>>> rm = Client() >>> rm = Client()
>>> rm.renew_token() >>> rm.renew_token()
True True
>>> rawDocument = ZipDocument(doc="/home/svancampenhout/27-11-2019.pdf") >>> rawDocument = ZipDocument(doc="/home/svancampenhout/27-11-2019.pdf")
>>> rawDocument >>> rawDocument
<rmapi.document.ZipDocument b926ffc2-3600-460e-abfa-0fcf20b0bf99> <rmapy.document.ZipDocument b926ffc2-3600-460e-abfa-0fcf20b0bf99>
>>> rawDocument.metadata["VissibleName"] >>> rawDocument.metadata["VissibleName"]
'27-11-2019' '27-11-2019'

View File

@@ -1,77 +1,77 @@
rmapi package rmapy package
============= =============
Submodules Submodules
---------- ----------
rmapi.api module rmapy.api module
---------------- ----------------
.. automodule:: rmapi.api .. automodule:: rmapy.api
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
rmapi.collections module rmapy.collections module
------------------------ ------------------------
.. automodule:: rmapi.collections .. automodule:: rmapy.collections
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
rmapi.config module rmapy.config module
------------------- -------------------
.. automodule:: rmapi.config .. automodule:: rmapy.config
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
rmapi.const module rmapy.const module
------------------ ------------------
.. automodule:: rmapi.const .. automodule:: rmapy.const
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
rmapi.document module rmapy.document module
--------------------- ---------------------
.. automodule:: rmapi.document .. automodule:: rmapy.document
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
rmapi.exceptions module rmapy.exceptions module
----------------------- -----------------------
.. automodule:: rmapi.exceptions .. automodule:: rmapy.exceptions
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
rmapi.folder module rmapy.folder module
------------------- -------------------
.. automodule:: rmapi.folder .. automodule:: rmapy.folder
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
rmapi.meta module rmapy.meta module
----------------- -----------------
.. automodule:: rmapi.meta .. automodule:: rmapy.meta
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
rmapi.types module rmapy.types module
------------------ ------------------
.. automodule:: rmapi.types .. automodule:: rmapy.types
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
@@ -80,7 +80,7 @@ rmapi.types module
Module contents Module contents
--------------- ---------------
.. automodule:: rmapi .. automodule:: rmapy
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -3,4 +3,5 @@ pyaml==19.4.1
sphinx==2.2.0 sphinx==2.2.0
sphinx-autodoc-typehints==1.8.0 sphinx-autodoc-typehints==1.8.0
guzzle-sphinx-theme==0.7.11 guzzle-sphinx-theme==0.7.11
urllib3>=1.26.5

View File

@@ -1,7 +0,0 @@
RFC3339Nano = "%Y-%m-%dT%H:%M:%SZ"
USER_AGENT = "rmapipy"
BASE_URL = "https://document-storage-production-dot-remarkable-production.appspot.com" # noqa
DEVICE_TOKEN_URL = "https://my.remarkable.com/token/json/2/device/new"
USER_TOKEN_URL = "https://my.remarkable.com/token/json/2/user/new"
DEVICE = "desktop-windows"

View File

@@ -1,7 +1,6 @@
import requests import requests
from logging import getLogger from logging import getLogger
from datetime import datetime from datetime import datetime
import json
from typing import Union, Optional from typing import Union, Optional
from uuid import uuid4 from uuid import uuid4
from .collections import Collection from .collections import Collection
@@ -20,19 +19,15 @@ from .const import (RFC3339Nano,
USER_TOKEN_URL, USER_TOKEN_URL,
DEVICE,) DEVICE,)
log = getLogger("rmapipy.rmapi") log = getLogger("rmapy")
DocumentOrFolder = Union[Document, Folder] DocumentOrFolder = Union[Document, Folder]
class Client(object): class Client(object):
"""API Client for Remarkable Cloud """API Client for Remarkable Cloud
This allows you to authenticate & communiticate with the Remarkable Cloud This allows you to authenticate & communicate with the Remarkable Cloud
and does all the heavy lifting for you. and does all the heavy lifting for you.
Attributes:
token_set: the authentication tokens
""" """
token_set = { token_set = {
@@ -49,7 +44,7 @@ class Client(object):
def request(self, method: str, path: str, def request(self, method: str, path: str,
data=None, data=None,
body=None, headers={}, body=None, headers=None,
params=None, stream=False) -> requests.Response: params=None, stream=False) -> requests.Response:
"""Creates a request against the Remarkable Cloud API """Creates a request against the Remarkable Cloud API
@@ -63,12 +58,14 @@ class Client(object):
body: the body to request with. This will be converted to json. body: the body to request with. This will be converted to json.
headers: a dict of additional headers to add to the request. headers: a dict of additional headers to add to the request.
params: Query params to append to the request. params: Query params to append to the request.
steam: Should the response be a stream? stream: Should the response be a stream?
Returns: Returns:
A Response instance containing most likely the response from A Response instance containing most likely the response from
the server. the server.
""" """
if headers is None:
headers = {}
if not path.startswith("http"): if not path.startswith("http"):
if not path.startswith('/'): if not path.startswith('/'):
path = '/' + path path = '/' + path
@@ -98,12 +95,12 @@ class Client(object):
"""Registers a device on the Remarkable Cloud. """Registers a device on the Remarkable Cloud.
This uses a unique code the user gets from This uses a unique code the user gets from
https://my.remarkable.com/connect/remarkable to register a new device https://my.remarkable.com/device/desktop/connect to register
or client to be able to execute api calls. a new device or client to be able to execute api calls.
Args: Args:
code: A unique One time code the user can get code: A unique One time code the user can get
at https://my.remarkable.com/connect/remarkable . at https://my.remarkable.com/device/desktop/connect.
Returns: Returns:
True True
Raises: Raises:
@@ -118,7 +115,7 @@ class Client(object):
"deviceID": uuid, "deviceID": uuid,
} }
response = self.request("POST", DEVICE_TOKEN_URL, body) response = self.request("POST", DEVICE_TOKEN_URL, body=body)
if response.ok: if response.ok:
self.token_set["devicetoken"] = response.text self.token_set["devicetoken"] = response.text
dump(self.token_set) dump(self.token_set)
@@ -185,13 +182,13 @@ class Client(object):
return collection return collection
def get_doc(self, ID: str) -> Optional[DocumentOrFolder]: def get_doc(self, _id: str) -> Optional[DocumentOrFolder]:
"""Get a meta item by ID """Get a meta item by ID
Fetch a meta item from the Remarkable Cloud by ID. Fetch a meta item from the Remarkable Cloud by ID.
Args: Args:
ID: The id of the meta item. _id: The id of the meta item.
Returns: Returns:
A Document or Folder instance of the requested ID. A Document or Folder instance of the requested ID.
@@ -199,10 +196,10 @@ class Client(object):
DocumentNotFound: When a document cannot be found. DocumentNotFound: When a document cannot be found.
""" """
log.debug(f"GETTING DOC {ID}") log.debug(f"GETTING DOC {_id}")
response = self.request("GET", "/document-storage/json/2/docs", response = self.request("GET", "/document-storage/json/2/docs",
params={ params={
"doc": ID, "doc": _id,
"withBlob": True "withBlob": True
}) })
log.debug(response.url) log.debug(response.url)
@@ -215,7 +212,7 @@ class Client(object):
elif data_response[0]["Type"] == "DocumentType": elif data_response[0]["Type"] == "DocumentType":
return Document(**data_response[0]) return Document(**data_response[0])
else: else:
raise DocumentNotFound(f"Cound not find document {ID}") raise DocumentNotFound(f"Could not find document {_id}")
return None return None
def download(self, document: Document) -> ZipDocument: def download(self, document: Document) -> ZipDocument:
@@ -251,7 +248,7 @@ class Client(object):
Args: Args:
doc: A Document or folder to delete. doc: A Document or folder to delete.
Raises: Raises:
ApiError: an error occured while uploading the document. ApiError: an error occurred while uploading the document.
""" """
response = self.request("PUT", "/document-storage/json/2/delete", response = self.request("PUT", "/document-storage/json/2/delete",
@@ -260,28 +257,29 @@ class Client(object):
"Version": doc.Version "Version": doc.Version
}]) }])
return self.check_reponse(response) return self.check_response(response)
def upload(self, zipDoc: ZipDocument, to: Folder = Folder(ID="")): def upload(self, zip_doc: ZipDocument, to: Folder = Folder(ID="")):
"""Upload a document to the cloud. """Upload a document to the cloud.
Add a new document to the Remarkable Cloud. Add a new document to the Remarkable Cloud.
Args: Args:
zipDoc: A ZipDocument instance containing the data of a Document. zip_doc: A ZipDocument instance containing the data of a Document.
to: the parent of the document. (Default root)
Raises: Raises:
ApiError: an error occured while uploading the document. ApiError: an error occurred while uploading the document.
""" """
BlobURLPut = self._upload_request(zipDoc) blob_url_put = self._upload_request(zip_doc)
zipDoc.dump(zipDoc.zipfile) zip_doc.dump(zip_doc.zipfile)
response = self.request("PUT", BlobURLPut, data=zipDoc.zipfile.read()) response = self.request("PUT", blob_url_put, data=zip_doc.zipfile.read())
# Reset seek # Reset seek
zipDoc.zipfile.seek(0) zip_doc.zipfile.seek(0)
if response.ok: if response.ok:
doc = Document(**zipDoc.metadata) doc = Document(**zip_doc.metadata)
doc.ID = zipDoc.ID doc.ID = zip_doc.ID
doc.Parent = to.ID doc.Parent = to.ID
return self.update_metadata(doc) return self.update_metadata(doc)
else: else:
@@ -305,7 +303,7 @@ class Client(object):
"/document-storage/json/2/upload/update-status", "/document-storage/json/2/upload/update-status",
body=[req]) body=[req])
return self.check_reponse(res) return self.check_response(res)
def get_current_version(self, docorfolder: DocumentOrFolder) -> int: def get_current_version(self, docorfolder: DocumentOrFolder) -> int:
"""Get the latest version info from a Document or Folder """Get the latest version info from a Document or Folder
@@ -319,7 +317,7 @@ class Client(object):
the version information. the version information.
Raises: Raises:
DocumentNotFound: cannot find the requested Document or Folder. DocumentNotFound: cannot find the requested Document or Folder.
ApiError: An error occured while processing the request. ApiError: An error occurred while processing the request.
""" """
try: try:
@@ -330,8 +328,8 @@ class Client(object):
return 0 return 0
return int(d.Version) return int(d.Version)
def _upload_request(self, zdoc: ZipDocument) -> dict: def _upload_request(self, zip_doc: ZipDocument) -> str:
zipFile, req = zdoc.create_request() zip_file, req = zip_doc.create_request()
res = self.request("PUT", "/document-storage/json/2/upload/request", res = self.request("PUT", "/document-storage/json/2/upload/request",
body=[req]) body=[req])
if not res.ok: if not res.ok:
@@ -363,7 +361,7 @@ class Client(object):
True if the folder is created. True if the folder is created.
""" """
zipFolder, req = folder.create_request() zip_folder, req = folder.create_request()
res = self.request("PUT", "/document-storage/json/2/upload/request", res = self.request("PUT", "/document-storage/json/2/upload/request",
body=[req]) body=[req])
if not res.ok: if not res.ok:
@@ -374,7 +372,7 @@ class Client(object):
if len(response) > 0: if len(response) > 0:
dest = response[0].get("BlobURLPut", None) dest = response[0].get("BlobURLPut", None)
if dest: if dest:
res = self.request("PUT", dest, data=zipFolder.read()) res = self.request("PUT", dest, data=zip_folder.read())
else: else:
raise ApiError( raise ApiError(
"Cannot create a folder. because BlobURLPut is not set", "Cannot create a folder. because BlobURLPut is not set",
@@ -383,7 +381,8 @@ class Client(object):
self.update_metadata(folder) self.update_metadata(folder)
return True return True
def check_reponse(self, response: requests.Response): @staticmethod
def check_response(response: requests.Response):
"""Check the response from an API Call """Check the response from an API Call
Does some sanity checking on the Response Does some sanity checking on the Response
@@ -417,4 +416,3 @@ class Client(object):
raise ApiError( raise ApiError(
f"Got An invalid HTTP Response: {response.status_code}", f"Got An invalid HTTP Response: {response.status_code}",
response=response) response=response)
return True

View File

@@ -9,71 +9,71 @@ DocumentOrFolder = Union[Document, Folder]
class Collection(object): class Collection(object):
"""A collection of meta items """A collection of meta items
This is basicly the content of the Remarkable Cloud. This is basically the content of the Remarkable Cloud.
Attributes: Attributes:
items: A list containing the items. items: A list containing the items.
""" """
items: List[DocumentOrFolder] = []
def __init__(self, *items: List[DocumentOrFolder]): def __init__(self, *items: List[DocumentOrFolder]):
self.items: List[DocumentOrFolder] = []
for i in items: for i in items:
self.items.append(i) self.items.append(i)
def add(self, docdict: dict) -> None: def add(self, doc_dict: dict) -> None:
"""Add an item to the collection. """Add an item to the collection.
It wraps it in the correct class based on the Type parameter of the It wraps it in the correct class based on the Type parameter of the
dict. dict.
Args: Args:
docdict: A dict representing a document or folder. doc_dict: A dict representing a document or folder.
""" """
if docdict.get("Type", None) == "DocumentType": if doc_dict.get("Type", None) == "DocumentType":
self.add_document(docdict) self.add_document(doc_dict)
elif docdict.get("Type", None) == "CollectionType": elif doc_dict.get("Type", None) == "CollectionType":
self.add_folder(docdict) self.add_folder(doc_dict)
else: else:
raise TypeError("Unsupported type: {_type}" raise TypeError("Unsupported type: {_type}"
.format(_type=docdict.get("Type", None))) .format(_type=doc_dict.get("Type", None)))
def add_document(self, docdict: dict) -> None: def add_document(self, doc_dict: dict) -> None:
"""Add a document to the collection """Add a document to the collection
Args: Args:
docdict: A dict respresenting a document. doc_dict: A dict representing a document.
""" """
self.items.append(Document(**docdict)) self.items.append(Document(**doc_dict))
def add_folder(self, dirdict: dict) -> None: def add_folder(self, dir_dict: dict) -> None:
"""Add a document to the collection """Add a document to the collection
Args: Args:
dirdict: A dict respresenting a folder. dir_dict: A dict representing a folder.
""" """
self.items.append(Folder(**dirdict)) self.items.append(Folder(**dir_dict))
def parent(self, docorfolder: DocumentOrFolder) -> Folder: def parent(self, doc_or_folder: DocumentOrFolder) -> Folder:
"""Returns the paren of a Document or Folder """Returns the paren of a Document or Folder
Args: Args:
docorfolder: A document or folder to get the parent from doc_or_folder: A document or folder to get the parent from
Returns: Returns:
The parent folder. The parent folder.
""" """
results = [i for i in self.items if i.ID == docorfolder.ID] results = [i for i in self.items if i.ID == doc_or_folder.ID]
if len(results) > 0 and isinstance(results[0], Folder): if len(results) > 0 and isinstance(results[0], Folder):
return results[0] return results[0]
else: else:
raise FolderNotFound("Could not found the parent of the document.") raise FolderNotFound("Could not found the parent of the document.")
def children(self, folder: Folder = None) -> List[DocumentOrFolder]: def children(self, folder: Folder = None) -> List[DocumentOrFolder]:
"""Get all the childern from a folder """Get all the children from a folder
Args: Args:
folder: A folder where to get the children from. If None, this will folder: A folder where to get the children from. If None, this will

View File

@@ -6,7 +6,7 @@ from typing import Dict
def load() -> dict: def load() -> dict:
"""Load the .rmapi config file""" """Load the .rmapy config file"""
config_file_path = Path.joinpath(Path.home(), ".rmapi") config_file_path = Path.joinpath(Path.home(), ".rmapi")
config: Dict[str, str] = {} config: Dict[str, str] = {}
@@ -18,7 +18,7 @@ def load() -> dict:
def dump(config: dict) -> None: def dump(config: dict) -> None:
"""Dump config to the .rmapi config file """Dump config to the .rmapy config file
Args: Args:
config: A dict containing data to dump to the .rmapi config: A dict containing data to dump to the .rmapi

8
rmapy/const.py Normal file
View File

@@ -0,0 +1,8 @@
RFC3339Nano = "%Y-%m-%dT%H:%M:%SZ"
USER_AGENT = "rmapy"
AUTH_BASE_URL = "https://webapp-production-dot-remarkable-production.appspot.com"
BASE_URL = "https://document-storage-production-dot-remarkable-production.appspot.com" # noqa
DEVICE_TOKEN_URL = AUTH_BASE_URL + "/token/json/2/device/new"
USER_TOKEN_URL = AUTH_BASE_URL + "/token/json/2/user/new"
DEVICE = "desktop-windows"
SERVICE_MGR_URL = "https://service-manager-production-dot-remarkable-production.appspot.com" # noqa

View File

@@ -4,12 +4,13 @@ from zipfile import ZipFile, ZIP_DEFLATED
import shutil import shutil
from uuid import uuid4 from uuid import uuid4
import json import json
from typing import NoReturn, TypeVar, List, Tuple from typing import TypeVar, List, Tuple
from logging import getLogger
from requests import Response from requests import Response
from .meta import Meta from .meta import Meta
log = getLogger("rmapy")
BytesOrString = TypeVar("BytesOrString", BytesIO, str) BytesOrString = TypeVar("BytesOrString", BytesIO, str)
BytesOrNone = TypeVar("BytesOrNone", BytesIO, None)
class RmPage(object): class RmPage(object):
@@ -18,7 +19,7 @@ class RmPage(object):
Contains the metadata, the page itself & thumbnail. Contains the metadata, the page itself & thumbnail.
""" """
def __init__(self, page, metadata=None, order=0, thumbnail=None, ID=None): def __init__(self, page, metadata=None, order=0, thumbnail=None, _id=None):
self.page = page self.page = page
if metadata: if metadata:
self.metadata = metadata self.metadata = metadata
@@ -28,19 +29,46 @@ class RmPage(object):
self.order = order self.order = order
if thumbnail: if thumbnail:
self.thumbnail = thumbnail self.thumbnail = thumbnail
if ID: if _id:
self.ID = ID self.ID = _id
else: else:
self.ID = str(uuid4()) self.ID = str(uuid4())
def __str__(self) -> str: def __str__(self) -> str:
"""String representation of this object""" """String representation of this object"""
return f"<rmapi.document.RmPage {self.order} for {self.ID}>" return f"<rmapy.document.RmPage {self.order} for {self.ID}>"
def __repr__(self) -> str: def __repr__(self) -> str:
"""String representation of this object""" """String representation of this object"""
return self.__str__() return self.__str__()
class Highlight(object):
""" Highlight represents all highlights on a page created using the highligher pen
in EPUB documents.
Functionality introduced in Remarkable 2.7 software.
Contains the page_id where the highlights are located and the highlights
metadata for the page from the Remarkable Cloud.
Corresponds to single .json file in the .highlights/ folder.
Attributes:
page_id: The ID of the page where the highlight is located.
highlight_data: A dictionary containing all highlight data.
"""
def __init__(self, page_id: str, highlight_data: str):
self.page_id = page_id
self.highlight_data = json.loads(highlight_data)
def __str__(self) -> str:
"""String representation of this object"""
return f"<rmapy.document.Highlight {self.page_id}>"
def __repr__(self) -> str:
"""String representation of this object"""
return self.__str__()
class Document(Meta): class Document(Meta):
""" Document represents a real object expected in most """ Document represents a real object expected in most
@@ -51,7 +79,7 @@ class Document(Meta):
Attributes: Attributes:
ID: Id of the meta object. ID: Id of the meta object.
Version: The version of this object. Version: The version of this object.
Success: If the last API Call was a succes. Success: If the last API Call was a success.
BlobURLGet: The url to get the data blob from. Can be empty. BlobURLGet: The url to get the data blob from. Can be empty.
BlobURLGetExpires: The expiration date of the Get url. BlobURLGetExpires: The expiration date of the Get url.
BlobURLPut: The url to upload the data blob to. Can be empty. BlobURLPut: The url to upload the data blob to. Can be empty.
@@ -73,7 +101,7 @@ class Document(Meta):
def __str__(self): def __str__(self):
"""String representation of this object""" """String representation of this object"""
return f"<rmapi.document.Document {self.ID}>" return f"<rmapy.document.Document {self.ID}>"
def __repr__(self): def __repr__(self):
"""String representation of this object""" """String representation of this object"""
@@ -89,6 +117,7 @@ class ZipDocument(object):
* 384326f5-133e-49c8-82ff-30aa19f3cfa40.rm * 384326f5-133e-49c8-82ff-30aa19f3cfa40.rm
* 384327f5-133e-49c8-82ff-30aa19f3cfa40.pagedata * 384327f5-133e-49c8-82ff-30aa19f3cfa40.pagedata
* 384327f5-133e-49c8-82ff-30aa19f3cfa40.thumbnails/0.jpg * 384327f5-133e-49c8-82ff-30aa19f3cfa40.thumbnails/0.jpg
* 384327f5-133e-49c8-82ff-30aa19f3cfa40.highlights/9b75d8df-1d06-4c59-8f3e-4cf69aa96cd9.json
As the .zip file from remarkable is simply a normal .zip file As the .zip file from remarkable is simply a normal .zip file
containing specific file formats, this package is a helper to containing specific file formats, this package is a helper to
@@ -105,91 +134,94 @@ class ZipDocument(object):
Attributes: Attributes:
content: Sane defaults for the .content file in the zip. content: Sane defaults for the .content file in the zip.
metadata: parameters describing this blob. metadata: parameters describing this blob.
highlights: list of contents of the .highlights folder
pagedata: the content of the .pagedata file. pagedata: the content of the .pagedata file.
zipfile: The raw zipfile in memory. zipfile: The raw zipfile in memory.
pdf: the raw pdf file if there is one. pdf: the raw pdf file if there is one.
epub: the raw epub file if there is one. epub: the raw epub file if there is one.
rm: A list of :class:rmapi.document.RmPage in this zip. rm: A list of :class:rmapy.document.RmPage in this zip.
""" """
# {"extraMetadata": {}, def __init__(self, _id=None, doc=None, file=None):
# "fileType": "pdf",
# "pageCount": 0,
# "lastOpenedPage": 0,
# "lineHeight": -1,
# "margins": 180,
# "textScale": 1,
# "transform": {}}
content = {
"extraMetadata": {
# "LastBrushColor": "Black",
# "LastBrushThicknessScale": "2",
# "LastColor": "Black",
# "LastEraserThicknessScale": "2",
# "LastEraserTool": "Eraser",
# "LastPen": "Ballpoint",
# "LastPenColor": "Black",
# "LastPenThicknessScale": "2",
# "LastPencil": "SharpPencil",
# "LastPencilColor": "Black",
# "LastPencilThicknessScale": "2",
# "LastTool": "SharpPencil",
# "ThicknessScale": "2"
},
# "FileType": "",
# "FontName": "",
"lastOpenedPage": 0,
"lineHeight": -1,
"margins": 180,
# "Orientation": "portrait",
"pageCount": 0,
# "Pages": [],
"textScale": 1,
"transform": {
# "M11": 1,
# "M12": 0,
# "M13": 0,
# "M21": 0,
# "M22": 1,
# "M23": 0,
# "M31": 0,
# "M32": 0,
# "M33": 1,
}
}
metadata = {
"deleted": False,
"lastModified": "1568368808000",
"metadatamodified": False,
"modified": False,
"parent": "",
"pinned": False,
"synced": True,
"type": "DocumentType",
"version": 1,
"VissibleName": "New Document"
}
pagedata = "b''"
zipfile = BytesIO()
pdf = None
epub = None
rm: List[RmPage] = []
ID = None
def __init__(self, ID=None, doc=None, file=None):
"""Create a new instance of a ZipDocument """Create a new instance of a ZipDocument
Args: Args:
ID: Can be left empty to generate one _id: Can be left empty to generate one
doc: a raw pdf, epub or rm (.lines) file. doc: a raw pdf, epub or rm (.lines) file.
file: a zipfile to convert from file: a zipfile to convert from
""" """
if not ID: # {"extraMetadata": {},
ID = str(uuid4()) # "fileType": "pdf",
self.ID = ID # "pageCount": 0,
# "lastOpenedPage": 0,
# "lineHeight": -1,
# "margins": 180,
# "textScale": 1,
# "transform": {}}
self.content = {
"extraMetadata": {
# "LastBrushColor": "Black",
# "LastBrushThicknessScale": "2",
# "LastColor": "Black",
# "LastEraserThicknessScale": "2",
# "LastEraserTool": "Eraser",
# "LastPen": "Ballpoint",
# "LastPenColor": "Black",
# "LastPenThicknessScale": "2",
# "LastPencil": "SharpPencil",
# "LastPencilColor": "Black",
# "LastPencilThicknessScale": "2",
# "LastTool": "SharpPencil",
# "ThicknessScale": "2"
},
# "FileType": "",
# "FontName": "",
"lastOpenedPage": 0,
"lineHeight": -1,
"margins": 180,
# "Orientation": "portrait",
"pageCount": 0,
# "Pages": [],
"textScale": 1,
"transform": {
# "M11": 1,
# "M12": 0,
# "M13": 0,
# "M21": 0,
# "M22": 1,
# "M23": 0,
# "M31": 0,
# "M32": 0,
# "M33": 1,
}
}
self.metadata = {
"deleted": False,
"lastModified": "1568368808000",
"metadatamodified": False,
"modified": False,
"parent": "",
"pinned": False,
"synced": True,
"type": "DocumentType",
"version": 1,
"VissibleName": "New Document"
}
self.pagedata = "b''"
self.zipfile = BytesIO()
self.pdf = None
self.epub = None
self.rm: List[RmPage] = []
self.ID = None
self.highlights: List[Highlight] = []
if not _id:
_id = str(uuid4())
self.ID = _id
if doc: if doc:
ext = doc[-4:] ext = doc[-4:]
if ext.endswith("pdf"): if ext.endswith("pdf"):
@@ -207,7 +239,7 @@ class ZipDocument(object):
elif ext.endswith("rm"): elif ext.endswith("rm"):
self.content["fileType"] = "notebook" self.content["fileType"] = "notebook"
with open(doc, 'rb') as fb: with open(doc, 'rb') as fb:
self.rm.append(RmPage(page=BytesIO(doc.read()))) self.rm.append(RmPage(page=BytesIO(fb.read())))
name = os.path.splitext(os.path.basename(doc))[0] name = os.path.splitext(os.path.basename(doc))[0]
self.metadata["VissibleName"] = name self.metadata["VissibleName"] = name
@@ -216,7 +248,7 @@ class ZipDocument(object):
def __str__(self) -> str: def __str__(self) -> str:
"""string representation of this class""" """string representation of this class"""
return f"<rmapi.document.ZipDocument {self.ID}>" return f"<rmapy.document.ZipDocument {self.ID}>"
def __repr__(self) -> str: def __repr__(self) -> str:
"""string representation of this class""" """string representation of this class"""
@@ -252,6 +284,10 @@ class ZipDocument(object):
zf.writestr(f"{self.ID}.epub", zf.writestr(f"{self.ID}.epub",
self.epub.read()) self.epub.read())
for highlight in self.highlights:
zf.writestr(f"{self.ID}.highlights/{highlight.page_id}.json",
json.dumps(highlight.highlight_data))
for page in self.rm: for page in self.rm:
zf.writestr(f"{self.ID}/{page.order}.rm", zf.writestr(f"{self.ID}/{page.order}.rm",
@@ -260,8 +296,12 @@ class ZipDocument(object):
zf.writestr(f"{self.ID}/{page.order}-metadata.json", zf.writestr(f"{self.ID}/{page.order}-metadata.json",
json.dumps(page.metadata)) json.dumps(page.metadata))
page.page.seek(0) page.page.seek(0)
zf.writestr(f"{self.ID}.thumbnails/{page.order}.jpg", try:
page.thumbnail.read()) zf.writestr(f"{self.ID}.thumbnails/{page.order}.jpg",
page.thumbnail.read())
except AttributeError:
log.debug(f"missing thumbnail during dump: {self.ID}: {page.order}")
pass
if isinstance(file, BytesIO): if isinstance(file, BytesIO):
file.seek(0) file.seek(0)
@@ -311,54 +351,69 @@ class ZipDocument(object):
except KeyError: except KeyError:
pass pass
# Get the RM pages # Get Highlights
highlights = [x for x in zf.namelist()
if x.startswith(f"{self.ID}.highlights/") and x.endswith('.json')]
for highlight in highlights:
with zf.open(highlight, 'r') as highlight_fp:
page_id = highlight.replace(f"{self.ID}.highlights/", "").replace(".json", "")
self.highlights.append(Highlight(page_id, highlight_fp.read()))
# Get the RM pages
pages = [x for x in zf.namelist() pages = [x for x in zf.namelist()
if x.startswith(f"{self.ID}/") and x.endswith('.rm')] if x.startswith(f"{self.ID}/") and x.endswith('.rm')]
for p in pages: for p in pages:
pagenumber = int(p.replace(f"{self.ID}/", "") page_number = int(p.replace(f"{self.ID}/", "")
.replace(".rm", "")) .replace(".rm", ""))
page = BytesIO()
thumbnail = BytesIO()
with zf.open(p, 'r') as rm: with zf.open(p, 'r') as rm:
page = BytesIO(rm.read()) page = BytesIO(rm.read())
page.seek(0) page.seek(0)
with zf.open(p.replace(".rm", "-metadata.json"), 'r') as md:
metadata = json.load(md) p_meta = p.replace(".rm", "-metadata.json")
try:
with zf.open(p_meta, 'r') as md:
metadata = json.load(md)
except KeyError:
log.debug(f"missing metadata: {p_meta}")
metadata = None
thumbnail_name = p.replace(".rm", ".jpg") thumbnail_name = p.replace(".rm", ".jpg")
thumbnail_name = thumbnail_name.replace("/", ".thumbnails/") thumbnail_name = thumbnail_name.replace("/", ".thumbnails/")
with zf.open(thumbnail_name, 'r') as tn: try:
thumbnail = BytesIO(tn.read()) with zf.open(thumbnail_name, 'r') as tn:
thumbnail.seek(0) thumbnail = BytesIO(tn.read())
thumbnail.seek(0)
except KeyError:
log.debug(f"missing thumbnail: {thumbnail_name}")
thumbnail = None
self.rm.append(RmPage(page, metadata, pagenumber, thumbnail, self.rm.append(RmPage(page, metadata, page_number, thumbnail,
self.ID)) self.ID))
self.zipfile.seek(0) self.zipfile.seek(0)
def from_zip(ID: str, file: str) -> ZipDocument: def from_zip(_id: str, file: str) -> ZipDocument:
"""Return A ZipDocument from a zipfile. """Return A ZipDocument from a zipfile.
Create a ZipDocument instance from a zipfile. Create a ZipDocument instance from a zipfile.
Args: Args:
ID: The object ID this zipfile represents. _id: The object ID this zipfile represents.
file: the filename of the zipfile. file: the filename of the zipfile.
Returns: Returns:
An instance of the supplied zipfile. An instance of the supplied zipfile.
""" """
return ZipDocument(ID, file=file) return ZipDocument(_id, file=file)
def from_request_stream(ID: str, stream: Response) -> ZipDocument: def from_request_stream(_id: str, stream: Response) -> ZipDocument:
"""Return a ZipDocument from a request stream containing a zipfile. """Return a ZipDocument from a request stream containing a zipfile.
This is used with the BlobGETUrl from a :class:`rmapi.document.Document`. This is used with the BlobGETUrl from a :class:`rmapy.document.Document`.
Args: Args:
ID: The object ID this zipfile represents. _id: The object ID this zipfile represents.
stream: a stream containing the zipfile. stream: a stream containing the zipfile.
Returns: Returns:
the object of the downloaded zipfile. the object of the downloaded zipfile.
@@ -367,6 +422,6 @@ def from_request_stream(ID: str, stream: Response) -> ZipDocument:
tmp = BytesIO() tmp = BytesIO()
for chunk in stream.iter_content(chunk_size=8192): for chunk in stream.iter_content(chunk_size=8192):
tmp.write(chunk) tmp.write(chunk)
zd = ZipDocument(ID=ID) zd = ZipDocument(_id=_id)
zd.load(tmp) zd.load(tmp)
return zd return zd

View File

@@ -27,4 +27,3 @@ class ApiError(Exception):
def __init__(self, msg, response=None): def __init__(self, msg, response=None):
self.response = response self.response = response
super(ApiError, self).__init__(msg) super(ApiError, self).__init__(msg)

View File

@@ -6,20 +6,21 @@ from zipfile import ZipFile, ZIP_DEFLATED
from .const import RFC3339Nano from .const import RFC3339Nano
from typing import Tuple, Optional from typing import Tuple, Optional
class ZipFolder(object): class ZipFolder(object):
"""A dummy zipfile to create a folder """A dummy zipfile to create a folder
This is needed to create a folder on the Remarkable Cloud This is needed to create a folder on the Remarkable Cloud
""" """
def __init__(self, ID: str): def __init__(self, _id: str):
"""Creates a zipfile in memory """Creates a zipfile in memory
Args: Args:
ID: the ID to create a zipFolder for _id: the ID to create a zipFolder for
""" """
super(ZipFolder, self).__init__() super(ZipFolder, self).__init__()
self.ID = ID self.ID = _id
self.file = BytesIO() self.file = BytesIO()
self.Version = 1 self.Version = 1
with ZipFile(self.file, 'w', ZIP_DEFLATED) as zf: with ZipFile(self.file, 'w', ZIP_DEFLATED) as zf:
@@ -37,7 +38,7 @@ class Folder(Meta):
Args: Args:
name: An optional name for this folder. In the end, a name is name: An optional name for this folder. In the end, a name is
really needed, but can be ommitted to set a later time. really needed, but can be omitted to set a later time.
""" """
super(Folder, self).__init__(**kwargs) super(Folder, self).__init__(**kwargs)
@@ -48,9 +49,9 @@ class Folder(Meta):
self.ID = str(uuid4()) self.ID = str(uuid4())
def create_request(self) -> Tuple[BytesIO, dict]: def create_request(self) -> Tuple[BytesIO, dict]:
"""Prepares the nessesary parameters to create this folder. """Prepares the necessary parameters to create this folder.
This creates a ZipFolder & the nessesary json body to This creates a ZipFolder & the necessary json body to
create an upload request. create an upload request.
""" """
@@ -61,9 +62,9 @@ class Folder(Meta):
} }
def update_request(self) -> dict: def update_request(self) -> dict:
"""Perpares the nessesary parameters to update a folder. """Prepares the necessary parameters to update a folder.
This sets some parameters in the datastructure to submit to the API. This sets some parameters in the data structure to submit to the API.
""" """
data = self.to_dict() data = self.to_dict()
@@ -72,10 +73,7 @@ class Folder(Meta):
return data return data
def __str__(self): def __str__(self):
return f"<rmapi.folder.Folder {self.ID}>" return f"<rmapy.folder.Folder {self.ID}>"
def __repr__(self): def __repr__(self):
return self.__str__() return self.__str__()

View File

@@ -7,7 +7,7 @@ class Meta(object):
Attributes: Attributes:
ID: Id of the meta object. ID: Id of the meta object.
Version: The version of this object. Version: The version of this object.
Success: If the last API Call was a succes. Success: If the last API Call was a success.
BlobURLGet: The url to get the data blob from. Can be empty. BlobURLGet: The url to get the data blob from. Can be empty.
BlobURLGetExpires: The expiration date of the Get url. BlobURLGetExpires: The expiration date of the Get url.
BlobURLPut: The url to upload the data blob to. Can be empty. BlobURLPut: The url to upload the data blob to. Can be empty.
@@ -26,7 +26,7 @@ class Meta(object):
ID = "" ID = ""
Version = 0 Version = 0
Message = "" Message = ""
Succes = True Success = True
BlobURLGet = "" BlobURLGet = ""
BlobURLGetExpires = "" BlobURLGetExpires = ""
BlobURLPut = "" BlobURLPut = ""
@@ -39,8 +39,8 @@ class Meta(object):
Parent = "" Parent = ""
def __init__(self, **kwargs): def __init__(self, **kwargs):
kkeys = self.to_dict().keys() k_keys = self.to_dict().keys()
for k in kkeys: for k in k_keys:
setattr(self, k, kwargs.get(k, getattr(self, k))) setattr(self, k, kwargs.get(k, getattr(self, k)))
def to_dict(self) -> dict: def to_dict(self) -> dict:
@@ -56,7 +56,7 @@ class Meta(object):
"ID": self.ID, "ID": self.ID,
"Version": self.Version, "Version": self.Version,
"Message": self.Message, "Message": self.Message,
"Succes": self.Succes, "Success": self.Success,
"BlobURLGet": self.BlobURLGet, "BlobURLGet": self.BlobURLGet,
"BlobURLGetExpires": self.BlobURLGetExpires, "BlobURLGetExpires": self.BlobURLGetExpires,
"BlobURLPut": self.BlobURLPut, "BlobURLPut": self.BlobURLPut,

View File

@@ -22,14 +22,14 @@ setup(
# package, this name will be registered for you. It will determine how # package, this name will be registered for you. It will determine how
# users can install this project, e.g.: # users can install this project, e.g.:
# #
# $ pip install sampleproject # $ pip install sampleprojectk
# #
# And where it will live on PyPI: https://pypi.org/project/sampleproject/ # And where it will live on PyPI: https://pypi.org/project/sampleproject/
# #
# There are some restrictions on what makes a valid project name # There are some restrictions on what makes a valid project name
# specification here: # specification here:
# https://packaging.python.org/specifications/core-metadata/#name # https://packaging.python.org/specifications/core-metadata/#name
name='rmapi', # Required name='rmapy', # Required
# Versions should comply with PEP 440: # Versions should comply with PEP 440:
# https://www.python.org/dev/peps/pep-0440/ # https://www.python.org/dev/peps/pep-0440/
@@ -37,7 +37,7 @@ setup(
# For a discussion on single-sourcing the version across setup.py and the # For a discussion on single-sourcing the version across setup.py and the
# project code, see # project code, see
# https://packaging.python.org/en/latest/single_source_version.html # https://packaging.python.org/en/latest/single_source_version.html
version='0.2.0', # Required version='0.3.1', # Required
# This is a one-line description or tagline of what your project does. This # This is a one-line description or tagline of what your project does. This
# corresponds to the "Summary" metadata field: # corresponds to the "Summary" metadata field:
@@ -70,7 +70,7 @@ setup(
# #
# This field corresponds to the "Home-Page" metadata field: # This field corresponds to the "Home-Page" metadata field:
# https://packaging.python.org/specifications/core-metadata/#home-page-optional # https://packaging.python.org/specifications/core-metadata/#home-page-optional
url='https://github.com/subutux/rmapi', # Optional url='https://github.com/subutux/rmapy', # Optional
# This should be your name or the name of the organization which owns the # This should be your name or the name of the organization which owns the
# project. # project.
@@ -88,8 +88,8 @@ setup(
# 3 - Alpha # 3 - Alpha
# 4 - Beta # 4 - Beta
# 5 - Production/Stable # 5 - Production/Stable
'Development Status :: 3 - Alpha', #'Development Status :: 3 - Alpha',
#'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',
#'Development Status :: 5 - Production/Stable' #'Development Status :: 5 - Production/Stable'
#'Development Status :: 6 - Mature' #'Development Status :: 6 - Mature'
# Indicate who your project is intended for # Indicate who your project is intended for
@@ -110,7 +110,7 @@ setup(
# project page. What does your project relate to? # project page. What does your project relate to?
# #
# Note that this is a string of words separated by whitespace, not a list. # Note that this is a string of words separated by whitespace, not a list.
keywords='remarkable rmapi cloud paper tablet', # Optional keywords='remarkable rmapy cloud paper tablet', # Optional
# You can just specify package directories manually here if your project is # You can just specify package directories manually here if your project is
# simple. Or you can use find_packages(). # simple. Or you can use find_packages().
@@ -180,7 +180,7 @@ setup(
# #
# For example, the following would provide a command called `sample` which # For example, the following would provide a command called `sample` which
# executes the function `main` from this package when invoked: # executes the function `main` from this package when invoked:
# TODO # TODO Create a command line tool
# entry_points={ # Optional # entry_points={ # Optional
# 'console_scripts': [ # 'console_scripts': [
# 'sample=sample:main', # 'sample=sample:main',
@@ -197,9 +197,9 @@ setup(
# maintainers, and where to support the project financially. The key is # maintainers, and where to support the project financially. The key is
# what's used to render the link text on PyPI. # what's used to render the link text on PyPI.
project_urls={ # Optional project_urls={ # Optional
'Bug Reports': 'https://github.com/subutux/rmapi/issues', 'Bug Reports': 'https://github.com/subutux/rmapy/issues',
'Funding': 'https://donate.pypi.org', 'Funding': 'https://donate.pypi.org',
'Say Thanks!': 'https://www.paypal.me/subutux', 'Say Thanks!': 'https://www.paypal.me/subutux',
'Source': 'https://github.com/subutux/rmapi/', 'Source': 'https://github.com/subutux/rmapy/',
}, },
) )