+7 (999) 881-15-11

Файл из 1С в Max: загрузка и отправка

Март 2026 · Редакция «Арете-ИТ»

Сообщение с файлом отправляется не одним запросом. Сначала Max выдаёт адрес для загрузки, затем принимает двоичные данные и только после этого — сообщение с token вложения. Для печатной формы из 1С к цепочке добавляется ещё один шаг: её нужно заранее сохранить в PDF.

От печатной формы к двоичным данным

Источник файла может быть любым: печатная форма документа, результат отчёта, присоединённый файл или содержимое хранилища значения. Для Max всё это в итоге имя файла и двоичные данные.

УПД, счета, акты и расчётные листки обычно передают в PDF — получатель откроет такой файл с телефона, а вёрстка не будет зависеть от установленного офисного пакета. Прайс-лист или детализацию отчёта иногда разумнее оставить в XLSX. Получение самой печатной формы зависит от конфигурации, поэтому универсального вызова для УТ, ERP, БП и ЗУП не существует.

Табличный документ → PDF в памяти
// ПечатнаяФорма — уже сформированный ТабличныйДокумент.
// Команда и параметры получения формы зависят от конфигурации и вида документа.
Поток = Новый ПотокВПамяти;
ПечатнаяФорма.Записать(Поток, ТипФайлаТабличногоДокумента.PDF);
ДвоичныеДанные = Поток.ЗакрытьИПолучитьДвоичныеДанные();
ИмяФайла = "document.pdf";

Разборы конкретных документов: УПД, счёт, счёт-фактура, заказ, акт сверки, PDF, письмо, квитанция, расчётные листки.

POST /uploads — ещё не отправка

По документации POST /uploads с параметром type=file возвращает URL, по которому нужно загрузить один файл. Предельный размер обычного файла — 4 ГБ. Для изображений, видео и аудио предусмотрены отдельные типы и свои ограничения.

Четыре гигабайта — предел API, а не рекомендация для примера ниже: multipart-тело целиком собирается в памяти и при обрыве загружается заново. Для крупных вложений документация Max предлагает возобновляемую загрузку; обычным печатным формам PDF достаточно более простого multipart-варианта.

Адрес загрузки может вести на fu.oneme.ru, а не на основной домен API. Это ожидаемо: используйте полученный URL целиком, вместе с путём и query-параметрами.

К сообщению можно приложить один объект file; совместно с ним допускается только inline-клавиатура. Изображение или видео в то же сообщение добавлять нельзя. Для нескольких документов создайте несколько сообщений с учётом лимита диалога.

Шаг 1. Просим URL загрузки
Запрос = Новый HTTPЗапрос("/uploads?type=file");
Запрос.Заголовки.Вставить("Authorization", ТокенБота);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния <> 200 Тогда
	ВызватьИсключение Ответ.ПолучитьТелоКакСтроку();
КонецЕсли;

ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(Ответ.ПолучитьТелоКакСтроку());
ОписаниеЗагрузки = ПрочитатьJSON(ЧтениеJSON);
Если НЕ ОписаниеЗагрузки.Свойство("url") Тогда
	ВызватьИсключение "Max не вернул URL загрузки.";
КонецЕсли;
URLЗагрузки = ОписаниеЗагрузки.url; // Для type=file обычно домен fu.oneme.ru.
Шаг 2. Собираем multipart и заливаем
Один URL предназначен для одного файла. Для следующего вложения запросите новый.
Разделитель = Строка(Новый УникальныйИдентификатор);
ПереводСтроки = Символы.ВК + Символы.ПС;
БезопасноеИмяФайла = СтрЗаменить(ИмяФайла, """", "_");
БезопасноеИмяФайла = СтрЗаменить(БезопасноеИмяФайла, Символы.ВК, "");
БезопасноеИмяФайла = СтрЗаменить(БезопасноеИмяФайла, Символы.ПС, "");
Поток = Новый ПотокВПамяти;
ЗаписьДанных = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8);
ЗаписьДанных.ЗаписатьСимволы("--" + Разделитель + ПереводСтроки);
ЗаписьДанных.ЗаписатьСимволы(
	"Content-Disposition: form-data; name=""data""; filename=""" +
	БезопасноеИмяФайла + """" + ПереводСтроки);
ЗаписьДанных.ЗаписатьСимволы(
	"Content-Type: application/octet-stream" + ПереводСтроки + ПереводСтроки);
ЗаписьДанных.Записать(ДвоичныеДанные);
ЗаписьДанных.ЗаписатьСимволы(ПереводСтроки + "--" + Разделитель + "--" + ПереводСтроки);
ЗаписьДанных.Закрыть();
ТелоЗагрузки = Поток.ЗакрытьИПолучитьДвоичныеДанные();

СерверЗагрузки = СтрЗаменить(URLЗагрузки, "https://", "");
СерверЗагрузки = Лев(СерверЗагрузки, СтрНайти(СерверЗагрузки, "/") - 1);
АдресРесурса = СтрЗаменить(URLЗагрузки, "https://", "");
АдресРесурса = Сред(АдресРесурса, СтрНайти(АдресРесурса, "/"));

СоединениеЗагрузки = Новый HTTPСоединение(
	СерверЗагрузки, 443, , , , 60, Новый ЗащищенноеСоединениеOpenSSL);
ЗапросЗагрузки = Новый HTTPЗапрос(АдресРесурса);
ЗапросЗагрузки.Заголовки.Вставить("Authorization", ТокенБота);
ЗапросЗагрузки.Заголовки.Вставить(
	"Content-Length", Формат(ТелоЗагрузки.Размер(), "ЧГ="));
ЗапросЗагрузки.Заголовки.Вставить(
	"Content-Type", "multipart/form-data; boundary=" + Разделитель);
ЗапросЗагрузки.УстановитьТелоИзДвоичныхДанных(ТелоЗагрузки);
ОтветЗагрузки = СоединениеЗагрузки.ОтправитьДляОбработки(ЗапросЗагрузки);

Если ОтветЗагрузки.КодСостояния <> 200 Тогда
	ВызватьИсключение "Не удалось загрузить файл: " + ОтветЗагрузки.ПолучитьТелоКакСтроку();
КонецЕсли;

ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ОтветЗагрузки.ПолучитьТелоКакСтроку());
РезультатЗагрузки = ПрочитатьJSON(ЧтениеJSON);
ТокенВложения = РезультатЗагрузки.token;
Шаг 3. POST /messages с payload.token
Нагрузка = Новый Структура("token", ТокенВложения);
Вложение = Новый Структура("type, payload", "file", Нагрузка);
Вложения = Новый Массив;
Вложения.Добавить(Вложение);

Тело = Новый Структура;
Тело.Вставить("text", "УПД по реализации № 145");
Тело.Вставить("attachments", Вложения);

ПараметрыJSON = Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет, "");
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку(ПараметрыJSON);
ЗаписатьJSON(ЗаписьJSON, Тело);

Запрос = Новый HTTPЗапрос("/messages?user_id=" + Формат(ИдПользователя, "ЧГ="));
Запрос.Заголовки.Вставить("Authorization", ТокенБота);
Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Запрос.УстановитьТелоИзСтроки(
	ЗаписьJSON.Закрыть(), КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
СтрокаОтвета = Ответ.ПолучитьТелоКакСтроку();

Если СтрНайти(СтрокаОтвета, "attachment.not.ready") > 0 Тогда
	// Не ждите в пустом цикле: поставьте эту отправку в очередь на повтор
	// через 5 секунд, затем увеличивайте интервал между попытками.
	ВызватьИсключение "Max ещё обрабатывает вложение. Нужна отложенная повторная отправка.";
КонецЕсли;
Если Ответ.КодСостояния <> 200 Тогда
	ВызватьИсключение СтрокаОтвета;
КонецЕсли;

Готовая процедура на встроенном языке

Ниже те же три шага собраны в одну процедуру. В ней нет внешних библиотек и вызовов «Арете»: только стандартные объекты платформы 1С — HTTPСоединение, HTTPЗапрос, JSON и потоки.

ОтправитьФайлВMax без внешних компонентов
// Все используемые типы и методы входят в платформу 1С:Предприятие.
Процедура ОтправитьФайлВMax(
	ТокенБота,
	ИдПользователя,
	ДвоичныеДанные,
	ИмяФайла,
	ТекстСообщения,
	ВидВложения = "file") Экспорт

	Если СтрНайти(",file,image,video,audio,", "," + ВидВложения + ",") = 0 Тогда
		ВызватьИсключение "Неподдерживаемый тип вложения: " + ВидВложения;
	КонецЕсли;
	БезопасноеИмяФайла = СтрЗаменить(ИмяФайла, """", "_");
	БезопасноеИмяФайла = СтрЗаменить(БезопасноеИмяФайла, Символы.ВК, "");
	БезопасноеИмяФайла = СтрЗаменить(БезопасноеИмяФайла, Символы.ПС, "");

	СоединениеAPI = НовоеHTTPSСоединение("platform-api2.max.ru", 30);

	// 1. Получаем URL, предназначенный для загрузки одного файла.
	ЗапросURL = Новый HTTPЗапрос("/uploads?type=" + ВидВложения);
	ЗапросURL.Заголовки.Вставить("Authorization", ТокенБота);
	ОтветURL = СоединениеAPI.ОтправитьДляОбработки(ЗапросURL);
	ПроверитьОтветMax(ОтветURL, "Не удалось получить URL загрузки");

	ОписаниеЗагрузки = ПрочитатьОтветJSON(ОтветURL);
	URLЗагрузки = ОписаниеЗагрузки.url;
	Если НЕ СтрНачинаетсяС(URLЗагрузки, "https://") Тогда
		ВызватьИсключение "Max вернул URL загрузки без HTTPS.";
	КонецЕсли;
	ТокенВложения = Неопределено;
	Если ОписаниеЗагрузки.Свойство("token") Тогда
		ТокенВложения = ОписаниеЗагрузки.token; // video и audio
	КонецЕсли;

	// 2. Отправляем файл как multipart/form-data.
	Разделитель = Строка(Новый УникальныйИдентификатор);
	ПереводСтроки = Символы.ВК + Символы.ПС;
	Поток = Новый ПотокВПамяти;
	ЗаписьДанных = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8);
	ЗаписьДанных.ЗаписатьСимволы("--" + Разделитель + ПереводСтроки);
	ЗаписьДанных.ЗаписатьСимволы(
		"Content-Disposition: form-data; name=""data""; filename=""" +
		БезопасноеИмяФайла + """" + ПереводСтроки);
	ЗаписьДанных.ЗаписатьСимволы(
		"Content-Type: application/octet-stream" +
		ПереводСтроки + ПереводСтроки);
	ЗаписьДанных.Записать(ДвоичныеДанные);
	ЗаписьДанных.ЗаписатьСимволы(
		ПереводСтроки + "--" + Разделитель + "--" + ПереводСтроки);
	ЗаписьДанных.Закрыть();
	ТелоЗагрузки = Поток.ЗакрытьИПолучитьДвоичныеДанные();

	БезСхемы = СтрЗаменить(URLЗагрузки, "https://", "");
	ПозицияСлеша = СтрНайти(БезСхемы, "/");
	Если ПозицияСлеша = 0 Тогда
		ВызватьИсключение "Max вернул некорректный URL загрузки.";
	КонецЕсли;
	СерверЗагрузки = Лев(БезСхемы, ПозицияСлеша - 1);
	АдресРесурса = Сред(БезСхемы, ПозицияСлеша);

	СоединениеЗагрузки = НовоеHTTPSСоединение(СерверЗагрузки, 120);
	ЗапросЗагрузки = Новый HTTPЗапрос(АдресРесурса);
	ЗапросЗагрузки.Заголовки.Вставить("Authorization", ТокенБота);
	ЗапросЗагрузки.Заголовки.Вставить(
		"Content-Length", Формат(ТелоЗагрузки.Размер(), "ЧГ="));
	ЗапросЗагрузки.Заголовки.Вставить(
		"Content-Type", "multipart/form-data; boundary=" + Разделитель);
	ЗапросЗагрузки.УстановитьТелоИзДвоичныхДанных(ТелоЗагрузки);
	ОтветЗагрузки = СоединениеЗагрузки.ОтправитьДляОбработки(ЗапросЗагрузки);
	ПроверитьОтветMax(ОтветЗагрузки, "Не удалось загрузить файл");

	Если НЕ ЗначениеЗаполнено(ТокенВложения) Тогда
		РезультатЗагрузки = ПрочитатьОтветJSON(ОтветЗагрузки);
		ТокенВложения = РезультатЗагрузки.token; // file и image
	КонецЕсли;

	// 3. Создаём сообщение с token загруженного файла.
	Нагрузка = Новый Структура("token", ТокенВложения);
	Вложение = Новый Структура("type, payload", ВидВложения, Нагрузка);
	Вложения = Новый Массив;
	Вложения.Добавить(Вложение);

	ТелоСообщения = Новый Структура;
	ТелоСообщения.Вставить("text", ТекстСообщения);
	ТелоСообщения.Вставить("attachments", Вложения);

	ЗапросСообщения = Новый HTTPЗапрос(
		"/messages?user_id=" + Формат(ИдПользователя, "ЧГ="));
	ЗапросСообщения.Заголовки.Вставить("Authorization", ТокенБота);
	ЗапросСообщения.Заголовки.Вставить(
		"Content-Type", "application/json; charset=utf-8");
	ЗапросСообщения.УстановитьТелоИзСтроки(
		ЗаписатьВJSON(ТелоСообщения),
		КодировкаТекста.UTF8,
		ИспользованиеByteOrderMark.НеИспользовать);

	ОтветСообщения = СоединениеAPI.ОтправитьДляОбработки(ЗапросСообщения);
	СтрокаОтвета = ОтветСообщения.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
	Если СтрНайти(СтрокаОтвета, "attachment.not.ready") > 0 Тогда
		// Сохраните параметры операции в очереди и повторите шаг 3 позднее.
		ВызватьИсключение "Max ещё обрабатывает вложение.";
	КонецЕсли;
	ПроверитьОтветMax(ОтветСообщения, "Не удалось отправить сообщение");

КонецПроцедуры

Функция НовоеHTTPSСоединение(Сервер, Таймаут)

	ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL;
	Возврат Новый HTTPСоединение(
		Сервер, 443, , , , Таймаут, ЗащищенноеСоединение);

КонецФункции

Функция ЗаписатьВJSON(Значение)

	ЗаписьJSON = Новый ЗаписьJSON;
	ЗаписьJSON.УстановитьСтроку();
	ЗаписатьJSON(ЗаписьJSON, Значение);
	Возврат ЗаписьJSON.Закрыть();

КонецФункции

Функция ПрочитатьОтветJSON(Ответ)

	ЧтениеJSON = Новый ЧтениеJSON;
	ЧтениеJSON.УстановитьСтроку(
		Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8));
	Возврат ПрочитатьJSON(ЧтениеJSON);

КонецФункции

Процедура ПроверитьОтветMax(Ответ, Пояснение)

	Если Ответ.КодСостояния <> 200 Тогда
		ВызватьИсключение СтрШаблон(
			"%1. HTTP %2: %3",
			Пояснение,
			Ответ.КодСостояния,
			Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8));
	КонецЕсли;

КонецПроцедуры

Если вложение ещё не готово

После загрузки Max некоторое время обрабатывает файл. Если сразу вызвать POST /messages, сервер может вернуть attachment.not.ready. Такой ответ требует не новой загрузки, а повторной отправки сообщения с тем же token. Интервал между попытками лучше увеличивать.

Не устраивайте ожидание пустым циклом внутри проведения документа: оно занимает рабочий процесс 1С и расходует процессорное время. Для повторов нужна очередь или фоновое задание. Сохраните token, адресата и текст в регистре сведений, а регламентному заданию поручите повторить только третий шаг через несколько секунд. Следующие интервалы можно увеличить, например до 10 и 15 секунд.

Повторное использование token

Часто используемый файл не обязательно загружать перед каждым сообщением. Сохраните соответствие между контрольной суммой двоичных данных и token, а при следующей отправке используйте уже известное значение. Если Max сообщит, что token недействителен, файл можно загрузить заново.

Для собственного кеша достаточно периодического регистра сведений с измерениями «бот» и «контрольная сумма», ресурсами «token», «вид вложения» и «имя файла». При ошибке недействительного token удалите запись, снова вызовите POST /uploads и сохраните новый результат.

Как выглядит тот же вызов в подсистеме

В «Арете — сервер мессенджеров, Max edition» загрузка, кеш token, журнал и повторные попытки уже находятся в транспортном слое. Поэтому прикладной код сводится к подготовке параметров:

ВложитьФайл + ОтправитьСообщение
ИдПользователя = 123456789; // user_id из карточки получателя
СопроводительныйТекст = "Направляем документ из 1С.";

Параметры = Новый Структура;
Параметры.Вставить("user_id", ИдПользователя);
Параметры.Вставить("text", СопроводительныйТекст);
АСМ_М.ВложитьФайл(Параметры, ДвоичныеДанные, ИмяФайла);
АСМ_М.ОтправитьСообщение(Бот, Параметры);

Это не другой способ работы с Max, а готовая реализация той же последовательности, которая показана выше на стандартных средствах 1С.

Читать дальше