Skip to content

Instantly share code, notes, and snippets.

@westonkd
Created March 19, 2023 02:41
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save westonkd/7aa5a9da24d3360084c2d9fa47f71a88 to your computer and use it in GitHub Desktop.
Save westonkd/7aa5a9da24d3360084c2d9fa47f71a88 to your computer and use it in GitHub Desktop.
// Main function
function backupNotebooks() {
const urls = notebookUrlsFromInterval();
uploadNotebooks(urls)
}
/* Gmail Concerns */
const intervalDays = 1
const threadFilter = `from:do-not-reply@amazon.com "kindle" newer_than:${intervalDays}d`
function threadsFromLastInterval() {
return GmailApp.search(threadFilter)
}
/* Google Drive Concerns */
const folderName = "Kindle Scribe Notes"
function uploadNotebooks(urls) {
const folder = notebookFolder()
uploadFilesToFolder(urls, folder)
}
function uploadFilesToFolder(urls, folder) {
urls.forEach((url) => {
const blob = UrlFetchApp.fetch(url).getBlob()
const file = folder.createFile(blob)
const name = filenameFor(url)
file.setName(name)
file.setDescription(`Backup of #{name} from Kindle Scribe`)
})
}
function filenameFor(url) {
var match = /^https?:\/\/([^\/]+)\/([^?]*\/)?([^\/?]+)/.exec(url);
if (match) {
return decodeURIComponent(match[3]);
}
return "unkown_name"
}
function notebookFolder() {
let folders = DriveApp.getFoldersByName(folderName)
console.log(`folders: ${folders.hasNext()}`)
if (!folders || !folders.hasNext()) {
return DriveApp.createFolder(folderName)
}
return folders.next()
}
/* URL Concerns */
function notebookUrlFrom(message) {
const notebookUrlPattern = /U=(https%3A%2F%2Fkindle-content-requests-prod\.s3\.amazonaws\.com.*Signature%3[a-zA-Z0-9]{65})/
const matches = message.match(notebookUrlPattern)
if (!matches) return
return matches[1]
}
function notebookUrlsFromInterval() {
return threadsFromLastInterval().map((thread) => {
return thread.getMessages().map((message) => notebookUrlFrom(message.getPlainBody()))
}).flat().filter((url) => !!url).map((url) => decodeURIComponent(url))
}
@westonkd
Copy link
Author

This script does the following:

  1. Searches all Gmail threads for messages from Amazon indicating a Kindle Scribe notebook has been shared
  2. Iterates through all messages in that thread and extracts the links to those notebooks. These links are valid for seven days.
  3. Downloads the notebooks pointed to by the extracted links
  4. Finds or creates the folder in the user's Google Drive folder named "Kindle Scribe Notes"
  5. Uploads the notebook PDF to this folder in Google Drive

@curioso5288
Copy link

Hi Weston,

First of all, thank you for sharing this code. I'm really a newbie so I'd like to ask for clarification about the point 2 of your comment.
Can you expand on when you said, "These links are valid for seven days." Which part of the code set it to be valid just for 7 days? If valid for a limited time it means that are stored in someplace? Thank you for your kind reply.

@westonkd
Copy link
Author

westonkd commented Oct 26, 2023

Hi @curioso5288!

Which part of the code set it to be valid just for 7 days?
This expiration is actually determined by Amazon. When you share a notebook via email, the link they provide to the notebook within

that email is only valid for seven days. There is not a way to modify this expiration time.

If valid for a limited time it means that are stored in someplace?

Yep! Amazon stores it in their cloud. In-particular, an AWS service called S3. To get another link to the notebook you'll need to re-share it

BTW I've not looked at the state of the share emails Amazon sends in quite some time. It's possible that this script no longer works if they've changed it in a significant way.

@trimonkee
Copy link

HI,

Thank you for this. The const matches = message.match(notebookUrlPattern)
is now failing to match for some reason. I added logging to see the message data and it looks like it should work but matches is always null.

Any thoughts on that ?

@trimonkee
Copy link

This is absolute crap code but its working for now. I'mnot able to find good docs on the match function.

Here ya go

/* URL Concerns */
function notebookUrlFrom(message) {

Logger.log(message)
//const notebookUrlPattern = /U=(https%3A%2F%2Fkindle-content-requests-prod.s3.amazonaws.com.*Signature%3[a-zA-Z0-9]{65})/
//const matches = message.match(notebookUrlPattern)

const match1 = message.match(/https/)

if (!match1) return

const Start = match1.index

Logger.log(match1)

const match2 = message.match(/)Quest/)

if (!match2) return

const End = match2.index

const Url = message.substring(Start,End)

Logger.log(Url)

return(Url)

//return matches[1]
}

@mandbru99
Copy link

Thanks for posting that @trimonkee . Is this why it would be creating the folder in google drive and not saving the file there?

@thomasjudd
Copy link

thomasjudd commented Apr 23, 2024

@trimonkee This is my fix, for some reason it was tripping up on matching alphanumerics. Not ideal, but it works

function notebookUrlFrom(message) {
  const matches = message.match(/https:\/\/kindle\-content\-requests\-prod.s3.amazonaws.com.*Signature\=.{64}/)
  
  console.log(`${matches[0]}`)

  if (!matches) {
    return
  }
  return matches[0]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment