mirror of
https://github.com/subutux/rmapy.git
synced 2025-12-09 15:05:35 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45485d4dbf | ||
|
|
8916261afd | ||
|
|
081db1ddc2 | ||
|
|
5c78598271 | ||
|
|
42430b0eb8 | ||
|
|
934e270c6d | ||
|
|
8b120202af | ||
|
|
5595ade375 | ||
|
|
18865083d7 | ||
|
|
10a27bcdb2 | ||
|
|
011cb2f221 | ||
|
|
7e2a3033e0 | ||
|
|
fbc75858d7 | ||
|
|
50fef896d3 | ||
|
|
a0965d58b3 | ||
|
|
544bd11dd1 | ||
|
|
b5f0367dbb | ||
|
|
bc36d7b7c6 | ||
|
|
15d8090e79 | ||
|
|
8bb29e3bd7 | ||
|
|
29d113f3af | ||
|
|
433eb8e722 | ||
|
|
7ceac42fdb | ||
|
|
f5e4972775 | ||
|
|
cca7ea05fd | ||
|
|
48239c9b9f | ||
|
|
3554640693 | ||
|
|
f20916bd34 | ||
|
|
7d70d58f6e | ||
|
|
506b34d40a | ||
|
|
3f0e0e36a2 | ||
|
|
00dc50550f |
31
.github/workflows/python-publish.yml
vendored
Normal file
31
.github/workflows/python-publish.yml
vendored
Normal 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/*
|
||||||
1
Pipfile
1
Pipfile
@@ -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
268
Pipfile.lock
generated
@@ -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": {}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
rmapi
|
rmapy
|
||||||
=====
|
=====
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 4
|
:maxdepth: 4
|
||||||
|
|
||||||
rmapi
|
rmapy
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
@@ -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
8
rmapy/const.py
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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__()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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,
|
||||||
20
setup.py
20
setup.py
@@ -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/',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user