diff --git a/src/main/kotlin/com/github/djaler/evilbot/Application.kt b/src/main/kotlin/com/github/djaler/evilbot/Application.kt index 78f7bd42..e0ee42e9 100644 --- a/src/main/kotlin/com/github/djaler/evilbot/Application.kt +++ b/src/main/kotlin/com/github/djaler/evilbot/Application.kt @@ -6,6 +6,7 @@ import com.github.djaler.evilbot.config.TelegramProperties import com.github.djaler.evilbot.config.fixer.FixerApiProperties import com.github.djaler.evilbot.config.locationiq.LocationiqApiProperties import com.github.djaler.evilbot.config.vkcloud.VKCloudApiProperties +import com.github.djaler.evilbot.config.yandex.YandexApiProperties import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.runApplication @@ -21,7 +22,8 @@ import org.springframework.scheduling.annotation.EnableScheduling BotProperties::class, FixerApiProperties::class, LocationiqApiProperties::class, - VKCloudApiProperties::class + VKCloudApiProperties::class, + YandexApiProperties::class, ) class Application diff --git a/src/main/kotlin/com/github/djaler/evilbot/clients/YandexGptClient.kt b/src/main/kotlin/com/github/djaler/evilbot/clients/YandexGptClient.kt new file mode 100644 index 00000000..8e6dceba --- /dev/null +++ b/src/main/kotlin/com/github/djaler/evilbot/clients/YandexGptClient.kt @@ -0,0 +1,49 @@ +package com.github.djaler.evilbot.clients + +import com.fasterxml.jackson.databind.PropertyNamingStrategy +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.request.* +import io.ktor.http.* +import org.springframework.context.annotation.Conditional +import org.springframework.stereotype.Component + +@Component +@Conditional(YandexApiCondition::class) +@RecordBreadcrumb +class YandexGptClient( + private val httpClient: HttpClient, + private val yandexApiProperties: YandexApiProperties +) { + suspend fun generateLinkThesis(link: String, sessionId: String? = null): ThesisResult { + return httpClient.post { + url("https://300.ya.ru/api/generation") + + contentType(ContentType.Application.Json) + setBody(mapOf( + "article_url" to link, + "session_id" to sessionId + )) + + headers { + append(HttpHeaders.Authorization, "OAuth ${yandexApiProperties.token}") + } + }.body() + } +} + +@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) +data class ThesisResult( + val sessionId: String, + val pollIntervalMs: Long, + val statusCode: Int, + val thesis: List? +) + +data class Thesis( + val content: String +) diff --git a/src/main/kotlin/com/github/djaler/evilbot/config/yandex/YandexApiCondition.kt b/src/main/kotlin/com/github/djaler/evilbot/config/yandex/YandexApiCondition.kt new file mode 100644 index 00000000..bd79b996 --- /dev/null +++ b/src/main/kotlin/com/github/djaler/evilbot/config/yandex/YandexApiCondition.kt @@ -0,0 +1,11 @@ +package com.github.djaler.evilbot.config.yandex + +import org.springframework.context.annotation.Condition +import org.springframework.context.annotation.ConditionContext +import org.springframework.core.type.AnnotatedTypeMetadata + +class YandexApiCondition : Condition { + override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean { + return context.environment.getProperty("yandex.api.token", "").isNotBlank() + } +} 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 new file mode 100644 index 00000000..387b06de --- /dev/null +++ b/src/main/kotlin/com/github/djaler/evilbot/config/yandex/YandexApiProperties.kt @@ -0,0 +1,10 @@ +package com.github.djaler.evilbot.config.yandex + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.ConstructorBinding + +@ConfigurationProperties(prefix = "yandex.api") +@ConstructorBinding +data class YandexApiProperties( + val token: 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 new file mode 100644 index 00000000..7af5eb81 --- /dev/null +++ b/src/main/kotlin/com/github/djaler/evilbot/handlers/commands/TlDrHandler.kt @@ -0,0 +1,78 @@ +package com.github.djaler.evilbot.handlers.commands + +import com.github.djaler.evilbot.clients.SentryClient +import com.github.djaler.evilbot.handlers.base.CommandHandler +import com.github.djaler.evilbot.service.YandexGptService +import dev.inmo.tgbotapi.bot.RequestsExecutor +import dev.inmo.tgbotapi.extensions.api.send.reply +import dev.inmo.tgbotapi.extensions.api.send.withTypingAction +import dev.inmo.tgbotapi.extensions.utils.asContentMessage +import dev.inmo.tgbotapi.extensions.utils.asURLTextSource +import dev.inmo.tgbotapi.types.chat.ExtendedBot +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 org.apache.logging.log4j.LogManager +import org.springframework.stereotype.Component + +@Component +class TlDrHandler( + private val requestsExecutor: RequestsExecutor, + private val yandexGptService: YandexGptService, + private val sentryClient: SentryClient, + botInfo: ExtendedBot +) : CommandHandler( + botInfo, + command = arrayOf("tldr"), + commandDescription = "пересказать содержимое по ссылке" +) { + companion object { + private val log = LogManager.getLogger() + } + + override suspend fun handleCommand( + message: TextMessage, + args: String? + ) { + val messageToReply: Message + val link: String? + + val replyTo = message.replyTo + val replyMessageLink = replyTo?.asContentMessage()?.let { extractLink(it) } + if (replyMessageLink !== null) { + messageToReply = replyTo + link = replyMessageLink + } else { + messageToReply = message + link = extractLink(message) + } + + if (link === null) { + requestsExecutor.reply(messageToReply, "Либо пришли ссылку, либо ответь командой на сообщение со ссылкой") + return + } + + requestsExecutor.withTypingAction(message.chat) { + try { + val thesis = yandexGptService.generateLinkThesis(link) + if (thesis != null) { + requestsExecutor.reply(messageToReply, thesis) + } else { + log.warn("Empty thesis generation result") + } + } catch (e: Exception) { + log.error("Exception in thesis generation", e) + sentryClient.captureException(e) + requestsExecutor.reply(messageToReply, "Не получилось, попробуй ещё") + } + } + } + + private fun extractLink(message: ContentMessage<*>): String? { + return when (val content = message.content) { + is TextContent -> content.textSources.firstNotNullOfOrNull { it.asURLTextSource()?.source } + else -> null + } + } +} diff --git a/src/main/kotlin/com/github/djaler/evilbot/service/YandexGptService.kt b/src/main/kotlin/com/github/djaler/evilbot/service/YandexGptService.kt new file mode 100644 index 00000000..c4472011 --- /dev/null +++ b/src/main/kotlin/com/github/djaler/evilbot/service/YandexGptService.kt @@ -0,0 +1,21 @@ +package com.github.djaler.evilbot.service + +import com.github.djaler.evilbot.clients.YandexGptClient +import kotlinx.coroutines.delay +import org.springframework.stereotype.Service + +@Service +class YandexGptService( + private val yandexGptClient: YandexGptClient +) { + suspend fun generateLinkThesis(link: String): String? { + var result = yandexGptClient.generateLinkThesis(link) + + while (result.statusCode == 1) { + delay(result.pollIntervalMs) + result = yandexGptClient.generateLinkThesis(link, result.sessionId) + } + + return result.thesis?.joinToString(separator = "\n") { it.content } + } +}