
Как энтузиаст в освоении технологий я не всегда следую трендам, а пытаюсь увидеть ценность там, куда люди могли не заглянуть. По этой причине исследовательская дорога привела меня к изучению вопроса, как создать встречу в Yandex Calendar и приложить в нее ссылку на Telemost используя доступный API и мой любимый Kotlin. Об этом опыте я и поделюсь в статье.
Моей целью является поделиться наработками, поиск которых оказался на данный момент не тривиальным. Бизнес ценность решений я оставлю за рамками данной статьи.
Как получить доступы для работы с календарем
Для этого идем в настройки Yandex аккаунта, Безопасность, Пароли приложений и выбираем Календарь CalDAV, внутри которого создаем приложение и сохраняем от него пароль. Данный пароль вместе с вашей почтой будет использоваться в API для управлением встречами.
Как создать встречу в Yandex Calendar
Для работы с календарем мне потребовалось ознакомиться с сетевым протоколом CalDAV (Calendaring Extensions to WebDAV), который предназначен для управления встречами в календаре и синхронизации их между устройствами. Данный протокол позволяет работать с календарем по HTTP, передавая в запросе особый формат данный iCalendar, где можно разместить все параметры создаваемой или редактируемой вами встречи. Пример таких настроек:
val uid = UUID.randomUUID().toString() val ics = """ BEGIN:VCALENDAR VERSION:2.0 PRODID:-//ProjectName//EN CALSCALE:GREGORIAN BEGIN:VEVENT UID:$uid DTSTAMP:$startUtc DTSTART:$startUtc DTEND:$endUtc LOCATION:$location SUMMARY:$title DESCRIPTION:$description ORGANIZER;CN="Pavel":mailto:$username ATTENDEE;CN="Attendee 1";RSVP=TRUE:mailto:$attendeeEmail END:VEVENT END:VCALENDAR """.trimIndent()
В моем случае уникальным идентификатором встречи будет являться UUID, который мы создадим. Далее созданный uid можно при необходимости сохранить, чтобы в будущем была возможность управлять данной встречей.
Дату встречи вы можете передать через формат
DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'")
Для локации можно передать ссылку на ваш сервис видео конференций. Я в своей эксперименте использовал Telemost, интеграция с которым будет дальше в статье. В информацию о встрече можно так же передать необходимый заголовок и описание (убедитесь, что в описании нет спец символов, который могут сломать вам запрос, либо особые символы экранированы, например, запятая, точка с запятой, перевод строки и прочее). В качестве организатора указываем почтовый ящик вашего аккаунта, а ATTENDEE можете указать приглашенных на встречу гостей. Если гостей будет несколько, строку ATTENDEE нужно продублировать с почтой соответствующего гостя.
Далее нам нужно выполнить PUT запрос для создания встречи
val eventUrl = "<https://caldav.yandex.ru/calendars/$username/events-default/$uid.ics>" val request = HttpPut(eventUrl) request.addHeader("Content-Type", "text/calendar; charset=utf-8") request.entity = StringEntity(ics, StandardCharsets.UTF_8) request.addHeader("Authorization", basicAuth(username, appPassword)) closeableHttpClient.execute(request).use { response -> val statusCode = response.statusLine.statusCode val body = response.entity?.content?.bufferedReader()?.use { it.readText() } if (statusCode !in 200..299) { throw RuntimeException("Failed to create event. Status: $statusCode. Body: $body") } }
После этого встреча встреча появится в календаре, а все участники получат уведомление по почте о новом событии. Код не является идеальным, но зато хорошо подходит для иллюстрации работы. Вспомогательные сущности, которые я использовал на случай, если вы захотите проверить работоспособность кода:
private fun basicAuth(user: String, password: String): String { val auth = "$user:$password" val encoded = Base64.getEncoder().encodeToString(auth.toByteArray(StandardCharsets.UTF_8)) return "Basic $encoded" } fun poolingHttpClient(): CloseableHttpClient { val connectionManager = PoolingHttpClientConnectionManager().apply { maxTotal = 50 defaultMaxPerRoute = 20 } val requestConfig = RequestConfig.custom() .setConnectTimeout(5000) .setSocketTimeout(10000) .setConnectionRequestTimeout(2000) .setCookieSpec(CookieSpecs.IGNORE_COOKIES) .build() return HttpClients.custom() .setConnectionManager(connectionManager) .setDefaultRequestConfig(requestConfig) .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE) .build() }
Для того, чтобы обновить встречу, мы должны вызвать точно такой же PUT запрос с uid интересующего нас события, внутри которого изменить все необходимые параметры, например, состав участников.
Для удаления события необходимо выполнить запрос с передачей uid события, которое вы хотите удалить:
fun deleteEvent(uid: String) { val eventUrl = "<https://caldav.yandex.ru/calendars/$username/events-default/$uid.ics>" val request = HttpDelete(eventUrl) request.addHeader("Authorization", basicAuth(username, appPassword)) return closeableHttpClient.execute(request).use { response -> val statusCode = response.statusLine.statusCode val body = response.entity?.content?.bufferedReader()?.use { it.readText() } if (statusCode !in 200..299) { throw RuntimeException("Failed to create event. Status: $statusCode. Body: $body") } } }
Давайте теперь разберемся, как добавить во встречу ссылку на Telemost.
Создаем креды для доступа к Telemost
Для создания OAuth токена для работы с Telomost API нужно в https://oauth.yandex.ru/ создать приложение для доступа к API, после чего сохранить полученный token. Давайте теперь для примера создадим ссылку на будущую встречу в Telemost
Создание встречи
Подробное описание по работе с Telemost API находится в документации. Я лишь рассмотрю один интересующий меня кейс - создание встречи, а для разнообразия буду использовать использовать WebClient .
val monoMeetingResponse = webClient.post() .uri("<https://cloud-api.yandex.net/v1/telemost-api/conferences>") .bodyValue(meetingSettings) .accept(MediaType.APPLICATION_JSON) .header("Authorization", "OAuth $YANDEX_TOKEN") .retrieve() .bodyToMono(MeetingResponse::class.java) .doOnError { e -> logger.error("Telemost service error: ${e.message}") } .retryWhen( Retry.backoff(3, Duration.ofSeconds(2)) .doBeforeRetry { signal -> logger.warn("Retrying Yandex API call. Attempt: ${signal.totalRetries() + 1}") } )
Настройка встречи достаточно простая
val meetingSettings = MeetingSettings( waitingRoomLevel = "PUBLIC", cohosts = listOf(Cohost("login@yandex.ru")) )
После выполнения запроса мы получим join_url , который можно будет использовать внутри location нашего объекта для создания встречи.
Итоги
Таким образом мне удалось освоить новые API, заглянуть в очередной раз в недра документации и получить удовольствие от работоспособности написанной программы. Данный код в будущем может использоваться для автоматизации работы со встречами в календаре, если в кашей компании кто-то до сих пора занимается этим руками.
