diff --git a/build.gradle.kts b/build.gradle.kts index 73973ba..8e6261b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,7 +32,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) + implementation("io.minio:minio:8.5.17") implementation("org.springframework.boot:spring-boot-starter-web") developmentOnly("org.springframework.boot:spring-boot-devtools") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") diff --git a/settings.gradle.kts b/settings.gradle.kts index dd92b7a..c413130 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,10 +4,4 @@ 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/memevizor/bot/BotConfiguration.kt b/src/main/kotlin/com/pischule/memevizor/bot/BotConfiguration.kt index 0f3fab4..c28908c 100644 --- a/src/main/kotlin/com/pischule/memevizor/bot/BotConfiguration.kt +++ b/src/main/kotlin/com/pischule/memevizor/bot/BotConfiguration.kt @@ -4,14 +4,17 @@ import com.github.kotlintelegrambot.Bot import com.github.kotlintelegrambot.bot import com.github.kotlintelegrambot.dispatch import com.github.kotlintelegrambot.dispatcher.command +import com.github.kotlintelegrambot.dispatcher.handlers.media.MediaHandlerEnvironment import com.github.kotlintelegrambot.dispatcher.message import com.github.kotlintelegrambot.dispatcher.photos +import com.github.kotlintelegrambot.dispatcher.video import com.github.kotlintelegrambot.entities.ChatId import com.github.kotlintelegrambot.entities.Message import com.github.kotlintelegrambot.entities.ParseMode -import com.pischule.memevizor.bot.handler.PhotoHandlerService +import com.pischule.memevizor.bot.handler.MediaHandlerService import com.pischule.memevizor.bot.handler.ThisCommandHandlerService import com.pischule.memevizor.util.getMaxResPhotoId +import com.pischule.memevizor.util.getVideoFileId import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.withLoggingContext import org.springframework.boot.context.properties.EnableConfigurationProperties @@ -25,7 +28,7 @@ private val logger = KotlinLogging.logger {} class BotConfiguration( private val botProps: BotProps, private val thisCommandHandlerService: ThisCommandHandlerService, - private val photoHandlerService: PhotoHandlerService, + private val mediaHandlerService: MediaHandlerService, ) { @Bean @@ -46,15 +49,8 @@ class BotConfiguration( } } } - photos { - withLoggingContext(messageContext(message)) { - try { - photoHandlerService.create(this) - } catch (e: Error) { - logger.error(e) { "Error while handling photo" } - } - } - } + photos { handleMedia() } + video { handleMedia() } command("whoami") { withLoggingContext(messageContext(message)) { bot.sendMessage( @@ -67,12 +63,22 @@ class BotConfiguration( } } + private suspend fun MediaHandlerEnvironment<*>.handleMedia() { + withLoggingContext(messageContext(message)) { + try { + mediaHandlerService.create(this) + } catch (e: Error) { + logger.error(e) { "Error while handling photo" } + } + } + } + private fun messageContext(message: Message): Map = mapOf( "message_id" to message.messageId.toString(), "chat_id" to message.chat.id.toString(), "from_user_id" to message.from?.id.toString(), "from_user_username" to message.from?.username.toString(), - "file_id" to message.getMaxResPhotoId(), + "file_id" to (message.getMaxResPhotoId() ?: message.getVideoFileId()), ) } diff --git a/src/main/kotlin/com/pischule/memevizor/bot/handler/PhotoHandlerService.kt b/src/main/kotlin/com/pischule/memevizor/bot/handler/MediaHandlerService.kt similarity index 74% rename from src/main/kotlin/com/pischule/memevizor/bot/handler/PhotoHandlerService.kt rename to src/main/kotlin/com/pischule/memevizor/bot/handler/MediaHandlerService.kt index 6c8e16f..33b91c0 100644 --- a/src/main/kotlin/com/pischule/memevizor/bot/handler/PhotoHandlerService.kt +++ b/src/main/kotlin/com/pischule/memevizor/bot/handler/MediaHandlerService.kt @@ -2,7 +2,6 @@ package com.pischule.memevizor.bot.handler import com.github.kotlintelegrambot.dispatcher.handlers.media.MediaHandlerEnvironment import com.github.kotlintelegrambot.entities.ChatId -import com.github.kotlintelegrambot.entities.files.PhotoSize import com.github.kotlintelegrambot.entities.reaction.ReactionType import com.pischule.memevizor.bot.BotProps import io.github.oshai.kotlinlogging.KotlinLogging @@ -11,21 +10,21 @@ import org.springframework.stereotype.Service private val logger = KotlinLogging.logger {} @Service -class PhotoHandlerService(private val botProps: BotProps) { +class MediaHandlerService(private val botProps: BotProps) { - suspend fun create(env: MediaHandlerEnvironment>) { + suspend fun create(env: MediaHandlerEnvironment<*>) { if (shouldForwardMessage(env)) { - forwardPhotoMessage(env) + forwardMessage(env) } reactToMessage(env, "πŸ‘€") } - private fun shouldForwardMessage(env: MediaHandlerEnvironment>): Boolean { + private fun shouldForwardMessage(env: MediaHandlerEnvironment<*>): Boolean { return env.message.chat.id != botProps.forwardChatId } - private suspend fun forwardPhotoMessage(env: MediaHandlerEnvironment>) { + private suspend fun forwardMessage(env: MediaHandlerEnvironment<*>) { env.bot .forwardMessage( chatId = ChatId.fromId(botProps.forwardChatId), @@ -38,10 +37,7 @@ class PhotoHandlerService(private val botProps: BotProps) { ) } - private suspend fun reactToMessage( - env: MediaHandlerEnvironment>, - emoji: String, - ) { + private suspend fun reactToMessage(env: MediaHandlerEnvironment<*>, emoji: String) { env.bot .setMessageReaction( chatId = ChatId.fromId(env.message.chat.id), diff --git a/src/main/kotlin/com/pischule/memevizor/bot/handler/ThisCommandHandlerService.kt b/src/main/kotlin/com/pischule/memevizor/bot/handler/ThisCommandHandlerService.kt index efd0df4..f487ceb 100644 --- a/src/main/kotlin/com/pischule/memevizor/bot/handler/ThisCommandHandlerService.kt +++ b/src/main/kotlin/com/pischule/memevizor/bot/handler/ThisCommandHandlerService.kt @@ -2,10 +2,12 @@ package com.pischule.memevizor.bot.handler import com.github.kotlintelegrambot.dispatcher.handlers.MessageHandlerEnvironment import com.github.kotlintelegrambot.entities.ChatId +import com.github.kotlintelegrambot.entities.Message import com.github.kotlintelegrambot.entities.reaction.ReactionType import com.pischule.memevizor.bot.BotProps import com.pischule.memevizor.upload.FileUploaderService import com.pischule.memevizor.util.getMaxResPhotoId +import com.pischule.memevizor.util.getVideoFileId import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.withLoggingContext import org.springframework.stereotype.Component @@ -22,24 +24,34 @@ class ThisCommandHandlerService( suspend fun create(env: MessageHandlerEnvironment) { if (!shouldHandleMessage(env)) return - val maxResPhotoId = env.message.replyToMessage?.getMaxResPhotoId() ?: return + val replyToMessage = env.message.replyToMessage ?: return - withLoggingContext("file_id" to maxResPhotoId) { - val fileBytes = env.bot.downloadFileBytes(maxResPhotoId) ?: return - logger.info { "Downloaded a file from Telegram" } + val (fileId, contentType) = getFileInfo(replyToMessage) ?: return - fileUploaderService.uploadFile(fileBytes, "_.jpeg", "image/jpeg") + withLoggingContext("file_id" to fileId) { + val fileBytes = env.bot.downloadFileBytes(fileId) ?: return + logger.info { "Downloaded a file from Telegram, size=${fileBytes.size}" } + + fileUploaderService.uploadFile(fileBytes, "_", contentType) reactToMessage(env, "πŸ‘") } } + private fun getFileInfo(message: Message): Pair? { + return message.getMaxResPhotoId()?.let { it to "image/jpeg" } + ?: message.getVideoFileId()?.let { it to "video/mp4" } + } + private fun shouldHandleMessage(env: MessageHandlerEnvironment): Boolean { val isApprover = env.message.from?.id?.let { botProps.approverUserIds.contains(it) } == true val command = env.message.text?.lowercase() val isConfirmCommand = command in confirmCommands - val hasPhotoReply = env.message.replyToMessage?.photo?.isNotEmpty() == true - return isApprover && isConfirmCommand && hasPhotoReply + val hasMediaReply = + env.message.replyToMessage?.let { + it.photo?.isNotEmpty() == true || it.video != null + } == true + return isApprover && isConfirmCommand && hasMediaReply } private suspend fun reactToMessage(env: MessageHandlerEnvironment, emoji: String) { diff --git a/src/main/kotlin/com/pischule/memevizor/upload/DummyFileUploadService.kt b/src/main/kotlin/com/pischule/memevizor/upload/DummyFileUploadService.kt index 670c65b..41edef9 100644 --- a/src/main/kotlin/com/pischule/memevizor/upload/DummyFileUploadService.kt +++ b/src/main/kotlin/com/pischule/memevizor/upload/DummyFileUploadService.kt @@ -10,7 +10,7 @@ private val logger = KotlinLogging.logger {} @Service class DummyFileUploadService() : FileUploaderService { - override suspend fun uploadFile(fileBytes: ByteArray, filename: String, contentType: String) { + override fun uploadFile(fileBytes: ByteArray, filename: String, contentType: String) { logger.info { "File $filename has been successfully uploaded to nowhere" } } } diff --git a/src/main/kotlin/com/pischule/memevizor/upload/FileUploaderService.kt b/src/main/kotlin/com/pischule/memevizor/upload/FileUploaderService.kt index 583996b..39d7f51 100644 --- a/src/main/kotlin/com/pischule/memevizor/upload/FileUploaderService.kt +++ b/src/main/kotlin/com/pischule/memevizor/upload/FileUploaderService.kt @@ -4,5 +4,5 @@ import org.springframework.stereotype.Service @Service interface FileUploaderService { - suspend fun uploadFile(fileBytes: ByteArray, filename: String, contentType: String) + fun uploadFile(fileBytes: ByteArray, filename: String, contentType: String) } diff --git a/src/main/kotlin/com/pischule/memevizor/upload/IndexInitializer.kt b/src/main/kotlin/com/pischule/memevizor/upload/IndexInitializer.kt index d18cb9c..798123e 100644 --- a/src/main/kotlin/com/pischule/memevizor/upload/IndexInitializer.kt +++ b/src/main/kotlin/com/pischule/memevizor/upload/IndexInitializer.kt @@ -1,7 +1,6 @@ package com.pischule.memevizor.upload import io.github.oshai.kotlinlogging.KotlinLogging -import kotlinx.coroutines.runBlocking import org.springframework.boot.context.event.ApplicationStartedEvent import org.springframework.context.annotation.Configuration import org.springframework.context.event.EventListener @@ -17,7 +16,7 @@ class IndexInitializer(val fileUploaderService: FileUploaderService) { fun applicationStartedHandler(event: ApplicationStartedEvent) { try { val fileBytes = readResourceAsByteArray("static/index.html") - runBlocking { fileUploaderService.uploadFile(fileBytes, "index.html", "text/html") } + fileUploaderService.uploadFile(fileBytes, "index.html", "text/html") } catch (e: Error) { logger.warn(e) { "Failed to upload " } } diff --git a/src/main/kotlin/com/pischule/memevizor/upload/S3Config.kt b/src/main/kotlin/com/pischule/memevizor/upload/S3Config.kt index b971491..983efaf 100644 --- a/src/main/kotlin/com/pischule/memevizor/upload/S3Config.kt +++ b/src/main/kotlin/com/pischule/memevizor/upload/S3Config.kt @@ -1,8 +1,6 @@ package com.pischule.memevizor.upload -import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider -import aws.sdk.kotlin.services.s3.S3Client -import aws.smithy.kotlin.runtime.net.url.Url +import io.minio.MinioClient import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -13,14 +11,10 @@ import org.springframework.context.annotation.Profile @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 - } - } - } + fun s3Client(s3Props: S3Props): MinioClient = + MinioClient.builder() + .endpoint("https://storage.yandexcloud.net") + .region("ru-central1") + .credentials(s3Props.accessKeyId, s3Props.secretAccessKey) + .build() } diff --git a/src/main/kotlin/com/pischule/memevizor/upload/S3FileUploaderService.kt b/src/main/kotlin/com/pischule/memevizor/upload/S3FileUploaderService.kt index 08adbf4..49d3636 100644 --- a/src/main/kotlin/com/pischule/memevizor/upload/S3FileUploaderService.kt +++ b/src/main/kotlin/com/pischule/memevizor/upload/S3FileUploaderService.kt @@ -1,9 +1,8 @@ package com.pischule.memevizor.upload -import aws.sdk.kotlin.services.s3.S3Client -import aws.sdk.kotlin.services.s3.model.PutObjectRequest -import aws.smithy.kotlin.runtime.content.ByteStream import io.github.oshai.kotlinlogging.KotlinLogging +import io.minio.MinioClient +import io.minio.PutObjectArgs import org.springframework.boot.autoconfigure.condition.ConditionalOnBean import org.springframework.stereotype.Service @@ -11,16 +10,17 @@ private val logger = KotlinLogging.logger {} @ConditionalOnBean(S3Config::class) @Service -class S3FileUploaderService(private val s3Client: S3Client, private val s3Props: S3Props) : +class S3FileUploaderService(private val s3Client: MinioClient, private val s3Props: S3Props) : FileUploaderService { - override suspend fun uploadFile(fileBytes: ByteArray, filename: String, contentType: String) { + override fun uploadFile(fileBytes: ByteArray, filename: String, contentType: String) { + logger.info { "Before upload" } s3Client.putObject( - PutObjectRequest { - body = ByteStream.fromBytes(fileBytes) - bucket = s3Props.bucket - key = filename - this.contentType = contentType - } + PutObjectArgs.builder() + .bucket(s3Props.bucket) + .`object`(filename) + .stream(fileBytes.inputStream(), fileBytes.size.toLong(), -1) + .contentType(contentType) + .build() ) logger.info { "File $filename has been uploaded to S3" } } diff --git a/src/main/kotlin/com/pischule/memevizor/util/TelegramHelper.kt b/src/main/kotlin/com/pischule/memevizor/util/TelegramHelper.kt index bcc410b..b51af91 100644 --- a/src/main/kotlin/com/pischule/memevizor/util/TelegramHelper.kt +++ b/src/main/kotlin/com/pischule/memevizor/util/TelegramHelper.kt @@ -3,3 +3,5 @@ package com.pischule.memevizor.util import com.github.kotlintelegrambot.entities.Message fun Message.getMaxResPhotoId(): String? = this.photo?.lastOrNull()?.fileId + +fun Message.getVideoFileId(): String? = this.video?.fileId diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index f50a6e7..c6e9c75 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -27,22 +27,6 @@ position: relative; } - body::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-image: var(--bg-image); - background-size: cover; - background-position: center; - background-repeat: no-repeat; - filter: blur(20px); - transform: scale(1.1); - z-index: -1; - } - img { width: 100%; height: 100%; @@ -51,57 +35,52 @@ z-index: 1; } - .qr-code { - position: fixed; - z-index: 2; - bottom: 20px; - right: 20px; + video { + width: 100%; + height: 100%; + object-fit: contain; + position: relative; + z-index: 1; } БмСшная ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΊΠ° -QR Code