Skip to content

Commit

Permalink
Поддержка перезалива из тиктока(фу блин)
Browse files Browse the repository at this point in the history
  • Loading branch information
Spliterash committed Jan 28, 2024
1 parent 7e07a3f commit 50264c7
Show file tree
Hide file tree
Showing 26 changed files with 361 additions and 71 deletions.
Original file line number Diff line number Diff line change
@@ -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<StartupEvent> {
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()
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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")
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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/(?<attachment>(?:video|wall|story)-?\\d+_\\d+)")
Expand All @@ -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
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class HelpVideoChain : ActivationMessageHandler(
"Если же администратор чата выдаст мне доступ ко всей переписке, я смогу разблокировать видео в автоматическом режиме\n\n" +
"Дополнительные возможности:\n" +
"* Скачивание видео: перешли мне видео как обычно и напиши в сообщении 'скачать'(без кавычек)\n" +
"* Сохранение историй: перешли мне историю на нужном слайде"
"* Сохранение историй: перешли мне историю на нужном слайде\n"+
"* Перезалив из тиктока: просто пришли ссылку"
)

return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
Original file line number Diff line number Diff line change
@@ -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 += +'(?<token>[a-zA-Z0-9_-]+)'")
private val tikCdnVideoUrlPattern = Pattern.compile("https://tikcdn\\.io/ssstik/(?<id>\\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")
}
}
30 changes: 30 additions & 0 deletions src/main/java/ru/spliterash/vkVideoUnlocker/tiktok/TiktokChain.kt
Original file line number Diff line number Diff line change
@@ -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/(?:(?<id>\\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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ru.spliterash.vkVideoUnlocker.tiktok


interface TiktokDownloader {
suspend fun download(videoUrl: String): TiktokVideo
}
Original file line number Diff line number Diff line change
@@ -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
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ru.spliterash.vkVideoUnlocker.tiktok

import ru.spliterash.vkVideoUnlocker.video.accessor.VideoAccessor

class TiktokVideo(
val id: String,
val accessor: VideoAccessor
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ru.spliterash.vkVideoUnlocker.tiktok

class TiktokVideoEntity(
val id: String,
val vkId: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ru.spliterash.vkVideoUnlocker.tiktok

interface TiktokVideoRepository {
suspend fun findVideo(id: String): TiktokVideoEntity?
suspend fun save(entity: TiktokVideoEntity)
}
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 50264c7

Please sign in to comment.