Кратко
В 2.4.1 отправка голосового доходит дальше, чем в 2.4.0, но по-прежнему не работает.
upload_voice заливает аудио, получает HTTP 200 — и следующий за этим MSG_SEND сервер
отклоняет:
Key: errors.process.attachment.video.not.ready [errors.process.attachment.video.not.ready]
Похоже, сервер принимает байты, но никогда не переводит вложение в состояние «готово».
Ожидание не помогает: я вставлял паузу между загрузкой и отправкой (то есть внутрь
upload_voice, перед return) — 5 секунд, затем 15 секунд, и повторял всю попытку заново.
Суммарно больше 30 секунд ожидания на одно голосовое — ответ всё тот же not.ready.
Параллельно NOTIF_ATTACH (opcode 136) для голосовой загрузки не приходит вообще, ни разу, ни
через минуту. Для File в той же сессии и по тому же соединению он приходит меньше чем за секунду.
Окружение
| |
|---|
| maxapi-python | 2.4.1 (и 2.4.0 до неё) |
| Python | 3.12, Linux (Docker) |
| Сервер | api.oneme.ru |
| Аккаунт | обычный пользовательский |
| Аудио | голосовые из Telegram: Opus в контейнере Ogg, 29–57 КБ, длительность передаётся явно |
Логи снимались с ExtraConfig(log_level="DEBUG").
Что в 2.4.1 стало заметно лучше
Спасибо за исправление — по логам видно, что диагноз изменился:
- 2.4.0 отвечал
errors.process.attachment.video.not.supported — форма payload была не та. - 2.4.1 отвечает
errors.process.attachment.video.not.ready — форма уже принимается, дело
теперь в незавершённой обработке.
То есть переход на VoiceAttachPayload (с duration и wave) сервер устраивает.
Воспроизведение
awaitclient.send_message(
chat_id=...,
text="тест",
attachments=[Voice(raw=ogg_bytes, name="voice.ogg", duration=3400)],
)
ogg_bytes — обычное голосовое из Telegram (Opus/Ogg). Воспроизводится каждый раз, на разных
файлах и разной длительности.
Хронология из логов
1. В 2.4.0 уведомление не приходит вовсе
16:39:49 Voice upload waiter registered voice_id=3698883559635
16:39:49 Voice upload HTTP response status=200 voice_id=3698883559635
16:39:49 Waiting for voice processing notification voice_id=3698883559635
[следующие 60 с: только opcode=49 (CHAT_HISTORY) и opcode=1 (PING)]
16:40:49 Timed out waiting for voice processing notification voice_id=3698883559635
Для сравнения — загрузка File минутой позже, в той же сессии:
16:40:49 File upload waiter registered file_id=4760217947
16:40:50 File upload HTTP response status=200 file_id=4760217947
16:40:50 dispatching event type=EventType.FILE_READY
16:40:50 calling handler event=EventType.FILE_READY callback=UploadService.on_file_attach
16:40:50 File upload waiter resolved file_id=4760217947
За весь прогон opcode 136 встретился ровно один раз — вот этот, файловый.
2. В 2.4.1 отправка отклоняется сразу
15:05:34 Uploading voice
15:05:35 MAX rejected the voice note (errors.process.attachment.video.not.ready)
3. Пауза между загрузкой и отправкой не помогает
Обёрнутый upload_voice, который спит перед return (то есть MSG_SEND уходит уже после паузы),
15 секунд, две полные попытки подряд — каждая со своей загрузкой и своей паузой:
15:35:18 Uploading voice (пауза 15 с, затем отправка → not.ready)
15:35:36 Uploading voice (пауза 15 с, затем отправка → not.ready)
15:35:51 rejected: errors.process.attachment.video.not.ready
Отдельно отмечу: повторять всю отправку бессмысленно, потому что send_message заново
загружает файл, и жалоба каждый раз относится к новому, только что созданному вложению. Пауза
обязана находиться между загрузкой и отправкой — выше именно такой вариант.
Наблюдение: User-Agent в голосовой загрузке закодирован percent-encoding
Не утверждаю, что это причина, но это единственное, чем заголовки голосовой загрузки отличаются от
остальных. api/uploads/service.py:
user_agent= (
f"OKMessages/{self.app.config.app_version}"+f" ({self.app.config.device.user_agent.os_version};"+f" {self.app.config.device.user_agent.device_name};"+f" {self.app.config.device.user_agent.screen})"
)
headers= {
"Content-Disposition": f"attachment; filename={quote(voice.name)}",
...
"User-Agent": quote(user_agent), # <- целиком через quote()
}quote() применяется ко всей строке User-Agent, и на выходе получается:
OKMessages/26.27.1%20%28Android%2013%3B%20Samsung%20SM-A525F%3B%20405dpi%20405dpi%201080x2400%29
Пробелы, скобки и точки с запятой экранированы — настоящий клиент такой заголовок не отправляет.
Для filename внутри Content-Disposition строкой выше quote() уместен, для User-Agent
целиком — вряд ли.
Что делает это наблюдение любопытным: голосовая загрузка — единственная из трёх, которая вообще
шлёт User-Agent. upload_video и upload_file его не отправляют, и обе работают. И именно
голосовая — единственная, обработку которой сервер не завершает.
Что проверено и можно не перепроверять
- Аудио валидное: контейнер Ogg (сигнатура
OggS), Opus, из Telegram без перекодирования. - Загрузка проходит: POST отвечает 200, в логе нет
Voice upload failed with status. - Дело не в размере: 29–57 КБ.
- Дело не в длительности:
duration передаётся явно, в миллисекундах. - Дело не в отсутствии
bytes в Content-Range: в upload_file он тоже без префикса
(f"0-{file_size - 1}/{file_size}"), и файлы загружаются нормально. Так что исправление этого
префикса в голосовой ветке, скорее всего, было не тем, что чинило проблему. - Дело не в нехватке времени: больше 30 секунд ожидания суммарно, результат не меняется.
Мелочь на будущее
В 2.4.1 ожидание убрано (закомментировано), но вокруг него остался мёртвый код: on_voice_attach
и очистка voice_upload_waiters в finally. Если ожидание когда-нибудь вернут, стоит учесть, что
регистрация и разрешение waiter-а шли по разным ключам: upload_voice клал future в
voice_upload_waiters[video_id], а on_voice_attach искал по attach.audio_id. Плюс
resolve_attach определяет тип фрейма перебором FileUploadSignal → VideoUploadSignal →
AudioUploadSignal, и у всех моделей extra="allow" — значит payload с videoId совпадёт с
VideoUploadSignal первым и уйдёт в on_video_attach. Это вывод из чтения исходников, а не
наблюдение: подходящего фрейма мы так и не увидели.
Вопросы
- Работает ли отправка голосовых у вас сейчас, на актуальном сервере? Если да — очень поможет
узнать версию приложения/сборки, под которую клиент представляется. - С какого клиента снимался протокол голосовой загрузки? Веб-версия MAX голосовые записывать не
умеет (в меню вложений только «Фото или видео», «Файл», «Контакт», «Опрос», и микрофона в
поле ввода нет), так что, видимо, с мобильного — и, возможно, с тех пор что-то поменялось. - Стоит ли ждать
NOTIF_ATTACH для голосовых в принципе, или у аудио другой путь
подтверждения? В pymax.protocol.Opcode не занят код 85 (между VIDEO_PLAY=83 и
FILE_UPLOAD=87) — это ни на что не намекает, просто бросилось в глаза.
Готов снять любые дополнительные логи — DEBUG со стороны PyMax включается одной строкой, и
воспроизводится это стабильно.
Кратко
В 2.4.1 отправка голосового доходит дальше, чем в 2.4.0, но по-прежнему не работает.
upload_voiceзаливает аудио, получает HTTP 200 — и следующий за этимMSG_SENDсерверотклоняет:
Похоже, сервер принимает байты, но никогда не переводит вложение в состояние «готово».
Ожидание не помогает: я вставлял паузу между загрузкой и отправкой (то есть внутрь
upload_voice, передreturn) — 5 секунд, затем 15 секунд, и повторял всю попытку заново.Суммарно больше 30 секунд ожидания на одно голосовое — ответ всё тот же
not.ready.Параллельно
NOTIF_ATTACH(opcode 136) для голосовой загрузки не приходит вообще, ни разу, ничерез минуту. Для
Fileв той же сессии и по тому же соединению он приходит меньше чем за секунду.Окружение
Логи снимались с
ExtraConfig(log_level="DEBUG").Что в 2.4.1 стало заметно лучше
Спасибо за исправление — по логам видно, что диагноз изменился:
errors.process.attachment.video.not.supported— форма payload была не та.errors.process.attachment.video.not.ready— форма уже принимается, делотеперь в незавершённой обработке.
То есть переход на
VoiceAttachPayload(сdurationиwave) сервер устраивает.Воспроизведение
ogg_bytes— обычное голосовое из Telegram (Opus/Ogg). Воспроизводится каждый раз, на разныхфайлах и разной длительности.
Хронология из логов
1. В 2.4.0 уведомление не приходит вовсе
Для сравнения — загрузка
Fileминутой позже, в той же сессии:За весь прогон opcode 136 встретился ровно один раз — вот этот, файловый.
2. В 2.4.1 отправка отклоняется сразу
3. Пауза между загрузкой и отправкой не помогает
Обёрнутый
upload_voice, который спит передreturn(то естьMSG_SENDуходит уже после паузы),15 секунд, две полные попытки подряд — каждая со своей загрузкой и своей паузой:
Отдельно отмечу: повторять всю отправку бессмысленно, потому что
send_messageзановозагружает файл, и жалоба каждый раз относится к новому, только что созданному вложению. Пауза
обязана находиться между загрузкой и отправкой — выше именно такой вариант.
Наблюдение:
User-Agentв голосовой загрузке закодирован percent-encodingНе утверждаю, что это причина, но это единственное, чем заголовки голосовой загрузки отличаются от
остальных.
api/uploads/service.py:quote()применяется ко всей строкеUser-Agent, и на выходе получается:Пробелы, скобки и точки с запятой экранированы — настоящий клиент такой заголовок не отправляет.
Для
filenameвнутриContent-Dispositionстрокой вышеquote()уместен, дляUser-Agentцеликом — вряд ли.
Что делает это наблюдение любопытным: голосовая загрузка — единственная из трёх, которая вообще
шлёт
User-Agent.upload_videoиupload_fileего не отправляют, и обе работают. И именноголосовая — единственная, обработку которой сервер не завершает.
Что проверено и можно не перепроверять
OggS), Opus, из Telegram без перекодирования.Voice upload failed with status.durationпередаётся явно, в миллисекундах.bytesвContent-Range: вupload_fileон тоже без префикса(
f"0-{file_size - 1}/{file_size}"), и файлы загружаются нормально. Так что исправление этогопрефикса в голосовой ветке, скорее всего, было не тем, что чинило проблему.
Мелочь на будущее
В 2.4.1 ожидание убрано (закомментировано), но вокруг него остался мёртвый код:
on_voice_attachи очистка
voice_upload_waitersвfinally. Если ожидание когда-нибудь вернут, стоит учесть, чторегистрация и разрешение waiter-а шли по разным ключам:
upload_voiceклал future вvoice_upload_waiters[video_id], аon_voice_attachискал поattach.audio_id. Плюсresolve_attachопределяет тип фрейма переборомFileUploadSignal→VideoUploadSignal→AudioUploadSignal, и у всех моделейextra="allow"— значит payload сvideoIdсовпадёт сVideoUploadSignalпервым и уйдёт вon_video_attach. Это вывод из чтения исходников, а ненаблюдение: подходящего фрейма мы так и не увидели.
Вопросы
узнать версию приложения/сборки, под которую клиент представляется.
умеет (в меню вложений только «Фото или видео», «Файл», «Контакт», «Опрос», и микрофона в
поле ввода нет), так что, видимо, с мобильного — и, возможно, с тех пор что-то поменялось.
NOTIF_ATTACHдля голосовых в принципе, или у аудио другой путьподтверждения? В
pymax.protocol.Opcodeне занят код 85 (междуVIDEO_PLAY=83 иFILE_UPLOAD=87) — это ни на что не намекает, просто бросилось в глаза.Готов снять любые дополнительные логи — DEBUG со стороны PyMax включается одной строкой, и
воспроизводится это стабильно.