Skip to content

Commit

Permalink
add support for qownnotes
Browse files Browse the repository at this point in the history
  • Loading branch information
marph91 committed Sep 13, 2024
1 parent 6ab0a7f commit c0fbc0f
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 0 deletions.
19 changes: 19 additions & 0 deletions docs/formats/qownnotes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
This page describes how to import notes from QOwnNotes to Joplin.

## General Information

- [Website](https://www.qownnotes.org/)
- Typical extension: Folder with `.md` files (notes), a `media` subfolder (containing resources) and a `notes.sqlite` file (containing tags)

## Instructions

1. [Install jimmy](../index.md#installation)
2. Import to Joplin. Example: `jimmy-cli-linux qownnotes_folder/ --format qownnotes`

## Import Structure

- Markdown style links (`[Link to Markdown Cheatsheet](Markdown Cheatsheet.md)`) and QOwnNotes style links (`<Markdown Cheatsheet.md>`) as described [here](https://www.qownnotes.org/getting-started/markdown.html#links) are converted.

## Known Limitations

- Encrypted notes aren't converted.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ nav:
- jrnl: formats/jrnl.md
- Notion: formats/notion.md
- Obsidian: formats/obsidian.md
- QOwnNotes: formats/qownnotes.md
- Simplenote: formats/simplenote.md
- Standard Notes: formats/standard_notes.md
- Synology Note Station: formats/synology_note_station.md
Expand Down
103 changes: 103 additions & 0 deletions src/formats/qownnotes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""Convert QOwnNotes notes to the intermediate format."""

from collections import defaultdict
from pathlib import Path
import re
import sqlite3
from urllib.parse import unquote

import common
import converter
import intermediate_format as imf


class Converter(converter.BaseConverter):
accept_folder = True

qownnote_link_re = re.compile(r"<(.*?.md)>")

def handle_markdown_links(self, body: str) -> tuple[list, list]:
# markdown style links
note_links = []
resources = []
for link in common.get_markdown_links(body):
if link.is_web_link or link.is_mail_link:
continue # keep the original links
if link.url.endswith(".md"):
# internal link
note_links.append(
imf.NoteLink(str(link), Path(unquote(link.url)).stem, link.text)
)
else:
# resource
resources.append(
imf.Resource(self.root_path / link.url, str(link), link.text)
)

# qownnote style links
# https://www.qownnotes.org/getting-started/markdown.html#internal-links
for link in self.qownnote_link_re.findall(body):
note_links.append(imf.NoteLink(f"<{link}>", Path(unquote(link)).stem, link))

return resources, note_links

def parse_tags(self):
"""Parse tags from the sqlite DB."""
assert self.root_path is not None
db_file = self.root_path / "notes.sqlite"
if not db_file.is_file():
self.logger.debug(f"Couldn't find {db_file}")
return {}

tag_id_name_map = {}
note_tag_map = defaultdict(list)
conn = sqlite3.connect(db_file)
try:
cur = conn.cursor()

# check version
cur.execute("SELECT * FROM appData")
for name, value in cur.fetchall():
if name == "database_version" and value != "15":
self.logger.warning(f"Untested DB version {value}")

# get tags
cur.execute("SELECT * FROM tag")
for tag_id, tag_name, *_ in cur.fetchall():
tag_id_name_map[tag_id] = tag_name

# get related notes and assign the tags
cur.execute("SELECT * FROM noteTagLink")
for _, tag_id, note_id, *_ in cur.fetchall():
note_tag_map[note_id].append(
imf.Tag({"title": tag_id_name_map[tag_id]}, tag_id)
)
except sqlite3.OperationalError as exc:
self.logger.warning("Parsing the tag DB failed.")
self.logger.debug(exc, exc_info=True)
return {}
finally:
conn.close()
return note_tag_map

def convert(self, file_or_folder: Path):
self.root_path = file_or_folder

note_tag_map = self.parse_tags()

for note_qownnotes in file_or_folder.glob("*.md"):
note_body = note_qownnotes.read_text()

resources, note_links = self.handle_markdown_links(note_body)
note_joplin = imf.Note(
{
"title": note_qownnotes.stem,
"body": "\n".join(note_body.split("\n")[3:]), # TODO: make robust
**common.get_ctime_mtime_ms(note_qownnotes),
"source_application": self.format,
},
tags=note_tag_map.get(note_qownnotes.stem, []),
resources=resources,
note_links=note_links,
)
self.root_notebook.child_notes.append(note_joplin)
1 change: 1 addition & 0 deletions test/example_commands.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ $EXECUTABLE "$CACHE/jrnl/myjournal.json" --format jrnl
$EXECUTABLE "$CACHE/nimbus_note/Food" --format nimbus_note
$EXECUTABLE "$CACHE/notion/72a2f31c-3a46-4b44-826d-ae046e693551_Export-d609fb9f-43a4-475d-ba88-1db3e9e6bcd2.zip" --format notion
$EXECUTABLE "$CACHE/obsidian/obsidian_vault" --format obsidian
$EXECUTABLE "$CACHE/qownnotes/qownnotes_folder" --format qownnotes
$EXECUTABLE "$CACHE/simplenote/notes.zip" --format simplenote
$EXECUTABLE "$CACHE/standard_notes/Standard Notes Backup - Sun Apr 28 2024 12_56_55 GMT+0200.zip" --format standard_notes
$EXECUTABLE "$CACHE/synology_note_station/notestation-test-books.nsx" --format synology_note_station
Expand Down

0 comments on commit c0fbc0f

Please sign in to comment.