# email-attachments Specification ## Purpose Скачивание вложений письма в каталог этого письма. Сейчас `mail_archive.py` вызывает `himalaya attachment download --dir`, но правильный флаг в Himalaya — `--downloads-dir`, из-за чего команда падает (exit 2), ошибка молча глотается `except: pass`, и папка `attachments/` всегда пустая. Вложения теряются. ## Requirements ### Requirement: Вложения сохраняются в каталог письма Для каждого письма с вложениями (флаг `has_attachment: true` в frontmatter) вложения MUST быть сохранены в подкаталог `attachments/` каталога письма (`/opt/hermes/email//YYYY/MM//attachments/`). #### Scenario: Письмо с вложением архивировано - **WHEN** `mail_archive.py` заархивировал письмо с `has_attachment: true` - **THEN** файлы вложений лежат в `/attachments/` и совпадают с вложениями на IMAP-сервере ### Requirement: Правильный флаг Himalaya Скачивание вложений MUST использовать флаг `--downloads-dir` (а не несуществующий `--dir`) команды `himalaya attachment download`, и передавать ему каталог письма. #### Scenario: Вызов himalaya с корректным флагом - **WHEN** `get_attachments()` выполняется для письма - **THEN** используется `himalaya attachment download --folder --downloads-dir /attachments `, exit code 0 при успехе ### Requirement: Учёт отсутствия вложений Если письмо не имеет вложений (`has_attachment: false` или команда вернула «нет вложений»), скрипт MUST NOT создавать пустую папку `attachments/` и MUST NOT считать это ошибкой. #### Scenario: Письмо без вложений - **WHEN** `mail_archive.py` обрабатывает письмо без вложений - **THEN** каталог `attachments/` не создаётся, ошибка не логируется ### Requirement: Повторная обработка существующих писем Повторный запуск `mail_archive.py` MUST NOT повторно качать уже сохранённые вложения (проверка по наличию каталога/файлов). #### Scenario: Повторный запуск - **WHEN** `mail_archive.py` запущен повторно на письме с уже скачанными вложениями - **THEN** вложения не скачиваются повторно (идемпотентность) ### Requirement: Бэкфилл вложений для ранее заархивированных писем Письма, заархивированные до внедрения `--downloads-dir` (пустой `attachments/` при `has_attachment: true`), MUST поддерживать докачку вложений через флаг `--attachments-backfill`. Бэкфилл MUST NOT трогать письма, где вложения уже скачаны, и MUST корректно определять IMAP-папку из пути (`//YYYY/MM//`, папка может быть вложенной, например `INBOX/!Битрикс`). #### Scenario: Запуск бэкфилла - **WHEN** `mail_archive.py --attachments-backfill` запущен на архиве с письмами, у которых `has_attachment: true`, но пустой `attachments/` - **THEN** вложения скачиваются в эти папки; письма с уже скачанными вложениями пропускаются #### Scenario: Нестандартная структура пути - **WHEN** бэкфилл встречает путь, не соответствующий `//YYYY/MM//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.]`, `backend.*`, пароль — `backend.auth.raw`) и MUST NOT логировать или выводить пароль. #### Scenario: Доступ к конфигу - **WHEN** `get_attachments()` запускается для письма - **THEN** подключение к IMAP выполняется с учётными данными из конфига himalaya, пароль никуда не выводится