Skip to content

Instantly share code, notes, and snippets.

@baptiste-roullin
Last active May 24, 2024 21:08
Show Gist options
  • Save baptiste-roullin/29459f021d3d0989461a681052d467d1 to your computer and use it in GitHub Desktop.
Save baptiste-roullin/29459f021d3d0989461a681052d467d1 to your computer and use it in GitHub Desktop.
Crude script to export Raindrop bookmarks and images into Eagle.app
# Crude, offensively non-pythonic script to export Raindrop bookmarks in Eagle.app, as URL objects or media.
# Needs a CSV export from a Raindrop collection. https://help.raindrop.io/export/
# The collection needs to be set to public.
import csv
from random import choice
import re
from sys import argv
from os import path, sep, mkdir
from datetime import datetime
import string
import requests
outputEncoding = "utf-8"
inputEncoding = "utf-8"
# the path to the IMAGES folder in your Eagle library.
libraryPath = ""
# beginning of the name of the folder of last item added to your Eagle Library,
# exemple:
# prefix = "LWJKQGX4"
# TODO: Automate this part
prefix = ""
chars = string.ascii_uppercase + string.digits
class PasswdDialect(csv.Dialect):
# field separator
delimiter = ","
# string separator
quotechar = '"'
escapechar = None
doublequote = True
lineterminator = "\n"
quoting = csv.QUOTE_MINIMAL
skipinitialspace = True
def retrieveFilePath(fileSendedByArg):
if path.isabs(fileSendedByArg): # is path absolute
return fileSendedByArg
else:
return path.dirname(path.abspath(__file__)) + sep + fileSendedByArg
def getInputFile():
if len(argv) > 1:
csvInputFile = retrieveFilePath(argv[1])
if not path.isfile(csvInputFile):
exit("the file " + csvInputFile + " doesn't exist")
print("Using :" + csvInputFile)
else:
print("You must provide path to an input file")
exit()
print("######################################")
print("# PARSING FILE : " + csvInputFile)
print("######################################")
return csvInputFile
def importItems(reader, count):
# The tricky, hacky, brittle part.
# From what I can tell, Eagle generate ids for each items this way
# [4 chars][4 chars][5 chars]
# The start seems linked to folders.
# For instance: LWJKQGX40BGQ4
# We can add stuff directly to the library folder and it will be recognized IFF:
# the metadata.json inside the item folder has the proper format
# AND it finds any .url file in the item folder.
# AND the first two part of the id is the same as some item recently
# So we generate as many ids as we need.
# And a bit more to account for potential duplicates from collisions of ids.
# Didn't I say it was hacky?
countWithSpare = count + 20
ids = [
prefix + "".join([choice(chars) for i in range(5)])
for j in range(countWithSpare)
]
# removing duplicates.
uids = set(ids)
for row in reader:
id = uids.pop()
title = re.sub("[,:'/\\\"\\n]", "", row["title"])
note = row["note"] or row["excerpt"]
url = row["url"]
thumb = row["cover"]
created = str(row["created"])[0:10]
tags = str(row["tags"].replace("-", " ").replace(", ", ",").split(",")).replace(
"'", '"'
)
btime = str(datetime.fromisoformat(created).timestamp()).split(".0")[0]
# creating folder
folderPath = path.join(libraryPath, id + ".info")
ext = "url"
if not path.exists(folderPath):
mkdir(folderPath)
if url.startswith("https://api.raindrop"):
res = requests.get(url, timeout=5)
contentType = res.headers["Content-Type"]
if contentType.startswith("image"):
ext = contentType.replace("image/", "")
filename = title + "." + ext
with open(path.join(folderPath, filename), "wb") as handler:
handler.write(res.content)
elif contentType.startswith("video"):
ext = contentType.replace("video/", "")
filename = "test" + "." + ext
with open(path.join(folderPath, filename), "wb") as handler:
handler.write(res.content)
else:
with open(path.join(folderPath, title + ".url"), "a") as file:
file.write(f"[InternetShortcut]\nURL={url}")
# Not bothering with a real JSON manipulation lib.
with open(path.join(folderPath, "metadata.json"), "a") as file:
file.writelines(
[
"{",
f'"id": "{id}",',
f'"name": "{title}",',
'"size": 56,',
f'"btime": {btime},',
'"mtime": 1716478593302,',
f'"ext": "{ext}",',
f'"tags": {tags},',
f'"folders": [],',
'"isDeleted": false,',
f'"url": "{url}",',
f'"annotation": "{note}",',
'"modificationTime": 1716478593300,',
'"height": 408,',
'"width": 720,',
'"lastModified": 1716479798004,',
'"palettes": [],',
f'"noThumbnail": false',
"}",
]
)
else:
# should not happen hashtag Famous Last Words.
print("folder already exists")
continue
csvInputFile = getInputFile()
with open(csvInputFile, mode="r", newline="", encoding=inputEncoding) as csvfile:
counter = csv.DictReader(csvfile, dialect=PasswdDialect())
count = sum(1 for _ in counter)
csvfile.seek(0)
# TODO : read file only once.
reader = csv.DictReader(csvfile, dialect=PasswdDialect())
importItems(reader, count)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment