From bb78884c28807cd726842bd6df62e2d24cc41358 Mon Sep 17 00:00:00 2001 From: Spliterash Date: Tue, 8 Oct 2024 19:17:18 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=B8=D0=B2=D0=B0=D1=82=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=B2=D0=B8=D0=B4=D0=BE=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/VkConfiguration.kt | 2 +- .../exceptions/AlwaysNotifyException.kt | 3 + .../group/WorkUserGroupService.kt | 4 +- .../vkVideoUnlocker/group/api/Groups.kt | 4 +- .../vkVideoUnlocker/group/api/GroupsImpl.kt | 6 +- .../longpoll/message/RootMessage.kt | 1 + .../longpoll/message/attachments/Wall.kt | 4 + .../vkVideoUnlocker/message/api/Messages.kt | 7 ++ .../message/api/MessagesImpl.kt | 20 +++++ .../message/utils/MessageContentScanner.kt | 38 +++++++++ .../message/utils/MessageUtils.kt | 33 ++------ .../handlers/DefaultVideoChain.kt | 9 +- .../handlers/DownloadVideoChain.kt | 8 +- .../vkVideoUnlocker/video/api/Videos.kt | 2 +- .../vkVideoUnlocker/video/api/VideosImpl.kt | 4 +- .../video/controller/VideoController.kt | 13 ++- .../exceptions/ContentNotFoundException.kt | 10 +++ .../exceptions/NoSenseReuploadUserVideos.kt | 9 ++ .../video/exceptions/NoSourceException.kt | 9 ++ ...eDoNotWorkWithLockedUserVideosException.kt | 7 -- .../holder/AbstractVideoContentHolder.kt | 6 +- .../video/holder/VideoContentHolder.kt | 5 +- .../video/service/VideoService.kt | 83 +++++++++++++++---- .../vkVideoUnlocker/video/vkModels/VkVideo.kt | 8 +- .../video/vkModels/VkVideoExt.kt | 8 +- .../vkVideoUnlocker/vk/MessageScanner.kt | 29 +++++-- .../spliterash/vkVideoUnlocker/vk/VkConst.kt | 2 +- .../vkVideoUnlocker/vk/actor/types/Actor.kt | 2 +- .../vkVideoUnlocker/vk/api/VkApi.kt | 2 +- .../vkVideoUnlocker/vk/api/VkApiImpl.kt | 2 +- .../vk/exceptions/VkApiException.kt | 3 +- .../vkVideoUnlocker/wall/api/WallsImpl.kt | 2 + .../vkVideoUnlocker/wall/vkModels/WallPost.kt | 4 - 33 files changed, 258 insertions(+), 91 deletions(-) create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/common/exceptions/AlwaysNotifyException.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/message/utils/MessageContentScanner.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/ContentNotFoundException.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/NoSenseReuploadUserVideos.kt create mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/NoSourceException.kt delete mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/WeDoNotWorkWithLockedUserVideosException.kt delete mode 100644 src/main/java/ru/spliterash/vkVideoUnlocker/wall/vkModels/WallPost.kt diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/application/VkConfiguration.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/application/VkConfiguration.kt index 597f212..004c7bc 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/application/VkConfiguration.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/application/VkConfiguration.kt @@ -37,7 +37,7 @@ class VkConfiguration( @Bean @GroupUser fun groupActor( - @Value("\${vk-unlocker.group.id}") id: Int, + @Value("\${vk-unlocker.group.id}") id: Long, @Value("\${vk-unlocker.group.token}") token: String ): VkApi = context.createBean(VkApiImpl::class.java, Actor(id, token)) } \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/common/exceptions/AlwaysNotifyException.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/common/exceptions/AlwaysNotifyException.kt new file mode 100644 index 0000000..5ea62b8 --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/common/exceptions/AlwaysNotifyException.kt @@ -0,0 +1,3 @@ +package ru.spliterash.vkVideoUnlocker.common.exceptions + +interface AlwaysNotifyException \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/group/WorkUserGroupService.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/group/WorkUserGroupService.kt index 04fd1c3..bae72d1 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/group/WorkUserGroupService.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/group/WorkUserGroupService.kt @@ -15,7 +15,7 @@ import ru.spliterash.vkVideoUnlocker.vk.api.VkApi class WorkUserGroupService( @DownloadUser private val user: VkApi ) { - private val groups = hashMapOf() + private val groups = hashMapOf() private val lock = Mutex() /** @@ -28,7 +28,7 @@ class WorkUserGroupService( * @return Статус группы */ @Throws(VideoGroupPrivateException::class, VideoGroupRequestSendException::class) - suspend fun joinGroup(groupId: Int): GroupStatus { + suspend fun joinGroup(groupId: Long): GroupStatus { val groupInfo = lock.withLock { groups[groupId] } if (groupInfo != null) { val groupIsOpen = groupInfo.groupStatus == GroupStatus.PUBLIC diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/group/api/Groups.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/group/api/Groups.kt index bdf72ec..94bf3ae 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/group/api/Groups.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/group/api/Groups.kt @@ -4,8 +4,8 @@ import ru.spliterash.vkVideoUnlocker.group.dto.GroupInfo import ru.spliterash.vkVideoUnlocker.group.vkModels.LongPollServerResponse interface Groups { - suspend fun status(groupId: Int): GroupInfo - suspend fun join(groupId: Int) + suspend fun status(groupId: Long): GroupInfo + suspend fun join(groupId: Long) suspend fun getLongPollServer(): LongPollServerResponse diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/group/api/GroupsImpl.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/group/api/GroupsImpl.kt index f24292b..69fdc7a 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/group/api/GroupsImpl.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/group/api/GroupsImpl.kt @@ -11,9 +11,9 @@ import ru.spliterash.vkVideoUnlocker.group.dto.GroupStatus import ru.spliterash.vkVideoUnlocker.group.dto.MemberStatus import ru.spliterash.vkVideoUnlocker.group.vkModels.LongPollServerResponse import ru.spliterash.vkVideoUnlocker.group.vkModels.VkGroupGetByIdResponse +import ru.spliterash.vkVideoUnlocker.vk.VkConst import ru.spliterash.vkVideoUnlocker.vk.VkHelper import ru.spliterash.vkVideoUnlocker.vk.actor.types.Actor -import ru.spliterash.vkVideoUnlocker.vk.VkConst @Prototype class GroupsImpl( @@ -27,7 +27,7 @@ class GroupsImpl( private val log = LoggerFactory.getLogger(GroupsImpl::class.java) } - override suspend fun status(groupId: Int): GroupInfo { + override suspend fun status(groupId: Long): GroupInfo { val request = VkConst.requestBuilder() .url( VkConst.urlBuilder("groups.getById") // Hidden method, taken from mobile app @@ -62,7 +62,7 @@ class GroupsImpl( ) } - override suspend fun join(groupId: Int) { + override suspend fun join(groupId: Long) { val request = VkConst.requestBuilder() .url( VkConst.urlBuilder("groups.join") diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/longpoll/message/RootMessage.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/longpoll/message/RootMessage.kt index 89c0dfc..335443b 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/longpoll/message/RootMessage.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/longpoll/message/RootMessage.kt @@ -3,6 +3,7 @@ package ru.spliterash.vkVideoUnlocker.longpoll.message import com.fasterxml.jackson.annotation.JsonProperty data class RootMessage( + @JsonProperty("id") val id: String, @JsonProperty("attachments") override val attachments: List, @JsonProperty("conversation_message_id") val conversationMessageId: Long, @JsonProperty("fwd_messages") override val fwdMessages: List = listOf(), diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/longpoll/message/attachments/Wall.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/longpoll/message/attachments/Wall.kt index 06445f3..f9c99aa 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/longpoll/message/attachments/Wall.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/longpoll/message/attachments/Wall.kt @@ -4,6 +4,10 @@ import com.fasterxml.jackson.annotation.JsonProperty import ru.spliterash.vkVideoUnlocker.longpoll.message.Attachment data class Wall( + @JsonProperty("id") + val id: Long, + @JsonProperty("owner_id") + val ownerId: Long, @JsonProperty("attachments") val attachments: List = listOf(), @JsonProperty("copy_history") diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/message/api/Messages.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/message/api/Messages.kt index 7be5623..d81393c 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/message/api/Messages.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/message/api/Messages.kt @@ -1,6 +1,13 @@ package ru.spliterash.vkVideoUnlocker.message.api +import ru.spliterash.vkVideoUnlocker.longpoll.message.RootMessage + interface Messages { + suspend fun messageById( + groupId: Long, + messageId: String + ): RootMessage + suspend fun sendMessage( peerId: Long, message: String? = null, diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/message/api/MessagesImpl.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/message/api/MessagesImpl.kt index a949a6d..1f1c107 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/message/api/MessagesImpl.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/message/api/MessagesImpl.kt @@ -6,10 +6,13 @@ import io.micronaut.context.annotation.Parameter import io.micronaut.context.annotation.Prototype import okhttp3.OkHttpClient import ru.spliterash.vkVideoUnlocker.common.okHttp.executeAsync +import ru.spliterash.vkVideoUnlocker.longpoll.message.RootMessage import ru.spliterash.vkVideoUnlocker.message.vkModels.request.Forward import ru.spliterash.vkVideoUnlocker.message.vkModels.response.MessageSendResponse +import ru.spliterash.vkVideoUnlocker.video.api.VideosImpl import ru.spliterash.vkVideoUnlocker.vk.VkConst import ru.spliterash.vkVideoUnlocker.vk.VkHelper +import ru.spliterash.vkVideoUnlocker.vk.vkModels.VkItemsResponse @Prototype class MessagesImpl( @@ -23,6 +26,23 @@ class MessagesImpl( ) + "..." else this + override suspend fun messageById(groupId: Long, messageId: String): RootMessage { + val response = VkConst + .requestBuilder() + .header("user-agent", VideosImpl.USER_AGENT) + .url( + VkConst.urlBuilder("messages.getById") + .addQueryParameter("group_id", groupId.toString()) + .addQueryParameter("message_ids", messageId) + .build() + ) + .build() + .executeAsync(client) + + val result = vkHelper.readResponse(response, object : TypeReference>() {}) + return result.items.first() + } + override suspend fun sendMessage(peerId: Long, message: String?, replyTo: Long?, attachments: String?): Long { val response = VkConst diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/message/utils/MessageContentScanner.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/message/utils/MessageContentScanner.kt new file mode 100644 index 0000000..dc3314f --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/message/utils/MessageContentScanner.kt @@ -0,0 +1,38 @@ +package ru.spliterash.vkVideoUnlocker.message.utils + +import jakarta.inject.Singleton +import ru.spliterash.vkVideoUnlocker.longpoll.message.ReplyMessage +import ru.spliterash.vkVideoUnlocker.longpoll.message.RootMessage +import ru.spliterash.vkVideoUnlocker.longpoll.message.attachments.AttachmentContainer +import ru.spliterash.vkVideoUnlocker.longpoll.message.hasPing +import ru.spliterash.vkVideoUnlocker.longpoll.message.isPersonalChat +import ru.spliterash.vkVideoUnlocker.vk.MessageScanner +import ru.spliterash.vkVideoUnlocker.vk.actor.GroupUser +import ru.spliterash.vkVideoUnlocker.vk.api.VkApi +import java.util.* +import java.util.function.Predicate + +@Singleton +class MessageContentScanner( + @GroupUser private val groupUser: VkApi, + private val messageScanner: MessageScanner, +) { + fun findContent(root: RootMessage): MessageScanner.ScanResult? { + val containerPredicate: Predicate = + if (root.isPersonalChat() || root.hasPing(groupUser)) + Predicate { true } + else + Predicate { it !is ReplyMessage } + + val init = LinkedList() + init += root + return messageScanner.scanForAttachment( + init, + listOf( + MessageScanner.Checker { it.video }, + MessageScanner.Checker { it.story }, + ), + containerPredicate + ) + } +} \ 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 c8a6ceb..c5c2a1e 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/message/utils/MessageUtils.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/message/utils/MessageUtils.kt @@ -1,47 +1,26 @@ package ru.spliterash.vkVideoUnlocker.message.utils import jakarta.inject.Singleton -import ru.spliterash.vkVideoUnlocker.longpoll.message.ReplyMessage import ru.spliterash.vkVideoUnlocker.longpoll.message.RootMessage -import ru.spliterash.vkVideoUnlocker.longpoll.message.attachments.AttachmentContainer -import ru.spliterash.vkVideoUnlocker.longpoll.message.hasPing -import ru.spliterash.vkVideoUnlocker.longpoll.message.isPersonalChat 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.MessageScanner -import ru.spliterash.vkVideoUnlocker.vk.actor.GroupUser -import ru.spliterash.vkVideoUnlocker.vk.api.VkApi -import ru.spliterash.vkVideoUnlocker.wall.vkModels.WallPost -import java.util.function.Predicate import java.util.regex.Pattern @Singleton class MessageUtils( - @GroupUser private val groupUser: VkApi, - private val messageScanner: MessageScanner, + private val messageContentScanner: MessageContentScanner, private val videoService: VideoService, ) { private val vkUrlPattern = Pattern.compile("(?:https?://)?vk\\.com/(?(?:video|wall|story)-?\\d+_\\d+)") - suspend fun scanForVideoContent(root: RootMessage): VideoContentHolder? { - val containerPredicate: Predicate = - if (root.isPersonalChat() || root.hasPing(groupUser)) - Predicate { true } - else - Predicate { it !is ReplyMessage } - val attachmentContent = messageScanner.scanForAttachment( - root, - listOf( - MessageScanner.Checker { it.video }, - MessageScanner.Checker { it.story }, - ), - containerPredicate - ) - if (attachmentContent != null) { + suspend fun scanForVideoContent(root: RootMessage): VideoContentHolder? { + val scanResult = messageContentScanner.findContent(root) + if (scanResult != null) { + val (attachmentContent, chain) = scanResult return when (attachmentContent) { - is VkVideo -> videoService.wrap(attachmentContent) + is VkVideo -> videoService.wrap(attachmentContent, chain) is VkStory -> videoService.wrap(attachmentContent) else -> throw IllegalArgumentException("Impossible exception") } diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/messageChain/handlers/DefaultVideoChain.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/messageChain/handlers/DefaultVideoChain.kt index d84c1e3..3818913 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/messageChain/handlers/DefaultVideoChain.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/messageChain/handlers/DefaultVideoChain.kt @@ -4,6 +4,7 @@ import jakarta.inject.Singleton import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import ru.spliterash.vkVideoUnlocker.common.exceptions.AlwaysNotifyException import ru.spliterash.vkVideoUnlocker.common.exceptions.VkUnlockerException import ru.spliterash.vkVideoUnlocker.longpoll.message.RootMessage import ru.spliterash.vkVideoUnlocker.longpoll.message.hasPing @@ -13,6 +14,7 @@ import ru.spliterash.vkVideoUnlocker.message.editableMessage.EditableMessage import ru.spliterash.vkVideoUnlocker.message.utils.MessageUtils import ru.spliterash.vkVideoUnlocker.messageChain.MessageHandler import ru.spliterash.vkVideoUnlocker.video.DownloadUrlSupplier +import ru.spliterash.vkVideoUnlocker.video.exceptions.NoSenseReuploadUserVideos import ru.spliterash.vkVideoUnlocker.video.exceptions.PrivateVideoDisabledException import ru.spliterash.vkVideoUnlocker.video.exceptions.VideoTooLongException import ru.spliterash.vkVideoUnlocker.video.service.VideoReUploadService @@ -33,6 +35,11 @@ class DefaultVideoChain( handleException(ex, message) return@coroutineScope true } ?: return@coroutineScope false + if (video.ownerId > 0) { + handleException(NoSenseReuploadUserVideos(), message) + return@coroutineScope true + } + val notifyJob = launch { delay(3000) @@ -75,7 +82,7 @@ class DefaultVideoChain( } private fun handleException(ex: VkUnlockerException, message: RootMessage) { - if (message.isPersonalChat()) + if (message.isPersonalChat() || ex is AlwaysNotifyException) throw ex } diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/messageChain/handlers/DownloadVideoChain.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/messageChain/handlers/DownloadVideoChain.kt index 68655f8..34056e5 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/messageChain/handlers/DownloadVideoChain.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/messageChain/handlers/DownloadVideoChain.kt @@ -6,6 +6,8 @@ import ru.spliterash.vkVideoUnlocker.message.editableMessage.EditableMessage import ru.spliterash.vkVideoUnlocker.message.utils.MessageUtils import ru.spliterash.vkVideoUnlocker.messageChain.ActivationMessageHandler import ru.spliterash.vkVideoUnlocker.video.DownloadUrlSupplier +import ru.spliterash.vkVideoUnlocker.video.holder.VideoHolder +import ru.spliterash.vkVideoUnlocker.video.vkModels.fullId @Singleton class DownloadVideoChain( @@ -24,7 +26,11 @@ class DownloadVideoChain( if (videoHolder == null) { editableMessage.sendOrUpdate("Прикрепи видео к сообщению, ну или перешли его как обычно, чтобы я знал что тебе нужно") } else { - val url = downloadUrlSupplier.downloadUrl(videoHolder.attachmentId) + val baseUrl = if (videoHolder is VideoHolder) { + "video" + videoHolder.fullVideo().video.fullId() + } else videoHolder.attachmentId + + val url = downloadUrlSupplier.downloadUrl(baseUrl) editableMessage.sendOrUpdate("Скачать: $url") } 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 2bc2c07..9f17174 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/api/Videos.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/api/Videos.kt @@ -13,7 +13,7 @@ interface Videos { * Выгрузить видос */ suspend fun upload( - groupId: Int, + groupId: Long, name: String, private: Boolean, accessor: VideoAccessor, diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/api/VideosImpl.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/api/VideosImpl.kt index e879195..1b2b56f 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/api/VideosImpl.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/api/VideosImpl.kt @@ -6,7 +6,6 @@ import com.fasterxml.jackson.module.kotlin.readValue import io.micronaut.context.annotation.Parameter import io.micronaut.context.annotation.Prototype import okhttp3.OkHttpClient -import org.apache.commons.io.IOUtils import ru.spliterash.vkVideoUnlocker.common.VkUploaderService import ru.spliterash.vkVideoUnlocker.common.okHttp.executeAsync import ru.spliterash.vkVideoUnlocker.common.vkModels.VkUploadUrlResponse @@ -19,7 +18,6 @@ import ru.spliterash.vkVideoUnlocker.vk.VkConst import ru.spliterash.vkVideoUnlocker.vk.VkHelper import ru.spliterash.vkVideoUnlocker.vk.readResponse import ru.spliterash.vkVideoUnlocker.vk.vkModels.VkItemsResponse -import java.io.FileOutputStream @Prototype class VideosImpl( @@ -55,7 +53,7 @@ class VideosImpl( } override suspend fun upload( - groupId: Int, + groupId: Long, name: String, private: Boolean, accessor: VideoAccessor, 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 ff6c738..22ea9d7 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/controller/VideoController.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/controller/VideoController.kt @@ -35,10 +35,19 @@ class VideoController( .expireAfterWrite(30, TimeUnit.MINUTES) .build> { scope.async { - load(it).toAccessor() + try { + load(it).toAccessor() + } catch (ex: Exception) { + invalidate(it) + throw ex + } } } + private fun invalidate(id: String) { + cache.invalidate(id) + } + private suspend fun load(attachmentId: String): FullVideo { val holder = videoService.wrapAttachmentId(attachmentId) @@ -85,7 +94,7 @@ class VideoController( @Header("Range", defaultValue = "") rangeHeader: String?, @Header("X-Forwarded-For", defaultValue = "idk") ipHeader: String? ): HttpResponse { - val accessor = cache.get(attachmentId).await() + val accessor = cache.get(attachmentId.removeSuffix(".mp4")).await() val qualityInt = try { quality.toInt() } catch (ex: Exception) { diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/ContentNotFoundException.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/ContentNotFoundException.kt new file mode 100644 index 0000000..e707a8c --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/ContentNotFoundException.kt @@ -0,0 +1,10 @@ +package ru.spliterash.vkVideoUnlocker.video.exceptions + +import ru.spliterash.vkVideoUnlocker.common.exceptions.AlwaysNotifyException +import ru.spliterash.vkVideoUnlocker.common.exceptions.VkUnlockerException + +class ContentNotFoundException : VkUnlockerException(),AlwaysNotifyException { + override fun messageForUser(): String { + return "Я не знаю каким образом, но при получении сообщения от пользователя, содержимое отличается" + } +} diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/NoSenseReuploadUserVideos.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/NoSenseReuploadUserVideos.kt new file mode 100644 index 0000000..ae0ece1 --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/NoSenseReuploadUserVideos.kt @@ -0,0 +1,9 @@ +package ru.spliterash.vkVideoUnlocker.video.exceptions + +import ru.spliterash.vkVideoUnlocker.common.exceptions.VkUnlockerException + +class NoSenseReuploadUserVideos : VkUnlockerException() { + override fun messageForUser(): String { + return "Мне нет смысла перезаливать пользовательские видео" + } +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/NoSourceException.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/NoSourceException.kt new file mode 100644 index 0000000..8fc2ab8 --- /dev/null +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/NoSourceException.kt @@ -0,0 +1,9 @@ +package ru.spliterash.vkVideoUnlocker.video.exceptions + +import ru.spliterash.vkVideoUnlocker.common.exceptions.AlwaysNotifyException +import ru.spliterash.vkVideoUnlocker.common.exceptions.VkUnlockerException + +class NoSourceException : VkUnlockerException(), AlwaysNotifyException { + override fun messageForUser() = + "Перешли видео в личные сообщения сообщества. Из за ограничений API я не могу сделать это в беседе" +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/WeDoNotWorkWithLockedUserVideosException.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/WeDoNotWorkWithLockedUserVideosException.kt deleted file mode 100644 index c4c50e0..0000000 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/exceptions/WeDoNotWorkWithLockedUserVideosException.kt +++ /dev/null @@ -1,7 +0,0 @@ -package ru.spliterash.vkVideoUnlocker.video.exceptions - -import ru.spliterash.vkVideoUnlocker.common.exceptions.VkUnlockerException - -class WeDoNotWorkWithLockedUserVideosException : VkUnlockerException() { - override fun messageForUser() = "Я не работаю с закрытыми видео пользователей" -} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/holder/AbstractVideoContentHolder.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/holder/AbstractVideoContentHolder.kt index 716b8f3..8fb69bd 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/holder/AbstractVideoContentHolder.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/holder/AbstractVideoContentHolder.kt @@ -3,16 +3,16 @@ package ru.spliterash.vkVideoUnlocker.video.holder import ru.spliterash.vkVideoUnlocker.video.dto.FullVideo abstract class AbstractVideoContentHolder( - val contentId:String + val contentId: String ) : VideoContentHolder { protected var fullVideo: FullVideo? = null - override val ownerId: Int + override val ownerId: Long get() { val contentId = contentId val split = contentId.split("_") - return split[0].toInt() + return split[0].toLong() } protected abstract suspend fun loadFullVideo(): FullVideo diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/holder/VideoContentHolder.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/holder/VideoContentHolder.kt index 41633e8..d3d6666 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/holder/VideoContentHolder.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/holder/VideoContentHolder.kt @@ -1,7 +1,7 @@ package ru.spliterash.vkVideoUnlocker.video.holder +import ru.spliterash.vkVideoUnlocker.longpoll.message.attachments.AttachmentContainer import ru.spliterash.vkVideoUnlocker.video.dto.FullVideo -import ru.spliterash.vkVideoUnlocker.video.exceptions.VideoOpenException import ru.spliterash.vkVideoUnlocker.video.vkModels.VkVideo sealed interface VideoContentHolder { @@ -9,11 +9,12 @@ sealed interface VideoContentHolder { * ID attachment'а */ val attachmentId: String + val source: List? /** * Видео без ссылок на скачивание, чисто инфа */ suspend fun video(): VkVideo suspend fun fullVideo(): FullVideo - val ownerId: Int + val ownerId: Long } \ No newline at end of file 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 4dc7017..f085fa0 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/service/VideoService.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/service/VideoService.kt @@ -2,6 +2,11 @@ package ru.spliterash.vkVideoUnlocker.video.service import jakarta.inject.Singleton import ru.spliterash.vkVideoUnlocker.group.WorkUserGroupService +import ru.spliterash.vkVideoUnlocker.longpoll.message.RootMessage +import ru.spliterash.vkVideoUnlocker.longpoll.message.attachments.AttachmentContainer +import ru.spliterash.vkVideoUnlocker.longpoll.message.attachments.Wall +import ru.spliterash.vkVideoUnlocker.longpoll.message.isGroupChat +import ru.spliterash.vkVideoUnlocker.message.utils.MessageContentScanner import ru.spliterash.vkVideoUnlocker.story.exceptions.CantSeeStoryException import ru.spliterash.vkVideoUnlocker.story.exceptions.StoryExpiredException import ru.spliterash.vkVideoUnlocker.story.exceptions.StoryNotVideoException @@ -14,18 +19,21 @@ import ru.spliterash.vkVideoUnlocker.video.holder.StoryHolder 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.video.vkModels.publicId 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.GroupUser import ru.spliterash.vkVideoUnlocker.vk.actor.types.DownloadUser +import ru.spliterash.vkVideoUnlocker.vk.actor.types.PokeUser import ru.spliterash.vkVideoUnlocker.vk.api.VkApi @Singleton class VideoService( private val messageScanner: MessageScanner, + private val messageContentScanner: MessageContentScanner, private val workUserGroupService: WorkUserGroupService, private val videoAccessorFactory: VideoAccessorFactory, + @GroupUser private val group: VkApi, @DownloadUser private val downloadUser: VkApi, @PokeUser private val pokeUser: VkApi, ) { @@ -33,8 +41,6 @@ class VideoService( * Базовые проверки видосов */ private fun baseCheckVideo(video: VkVideo) { - if (video.isPrivate) - throw VideoPrivateException() if (video.platform != null) throw VideoFromAnotherPlatformException() } @@ -55,7 +61,10 @@ class VideoService( val type = matcher.group("type") val ownerId = matcher.group("owner") val id = matcher.group("id") - val normalId = "${ownerId}_${id}" + val key = matcher.group("key") + + var normalId = "${ownerId}_${id}" + if (key != null) normalId += "_$key" return when (type) { "video" -> wrapVideoId(normalId) @@ -74,9 +83,9 @@ class VideoService( throw VideoNotFoundException() } - fun wrap(video: VkVideo): VideoContentHolder { + fun wrap(video: VkVideo, chain: List): VideoContentHolder { baseCheckVideo(video) - return InfoVideoHolder(video) + return InfoVideoHolder(video, chain) } fun wrap(story: VkStory): VideoContentHolder { @@ -108,10 +117,15 @@ class VideoService( } catch (_: VideoLockedException) { } catch (_: CantSeeStoryException) { } + holder as VideoContentHolder // КРИНЖАТИНА!!!! + val ownerId = holder.ownerId - if (ownerId > 0) - throw WeDoNotWorkWithLockedUserVideosException() + if (ownerId > 0) { + val video = tryMessageGetBehavior(holder) + + return FullVideo(video, null, videoAccessorFactory, workUserGroupService) + } val groupId = -ownerId val status = workUserGroupService.joinGroup(groupId) @@ -119,12 +133,24 @@ class VideoService( val video = try { holder.loadVideo() } catch (locked: VideoLockedException) { - // Вк по какой то непонятной причине говорит что видео доступно только подписчикам, если оно приватное - throw VideoPrivateException() + tryMessageGetBehavior(holder) } return FullVideo(video, status, videoAccessorFactory, workUserGroupService) } + private suspend fun tryMessageGetBehavior(holder: VideoContentHolder): VkVideo { + if (holder !is InfoVideoHolder) throw NoSourceException() + val chain = holder.source ?: throw NoSourceException() + val source = chain.first() as RootMessage + if (source.isGroupChat()) throw NoSourceException() + + val rootMessageByUser = downloadUser.messages.messageById(messageId = source.id, groupId = group.id) + val result = messageContentScanner.findContent(rootMessageByUser) + if (result == null || result.content !is VkVideo || result.content.publicId() != holder.contentId) throw ContentNotFoundException() + + return result.content + } + suspend fun isLocked(videoId: String): Boolean { return try { pokeUser.videos.getVideo(videoId) @@ -134,8 +160,9 @@ class VideoService( } } + // Какой же кринж, я всё понимаю, но мне мега впадлу переделывать + // Простите если вы это видите private interface VideoLoader { - val ownerId: Int suspend fun loadVideo(): VkVideo } @@ -163,10 +190,14 @@ class VideoService( return full } + override val source: List? + get() = null + override suspend fun loadVideo() = downloadUser.videos.getVideo(contentId) } - private inner class FullVideoHolder(val video: VkVideo) : AbstractVideoHolder(video.normalId()) { + private inner class FullVideoHolder(val video: VkVideo) : + AbstractVideoHolder(video.publicId()) { override suspend fun video(): VkVideo { return video @@ -180,9 +211,13 @@ class VideoService( workUserGroupService ) } + + override val source: List? + get() = null } - private inner class InfoVideoHolder(val video: VkVideo) : AbstractVideoHolder(video.normalId()), VideoLoader { + private inner class InfoVideoHolder(val video: VkVideo, override val source: List) : + AbstractVideoHolder(video.publicId()), VideoLoader { override suspend fun video() = video override suspend fun loadFullVideo(): FullVideo { val full = getVideoWithTryingLockBehavior(this) @@ -191,7 +226,20 @@ class VideoService( return full } - override suspend fun loadVideo() = downloadUser.videos.getVideo(contentId) + override suspend fun loadVideo(): VkVideo { + val wall = source.firstOrNull { it is Wall } + if (wall is Wall) { + val downloadUserWall = downloadUser.walls.getById("${wall.ownerId}_${wall.id}") + val wallVideo = messageScanner.scanForAttachment(downloadUserWall) { it.video } + if (wallVideo != null) { + if (wallVideo.publicId() != this.video.publicId()) throw ContentNotFoundException() + if (wallVideo.contentRestricted) throw VideoLockedException() + + return wallVideo + } + } + return downloadUser.videos.getVideo(contentId) + } } private fun baseCheckStory(story: VkStory) { @@ -224,6 +272,9 @@ class VideoService( return getVideoWithTryingLockBehavior(this) } + override val source: List? + get() = null + override suspend fun loadVideo(): VkVideo { val story = findStoryById(contentId) baseCheckStory(story) @@ -234,7 +285,7 @@ class VideoService( // Тип держит историю которая пришла в личку группы private inner class GroupStoryHolder( - val story: VkStory + val story: VkStory, override val source: List? ) : AbstractStoryHolder(story.normalId()) { override suspend fun video(): VkVideo { diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/vkModels/VkVideo.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/vkModels/VkVideo.kt index a508a81..228b3bc 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/vkModels/VkVideo.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/vkModels/VkVideo.kt @@ -22,6 +22,9 @@ data class VkVideo( @JsonProperty( "description" ) val description: String?, + @JsonProperty( + "access_key" + ) val accessKey: String?, /** * Video duration in seconds */ @@ -30,14 +33,14 @@ data class VkVideo( * Video ID */ @JsonProperty("id") - val id: Int, + val id: Long, @JsonProperty("content_restricted") val contentRestricted: Boolean = false, /** * Video owner ID */ @JsonProperty("owner_id") - val ownerId: Int, + val ownerId: Long, /** * Video title */ @@ -80,6 +83,7 @@ data class VkVideo( return map } + @Throws(VideoEmptyUrlException::class) fun maxQuality(): Pair = files?.run { if (mp41080 != null) diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/video/vkModels/VkVideoExt.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/video/vkModels/VkVideoExt.kt index a810744..b014fab 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/video/vkModels/VkVideoExt.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/video/vkModels/VkVideoExt.kt @@ -1,3 +1,9 @@ package ru.spliterash.vkVideoUnlocker.video.vkModels -fun VkVideo.normalId() = "${ownerId}_$id" \ No newline at end of file +fun VkVideo.publicId() = "${ownerId}_$id" +fun VkVideo.fullId(): String { + var id = publicId() + if (accessKey != null) id = id + "_" + accessKey + + return id +} \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/MessageScanner.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/MessageScanner.kt index 627848d..4640c6c 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/MessageScanner.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/MessageScanner.kt @@ -5,6 +5,7 @@ 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.* import java.util.function.Predicate @Singleton @@ -33,44 +34,56 @@ class MessageScanner { root: AttachmentContainer, checker: Checker, ): T? { - return scanForAttachment(root, listOf(checker)) as T? + val init = LinkedList() + init += root + return scanForAttachment(init, listOf(checker))?.content as T? } @Suppress("MemberVisibilityCanBePrivate") fun scanForAttachment( - root: AttachmentContainer, + chain: LinkedList, checkers: List>, allowedContainerChecker: Predicate = Predicate { true } - ): AttachmentContent? { + ): ScanResult? { + val root = chain.last() if (!allowedContainerChecker.test(root)) return null for (attachment in root.attachments()) { for (checker in checkers) { val needle = checker.check(attachment) if (needle != null) - return needle + return ScanResult(needle, chain) } if (attachment.wall != null) { - val scanResult = scanForAttachment(attachment.wall, checkers, allowedContainerChecker) + chain.add(attachment.wall) + val scanResult = scanForAttachment(chain, checkers, allowedContainerChecker) if (scanResult != null) return scanResult + chain.removeLast() } if (attachment.wallReply != null) { - val scanResult = scanForAttachment(attachment.wallReply, checkers, allowedContainerChecker) + chain.add(attachment.wallReply) + val scanResult = scanForAttachment(chain, checkers, allowedContainerChecker) if (scanResult != null) return scanResult + chain.removeLast() } } for (somethingWithAttachments in root.containers()) { - val result = scanForAttachment(somethingWithAttachments, checkers, allowedContainerChecker) + chain.add(somethingWithAttachments) + val result = scanForAttachment(chain, checkers, allowedContainerChecker) if (result != null) return result + chain.removeLast() } - return null } + data class ScanResult( + val content: AttachmentContent, + val chain: List, + ) fun interface Checker { fun check(obj: Attachment): T? diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/VkConst.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/VkConst.kt index f6fabcc..02ba199 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/VkConst.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/VkConst.kt @@ -8,7 +8,7 @@ object VkConst { const val HOST = "api.vk.com" const val START = "https://$HOST/method/" const val VERSION = "5.131" - val VK_ATTACHMENT_PATTERN: Pattern = Pattern.compile("(?video|wall|story)(?-?\\d+)_(?\\d+)") + val VK_ATTACHMENT_PATTERN: Pattern = Pattern.compile("(?video|wall|story)(?-?\\d+)_(?\\d+)(?:_(?[a-z0-9]+))?") fun urlBuilder(method: String): HttpUrl.Builder { return HttpUrl.Builder() diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/actor/types/Actor.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/actor/types/Actor.kt index 6bbcd66..27bfdec 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/actor/types/Actor.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/actor/types/Actor.kt @@ -1,6 +1,6 @@ package ru.spliterash.vkVideoUnlocker.vk.actor.types data class Actor( - val id: Int, + val id: Long, val token: String ) \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/api/VkApi.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/api/VkApi.kt index eae9faf..ebc6a95 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/api/VkApi.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/api/VkApi.kt @@ -9,7 +9,7 @@ import ru.spliterash.vkVideoUnlocker.video.api.Videos import ru.spliterash.vkVideoUnlocker.wall.api.Walls interface VkApi { - val id: Int + val id: Long val videos: Videos val messages: Messages diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/api/VkApiImpl.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/api/VkApiImpl.kt index bfbaf19..251f98d 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/api/VkApiImpl.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/api/VkApiImpl.kt @@ -28,7 +28,7 @@ class VkApiImpl( .readTimeout(30, TimeUnit.SECONDS) .addInterceptor(VkInterceptor(actor)) .build() - override val id: Int + override val id: Long get() = actor.id override val videos: Videos by lazy { context.createBean(Videos::class.java, client, actor) } diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/exceptions/VkApiException.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/exceptions/VkApiException.kt index 9efe10b..62f9845 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/vk/exceptions/VkApiException.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/vk/exceptions/VkApiException.kt @@ -1,10 +1,11 @@ package ru.spliterash.vkVideoUnlocker.vk.exceptions +import ru.spliterash.vkVideoUnlocker.common.exceptions.AlwaysNotifyException import ru.spliterash.vkVideoUnlocker.common.exceptions.VkUnlockerException class VkApiException( val code: Int, val info: String -) : VkUnlockerException("Vk request failed, code $code, info: $info") { +) : VkUnlockerException("Vk request failed, code $code, info: $info"), AlwaysNotifyException { override fun messageForUser() = "Произошла ошибка при выполнении метода VK. Код ошибки: $code, информация: $info" } \ No newline at end of file diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/wall/api/WallsImpl.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/wall/api/WallsImpl.kt index 7b276a7..ef3bb62 100644 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/wall/api/WallsImpl.kt +++ b/src/main/java/ru/spliterash/vkVideoUnlocker/wall/api/WallsImpl.kt @@ -6,6 +6,7 @@ import io.micronaut.context.annotation.Prototype import okhttp3.OkHttpClient import ru.spliterash.vkVideoUnlocker.common.okHttp.executeAsync import ru.spliterash.vkVideoUnlocker.longpoll.message.attachments.SomethingWithAttachments +import ru.spliterash.vkVideoUnlocker.video.api.VideosImpl import ru.spliterash.vkVideoUnlocker.vk.VkConst import ru.spliterash.vkVideoUnlocker.vk.VkHelper import ru.spliterash.vkVideoUnlocker.vk.vkModels.VkItemsResponse @@ -20,6 +21,7 @@ class WallsImpl( val response = VkConst .requestBuilder() .get() + .header("user-agent", VideosImpl.USER_AGENT) .url( VkConst.urlBuilder("wall.getById") .addQueryParameter("posts", id) diff --git a/src/main/java/ru/spliterash/vkVideoUnlocker/wall/vkModels/WallPost.kt b/src/main/java/ru/spliterash/vkVideoUnlocker/wall/vkModels/WallPost.kt deleted file mode 100644 index 2211982..0000000 --- a/src/main/java/ru/spliterash/vkVideoUnlocker/wall/vkModels/WallPost.kt +++ /dev/null @@ -1,4 +0,0 @@ -package ru.spliterash.vkVideoUnlocker.wall.vkModels - -class WallPost { -} \ No newline at end of file