Skip to content

Instantly share code, notes, and snippets.

@Jayy001
Last active September 25, 2023 18:56
Show Gist options
  • Save Jayy001/fd6c2533cf0dab5c86e55c9c276a4942 to your computer and use it in GitHub Desktop.
Save Jayy001/fd6c2533cf0dab5c86e55c9c276a4942 to your computer and use it in GitHub Desktop.
ReMarkable Fast Full PDF Sync

How to use

webInterface.sync("Root", overwrite=True) # Syncs all documents & folders to a local folder called "Root", overwriting previous files
webInterface.sync("Root", remoteFolder='maths') # Syncs all documents & folders from maths folder to "Root" local folder, not overwriten current files
webInterface.sync("Root", remoteFolder='maths' recursive=False) # Syncs only documents from maths folder to "Root" local folder (with no recursion)

How it works

This uses the USB storage web interface API (documented at https://remarkable.guide/tech/usb-web-interface.html) to extract & download the files. For the main sync, it recursivley traverses each folder from root and returns the documents in an array that then gets all downloaded.

Performance

Because it utilises the ReMarkable tablets inbuilt conversion function, not only is it reliable but the speeds are pretty fast too - I was able to transfer 50 of my documents in around 10 seconds. Note this was over USB, so over WiFi might be slower...

import requests
import os
NAME_ATTRIBUTE = "VissibleName" # I have a feeling they might fix this at some point...
ID_ATTRIBUTE = "ID"
class RmWebInterfaceAPI(object):
def __init__(self, BASE="http://10.11.99.1/"):
self.BASE = BASE
def __POST(self, endpoint, data={}):
try:
result = requests.post(self.BASE + endpoint, data=data)
if "application/json" in result.headers["Content-Type"]:
return result.json()
return result.content
except Exception:
return {}
def __get_documents_recursive(
self, folderId="", currentLocation="", currentDocuments=[]
):
data = self.__POST(f"documents/{folderId}")
for item in data:
if "fileType" in item:
item["location"] = currentLocation
currentDocuments.append(item)
else:
self.__get_documents_recursive(
item[ID_ATTRIBUTE],
f"{currentLocation}/{item[NAME_ATTRIBUTE]}",
currentDocuments,
)
return currentDocuments
def __get_folder_id(self, folderName, _from=""):
results = self.__POST(f"documents/{_from}")[::-1] # Get folders first
for data in results:
if "fileType" in data:
return None
if data[NAME_ATTRIBUTE].strip() == folderName.strip():
return data[ID_ATTRIBUTE]
recursiveResults = self.__get_folder_id(folderName, data[ID_ATTRIBUTE])
if recursiveResults is None:
continue
else:
return recursiveResults
def __get_docs(self, folderName="", recursive=True):
folderId = ""
if folderName:
folderId = self.__get_folder_id(folderName)
if folderId is None:
return {}
if recursive:
return self.__get_documents_recursive(
folderId=folderId, currentLocation=folderName
)
data = self.__POST(f"documents/{folderId}")
return [item for item in data if "fileType" in item]
def __download(self, document, location="", overwrite=False):
filename = document[NAME_ATTRIBUTE]
if "/" in filename:
filename = filename.replace("/", "_")
if not os.path.exists(location):
os.makedirs(location)
try:
fileLocation = f"{location}/{filename}.pdf"
if os.path.isfile(fileLocation) and overwrite is False:
return True
binaryData = self.__POST(f"download/{document[ID_ATTRIBUTE]}/placeholder")
if isinstance(binaryData, dict):
print(f"Error trying to download {filename}: {binaryData}")
return False
with open(fileLocation, "wb") as outFile:
outFile.write(binaryData)
return True
except Exception as error:
print(f"Error trying to download {filename}: {error}")
return False
def sync(self, localFolder, remoteFolder="", overwrite=False):
if not os.path.exists(localFolder):
os.mkdir(localFolder)
documents = self.__get_docs(remoteFolder)
for doc in documents:
self.__download(
doc, f"{localFolder}/{doc['location']}", overwrite=overwrite
)
webInterface = RmWebInterfaceAPI()
#webInterface.sync("Root", overwrite=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment