diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f6f316f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM openjdk:eclipse-temurin:17-alpine +RUN addgroup -S spring && adduser -S spring -G spring +USER spring:spring +ARG JAR_FILE=build/libs/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index cca964a..8c314a0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("jvm") version "1.9.25" + kotlin("jvm") version "2.1.0" kotlin("plugin.spring") version "1.9.25" id("org.springframework.boot") version "3.4.4" id("io.spring.dependency-management") version "1.1.7" @@ -30,6 +30,7 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("io.github.kotlin-telegram-bot.kotlin-telegram-bot:telegram:6.3.0") implementation("io.github.oshai:kotlin-logging-jvm:7.0.3") + implementation(awssdk.services.s3) developmentOnly("org.springframework.boot:spring-boot-devtools") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/settings.gradle.kts b/settings.gradle.kts index c606d7d..56f6f41 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,13 @@ rootProject.name = "memes-tv" + +dependencyResolutionManagement { + repositories { + mavenCentral() + } + + versionCatalogs { + create("awssdk") { + from("aws.sdk.kotlin:version-catalog:1.4.56") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/pischule/memestv/BotService.kt b/src/main/kotlin/com/pischule/memestv/BotService.kt index 6152f7e..f10c122 100644 --- a/src/main/kotlin/com/pischule/memestv/BotService.kt +++ b/src/main/kotlin/com/pischule/memestv/BotService.kt @@ -3,6 +3,7 @@ package com.pischule.memestv import com.github.kotlintelegrambot.Bot import com.github.kotlintelegrambot.bot import com.github.kotlintelegrambot.dispatch +import com.github.kotlintelegrambot.dispatcher.message import com.github.kotlintelegrambot.dispatcher.photos import com.github.kotlintelegrambot.entities.ChatId import com.github.kotlintelegrambot.entities.reaction.ReactionType @@ -18,7 +19,10 @@ val log = KotlinLogging.logger {} @Profile("!test") @EnableConfigurationProperties(BotProps::class) @Service -class BotService(val botProps: BotProps) { +class BotService( + private val botProps: BotProps, + private val fileUploaderService: FileUploaderService, +) { private lateinit var bot: Bot @PostConstruct @@ -26,27 +30,61 @@ class BotService(val botProps: BotProps) { bot = bot { token = botProps.token dispatch { + message { + try { + val chatId = message.chat.id + val replyToPhotos = message.replyToMessage + ?.photo + ?.takeIf { it.isNotEmpty() } + if (chatId == botProps.destinationChatId + && message.text?.lowercase() == "this" + && replyToPhotos != null + ) { + val maxResPhoto = replyToPhotos.last().fileId + val fileBytes = bot.downloadFileBytes(maxResPhoto) + fileBytes?.let { + log.info { "Downloaded a file $maxResPhoto from telegram" } + fileUploaderService.uploadFile(it) + log.info { "Uploaded a file $maxResPhoto to s3" } + bot.setMessageReaction( + chatId = ChatId.fromId(message.chat.id), + messageId = message.messageId, + reaction = listOf(ReactionType.Emoji("👍")) + ).onError { error -> + log.warn { "Failed to react to message: $error" } + } + } + } + } catch (e: Error) { + log.error(e) { "Error while handling message" } + } + } photos { val message = this.message + if (message.chat.id != botProps.destinationChatId) { + bot.forwardMessage( + chatId = ChatId.fromId(botProps.destinationChatId), + fromChatId = ChatId.fromId(message.chat.id), + messageId = message.messageId, + ).fold( + { + log.info { "Forwarded pictures message: $it" } + }, + { + log.error { "Failed to forward message: $it" } + } + ) + } + bot.setMessageReaction( chatId = ChatId.fromId(message.chat.id), messageId = message.messageId, reaction = listOf(ReactionType.Emoji("👀")) - ) + ).onError { error -> + log.warn { "Failed to react to message: $error" } + } - bot.forwardMessage( - chatId = ChatId.fromId(botProps.destinationChatId), - fromChatId = ChatId.fromId(message.chat.id), - messageId = message.messageId, - ).fold( - { - log.info { "Forwarded pictures message: $it" } - }, - { - log.error { "Failed to forward message: $it" } - } - ) } } } diff --git a/src/main/kotlin/com/pischule/memestv/FileUploaderService.kt b/src/main/kotlin/com/pischule/memestv/FileUploaderService.kt new file mode 100644 index 0000000..0b7106e --- /dev/null +++ b/src/main/kotlin/com/pischule/memestv/FileUploaderService.kt @@ -0,0 +1,21 @@ +package com.pischule.memestv + +import aws.sdk.kotlin.services.s3.S3Client +import aws.sdk.kotlin.services.s3.model.PutObjectRequest +import aws.smithy.kotlin.runtime.content.ByteStream +import org.springframework.stereotype.Service + +@Service +class FileUploaderService( + private val s3Client: S3Client, + private val s3Props: S3Props, +) { + + suspend fun uploadFile(fileBytes: ByteArray) { + s3Client.putObject(PutObjectRequest{ + body = ByteStream.fromBytes(fileBytes) + bucket = s3Props.bucket + key = "_.jpeg" + }) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/pischule/memestv/S3Config.kt b/src/main/kotlin/com/pischule/memestv/S3Config.kt new file mode 100644 index 0000000..d04d9f0 --- /dev/null +++ b/src/main/kotlin/com/pischule/memestv/S3Config.kt @@ -0,0 +1,24 @@ +package com.pischule.memestv + +import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider +import aws.sdk.kotlin.services.s3.S3Client +import aws.smithy.kotlin.runtime.net.url.Url +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@EnableConfigurationProperties(S3Props::class) +@Configuration +class S3Config { + @Bean + fun s3Client(s3Props: S3Props): S3Client { + return S3Client { + endpointUrl = Url.parse("https://storage.yandexcloud.net") + region = "ru-central1" + credentialsProvider = StaticCredentialsProvider { + accessKeyId = s3Props.accessKeyId + secretAccessKey = s3Props.secretAccessKey + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/pischule/memestv/S3Props.kt b/src/main/kotlin/com/pischule/memestv/S3Props.kt new file mode 100644 index 0000000..777d160 --- /dev/null +++ b/src/main/kotlin/com/pischule/memestv/S3Props.kt @@ -0,0 +1,10 @@ +package com.pischule.memestv + +import org.springframework.boot.context.properties.ConfigurationProperties + +@ConfigurationProperties("s3") +data class S3Props( + val accessKeyId: String, + val secretAccessKey: String, + val bucket: String, +) diff --git a/src/test/kotlin/com/pischule/memestv/MemesTvApplicationTests.kt b/src/test/kotlin/com/pischule/memestv/MemesTvApplicationTests.kt index d6d0231..635082a 100644 --- a/src/test/kotlin/com/pischule/memestv/MemesTvApplicationTests.kt +++ b/src/test/kotlin/com/pischule/memestv/MemesTvApplicationTests.kt @@ -1,9 +1,11 @@ package com.pischule.memestv +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ActiveProfiles +@Disabled @ActiveProfiles("test") @SpringBootTest class MemesTvApplicationTests {