Metadata-Version: 2.4
Name: sloppynotes
Version: 2.0.1
Summary: Simple Evernote client powered by a browser-captured web session
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: playwright>=1.58.0
Requires-Dist: requests>=2.32.5
Requires-Dist: thriftpy2>=0.6.0

# sloppynotes

A small Python library and CLI that reuses an authenticated Evernote web session without API keys.

Install the PyPI distribution as `sloppynotes`.

## What this package does

This project reuses an already authenticated Evernote web session and then talks to Evernote's legacy NoteStore APIs.

### How login works, and how this package avoids a separate API login

This package does not ask for Evernote API keys and does not implement a separate username/password login flow.
Instead, it reuses the login state that already exists in a Chromium-based browser.

High-level flow:

1. Launch or reuse a CDP-enabled Chromium/Chrome browser profile.
2. Check whether Evernote is already logged in in that browser.
3. Read the Evernote `auth` cookie from the logged-in browser session.
4. Extract the legacy monolith auth token and shard from that cookie.
5. Save them to a local `evernote-auth.json` file.

Reverse-engineered details:

1. The Evernote web client exchanges auth at:
   - `https://accounts.evernote.com/auth/token`
2. That response contains tokens such as:
   - `access_token`
   - `refresh_token`
   - `id_token`
3. The `access_token` payload contains `mono_authn_token`, which is the legacy auth token used by older Evernote services.
4. The same monolith token is also available in the `auth` cookie for `*.evernote.com`.
5. This package reads that browser cookie and saves the minimal auth data needed for later API calls.

### How this package reads notebooks and notes

After saving the auth token and shard, the package talks to Evernote's legacy `NoteStore` service.
The shard tells us which Evernote service URL to call, and the saved monolith token is used to authenticate those requests.

That is how the library and CLI can call methods such as:
- `listNotebooks`
- `findNotesMetadata`
- `getNote`

Playwright is only needed for the browser side of the flow.
It connects to a Chromium-based browser over CDP so this package can detect the logged-in Evernote session and read the `auth` cookie.
Once `evernote-auth.json` has been saved, the later NoteStore API calls do not depend on Playwright; they use the saved auth together with the Thrift client code.

### A short introduction to Thrift

Thrift is a remote procedure call and serialization system.
In practice, that means a client can call named backend methods such as `listNotebooks` and send structured request data in a format the server understands.

Evernote's older APIs use Thrift for services such as `NoteStore` and `UserStore`.
This project uses Python Thrift support to serialize those requests and parse the responses.

Legacy Evernote NoteStore/UserStore model:
- https://dev.evernote.com/doc/articles/core_concepts.php

## Usage

### Install

```bash
uv run --with sloppynotes -- evernote login
```

### Login flow

For Faltoobot, reuse the shared browser so Evernote cookies come from the same browser session:

```bash
faltoobot browser https://www.evernote.com/client/web
uv run --with sloppynotes -- evernote login --use-existing-browser
```

You can also let `evernote login` launch its own browser profile when you are not using Faltoobot:

```bash
evernote login  # saves login credentials to ~/.sloppynotes/evernote-auth.json
```

### Usage from Python

```python
from evernote.client import get_note, get_notes, get_notebooks, search_notes

notebooks = get_notebooks()
matching_notes = search_notes('intitle:"Project"')
notes = get_notes(notebooks[0])
note = get_note(notes[0])
```

These helpers return plain Python dictionaries.
Typical shapes look like this:

```python
notebooks = get_notebooks()
notebook = notebooks[0]
print(notebook)
# {
#   "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
#   "guid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
#   "name": "Work",
#   "defaultNotebook": False,
#   "serviceCreated": 1710000000000,
#   "serviceUpdated": 1710000000000,
#   "updateSequenceNum": 12345,
# }

notes = get_notes(notebook)
note_summary = notes[0]
print(note_summary)
# {
#   "id": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
#   "guid": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
#   "title": "Project plan",
#   "contentLength": 2048,
#   "created": 1710000000000,
#   "updated": 1710003600000,
#   "notebookGuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
#   "tagGuids": ["zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz"],
#   "updateSequenceNum": 23456,
# }

note = get_note(note_summary)
print(note)
# {
#   "id": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
#   "guid": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
#   "title": "Project plan",
#   "content": "<?xml version=...>",
#   "content_text": "Project plan\n- task 1\n- task 2",
#   "contentLength": 2048,
#   "created": 1710000000000,
#   "updated": 1710003600000,
#   "notebookGuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
#   "tagGuids": ["zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz"],
#   "updateSequenceNum": 23456,
# }
```

Some optional keys may be missing when Evernote does not return them.
