Skip to content

Python client for ETAPI of Trilium Note. Trilium 的 Python版 ETAPI 客户端

License

Notifications You must be signed in to change notification settings

Nriver/trilium-py

Repository files navigation

🐍 trilium-py

English | 简体中文

Python client for ETAPI of Trilium Note.

Downloads Supported Versions Supported Versions PyPI license Maintenance

🦮 Table of Contents

🔧 Installation

python3 -m pip install trilium-py --user

📖 (Basic) Usage

These are basic function that Trilium's ETAPI provides. Down below are some simple example code to use this package.

🚀 Initialization

If you have a ETAPI token, change the server_url and token to yours.

from trilium_py.client import ETAPI

server_url = 'http://localhost:8080'
token = 'YOUR_TOKEN'
ea = ETAPI(server_url, token)

If you haven't created ETAPI token, you can create one with your password. Please note, you can only see this token once, please save it if you want to reuse the token.

from trilium_py.client import ETAPI

server_url = 'http://localhost:8080'
password = '1234'
ea = ETAPI(server_url)
token = ea.login(password)
print(token)

After initialization, you can use Trilium ETAPI with python now. The following are some examples.

📊 Application Information

To start with, you can get the application information like this.

print(ea.app_info())

It should give you the version of your server application and some extra information.

🔍 Search note

Search note with keyword.

res = ea.search_note(
    search="python",
)

for x in res['results']:
    print(x['noteId'], x['title'])

Search with regular expression. For example, search and get all child notes under certain note:

res = ea.search_note(
    # regular expression search for note title
    search="note.title %= '.*'",
    ancestorNoteId="Parent Note ID",
    fastSearch=False,
    orderBy=["title"],
    limit=100,
)

Note: limit will not work without orderBy.

🏭 Create Note

You can create a simple note like this.

res = ea.create_note(
    parentNoteId="root",
    title="Simple note 1",
    type="text",
    content="Simple note example",
    noteId="note1"
)

The noteId is not mandatory, if not provided, Trilium will generate a random one. You can retrieve it in the return.

noteId = res['note']['noteId']

🖼️ Create Image note

Image note is a special kind of note. You can create an image note with minimal information like this. The image_file refers to the path of image.

res = ea.create_image_note(
    parentNoteId="root",
    title="Image note 1",
    image_file="shield.png",
)

👀 Get note

To retrieve the note's content.

ea.get_note_content("noteid")

You can get a note metadata by its id.

ea.get_note(note_id)

🔄 Update note

Update note content

ea.update_note_content("noteid", "updated by python")

Modify note title

ea.patch_note(
    noteId="noteid",
    title="Python client moded",
)

🗑️ Delete note

Simply delete a note by id.

ea.delete_note("noteid")

📅 Day note

You can get the content of a certain date with get_day_note. The date string should be in format of "%Y-%m-%d", e.g. " 2022-02-25".

ea.get_day_note("2022-02-25")

Then set/update a day note with set_day_note. The content should be a (html) string.

ea.set_day_note(date, new_content)

📤 Export note

Export note comes in two formats html or markdown/md.

res = ea.export_note(
    noteId='sK5fn4T6yZRI',
    format='md',
    save_path='/home/nate/data/1/test.zip',
)

📥 import note

This is the built-in feature in trilium. The input file should be a zip file.

res = ea.export_note(
    noteId='sK5fn4T6yZRI',
    file_path='/home/nate/data/1/test.zip',
)

💾 Save revision

Save note revision manually.

res = ea.save_revision(
    noteId='MJzyFRXAVaC9',
)

💾 Create data backup

This example will create a database backup file like this trilium-data/backup/backup-test.db.

res = ea.backup("test")

You can use the cron utility in Linux to schedule regular automatic backups. For example, to set up a daily backup at 3: 00 AM, you would use the following cron expression:

0 3 * * * python /path/to/backup-script.py

🏷 Create attribute

You can create a tag for a note

res = ea.create_attribute(
    noteId='noteid',
    type='label',
    name='name_of_the_tag',
    value='value_of_the_tag',
    isInheritable=True
)

The noteId is not mandatory, if not provided, Trilium will generate a random one. You can retrieve it in the return.

noteId = res['note']['noteId']

Get attachment list

Get all attachments of a single note.

res = ea.get_attachments('uMJt0Ajr1CuC')

Get attachment info

Get image title and etc.

res = ea.get_attachment('Y5V6pYq6nwXo')

Update attachment info

Change image title and etc.

res = ea.update_attachment(
    attachmentId='2b7pPzqocS1s', title='hello etapi', role='image', mime='image/png'
)

Get attachment content

Get the real image file

res = ea.get_attachment_content('icpDE4orQxlI')
with open('1.png', 'wb') as f:
    f.write(res)

Update attachment content

Replace the image with new one

res = ea.update_attachment_content('icWqV6zFtE0V', '/home/nate/data/1.png')

Create attachment

Upload a image file as attachment of a note.

res = ea.create_attachment(
    ownerId='8m8luXym5LxT',
    file_path='/home/nate/data/ksnip_20230630-103509.png',
)

(Advanced Usage) ✅ TODO List

With the power of Python, I have expanded the basic usage of ETAPI. You can do something with todo list now.

Add TODO item

You can use add_todo to add a TODO item, param is the TODO description

ea.add_todo("买暖宝宝")

Check/Uncheck a TODO item

param is the index of the TODO item

ea.todo_check(0)
ea.todo_uncheck(1)

Update a TODO item

Use update_todo to update a TODO item description at certain index.

ea.update_todo(0, "去码头整点薯条")

Delete a TODO item

Remove a TODO item by its index.

ea.delete_todo(1)

Move yesterday's unfinished todo to today

As the title suggests, you can move yesterday's unfinished things to today. Unfinished todo's will be deleted from yesterday's note.

ea.move_yesterday_unfinished_todo_to_today()

(Advanced Usage) 🚚 Upload Markdown files

Upload single Markdown file with images

You can import Markdown file with images into Trilium now! Trilium-py will help you to upload the images and fix the links for you!

res = ea.upload_md_file(
    parentNoteId="root",
    file="./md-demo/manjaro 修改caps lock.md",
)

Bulk upload Markdown files in a folder

You can upload a folder with lots of Markdown files to Trilium and preserve the folder structure!

Import from VNote

Say, upload all the notes from VNote, simply do this:

res = ea.upload_md_folder(
    parentNoteId="root",
    mdFolder="~/data/vnotebook/",
    ignoreFolder=['vx_notebook', 'vx_recycle_bin', 'vx_images', '_v_images'],
)

Import from Joplin

Joplin can be imported effortlessly.

res = ea.upload_md_folder(
    parentNoteId="root",
    mdFolder="/home/nate/data/joplin_data/",
    ignoreFolder=['_resources', ],
)

Import from Logseq

res = ea.upload_md_folder(
    parentNoteId="root",
    mdFolder="/home/nate/data/logseq_data/",
    ignoreFolder=['assets', 'logseq'],
)

Import from Obsidian

Obsidian has a very unique linking system for files. You should use obsidian-export to convert a Obsidian vault to regular Markdown files. Then you should be able to import the note into Trilium with trilium-py.

Convert it first.

obsidian-export /path/to/your/vault /out

Then import just like a normal markdown, trilium-py will handle the images for you.

res = ea.upload_md_folder(
    parentNoteId="root",
    mdFolder="E:/data/out",
)

Import from Youdao Note/有道云笔记

Youdao does not provide an export feature anymore. Luckily, you can use https://github.com/DeppWang/youdaonote-pull to download your notes and convert them into markdown files. After that, trilium-py should be able to help you import them.

res = ea.upload_md_folder(
    parentNoteId="root",
    mdFolder="/home/nate/gitRepo/youdaonote-pull/out/",
)

Import from Turtl

You need to convert Turtl from json to markdown first. See turtl-to-markdown for details.

Then you can import with trilium-py like this:

res = ea.upload_md_folder(
    parentNoteId="root",
    mdFolder="/home/nate/gitRepo/turtl-to-markdown/out/",
    ignoreFolder=['_resources'],
)

Import from other markdown software

In general, markdown files have variety of standards. You can always try import them with

res = ea.upload_md_folder(
    parentNoteId="root",
    mdFolder="/home/nate/data/your_markdown_files/",
)

If there is any problem, please feel free to create an issue.

(Advanced Usage) 🎨 Beautify notes

Because of the constraints imposed by the library utilized by Trilium, imported notes may experience minor formatting problems. These issues include an additional line appearing at the end of code blocks, images becoming integrated with the note content, and the absence of line breaks between headings, resulting in a cramped appearance of the note content.

Here is what you can do to beautify your note.

Beautify a note

Specify a note id to beautify note content.

ea.beautify_note('krm8B9JthNfi')

Beautify a note and its child notes

ea.beautify_sub_notes('tlPuzU2szLJh')

(Advanced Usage) 🧹 Sort note content

Sort a note by the heading names. This feature could prove invaluable for notes containing extensive lists, such as book titles sorted into various genres. It's equally useful for managing browser bookmarks or collecting links.

Additionally, you have the option to specify a language code for sorting based on your local language. This enhances the sorting process and tailors it to your linguistic preferences.

res = ea.sort_note_content('lPxtkknjR2bJ')
res = ea.sort_note_content('y6hROhWjNmHQ', 'zh_CN.UTF-8')

(Advanced Usage) 🧹 Delete empty new note

Sometimes I inadvertently create numerous "new notes" which remain undeleted within my note tree. These "new notes" clutter my workspace, scattered across various locations. I made this bulk deletion of these empty "new notes." Additionally, it generates warning messages for "new notes" that contain content, maybe we should change the title for those notes.

ea.delete_empty_note()

(Advanced Usage) 🗜️ Optimize image size

Try to reduce image size by using PIL's optimize feature. If the image in your note is not compressed, you can try this. I've successfully convert a note of 44MB to 9.9MB after this process. Backup your data before try this.

The default quality is set to 90.

optimize_image_attachments will keep the original image format and try to compress it.

ea.optimize_image_attachments('uMJt0Ajr1CuC')

To save even more space, you can try the following method.

The optimize_image_attachments_to_webp function converts images to the WebP format, significantly reducing file sizes. Based on my experience, WebP images can be as little as 25% to 50% of the size of PNG images.

ea.optimize_image_attachments_to_webp('H2q3901uFDCH')

This action can save significant space if you have many clipped pages. Whoever invented WebP is a genius.

(Advanced Usage) 🔗 Automatically Add Internal Links

This feature allows you to automatically create internal links within your notes. Let’s take a look at how it works.

Example

Here is a sample note:

auto-link-1

After running a single line of code:

auto_create_internal_link('put_note_id_here')

The note transforms into this:

auto-link-2

As you can see, some text has been replaced with internal links. The feature follows these rules:

  • Title Match: Content that matches any other note's title is replaced with an internal link.
  • Duplicate Titles Ignored: If multiple notes share the same title, no link is created for that title.
  • Longer Matches First: Longer titles take precedence. For example, in the above example, "Nate River" is linked, not just "River."
  • Existing Links Remain: Pre-existing links in the text are left untouched.

However, some words like "make" and "work" in the example are part of my "English Words That I Do Not Know" note. Since they are common and frequently used, I don’t want them to create excessive internal links.

Excluding Notes from Internal Linking

To prevent certain notes from being linked:

  • Add the tag #ignoreAutoInternalLink to a note. This note (and optionally its sub-notes) will be excluded from link creation.
  • You can make it inheritable—you can apply it to a parent note and inheritable, then it will be automatically exclude all its sub-notes.

Here’s how it looks after applying the exclusion rule:

auto-link-3

The result is cleaner and more intentional.

Special Case: Duplicate Titles

When multiple notes share the same title, a specific condition allows for internal links:

  • Direct Sub-Notes Have Priority: Direct child notes take precedence over other notes with the same title.

For instance:

auto-link-4

In this case, the note TriliumNext links "How to compile" to its own child note, not the one from Trilium.

Final Rule: No Self-Linking

A note will never create an internal link to itself.


Code Samples

Add an internal link to a specific note by its ID:

auto_create_internal_link('gLmmsIM8yPqx')

Add internal links for multiple notes:

auto_create_internal_link(target_notes=['gLmmsIM8yPqx', 'T4Ui3wNByO03'])

(Experimental - Use with Caution)
Add internal links to all text notes:

This is an experimental feature. Backup your database before using it, as it may irreversibly modify your notes. If issues occur, please provide a minimal note sample to help debug.

auto_create_internal_link(process_all_notes=True)

🛠️ Develop

Install with pip egg link to make package change without reinstall.

python -m pip install --user -e .

🔗 Original OpenAPI Documentation

The original OpenAPI document is here. You can open it with swagger editor.