Интеграция учетных систем 1С со сторонними мессенджерами — одна из наиболее частых задач автоматизации бизнеса. Отправка отчетов, печатных форм документов, договоров напрямую в чат сотруднику или клиенту существенно экономит время. Популярный мессенджер Skype (работающий на платформе Microsoft Bot Framework) позволяет автоматизировать этот процесс, однако при попытке отправить файлы документов, такие как .docx, разработчики часто сталкиваются с ошибками протокола HTTP, такими как 400 Bad Request или 404 Not Found. В этой статье мы подробно разберем причины возникновения этих ошибок и рассмотрим два рабочих сценария решения данной задачи.
Для начала вспомним, как строится работа со Skype Bot API при отправке стандартных текстовых сообщений. Взаимодействие происходит по протоколу HTTP с использованием POST-запросов к серверам Bot Framework (обычно используется точка подключения smba.trafficmanager.net). Авторизация запросов выполняется по протоколу OAuth 2.0 с передачей Bearer-токена в заголовках.
Рассмотрим структуру типовой функции 1С, которая отвечает за отправку обычного текстового сообщения:
Функция ОтправитьСообщениеSkype(Токен, IDЧата, ТекстСообщения) Экспорт
HTTPСоединение = Новый HTTPСоединение("smba.trafficmanager.net", , , , , 120, Новый ЗащищенноеСоединениеOpenSSL);
ЗаголовкиHTTP = Новый Соответствие;
ЗаголовкиHTTP.Вставить("Content-Type", "application/json");
ЗаголовкиHTTP.Вставить("Authorization", "Bearer " + Токен);
JSONСтруктура = Новый Структура;
JSONСтруктура.Вставить("conversation", Новый Структура);
JSONСтруктура.conversation.Вставить("id", IDЧата);
JSONСтруктура.Вставить("from", Новый Структура);
JSONСтруктура.from.Вставить("id", "");
JSONСтруктура.Вставить("type", "message");
JSONСтруктура.Вставить("text", ТекстСообщения);
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку;
ЗаписатьJSON(ЗаписьJSON, JSONСтруктура);
ТелоЗапросаJSON = ЗаписьJSON.Закрыть();
HTTPЗапрос = Новый HTTPЗапрос("apis/v3/conversations/" + IDЧата + "/activities", ЗаголовкиHTTP);
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаJSON);
HTTPОтвет = HTTPСоединение.ВызватьHTTPМетод("POST", HTTPЗапрос);
HTTPОтветТело = HTTPОтвет.ПолучитьТелоКакСтроку();
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(HTTPОтветТело);
HTTPОтветСтруктура = ПрочитатьJSON(ЧтениеJSON);
Если Не (HTTPОтвет.КодСостояния = 200 Или HTTPОтвет.КодСостояния = 201) Тогда
ОписаниеОшибки = "Ошибка: " + HTTPОтвет.КодСостояния;
ВызватьИсключение ОписаниеОшибки;
КонецЕсли;
Возврат HTTPОтветСтруктура;
КонецФункции
Этот код отлично справляется со своей задачей. Однако при попытке расширить структуру JSONСтруктура и добавить в нее массив вложений attachments для отправки локального файла .docx, разработчики неизменно сталкиваются с трудностями.
Проанализируем архитектурные особенности Microsoft Bot Framework для канала Skype, которые вызывают сбои при передаче документов:
data:...;base64,...). В Skype этот механизм жестко ограничен. Передавать файлы в Base64-виде разрешается только для изображений (PNG, JPG) и некоторых медиаформатов. Попытка передать документ .docx, .pdf или .xlsx напрямую в строке вызовет ошибку 400 Bad Request.POST /v3/conversations/{conversationId}/attachments, предназначенный для загрузки файлов во временное облачное хранилище Microsoft. Однако на практике для большинства публичных Skype-ботов этот эндпоинт возвращает ошибку 404 Not Found. Из соображений безопасности Microsoft существенно ограничила прямую загрузку не-медиа файлов через свои серверы.type, name, и originalBase64 вместо стандартных полей contentType, name, contentUrl), путаница в которых мгновенно приводит к ошибке 400.Учитывая данные ограничения, рассмотрим два надежных способа решения задачи.
Поскольку Skype отказывается принимать бинарное тело документа напрямую, наиболее стабильным и рекомендуемым решением является предварительная загрузка файла на ваш публичный или корпоративный веб-сервер (или облачное хранилище, такое как S3, Яндекс.Диск, Nextcloud) с последующей передачей ссылки в Skype.
Разберем этот процесс по шагам:
.docx на ваш внешний веб-ресурс по протоколу FTP, HTTP POST или WebDAV.attachments, указывая в поле contentUrl полученную ссылку, а в поле contentType — MIME-тип документа Word.Посмотрим на пример реализации формирования JSON-структуры для отправки документа по ссылке:
Функция ОтправитьДокументSkypeПоСсылке(Токен, IDЧата, ТекстСообщения, ИмяФайла, СсылкаНаФайл) Экспорт
HTTPСоединение = Новый HTTPСоединение("smba.trafficmanager.net", , , , , 120, Новый ЗащищенноеСоединениеOpenSSL);
ЗаголовкиHTTP = Новый Соответствие;
ЗаголовкиHTTP.Вставить("Content-Type", "application/json");
ЗаголовкиHTTP.Вставить("Authorization", "Bearer " + Токен);
// Создаем массив вложений
МассивВложений = Новый Массив;
СтруктураВложения = Новый Структура;
// Указываем корректный MIME-тип для формата .docx
СтруктураВложения.Вставить("contentType", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
// Прямая HTTPS ссылка на ваш файловый сервер
СтруктураВложения.Вставить("contentUrl", СсылкаНаФайл);
// Имя файла, которое будет отображаться пользователю в чате
СтруктураВложения.Вставить("name", ИмяФайла);
МассивВложений.Добавить(СтруктураВложения);
// Формируем основное тело JSON запроса
JSONСтруктура = Новый Структура;
JSONСтруктура.Вставить("conversation", Новый Структура);
JSONСтруктура.conversation.Вставить("id", IDЧата);
JSONСтруктура.Вставить("from", Новый Структура);
JSONСтруктура.from.Вставить("id", "");
JSONСтруктура.Вставить("type", "message");
JSONСтруктура.Вставить("text", ТекстСообщения);
JSONСтруктура.Вставить("attachments", МассивВложений);
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку;
ЗаписатьJSON(ЗаписьJSON, JSONСтруктура);
ТелоЗапросаJSON = ЗаписьJSON.Закрыть();
HTTPЗапрос = Новый HTTPЗапрос("apis/v3/conversations/" + IDЧата + "/activities", ЗаголовкиHTTP);
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаJSON);
HTTPОтвет = HTTPСоединение.ВызватьHTTPМетод("POST", HTTPЗапрос);
HTTPОтветТело = HTTPОтвет.ПолучитьТелоКакСтроку();
Если Не (HTTPОтвет.КодСостояния = 200 Или HTTPОтвет.КодСостояния = 201) Тогда
ВызватьИсключение "Ошибка отправки файла в Skype: " + HTTPОтвет.КодСостояния;
КонецЕсли;
Возврат Истина;
КонецФункции
При таком подходе Skype самостоятельно распознает MIME-тип вложения и отобразит пользователю интерактивную карточку документа с красивой иконкой MS Word и кнопкой для скачивания прямо внутри диалогового окна.
Если специфика вашей безопасности запрещает хранение корпоративных файлов на промежуточных веб-серверах, можно попробовать воспользоваться официальным механизмом временного хранения Microsoft. Обратите внимание, что работоспособность этого метода зависит от настроек безопасности вашего Skype-бота и тарифного плана подписки Azure.
Метод состоит из двух строго последовательных этапов:
attachmentId.activities, где в качестве contentUrl указывается специальная ссылка, содержащая полученный attachmentId.Проанализируем программную реализацию первого этапа — загрузки бинарного контента файла .docx из локального хранилища компьютера:
Функция ЗагрузитьФайлНаСерверMicrosoft(Токен, IDЧата, ПутьКФайлу, ИмяФайла) Экспорт
ФайлНаДиске = Новый Файл(ПутьКФайлу);
Если Не ФайлНаДиске.Существует() Тогда
ВызватьИсключение "Указанный файл не найден на диске!";
КонецЕсли;
// Считываем файл в двоичные данные и кодируем в Base64
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлу);
СтрокаBase64 = Base64Строка(ДвоичныеДанныеФайла);
// Очищаем строку Base64 от возможных символов переноса строк
СтрокаBase64 = СтрЗаменить(СтрокаBase64, Символы.ПС, "");
СтрокаBase64 = СтрЗаменить(СтрокаBase64, Символы.ВК, "");
HTTPСоединение = Новый HTTPСоединение("smba.trafficmanager.net", , , , , 120, Новый ЗащищенноеСоединениеOpenSSL);
ЗаголовкиHTTP = Новый Соответствие;
ЗаголовкиHTTP.Вставить("Content-Type", "application/json");
ЗаголовкиHTTP.Вставить("Authorization", "Bearer " + Токен);
// Важно: Структура данных для отправки вложения отличается от обычного Attachment
СтруктураЗагрузки = Новый Структура;
СтруктураЗагрузки.Вставить("type", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
СтруктураЗагрузки.Вставить("name", ИмяФайла);
СтруктураЗагрузки.Вставить("originalBase64", СтрокаBase64);
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку;
ЗаписатьJSON(ЗаписьJSON, СтруктураЗагрузки);
ТелоЗапросаJSON = ЗаписьJSON.Закрыть();
// Формируем запрос к специальному ресурсу вложений
АдресЗапроса = "apis/v3/conversations/" + IDЧата + "/attachments";
HTTPЗапрос = Новый HTTPЗапрос(АдресЗапроса, ЗаголовкиHTTP);
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаJSON);
HTTPОтвет = HTTPСоединение.ВызватьHTTPМетод("POST", HTTPЗапрос);
HTTPОтветТело = HTTPОтвет.ПолучитьТелоКакСтроку();
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(HTTPОтветТело);
ОтветСтруктура = ПрочитатьJSON(ЧтениеJSON);
Если HTTPОтвет.КодСостояния = 200 Или HTTPОтвет.КодСостояния = 201 Тогда
// Возвращаем уникальный ID вложения, предоставленный сервером Microsoft
Возврат ОтветСтруктура.id;
Иначе
ВызватьИсключение "Не удалось загрузить вложение на сервер Microsoft. Код: " + HTTPОтвет.КодСостояния;
КонецЕсли;
КонецФункции
Если первый этап прошел успешно и мы получили идентификатор вложения (например, "f:8347298347"), переходим ко второму шагу. Нам необходимо отправить стандартное сообщение, но сформировать специальный URL-адрес ресурса в поле contentUrl.
Рассмотрим структуру отправки сообщения на втором этапе:
Процедура ОтправитьСообщениеСЗагруженнымВложением(Токен, IDЧата, IDВложения, ИмяФайла)
HTTPСоединение = Новый HTTPСоединение("smba.trafficmanager.net", , , , , 120, Новый ЗащищенноеСоединениеOpenSSL);
ЗаголовкиHTTP = Новый Соответствие;
ЗаголовкиHTTP.Вставить("Content-Type", "application/json");
ЗаголовкиHTTP.Вставить("Authorization", "Bearer " + Токен);
// Конструируем системный URL-адрес вложения
СистемныйURL = "https://smba.trafficmanager.net/apis/v3/attachments/" + IDВложения + "/views/original";
МассивВложений = Новый Массив;
СтруктураВложения = Новый Структура;
СтруктураВложения.Вставить("contentType", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
СтруктураВложения.Вставить("contentUrl", СистемныйURL);
СтруктураВложения.Вставить("name", ИмяФайла);
МассивВложений.Добавить(СтруктураВложения);
JSONСтруктура = Новый Структура;
JSONСтруктура.Вставить("conversation", Новый Структура);
JSONСтруктура.conversation.Вставить("id", IDЧата);
JSONСтруктура.Вставить("from", Новый Структура);
JSONСтруктура.from.Вставить("id", "");
JSONСтруктура.Вставить("type", "message");
JSONСтруктура.Вставить("attachments", МассивВложений);
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку;
ЗаписатьJSON(ЗаписьJSON, JSONСтруктура);
ТелоЗапросаJSON = ЗаписьJSON.Закрыть();
HTTPЗапрос = Новый HTTPЗапрос("apis/v3/conversations/" + IDЧата + "/activities", ЗаголовкиHTTP);
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаJSON);
HTTPОтвет = HTTPСоединение.ВызватьHTTPМетод("POST", HTTPЗапрос);
Если Не (HTTPОтвет.КодСостояния = 200 Или HTTPОтвет.КодСостояния = 201) Тогда
ВызватьИсключение "Ошибка отправки финального сообщения с вложением: " + HTTPОтвет.КодСостояния;
КонецЕсли;
КонецПроцедуры
Важное предупреждение: Если при выполнении первого этапа (функция ЗагрузитьФайлНаСерверMicrosoft) вы получаете ошибку 404 Not Found, это однозначно указывает на то, что для вашего канала Skype возможность прямой загрузки бинарных файлов заблокирована со стороны Microsoft. В этом случае единственным жизнеспособным решением остается Способ 1 с использованием вашего внешнего веб-сервера.
Подводя итоги, дадим несколько важных советов по реализации обмена файлами между 1С и Skype:
Новый ЗащищенноеСоединениеOpenSSL) при инициализации объекта HTTPСоединение. Skype Bot API категорически отклоняет любые незащищенные HTTP-вызовы.application/vnd.openxmlformats-officedocument.wordprocessingml.document. Если вы решите отправлять таблицы Excel (.xlsx), указывайте тип application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.Символы.ПС, Символы.ВК), так как они могут нарушить валидность JSON-структуры запроса и вызвать ошибку с кодом 400.Использование предложенных методик позволит вам гибко настроить отправку любых документов прямо из среды 1С в чаты Skype, сделав процесс коммуникации автоматизированным и прозрачным.