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:
@@ -45,3 +45,56 @@
|
||||
#### Scenario: Повторный запуск
|
||||
- **WHEN** `mail_archive.py` запущен повторно на письме с уже скачанными вложениями
|
||||
- **THEN** вложения не скачиваются повторно (идемпотентность)
|
||||
|
||||
### Requirement: Бэкфилл вложений для ранее заархивированных писем
|
||||
|
||||
Письма, заархивированные до внедрения `--downloads-dir` (пустой `attachments/`
|
||||
при `has_attachment: true`), MUST поддерживать докачку вложений через флаг
|
||||
`--attachments-backfill`. Бэкфилл MUST NOT трогать письма, где вложения уже
|
||||
скачаны, и MUST корректно определять IMAP-папку из пути
|
||||
(`/<folder>/YYYY/MM/<uid>/`, папка может быть вложенной, например
|
||||
`INBOX/!Битрикс`).
|
||||
|
||||
#### Scenario: Запуск бэкфилла
|
||||
- **WHEN** `mail_archive.py --attachments-backfill` запущен на архиве с письмами,
|
||||
у которых `has_attachment: true`, но пустой `attachments/`
|
||||
- **THEN** вложения скачиваются в эти папки; письма с уже скачанными вложениями пропускаются
|
||||
|
||||
#### Scenario: Нестандартная структура пути
|
||||
- **WHEN** бэкфилл встречает путь, не соответствующий `/<folder>/YYYY/MM/<uid>/email.md`
|
||||
- **THEN** письмо пропускается без ошибки
|
||||
|
||||
### Requirement: Скачивание вложений не помечает письмо прочитанным
|
||||
|
||||
Скачивание вложений MUST NOT выставлять IMAP-флаг `\Seen` (письмо не должно
|
||||
становиться «прочитанным» в почтовом ящике).
|
||||
|
||||
Способ: `himalaya attachment download` ставит `\Seen` (использует `BODY[]`), и
|
||||
флага `--preview` у него нет. Поэтому `get_attachments()` MUST использовать
|
||||
сырой IMAP-запрос `BODY.PEEK[]` через stdlib `imaplib` (не ставит `\Seen` на
|
||||
Microsoft Exchange, проверено) и распаковку MIME через stdlib `email`.
|
||||
|
||||
#### Scenario: Скачивание вложения у непрочитанного письма
|
||||
- **GIVEN** письмо в INBOX с флагами `()` (непрочитанное)
|
||||
- **WHEN** `get_attachments()` скачивает его вложения
|
||||
- **THEN** файлы вложений сохранены в `attachments/`, а флаги письма на IMAP
|
||||
остаются `()` (флаг `\Seen` не выставлен)
|
||||
|
||||
#### Scenario: Фолбэк при сбое сырого IMAP
|
||||
- **WHEN** `fetch_attachments_imaplib()` не может получить письмо (ошибка IMAP)
|
||||
- **THEN** вложения скачиваются через `himalaya attachment download`, после чего
|
||||
флаг `\Seen` снимается через `himalaya flag remove` (письмо временно
|
||||
помечается, но восстанавливается) ИЛИ операция помечается как недоступная —
|
||||
письмо НЕ остаётся прочитанным навсегда
|
||||
|
||||
### Requirement: Пароль IMAP для скачивания вложений
|
||||
|
||||
`fetch_attachments_imaplib()` MUST брать учётные данные IMAP (host, port, login,
|
||||
пароль) из конфига himalaya (`~/.config/himalaya/config.toml`, секция
|
||||
`[accounts.<default>]`, `backend.*`, пароль — `backend.auth.raw`) и MUST NOT
|
||||
логировать или выводить пароль.
|
||||
|
||||
#### Scenario: Доступ к конфигу
|
||||
- **WHEN** `get_attachments()` запускается для письма
|
||||
- **THEN** подключение к IMAP выполняется с учётными данными из конфига
|
||||
himalaya, пароль никуда не выводится
|
||||
|
||||
@@ -48,3 +48,21 @@
|
||||
**GIVEN** архив `/opt/hermes/email/`
|
||||
**WHEN** change применён
|
||||
**THEN** файлы писем остаются без изменений (проверка: `find /opt/hermes/email -name 'email.md' | wc -l` — то же число, что и до change).
|
||||
|
||||
### Requirement: Архивация не помечает письмо прочитанным
|
||||
|
||||
Чтение тела письма при архивации MUST NOT выставлять IMAP-флаг `\Seen`.
|
||||
`mail_archive.py` для получения тела использует `himalaya message read` с
|
||||
флагом `--preview` (документировано: читает БЕЗ установки `\Seen`).
|
||||
|
||||
#### Scenario: Архивация непрочитанного письма
|
||||
- **GIVEN** письмо в INBOX с флагами `()` (непрочитанное)
|
||||
- **WHEN** `mail_archive.py` архивирует письмо (пишет `email.md`)
|
||||
- **THEN** в frontmatter `email.md` флаг `Seen` отсутствует И на IMAP флаги
|
||||
письма остаются `()`
|
||||
|
||||
#### Scenario: Флаги в frontmatter соответствуют IMAP
|
||||
- **GIVEN** письмо заархивировано после фикса
|
||||
- **WHEN** флаги письма на IMAP меняются (пользователь прочитал/ответил)
|
||||
- **THEN** frontmatter `email.md` после следующей синхронизации отражает флаги
|
||||
IMAP (Seen появляется только если письмо реально прочитано пользователем)
|
||||
|
||||
Reference in New Issue
Block a user