mirror of
https://gitverse.ru/kpa39l/email-assistant.git
synced 2026-09-29 09:15:09 +00:00
fix: архивация/вложения больше не помечают письма прочитанными (Seen)
Причина: himalaya message read и attachment download используют IMAP BODY[], который по RFC 3501 выставляет \Seen на сервере (Microsoft Exchange). Пользователь: письма в ящике после скачивания становятся прочитанными. Фикс: - чтение тела: himalaya message read --preview (не ставит Seen) - вложения: fetch_attachments_imaplib() — сырой IMAP stdlib (socket+ssl), UID FETCH (BODY.PEEK[]), папки в modified UTF-7, литералы до 1.5МБ, MIME-encoded words, фолбэк himalaya + flag remove seen Проверено живьём: UID 14200 (INBOX, docx 1.1МБ) флаги ()->() — Seen не выставлен. Openspec: change no-mark-seen-on-archive заархивирован (2026-09-14-no-mark-seen-on-archive), 7/7 validate OK
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# Design: не помечать письма прочитанными при архивации
|
||||
|
||||
## Контекст
|
||||
|
||||
- Сервер: Microsoft Exchange IMAP4 (`mail.corpoffice.tech:143`, STARTTLS).
|
||||
- himalaya v1.2.0.
|
||||
- Проверено на живом письме (UID 320, INBOX):
|
||||
- `himalaya message read` — ставит `\Seen` (документировано; есть флаг `--preview`).
|
||||
- `himalaya attachment download` — флага против Seen НЕТ (help проверен).
|
||||
- Raw `FETCH ... BODY.PEEK[]` (сырой IMAP) — НЕ ставит `\Seen` (проверено:
|
||||
флаги `()` до и после чтения тела 16430 байт).
|
||||
|
||||
## Решение
|
||||
|
||||
### 1. Чтение тела письма (get_email_content, mail_archive.py)
|
||||
|
||||
Было:
|
||||
```python
|
||||
HIMALAYA_CMD + ["message", "read", str(uid), "--folder", folder] + header_args
|
||||
```
|
||||
Стало:
|
||||
```python
|
||||
HIMALAYA_CMD + ["message", "read", str(uid), "--folder", folder, "--preview"] + header_args
|
||||
```
|
||||
`--preview` документирован: «Read the message **without** applying the "seen" flag».
|
||||
|
||||
### 2. Скачивание вложений (get_attachments, mail_archive.py)
|
||||
|
||||
`himalaya attachment download` ставит Seen, а `--preview` у него нет. Обходные
|
||||
варианты:
|
||||
|
||||
**A. Сырой IMAP через stdlib `imaplib` (выбрано).**
|
||||
Реализовать `fetch_attachments_imaplib(uid, folder, dest_dir)`:
|
||||
1. Читать `account`/`password` из конфига himalaya (`~/.config/himalaya/config.toml`,
|
||||
секция `[accounts.<default>]`, поля `backend.*`, пароль — `backend.auth.raw`).
|
||||
2. `imaplib.IMAP4(host, 143)` + `starttls()` + `login()`.
|
||||
3. `SELECT folder` (НЕ readonly — у Exchange readonly-режим может помешать
|
||||
корректному FETCH, проверить; PEEK работает в любом режиме).
|
||||
4. `UID FETCH <uid> (BODY.PEEK[])` — uid = номер письма **в папке** (как у нас
|
||||
в структуре архива — он и есть UID, см. ниже).
|
||||
5. Парсинг `email.message_from_bytes`, сбор частей с `get_filename()` или
|
||||
`content-disposition: attachment`, запись в `dest_dir`.
|
||||
|
||||
Преимущества: убирает himalaya из критического пути (лечит и зависания),
|
||||
гарантированно не ставит Seen. Недостатки: дублируется логика himalaya
|
||||
(пароль в конфиге, parsing) — но конфиг-формат стабилен, парсётся stdlib tomllib.
|
||||
|
||||
**Б. himalaya + выставление Seen обратно после скачивания.**
|
||||
`himalaya attachment download`, затем `himalaya flag remove <uid> --folder <folder> --flag seen`.
|
||||
Минусы: на время скачивания письмо становится прочитанным (мгновенно, но
|
||||
заметно на стороне Exchange-уведомлений); двойное обращение к IMAP; если
|
||||
скачивание упадёт — письмо останется Seen.
|
||||
|
||||
Выбрано **А** (сырой IMAP): единственный вариант, который вообще не трогает
|
||||
флаги. При этом `--preview` для тела — совместимость с himalaya-чтением.
|
||||
|
||||
### 3. Мелочи
|
||||
|
||||
- Оба места правятся в `mail_archive.py`; `email_classifier.py` и
|
||||
`email_handlers.py` не трогаем (они читают локальные `email.md`, не IMAP).
|
||||
- Пароль: НЕ логировать, НЕ выводить. Имя переменной — `imap_password`.
|
||||
- Фолбэк: если `fetch_attachments_imaplib` падает (сервер не отдаёт PEEK) —
|
||||
fallback на старый `himalaya attachment download` + `flag remove` (вариант Б).
|
||||
|
||||
## Проверка
|
||||
|
||||
1. `python3 -m py_compile scripts/mail_archive.py`
|
||||
2. На живом письме UID 320 (непрочитанное, INBOX):
|
||||
- `himalaya message read 320 --preview` → флаги остаются `()`.
|
||||
- `fetch_attachments_imaplib(...)` → вложение скачано, флаги остаются `()`.
|
||||
3. Прогнать `scripts/mail_archive.py --limit 2` на INBOX — флаги у обработанных
|
||||
писем не меняются (сравнить флаги в frontmatter email.md до/после).
|
||||
4. Включить cron обратно; наблюдать 1 цикл — новых `Seen` в frontmatter
|
||||
у свежих писем нет.
|
||||
|
||||
## Rollback
|
||||
|
||||
1. `git checkout -- scripts/mail_archive.py` (если не закоммичено) или revert коммита.
|
||||
2. Вернуть `--preview`/`fetch_attachments_imaplib` → исходные вызовы himalaya.
|
||||
3. Флаги писем, уже помеченных Seen этим багом, НЕ восстанавливаются
|
||||
(вне scope; отдельная задача при необходимости).
|
||||
Reference in New Issue
Block a user