From 50264c7f8af2c5755854283dcdbcd56d9d0fd281 Mon Sep 17 00:00:00 2001 From: Spliterash Date: Sun, 28 Jan 2024 19:06:06 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B7=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B2=D0=B0=20=D0=B8=D0=B7=20=D1=82=D0=B8=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0(=D1=84=D1=83=20=D0=B1=D0=BB=D0=B8=D0=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/MariaDbInitializer.kt | 40 ++++++++++++ .../application/VkUnlockerRunner.kt | 14 ----- .../vkVideoUnlocker/common/RedirectHelper.kt | 35 +++++++++++ .../editableMessage/EditableMessage.kt | 2 +- .../message/utils/MessageUtils.kt | 10 +-- .../messageChain/handlers/HelpVideoChain.kt | 3 +- .../MariaDbTiktokVideoRepositoryImpl.kt | 30 +++++++++ .../tiktok/TikCdnDownloader.kt | 61 +++++++++++++++++++ .../vkVideoUnlocker/tiktok/TiktokChain.kt | 30 +++++++++ .../tiktok/TiktokDownloader.kt | 6 ++ .../vkVideoUnlocker/tiktok/TiktokService.kt | 34 +++++++++++ .../vkVideoUnlocker/tiktok/TiktokVideo.kt | 8 +++ .../tiktok/TiktokVideoEntity.kt | 6 ++ .../tiktok/TiktokVideoRepository.kt | 6 ++ .../video/accessor/AdvancedVideoAccessor.kt | 12 ++++ .../video/accessor/UrlVideoAccessorImpl.kt | 45 ++++++++++++++ .../video/accessor/VideoAccessor.kt | 8 +-- .../video/accessor/VideoAccessorFactory.kt | 4 +- ...AccessorImpl.kt => VkVideoAccessorImpl.kt} | 8 ++- .../vkVideoUnlocker/video/api/Videos.kt | 2 +- .../video/controller/VideoController.kt | 3 +- .../vkVideoUnlocker/video/dto/FullVideo.kt | 5 +- .../repository/MariaDBVideoRepository.kt | 30 --------- .../video/service/VideoService.kt | 6 +- ...AttachmentScanner.kt => MessageScanner.kt} | 18 +++++- src/main/resources/MySQL_init.sql | 6 ++ 26 files changed, 361 insertions(+), 71 deletions(-) create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/application/MariaDbInitializer.kt delete mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/application/VkUnlockerRunner.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/common/RedirectHelper.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/MariaDbTiktokVideoRepositoryImpl.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TikCdnDownloader.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokChain.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokDownloader.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokService.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokVideo.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokVideoEntity.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokVideoRepository.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/AdvancedVideoAccessor.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/UrlVideoAccessorImpl.kt rename src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/{VideoAccessorImpl.kt => VkVideoAccessorImpl.kt} (93%) rename src/main/java/ru/spliterash/vkVideoUnlocker/vk/{AttachmentScanner.kt => MessageScanner.kt} (79%) diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/application/MariaDbInitializer.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/application/MariaDbInitializer.kt new file mode 100644 index 0000000..473405e --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/application/MariaDbInitializer.kt @@ -0,0 +1,40 @@ +package ru.spliterash.vkVideoUnlocker.application + +import io.micronaut.context.event.ApplicationEventListener +import io.micronaut.context.event.StartupEvent +import jakarta.inject.Singleton +import org.jdbi.v3.core.Jdbi +import org.jdbi.v3.core.kotlin.KotlinMapper +import org.jdbi.v3.core.kotlin.inTransactionUnchecked +import ru.spliterash.vkVideoUnlocker.video.entity.VideoEntity + +@Singleton +class MariaDbInitializer( + private val jdbi: Jdbi +) : ApplicationEventListener { + override fun onApplicationEvent(event: StartupEvent) { + val stream = Thread.currentThread().contextClassLoader.getResourceAsStream("MySQL_init.sql")!! + + val initSQL = stream + .readAllBytes() + .decodeToString() + jdbi.registerRowMapper(VideoEntity::class.java, KotlinMapper(VideoEntity::class)) + + jdbi.inTransactionUnchecked { handle -> + handle.begin() + + val batch = handle.createBatch() + for (line in initSQL.split(";")) { + val trimLine = line.trim() + if (trimLine.isBlank()) + continue + + batch.add(line) + } + + batch.execute() + + handle.commit() + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/application/VkUnlockerRunner.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/application/VkUnlockerRunner.kt deleted file mode 100644 index ccc9671..0000000 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/application/VkUnlockerRunner.kt +++ /dev/null @@ -1,14 +0,0 @@ -package ru.spliterash.vkVideoUnlocker.application - -import io.micronaut.context.event.ApplicationEventListener -import io.micronaut.context.event.StartupEvent -import jakarta.inject.Singleton -import kotlinx.coroutines.runBlocking -import javax.sql.DataSource - -@Singleton -class VkUnlockerRunner( -) : ApplicationEventListener { - override fun onApplicationEvent(event: StartupEvent) = runBlocking { - } -} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/common/RedirectHelper.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/common/RedirectHelper.kt new file mode 100644 index 0000000..571a13f --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/common/RedirectHelper.kt @@ -0,0 +1,35 @@ +package ru.spliterash.vkVideoUnlocker.common + +import jakarta.inject.Singleton +import okhttp3.Request +import okhttp3.executeAsync +import ru.spliterash.vkVideoUnlocker.common.okHttp.OkHttpFactory + +@Singleton +class RedirectHelper( + private val okHttpFactory: OkHttpFactory +) { + private val client = okHttpFactory.create().followRedirects(false).build() + + suspend fun finalUrl(startUrl: String): String { + var loop = 0 + var currentUrl = startUrl + while (loop < 50) { + val response = client.newCall( + Request.Builder() + .url(currentUrl) + .head() + .build() + ) + .executeAsync() + + if (response.isRedirect) + currentUrl = response.header("Location")!! + else { + return currentUrl + } + loop++ + } + throw RuntimeException("Too much redirects") + } +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/message/editableMessage/EditableMessage.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/message/editableMessage/EditableMessage.kt index e3f4abc..6090aab 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/message/editableMessage/EditableMessage.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/message/editableMessage/EditableMessage.kt @@ -1,5 +1,5 @@ package ru.spliterash.vkVideoUnlocker.message.editableMessage interface EditableMessage { - suspend fun sendOrUpdate(text: String?, attachments: String? = null) + suspend fun sendOrUpdate(text: String? = null, attachments: String? = null) } \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/message/utils/MessageUtils.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/message/utils/MessageUtils.kt index d008db2..0e68022 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/message/utils/MessageUtils.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/message/utils/MessageUtils.kt @@ -10,7 +10,7 @@ import ru.spliterash.vkVideoUnlocker.story.vkModels.VkStory import ru.spliterash.vkVideoUnlocker.video.holder.VideoContentHolder import ru.spliterash.vkVideoUnlocker.video.service.VideoService import ru.spliterash.vkVideoUnlocker.video.vkModels.VkVideo -import ru.spliterash.vkVideoUnlocker.vk.AttachmentScanner +import ru.spliterash.vkVideoUnlocker.vk.MessageScanner import ru.spliterash.vkVideoUnlocker.vk.actor.GroupUser import ru.spliterash.vkVideoUnlocker.vk.api.VkApi import java.util.function.Predicate @@ -19,7 +19,7 @@ import java.util.regex.Pattern @Singleton class MessageUtils( @GroupUser private val groupUser: VkApi, - private val attachmentScanner: AttachmentScanner, + private val messageScanner: MessageScanner, private val videoService: VideoService, ) { private val vkUrlPattern = Pattern.compile("(?:https?://)?vk\\.com/(?(?:video|wall|story)-?\\d+_\\d+)") @@ -30,11 +30,11 @@ class MessageUtils( else Predicate { it !is ReplyMessage } - val attachmentContent = attachmentScanner.scanForAttachment( + val attachmentContent = messageScanner.scanForAttachment( root, listOf( - AttachmentScanner.Checker { it.video }, - AttachmentScanner.Checker { it.story } + MessageScanner.Checker { it.video }, + MessageScanner.Checker { it.story } ), containerPredicate ) diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/messageChain/handlers/HelpVideoChain.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/messageChain/handlers/HelpVideoChain.kt index edda89f..428ae67 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/messageChain/handlers/HelpVideoChain.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/messageChain/handlers/HelpVideoChain.kt @@ -34,7 +34,8 @@ class HelpVideoChain : ActivationMessageHandler( "Если же администратор чата выдаст мне доступ ко всей переписке, я смогу разблокировать видео в автоматическом режиме\n\n" + "Дополнительные возможности:\n" + "* Скачивание видео: перешли мне видео как обычно и напиши в сообщении 'скачать'(без кавычек)\n" + - "* Сохранение историй: перешли мне историю на нужном слайде" + "* Сохранение историй: перешли мне историю на нужном слайде\n"+ + "* Перезалив из тиктока: просто пришли ссылку" ) return true; diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/MariaDbTiktokVideoRepositoryImpl.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/MariaDbTiktokVideoRepositoryImpl.kt new file mode 100644 index 0000000..111244b --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/MariaDbTiktokVideoRepositoryImpl.kt @@ -0,0 +1,30 @@ +package ru.spliterash.vkVideoUnlocker.tiktok + +import jakarta.inject.Singleton +import org.jdbi.v3.core.Jdbi +import org.jdbi.v3.core.kotlin.withHandleUnchecked + +@Singleton +class MariaDbTiktokVideoRepositoryImpl( + private val jdbi: Jdbi +) : TiktokVideoRepository { + override suspend fun findVideo(id: String): TiktokVideoEntity? = jdbi.withHandleUnchecked { handle -> + handle.createQuery("SELECT * FROM tiktok_videos where id = ?") + .bind(0, id) + .mapTo(TiktokVideoEntity::class.java) + .findOne() + .orElse(null) + } + + override suspend fun save(entity: TiktokVideoEntity) { + jdbi.withHandleUnchecked { handle -> + handle.createUpdate( + "INSERT INTO tiktok_videos (id,vk_id) values (:id,:vk_id) " + + "on duplicate key update vk_id = :vk_id" + ) + .bind("id", entity.id) + .bind("vk_id", entity.vkId) + .execute() + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TikCdnDownloader.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TikCdnDownloader.kt new file mode 100644 index 0000000..4554289 --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TikCdnDownloader.kt @@ -0,0 +1,61 @@ +package ru.spliterash.vkVideoUnlocker.tiktok + +import jakarta.inject.Singleton +import okhttp3.FormBody +import okhttp3.Request +import okhttp3.executeAsync +import ru.spliterash.vkVideoUnlocker.common.okHttp.OkHttpFactory +import ru.spliterash.vkVideoUnlocker.common.okHttp.executeAsync +import ru.spliterash.vkVideoUnlocker.video.accessor.UrlVideoAccessorImpl +import java.net.URL +import java.util.regex.Pattern + +@Singleton +class TikCdnDownloader( + okHttpFactory: OkHttpFactory +) : TiktokDownloader { + private val client = okHttpFactory.create().build() + private val tikCdnTokenPattern = Pattern.compile("s_tt += +'(?[a-zA-Z0-9_-]+)'") + private val tikCdnVideoUrlPattern = Pattern.compile("https://tikcdn\\.io/ssstik/(?\\d+)") + override suspend fun download(videoUrl: String): TiktokVideo { + val token = getActualToken() + + val response = Request.Builder() + .url("https://ssstik.io/abc?url=dl") + .post( + FormBody.Builder() + .addEncoded("id", videoUrl) + .addEncoded("locale", "en") + .addEncoded("tt", token) + .build() + ) + .header("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0") + .build() + .executeAsync(client) + .body + .string() + val matcher = tikCdnVideoUrlPattern.matcher(response) + if (!matcher.find()) { + println(response) + throw IllegalStateException("ssstik.io return wrong response") + } + + val url = matcher.group() + val id = matcher.group("id") + + return TiktokVideo(id, UrlVideoAccessorImpl(client, URL(url))) + + } + + private suspend fun getActualToken(): String { + val response = client + .newCall(Request.Builder().get().url("https://ssstik.io/en").build()) + .executeAsync() + + val page = response.body.string() + val matcher = tikCdnTokenPattern.matcher(page) + if (!matcher.find()) throw IllegalStateException("Fail to parse tikcdn token") + + return matcher.group("token") + } +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokChain.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokChain.kt new file mode 100644 index 0000000..f09ceb2 --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokChain.kt @@ -0,0 +1,30 @@ +package ru.spliterash.vkVideoUnlocker.tiktok + +import jakarta.inject.Singleton +import ru.spliterash.vkVideoUnlocker.common.RedirectHelper +import ru.spliterash.vkVideoUnlocker.longpoll.message.RootMessage +import ru.spliterash.vkVideoUnlocker.message.editableMessage.EditableMessage +import ru.spliterash.vkVideoUnlocker.messageChain.MessageHandler +import ru.spliterash.vkVideoUnlocker.vk.MessageScanner +import java.util.regex.Pattern + +@Singleton +class TiktokChain( + private val redirectHelper: RedirectHelper, + private val tiktokService: TiktokService, + private val messageScanner: MessageScanner, +) : MessageHandler { + private val pattern = Pattern.compile("https?://(?:www|vt)?\\.tiktok.com/(?:@\\w+/video/(?:(?\\d+))|\\w+)") + override suspend fun handle(message: RootMessage, editableMessage: EditableMessage): Boolean { + val videoUrl = messageScanner.scanForText(message) { + val matcher = pattern.matcher(it) + if (!matcher.find()) null + else matcher.group() + } ?: return false + + val id = tiktokService.getVkId(videoUrl) + editableMessage.sendOrUpdate(attachments = "video$id") + + return true + } +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokDownloader.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokDownloader.kt new file mode 100644 index 0000000..81def5f --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokDownloader.kt @@ -0,0 +1,6 @@ +package ru.spliterash.vkVideoUnlocker.tiktok + + +interface TiktokDownloader { + suspend fun download(videoUrl: String): TiktokVideo +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokService.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokService.kt new file mode 100644 index 0000000..867819c --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokService.kt @@ -0,0 +1,34 @@ +package ru.spliterash.vkVideoUnlocker.tiktok + +import jakarta.inject.Singleton +import ru.spliterash.vkVideoUnlocker.video.accessor.VideoAccessor +import ru.spliterash.vkVideoUnlocker.vk.actor.GroupUser +import ru.spliterash.vkVideoUnlocker.vk.actor.types.WorkUser +import ru.spliterash.vkVideoUnlocker.vk.api.VkApi + +@Singleton +class TiktokService( + @WorkUser private val workUser: VkApi, + @GroupUser private val groupUser: VkApi, + private val tiktokDownloader: TiktokDownloader, + private val tiktokVideoRepository: TiktokVideoRepository +) { + suspend fun getVkId(tiktokVideoUrl: String): String { + val info = tiktokDownloader.download(tiktokVideoUrl) + val video = tiktokVideoRepository.findVideo(info.id) + if (video != null) return video.vkId + + val vkId = reUpload(info.id, info.accessor) + + tiktokVideoRepository.save(TiktokVideoEntity(info.id, vkId)) + + return vkId + } + + private suspend fun reUpload(number: String, accessor: VideoAccessor) = workUser.videos.upload( + groupUser.id, + "tiktok-$number", + false, + accessor + ) +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokVideo.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokVideo.kt new file mode 100644 index 0000000..42c9b5c --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokVideo.kt @@ -0,0 +1,8 @@ +package ru.spliterash.vkVideoUnlocker.tiktok + +import ru.spliterash.vkVideoUnlocker.video.accessor.VideoAccessor + +class TiktokVideo( + val id: String, + val accessor: VideoAccessor +) \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokVideoEntity.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokVideoEntity.kt new file mode 100644 index 0000000..0a3ced2 --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokVideoEntity.kt @@ -0,0 +1,6 @@ +package ru.spliterash.vkVideoUnlocker.tiktok + +class TiktokVideoEntity( + val id: String, + val vkId: String +) \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokVideoRepository.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokVideoRepository.kt new file mode 100644 index 0000000..9d6957d --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokVideoRepository.kt @@ -0,0 +1,6 @@ +package ru.spliterash.vkVideoUnlocker.tiktok + +interface TiktokVideoRepository { + suspend fun findVideo(id: String): TiktokVideoEntity? + suspend fun save(entity: TiktokVideoEntity) +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/AdvancedVideoAccessor.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/AdvancedVideoAccessor.kt new file mode 100644 index 0000000..600a680 --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/AdvancedVideoAccessor.kt @@ -0,0 +1,12 @@ +package ru.spliterash.vkVideoUnlocker.video.accessor + +import java.net.URL + +interface AdvancedVideoAccessor : VideoAccessor { + val maxQuality: Int + val maxQualityUrl: URL + fun preview(): URL + + suspend fun load(quality: Int): VideoAccessor.Info + suspend fun load(quality: Int, range: String): VideoAccessor.Info +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/UrlVideoAccessorImpl.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/UrlVideoAccessorImpl.kt new file mode 100644 index 0000000..3f56029 --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/UrlVideoAccessorImpl.kt @@ -0,0 +1,45 @@ +package ru.spliterash.vkVideoUnlocker.video.accessor + +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.executeAsync +import java.net.URL + +class UrlVideoAccessorImpl( + private val client: OkHttpClient, + private val url: URL +) : VideoAccessor { + override suspend fun size(quality: Int): Long { + val response = client + .newCall( + Request.Builder() + .url(url) + .head() + .build() + ) + .executeAsync() + + return response.headers["Content-Length"]?.toLong() ?: -1L + } + + override suspend fun load(): VideoAccessor.Info { + val response = client + .newCall( + Request.Builder() + .url(url) + .get() + .build() + ) + .executeAsync() + val size = response.headers["Content-Length"]?.toLong() ?: -1L + + return VideoAccessor.Info( + response.code, + response + .body + .byteStream(), + null, + size + ) + } +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VideoAccessor.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VideoAccessor.kt index 46bc6dd..e8a5aa3 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VideoAccessor.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VideoAccessor.kt @@ -4,14 +4,8 @@ import java.io.InputStream import java.net.URL interface VideoAccessor { - val maxQuality: Int - val maxQualityUrl: URL - - fun preview(): URL suspend fun size(quality: Int): Long - suspend fun load() = load(maxQuality) - suspend fun load(quality: Int): Info - suspend fun load(quality: Int, range: String): Info + suspend fun load(): Info data class Info( val code: Int, diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VideoAccessorFactory.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VideoAccessorFactory.kt index 3d2451b..632fc90 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VideoAccessorFactory.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VideoAccessorFactory.kt @@ -10,7 +10,7 @@ class VideoAccessorFactory( ) { private val client = factory.create().build() - fun create(video: VkVideo): VideoAccessor { - return VideoAccessorImpl(client, video) + fun create(video: VkVideo): AdvancedVideoAccessor { + return VkVideoAccessorImpl(client, video) } } \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VideoAccessorImpl.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VkVideoAccessorImpl.kt similarity index 93% rename from src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VideoAccessorImpl.kt rename to src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VkVideoAccessorImpl.kt index 4071a62..11c563f 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VideoAccessorImpl.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/accessor/VkVideoAccessorImpl.kt @@ -8,10 +8,10 @@ import ru.spliterash.vkVideoUnlocker.video.vkModels.VkVideo import java.net.URL -class VideoAccessorImpl( +class VkVideoAccessorImpl( private val client: OkHttpClient, private val video: VkVideo, -) : VideoAccessor { +) : VideoAccessor, AdvancedVideoAccessor { override val maxQuality: Int override val maxQualityUrl: URL override fun preview(): URL = video.preview() @@ -35,6 +35,8 @@ class VideoAccessorImpl( return response.headers["Content-Length"]?.toLong() ?: -1L } + override suspend fun load() = load(maxQuality) + override suspend fun load(quality: Int): VideoAccessor.Info { val request = builder(video.qualityUrl(quality)) .get() @@ -100,6 +102,6 @@ class VideoAccessorImpl( } companion object { - private val log = LogFactory.getLog(VideoAccessorImpl::class.java) + private val log = LogFactory.getLog(VkVideoAccessorImpl::class.java) } } \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/api/Videos.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/api/Videos.kt index 53c041c..2b49a0f 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/api/Videos.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/api/Videos.kt @@ -1,7 +1,7 @@ package ru.spliterash.vkVideoUnlocker.video.api -import ru.spliterash.vkVideoUnlocker.video.vkModels.VkVideo import ru.spliterash.vkVideoUnlocker.video.accessor.VideoAccessor +import ru.spliterash.vkVideoUnlocker.video.vkModels.VkVideo interface Videos { /** diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/controller/VideoController.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/controller/VideoController.kt index 7e22cbe..eda3f1f 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/controller/VideoController.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/controller/VideoController.kt @@ -9,6 +9,7 @@ import io.micronaut.http.server.types.files.StreamedFile import kotlinx.coroutines.* import ru.spliterash.vkVideoUnlocker.video.DownloadUrlSupplier import ru.spliterash.vkVideoUnlocker.video.Routes +import ru.spliterash.vkVideoUnlocker.video.accessor.AdvancedVideoAccessor import ru.spliterash.vkVideoUnlocker.video.accessor.VideoAccessor import ru.spliterash.vkVideoUnlocker.video.controller.response.VideoResponse import ru.spliterash.vkVideoUnlocker.video.controller.response.VideoUnlockResponse @@ -28,7 +29,7 @@ class VideoController( private val cache = Caffeine .newBuilder() .expireAfterWrite(30, TimeUnit.MINUTES) - .build> { + .build> { scope.async { load(it).toAccessor() } diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/dto/FullVideo.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/dto/FullVideo.kt index 91973fe..a2b1504 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/dto/FullVideo.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/dto/FullVideo.kt @@ -2,6 +2,7 @@ package ru.spliterash.vkVideoUnlocker.video.dto import ru.spliterash.vkVideoUnlocker.group.WorkUserGroupService import ru.spliterash.vkVideoUnlocker.group.dto.GroupStatus +import ru.spliterash.vkVideoUnlocker.video.accessor.AdvancedVideoAccessor import ru.spliterash.vkVideoUnlocker.video.accessor.VideoAccessor import ru.spliterash.vkVideoUnlocker.video.accessor.VideoAccessorFactory import ru.spliterash.vkVideoUnlocker.video.vkModels.VkVideo @@ -15,7 +16,7 @@ class FullVideo( private val factory: VideoAccessorFactory, private val groupService: WorkUserGroupService, ) { - private var accessor: VideoAccessor? = null + private var accessor: AdvancedVideoAccessor? = null suspend fun status(): GroupStatus { if (video.ownerId > 0) @@ -30,7 +31,7 @@ class FullVideo( return status() != GroupStatus.PUBLIC } - fun toAccessor(): VideoAccessor { + fun toAccessor(): AdvancedVideoAccessor { return accessor ?: factory.create(video).also { accessor = it } diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/repository/MariaDBVideoRepository.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/repository/MariaDBVideoRepository.kt index 1f7c38e..86d6b1a 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/repository/MariaDBVideoRepository.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/repository/MariaDBVideoRepository.kt @@ -1,12 +1,9 @@ package ru.spliterash.vkVideoUnlocker.video.repository -import jakarta.annotation.PostConstruct import jakarta.inject.Singleton import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.jdbi.v3.core.Jdbi -import org.jdbi.v3.core.kotlin.KotlinMapper -import org.jdbi.v3.core.kotlin.inTransactionUnchecked import org.jdbi.v3.core.kotlin.withHandleUnchecked import ru.spliterash.vkVideoUnlocker.video.entity.VideoEntity @@ -14,33 +11,6 @@ import ru.spliterash.vkVideoUnlocker.video.entity.VideoEntity class MariaDBVideoRepository( private val jdbi: Jdbi ) : VideoRepository { - @PostConstruct - fun init() { - val stream = Thread.currentThread().contextClassLoader.getResourceAsStream("MySQL_init.sql")!! - - val initSQL = stream - .readAllBytes() - .decodeToString() - jdbi.registerRowMapper(VideoEntity::class.java, KotlinMapper(VideoEntity::class)) - - jdbi.inTransactionUnchecked { handle -> - handle.begin() - - val batch = handle.createBatch() - for (line in initSQL.split(";")) { - val trimLine = line.trim() - if (trimLine.isBlank()) - continue - - batch.add(line) - } - - batch.execute() - - handle.commit() - } - } - override suspend fun findVideo(id: String): VideoEntity? = withContext(Dispatchers.IO) { jdbi.withHandleUnchecked { handle -> handle.createQuery("SELECT * FROM videos where id = ?") diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/service/VideoService.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/service/VideoService.kt index e5b6efc..f06946b 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/service/VideoService.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/service/VideoService.kt @@ -15,7 +15,7 @@ import ru.spliterash.vkVideoUnlocker.video.holder.VideoContentHolder import ru.spliterash.vkVideoUnlocker.video.holder.VideoHolder import ru.spliterash.vkVideoUnlocker.video.vkModels.VkVideo import ru.spliterash.vkVideoUnlocker.video.vkModels.normalId -import ru.spliterash.vkVideoUnlocker.vk.AttachmentScanner +import ru.spliterash.vkVideoUnlocker.vk.MessageScanner import ru.spliterash.vkVideoUnlocker.vk.VkConst import ru.spliterash.vkVideoUnlocker.vk.actor.types.PokeUser import ru.spliterash.vkVideoUnlocker.vk.actor.types.WorkUser @@ -23,7 +23,7 @@ import ru.spliterash.vkVideoUnlocker.vk.api.VkApi @Singleton class VideoService( - private val attachmentScanner: AttachmentScanner, + private val messageScanner: MessageScanner, private val workUserGroupService: WorkUserGroupService, private val videoAccessorFactory: VideoAccessorFactory, @WorkUser private val workUser: VkApi, @@ -69,7 +69,7 @@ class VideoService( suspend fun wrapWallId(wallId: String): VideoContentHolder { val wall = workUser.walls.getById(wallId) - val video = attachmentScanner.scanForAttachment(wall) { it.video } + val video = messageScanner.scanForAttachment(wall) { it.video } if (video != null) return FullVideoHolder(video) // Пользователь получает полное видео если запросил стену else diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/AttachmentScanner.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/MessageScanner.kt similarity index 79% rename from src/main/java/ru/spliterash/vkVideoUnlocker/vk/AttachmentScanner.kt rename to src/main/java/ru/spliterash/vkVideoUnlocker/vk/MessageScanner.kt index 30f0f93..5acec2d 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/AttachmentScanner.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/MessageScanner.kt @@ -2,12 +2,28 @@ package ru.spliterash.vkVideoUnlocker.vk import jakarta.inject.Singleton import ru.spliterash.vkVideoUnlocker.longpoll.message.Attachment +import ru.spliterash.vkVideoUnlocker.longpoll.message.Message import ru.spliterash.vkVideoUnlocker.longpoll.message.attachments.AttachmentContainer import ru.spliterash.vkVideoUnlocker.longpoll.message.attachments.AttachmentContent import java.util.function.Predicate @Singleton -class AttachmentScanner { +class MessageScanner { + fun scanForText(message: Message, func: (String) -> T?): T? { + val text = message.text + if (text != null) { + val result = func(text) + if (result != null) return result + } + + for (fwdMessage in message.fwdMessages) { + val result = scanForText(fwdMessage, func) + if (result != null) return result + } + + return null + } + fun scanForAttachment( root: AttachmentContainer, checker: Checker, diff --git a/src/main/resources/MySQL_init.sql b/src/main/resources/MySQL_init.sql index bbef5ff..2e478a2 100644 --- a/src/main/resources/MySQL_init.sql +++ b/src/main/resources/MySQL_init.sql @@ -4,3 +4,9 @@ CREATE TABLE IF NOT EXISTS videos ( unlocked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, private BOOLEAN DEFAULT FALSE ) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS tiktok_videos ( + id VARCHAR(128) NOT NULL PRIMARY KEY, + vk_id VARCHAR(26) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) engine=InnoDB;