From 5f4debdb3b4b0bda53f193c185e6f1bdf89c2e06 Mon Sep 17 00:00:00 2001 From: Kirill Romanov Date: Sun, 7 Jul 2024 16:17:45 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D1=81=D1=83=D0=BC=D0=BC=D0=B0=D1=80?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B2=D0=B8=D0=B4=D0=B5?= =?UTF-8?q?=D0=BE=20=D1=81=20=D0=BF=D0=BE=D0=BC=D0=BE=D1=89=D1=8C=D1=8E=20?= =?UTF-8?q?yandex=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../djaler/evilbot/clients/YandexGptClient.kt | 61 ++++++++++++++++--- .../config/yandex/YandexApiProperties.kt | 3 +- .../evilbot/handlers/commands/TlDrHandler.kt | 52 ++++++++++++++-- .../evilbot/service/YandexGptService.kt | 20 ++++++ 4 files changed, 120 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/com/github/djaler/evilbot/clients/YandexGptClient.kt b/src/main/kotlin/com/github/djaler/evilbot/clients/YandexGptClient.kt index 21a6408..9490d18 100644 --- a/src/main/kotlin/com/github/djaler/evilbot/clients/YandexGptClient.kt +++ b/src/main/kotlin/com/github/djaler/evilbot/clients/YandexGptClient.kt @@ -1,14 +1,16 @@ package com.github.djaler.evilbot.clients -import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming import com.github.djaler.evilbot.components.RecordBreadcrumb import com.github.djaler.evilbot.config.yandex.YandexApiCondition import com.github.djaler.evilbot.config.yandex.YandexApiProperties -import io.ktor.client.* -import io.ktor.client.call.* +import io.ktor.client.HttpClient +import io.ktor.client.call.body import io.ktor.client.request.* -import io.ktor.http.* +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.contentType import org.springframework.context.annotation.Conditional import org.springframework.stereotype.Component @@ -41,23 +43,64 @@ class YandexGptClient( url("https://300.ya.ru/api/sharing") contentType(ContentType.Application.Json) - setBody(mapOf( - "token" to token - )) + setBody( + mapOf( + "token" to token + ) + ) + }.body() + } + + suspend fun generateVideoSummary( + link: String, + sessionId: String? = null, + ): VideoSummaryResult { + return httpClient.post { + url("https://300.ya.ru/api/generation") + + contentType(ContentType.Application.Json) + cookie("Session_id", yandexApiProperties.cookie) + setBody( + mapOf( + "video_url" to link, + "session_id" to sessionId, + ) + ) }.body() } } -@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) data class SharingUrlResult( val sharingUrl: String ) -@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) data class SummaryResult( val thesis: List ) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) +data class VideoSummaryResult( + val statusCode: Int, + val errorCode: Int?, + val message: String?, + val sessionId: String, + val pollIntervalMs: Long, + val keypoints: List? +) + +data class Keypoint( + val content: String, + val theses: List, +) + data class Thesis( var content: String ) + +object ResponseStatus { + const val SUCCESS_VIDEO = 0 + const val IN_PROGRESS = 1 + const val ERROR = 3 +} diff --git a/src/main/kotlin/com/github/djaler/evilbot/config/yandex/YandexApiProperties.kt b/src/main/kotlin/com/github/djaler/evilbot/config/yandex/YandexApiProperties.kt index 387b06d..cba0787 100644 --- a/src/main/kotlin/com/github/djaler/evilbot/config/yandex/YandexApiProperties.kt +++ b/src/main/kotlin/com/github/djaler/evilbot/config/yandex/YandexApiProperties.kt @@ -6,5 +6,6 @@ import org.springframework.boot.context.properties.ConstructorBinding @ConfigurationProperties(prefix = "yandex.api") @ConstructorBinding data class YandexApiProperties( - val token: String = "" + val token: String = "", + val cookie: String = "", ) diff --git a/src/main/kotlin/com/github/djaler/evilbot/handlers/commands/TlDrHandler.kt b/src/main/kotlin/com/github/djaler/evilbot/handlers/commands/TlDrHandler.kt index a330faf..abfe24b 100644 --- a/src/main/kotlin/com/github/djaler/evilbot/handlers/commands/TlDrHandler.kt +++ b/src/main/kotlin/com/github/djaler/evilbot/handlers/commands/TlDrHandler.kt @@ -14,6 +14,9 @@ import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage import dev.inmo.tgbotapi.types.message.abstracts.Message import dev.inmo.tgbotapi.types.message.content.TextContent import dev.inmo.tgbotapi.types.message.content.TextMessage +import dev.inmo.tgbotapi.utils.boldln +import dev.inmo.tgbotapi.utils.buildEntities +import dev.inmo.tgbotapi.utils.regular import org.apache.logging.log4j.LogManager import org.springframework.context.annotation.Conditional import org.springframework.stereotype.Component @@ -32,6 +35,7 @@ class TlDrHandler( ) { companion object { private val log = LogManager.getLogger() + private val youtubeVideoLinkRegex = Regex("^(https?://)?(www\\.|m\\.)?youtu(\\.be|be\\.\\w{2,3})+/") } override suspend fun handleCommand( @@ -58,12 +62,7 @@ class TlDrHandler( requestsExecutor.withTypingAction(message.chat) { try { - val thesis = yandexGptService.generateLinkThesis(link) - if (thesis != null) { - requestsExecutor.reply(messageToReply, thesis) - } else { - log.warn("Empty thesis generation result") - } + replyWithTlDr(link, messageToReply) } catch (e: Exception) { log.error("Exception in thesis generation", e) sentryClient.captureException(e) @@ -72,10 +71,51 @@ class TlDrHandler( } } + private suspend fun replyWithTlDr(link: String, messageToReply: Message) { + if (isYoutubeLink(link)) { + replyWithVideoKeypoints(link, messageToReply) + } else { + replyWithArticleThesis(link, messageToReply) + } + } + + private suspend fun replyWithVideoKeypoints(link: String, messageToReply: Message) { + val videoKeypoints = yandexGptService.generateVideoKeypoints(link) + if (videoKeypoints.isEmpty()) { + log.warn("Empty keypoints generation result") + return + } + + requestsExecutor.reply( + messageToReply, + buildEntities { + for (keypoint in videoKeypoints) { + boldln(keypoint.content) + for (thesis in keypoint.theses) { + regular("• $thesis") + } + } + } + ) + } + + private suspend fun replyWithArticleThesis(link: String, messageToReply: Message) { + val thesis = yandexGptService.generateLinkThesis(link) + if (thesis != null) { + requestsExecutor.reply(messageToReply, thesis) + } else { + log.warn("Empty thesis generation result") + } + } + private fun extractLink(message: ContentMessage<*>): String? { return when (val content = message.content) { is TextContent -> content.textSources.firstNotNullOfOrNull { it.asURLTextSource()?.source } else -> null } } + + private fun isYoutubeLink(link: String): Boolean { + return link.matches(youtubeVideoLinkRegex) + } } diff --git a/src/main/kotlin/com/github/djaler/evilbot/service/YandexGptService.kt b/src/main/kotlin/com/github/djaler/evilbot/service/YandexGptService.kt index 8f31c04..6d30f58 100644 --- a/src/main/kotlin/com/github/djaler/evilbot/service/YandexGptService.kt +++ b/src/main/kotlin/com/github/djaler/evilbot/service/YandexGptService.kt @@ -1,6 +1,10 @@ package com.github.djaler.evilbot.service +import com.github.djaler.evilbot.clients.Keypoint +import com.github.djaler.evilbot.clients.ResponseStatus +import com.github.djaler.evilbot.clients.VideoSummaryResult import com.github.djaler.evilbot.clients.YandexGptClient +import kotlinx.coroutines.delay import org.springframework.stereotype.Service @Service @@ -14,4 +18,20 @@ class YandexGptService( return generatedResult.thesis.joinToString(separator = "\n") { it.content } } + + suspend fun generateVideoKeypoints(link: String): List { + var result = yandexGptClient.generateVideoSummary(link) + + while (result.statusCode == ResponseStatus.IN_PROGRESS) { + delay(result.pollIntervalMs) + + result = yandexGptClient.generateVideoSummary(link, sessionId = result.sessionId) + } + + return result.keypoints ?: throw VideoThesisGenerationException(link, result) + } } + +class VideoThesisGenerationException(link: String, result: VideoSummaryResult) : RuntimeException( + "Error on video generation for $link. Status: ${result.statusCode}, error: ${result.errorCode}, message: ${result.message}" +)