reMarkable × NYT Crossword
When I saw that reMarkable had announced a new color and frontlit version of their tablet, I immediately went to buy it. I usually try to sleep on new gadget purchases to see if the allure of novelty fades. Ultimately most new gadgets don’t end up making my life better, so it’s best for me to avoid them. However, the reMarkable 2 has been helpful for all Bird Cartel projects. I frequently brainstorm ideas and sketch out screens on it. Even so, I decided to refrain from the purchase since I hadn’t been using my reMarkable much lately.
As a tactic to encourage myself to use my reMarkable more, I decided it would be fun to play New York Times Crosswords on it. I’d have the flexibility of pencil and paper with the added benefit of two-finger undo.
I wasn’t the first person to think this was a great use of the tablet. In fact, I found two different solutions to do the same thing. Unfortunately, it seems that reMarkable changes their URLs and APIs a fair bit and both solutions no longer worked. I decided to spend a few hours to see if I could do it. My plan was simple: each morning, download a PDF puzzle and upload it to the reMarkable cloud to sync with my tablet.
NYT Crossword
As an initial test, I went to NYT’s crossword website and printed a puzzle to PDF and transferred it to my reMarkable via their web site. It played great! After some sleuthing, I found NYT hosts their puzzles in a structured way:
https://www.nytimes.com/svc/crosswords/v2/puzzle/print/Sep0224.pdf
However, you need to be logged in with a subscriber’s account to see these puzzles. With a little help from Chrome Dev Tools, I pulled my NYT cookie. After a little remembering how to write some node.js, I used my cookie in a request and was able to fetch the puzzle and save it as a PDF to my local machine:
axios({
method: 'get',
url: 'https://www.nytimes.com/svc/crosswords/v2/puzzle/print/Sep0224.pdf',
responseType: 'arraybuffer',
responseEncoding: 'binary',
headers: {
'Cookie': nytCookie,
'Accept': '/',
'Connection': 'keep-alive'
}
})
.then(function (response) {
fs.writeFileSync('./crossword.pdf', response.data, 'binary');
});
Step 1 complete! Step 2 proved far more difficult!
reMarkable
I started by trying out some unofficial reMarkable API wrappers in various languages, but all the libraries I could find were 2+ years old and no longer functioned. I ended up using a combination of mitmproxy and Chrome Dev Tools to snoop the URL endpoints and my bearer token. reMarkable have a path to go from a user-facing code to bearer token, but I figured since I had mine already and I wasn’t productizing this solution, I would just use the token directly.
To avoid cluttering the root of my reMarkable with files, I wanted to push the
PDFs to a specific directly. I inspected a few web requests when I uploaded
files to different folders. I noticed one header, rm-meta
, changed as I
targeted different folders. The value of that header ended in a =
, the
tell-tale sign of base64. I decoded it and sure enough:
{"parent":"xxxxxxxx-52ab-48c5-99aa-xxxxxxxxxxxx","file_name":"crossword"}
It contained a GUID of the directory that I was targeting and a file name string
used by the reMarkable to display the file. If you’re looking to recreate this
project, the simplest way is to go to your
My Files page on remarkable.com, open
Chrome Dev Tools, and then add a new folder (e.g. Crosswords). You’ll see a few
network requests to a /files
endpoint. Inspecting the request of the final one
will yield your token (from the Authorization
header) and the directory GUID
(search for your folder name in the response and look for the ID).
I also noticed the header rm-source
, which tells the server where the file is
coming from (mobile, desktop, web, etc.). I pass this as WebLibrary
to mimic
the website. I didn’t test if this was required, but I don’t want to arouse too
much suspicion.
The complete file upload looks like:
var rmMetadata = { "parent": rmFolderGuid, "file_name": puzzleFriendlyName };
request({
url: 'https://web.eu.tectonic.remarkable.com/doc/v2/files',
method: 'POST',
headers: {
'Content-Type': 'application/pdf',
'Accept': '*/*',
'Authorization': rmToken,
'rm-meta': btoa(JSON.stringify(rmMetadata)),
'rm-source': 'WebLibrary',
'Content-Disposition': `file; filename="${puzzleName}.pdf"`
},
encoding: null,
body: response.data
}, (error, response, body) => {
if (error) {
console.log('Failed: ', error);
} else {
console.log(JSON.parse(response.body.toString()));
}
});
A few minor notes on this code. First, the filename in Content-Disposition
doesn’t seem to be used anywhere. And second, I never pass the filesize and
apparently I should because in the reMarkable UI, it shows the working PDFs as 0
bytes! I’m surprised that this value isn’t calculated locally, but is instead
part of some metadata that is passed around.
val town
Now that I had the puzzle downloading and re-uploading, I needed to find a way to run it every day. I don’t have a desktop or any machine besides some Raspberry Pis that are running 24/7 so I was looking for a web solution. I had heard of val town, but never had an excuse to use it. Their process and pricing made it a no-brainer. I could have a private val to protect my secrets and it used basically the same Javascript I had already written, I just needed to tweak some import statements. You can view a sanitized public val of the complete solution here.
I really enjoyed using val town and will be looking for more opportunities in the future. Specifically their cron syntax helper linked me over to CronGPT which is a legitimately useful AI product.
With my cron job properly configured, I eagerly awaited the next morning. To my delight, it was just as I’d hoped: I unlocked my reMarkable and had today’s crossword waiting for me!
E-Ink Crosswords
As it turns out, crossword puzzles are a great application for e-ink tablets like the reMarkable. One of my favorite parts is being able to cleanly or sloppily cross out clues and in general just markup the page. I also found out reMarkable supports layers so I can even let another person play the same crossword after I’m done! With the PDFs weighing in at 40-60KB, I can walk around with a huge archive of puzzles at all times.
Piping Data
Hacking on this project got me thinking about data and ownership and inputs and outputs. I’m disappointed that the data I pay for and the device I pay for can’t coexist using the plethora of standards we have. While I was proud to get everything working and to use my programming skills to improve my real life (a rare situation!), I’m disappointed that it takes a few hours on the weekend to come up with a one-off solution just to wire these things together. Most regular people don’t have the know-how to do this. Additionally, my cookie and token will expire at a random time and I’ll have to come back and service this code to keep it from rotting away.
There have been awesome efforts here in the past (IFTTT, Yahoo! Pipes, iOS Shortcuts, macOS Automator), but I wish we were actively encouraged to combine and remix the data and functionality we pay for. The idea that a handful of people will come up with the most universal design that fits everyone—or that one such universal design even exists—seems unlikely.
I understand the business realities of this. No company is incentivized to provide open access. In fact, it’s more work and potentially a larger risk to do so. However, it’s still attractive to think about how interesting the world would be if we could tailor our digital experiences to exactly meet our needs and share those recipes along the way. Not to mention the knock-on effects for curious minds to be able to get a peek behind the curtain. I’m sure a generation of electrical engineers took their first steps when they could open up a radio and see the schematic printed on the panel inside. The thrill of repairing, breaking, and augmenting your setup should be an inspiring rite of passage.
Regardless of the loftier thoughts, it was a great feeling waking up Monday morning and seeing the newest crossword ready for me on my reMarkable. Even better was the satisfaction of knowing I could now justify getting the Paper Pro—how else could I solve crosswords in the dark?