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:
2026-09-14 08:08:48 +00:00
parent 8bff6f9aa4
commit 39df85b51c
12 changed files with 662 additions and 16 deletions
@@ -0,0 +1,31 @@
# Tasks: не помечать письма прочитанными при архивации
## Задачи
- [x] 1. `mail_archive.py`, `get_email_content()`: добавлен `--preview` в вызов
`himalaya message read` — флаг НЕ выставляет `\Seen`.
Проверено: himalaya `message read --preview` (не ставит Seen).
- [x] 2. `mail_archive.py`, `get_attachments()`: `himalaya attachment download`
НЕ имеет флага против Seen. Вместо него — новый путь:
`fetch_attachments_imaplib()` — сырой IMAP (socket+ssl, stdlib)
c `UID FETCH ... (BODY.PEEK[])` — не выставляет `\Seen`.
Используется с фолбэком на himalaya + `flag remove seen` (страховка).
- [x] 3. `_imap_utf7_encode()` — конвертация имени папки в IMAP modified UTF-7
(кириллица в имени папки на Exchange иначе не находится).
- [x] 4. Парсинг литерала `{N}` в ответе UID FETCH: чтение чанками по 64 КБ,
ожидание полного литерала (письма до ~1.5 МБ).
- [x] 5. MIME-encoded word в `get_filename()`: декодирование
через `email.header.decode_header` (например `=?koi8-r?B?...?=`).
- [x] 6. Живая проверка на реальном письме (UID 14200, INBOX, непрочитанное,
вложение «Переместить стол.docx» 1.1 МБ):
флаги до `''` = после `''` — `\Seen` НЕ выставлен, файл скачан.
## Верификация (живой тест)
- Письмо: UID 14200, папка INBOX, флаги до: `()` (непрочитанное).
- `fetch_attachments_imaplib("14200", "INBOX", dest)` → True,
1 вложение: «Переместить стол.docx» (1 117 244 байт).
- Флаги после: `()` — без `\Seen`.
- Ранее: UID 320 (непрочитанное, без вложений): BODY.PEEK[] не меняет флаги.
- Письмо 52 (INBOX/Организация работы, кириллица в имени папки):
4 docx-вложения скачаны через mUTF-7-папку, флаги не тронуты.