#17 Show specific error if video is private

This commit is contained in:
Gergely Hegedus 2025-05-13 18:17:04 +03:00
parent b1e44dce43
commit f9bc9679dc
16 changed files with 136 additions and 29 deletions

View file

@ -7,6 +7,7 @@ sealed class ProcessState {
data object NetworkError : ProcessState() data object NetworkError : ProcessState()
data object ParsingError : ProcessState() data object ParsingError : ProcessState()
data object VideoDeletedError : ProcessState() data object VideoDeletedError : ProcessState()
data object VideoPrivateError : ProcessState()
data object CaptchaError : ProcessState() data object CaptchaError : ProcessState()
data object UnknownError : ProcessState() data object UnknownError : ProcessState()
data object StorageError : ProcessState() data object StorageError : ProcessState()

View file

@ -11,6 +11,7 @@ import org.fnives.tiktokdownloader.data.network.exceptions.HtmlException
import org.fnives.tiktokdownloader.data.network.exceptions.NetworkException import org.fnives.tiktokdownloader.data.network.exceptions.NetworkException
import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException
import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException
import org.fnives.tiktokdownloader.data.network.exceptions.VideoPrivateException
import org.fnives.tiktokdownloader.data.network.parsing.converter.VideoFileUrlConverter import org.fnives.tiktokdownloader.data.network.parsing.converter.VideoFileUrlConverter
import org.fnives.tiktokdownloader.data.network.parsing.response.VideoFileUrl import org.fnives.tiktokdownloader.data.network.parsing.response.VideoFileUrl
import org.fnives.tiktokdownloader.data.network.session.CookieStore import org.fnives.tiktokdownloader.data.network.session.CookieStore
@ -68,6 +69,8 @@ class TikTokDownloadRemoteSource(
throw captchaRequiredException throw captchaRequiredException
} catch (videoDeletedException: VideoDeletedException) { } catch (videoDeletedException: VideoDeletedException) {
throw videoDeletedException throw videoDeletedException
} catch (videoPrivateException: VideoPrivateException) {
throw videoPrivateException
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
throw NetworkException( throw NetworkException(
cause = throwable, cause = throwable,

View file

@ -0,0 +1,4 @@
package org.fnives.tiktokdownloader.data.network.exceptions
class VideoPrivateException(override val html: String) : Throwable(),
HtmlException

View file

@ -4,6 +4,7 @@ import okhttp3.ResponseBody
import org.fnives.tiktokdownloader.data.network.parsing.converter.ActualVideoPageUrlConverter import org.fnives.tiktokdownloader.data.network.parsing.converter.ActualVideoPageUrlConverter
import org.fnives.tiktokdownloader.data.network.parsing.converter.ThrowIfIsCaptchaResponse import org.fnives.tiktokdownloader.data.network.parsing.converter.ThrowIfIsCaptchaResponse
import org.fnives.tiktokdownloader.data.network.parsing.converter.ThrowIfVideoIsDeletedResponse import org.fnives.tiktokdownloader.data.network.parsing.converter.ThrowIfVideoIsDeletedResponse
import org.fnives.tiktokdownloader.data.network.parsing.converter.ThrowIfVideoIsPrivateResponse
import org.fnives.tiktokdownloader.data.network.parsing.converter.VideoFileUrlConverter import org.fnives.tiktokdownloader.data.network.parsing.converter.VideoFileUrlConverter
import org.fnives.tiktokdownloader.data.network.parsing.converter.VideoResponseConverter import org.fnives.tiktokdownloader.data.network.parsing.converter.VideoResponseConverter
import org.fnives.tiktokdownloader.data.network.parsing.response.ActualVideoPageUrl import org.fnives.tiktokdownloader.data.network.parsing.response.ActualVideoPageUrl
@ -15,7 +16,8 @@ import java.lang.reflect.Type
class TikTokWebPageConverterFactory( class TikTokWebPageConverterFactory(
private val throwIfIsCaptchaResponse: ThrowIfIsCaptchaResponse, private val throwIfIsCaptchaResponse: ThrowIfIsCaptchaResponse,
private val throwIfVideoIsDeletedResponse: ThrowIfVideoIsDeletedResponse private val throwIfVideoIsDeletedResponse: ThrowIfVideoIsDeletedResponse,
private val throwIfVideoIsPrivateResponse: ThrowIfVideoIsPrivateResponse,
) : Converter.Factory() { ) : Converter.Factory() {
override fun responseBodyConverter( override fun responseBodyConverter(
@ -26,10 +28,16 @@ class TikTokWebPageConverterFactory(
when (type) { when (type) {
ActualVideoPageUrl::class.java -> ActualVideoPageUrlConverter( ActualVideoPageUrl::class.java -> ActualVideoPageUrlConverter(
throwIfIsCaptchaResponse, throwIfIsCaptchaResponse,
throwIfVideoIsDeletedResponse throwIfVideoIsDeletedResponse,
throwIfVideoIsPrivateResponse,
)
VideoFileUrl::class.java -> VideoFileUrlConverter(
throwIfIsCaptchaResponse,
throwIfVideoIsDeletedResponse,
throwIfVideoIsPrivateResponse
) )
VideoFileUrl::class.java -> VideoFileUrlConverter(throwIfIsCaptchaResponse, throwIfVideoIsDeletedResponse)
VideoResponse::class.java -> VideoResponseConverter() VideoResponse::class.java -> VideoResponseConverter()
else -> super.responseBodyConverter(type, annotations, retrofit) else -> super.responseBodyConverter(type, annotations, retrofit)
} }

View file

@ -2,26 +2,33 @@ package org.fnives.tiktokdownloader.data.network.parsing.converter
import okhttp3.ResponseBody import okhttp3.ResponseBody
import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredException import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredException
import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException
import org.fnives.tiktokdownloader.data.network.exceptions.VideoPrivateException
import org.fnives.tiktokdownloader.data.network.parsing.response.ActualVideoPageUrl import org.fnives.tiktokdownloader.data.network.parsing.response.ActualVideoPageUrl
import kotlin.jvm.Throws
class ActualVideoPageUrlConverter( class ActualVideoPageUrlConverter(
private val throwIfIsCaptchaResponse: ThrowIfIsCaptchaResponse, private val throwIfIsCaptchaResponse: ThrowIfIsCaptchaResponse,
private val throwIfVideoIsDeletedResponse: ThrowIfVideoIsDeletedResponse private val throwIfVideoIsDeletedResponse: ThrowIfVideoIsDeletedResponse,
private val throwIfVideoIsPrivateResponse: ThrowIfVideoIsPrivateResponse
) : ParsingExceptionThrowingConverter<ActualVideoPageUrl>() { ) : ParsingExceptionThrowingConverter<ActualVideoPageUrl>() {
@Throws(IndexOutOfBoundsException::class, CaptchaRequiredException::class) @Throws(
IndexOutOfBoundsException::class, CaptchaRequiredException::class,
VideoDeletedException::class,
VideoPrivateException::class,
)
override fun convertSafely(responseBody: ResponseBody): ActualVideoPageUrl { override fun convertSafely(responseBody: ResponseBody): ActualVideoPageUrl {
val responseBodyAsString =responseBody.string() val responseBodyAsString = responseBody.string()
return try { return try {
val actualVideoPageUrl = responseBodyAsString val actualVideoPageUrl = responseBodyAsString
.also(throwIfIsCaptchaResponse::invoke) .also(throwIfIsCaptchaResponse::invoke)
.also(throwIfVideoIsDeletedResponse::invoke) .also(throwIfVideoIsDeletedResponse::invoke)
.also(throwIfVideoIsPrivateResponse::invoke)
.split("rel=\"canonical\" href=\"")[1] .split("rel=\"canonical\" href=\"")[1]
.split("\"")[0] .split("\"")[0]
ActualVideoPageUrl(actualVideoPageUrl, responseBodyAsString) ActualVideoPageUrl(actualVideoPageUrl, responseBodyAsString)
} catch(_: Throwable) { } catch (_: Throwable) {
ActualVideoPageUrl(null, responseBodyAsString) ActualVideoPageUrl(null, responseBodyAsString)
} }

View file

@ -5,24 +5,37 @@ import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredExcept
import org.fnives.tiktokdownloader.data.network.exceptions.HtmlException import org.fnives.tiktokdownloader.data.network.exceptions.HtmlException
import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException
import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException
import org.fnives.tiktokdownloader.data.network.exceptions.VideoPrivateException
import retrofit2.Converter import retrofit2.Converter
abstract class ParsingExceptionThrowingConverter<T> : Converter<ResponseBody, T> { abstract class ParsingExceptionThrowingConverter<T> : Converter<ResponseBody, T> {
@Throws(ParsingException::class, CaptchaRequiredException::class, VideoDeletedException::class) @Throws(
ParsingException::class,
CaptchaRequiredException::class,
VideoDeletedException::class,
VideoPrivateException::class
)
final override fun convert(value: ResponseBody): T? = final override fun convert(value: ResponseBody): T? =
doActionSafely { doActionSafely {
convertSafely(value) convertSafely(value)
} }
@Throws(ParsingException::class, CaptchaRequiredException::class, VideoDeletedException::class) @Throws(
ParsingException::class,
CaptchaRequiredException::class,
VideoDeletedException::class,
VideoPrivateException::class
)
fun doActionSafely(action: () -> T): T { fun doActionSafely(action: () -> T): T {
try { try {
return action() return action()
} catch (captchaRequiredException: CaptchaRequiredException) { } catch (captchaRequiredException: CaptchaRequiredException) {
throw captchaRequiredException throw captchaRequiredException
} catch(videoDeletedException: VideoDeletedException) { } catch (videoDeletedException: VideoDeletedException) {
throw videoDeletedException throw videoDeletedException
} catch (videoPrivateException: VideoPrivateException) {
throw videoPrivateException
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
throw ParsingException( throw ParsingException(
cause = throwable, cause = throwable,

View file

@ -6,7 +6,7 @@ class ThrowIfVideoIsDeletedResponse {
@Throws(VideoDeletedException::class) @Throws(VideoDeletedException::class)
fun invoke(html: String) { fun invoke(html: String) {
if (html.contains("\"statusMsg\":\"status_deleted\"")) { if (html.contains("\"statusMsg\":\"status_deleted")) {
throw VideoDeletedException(html = html) throw VideoDeletedException(html = html)
} }
} }

View file

@ -0,0 +1,15 @@
package org.fnives.tiktokdownloader.data.network.parsing.converter
import org.fnives.tiktokdownloader.data.network.exceptions.VideoPrivateException
class ThrowIfVideoIsPrivateResponse {
@Throws(VideoPrivateException::class)
fun invoke(html: String) {
if (html.contains("\"statusMsg\":\"status_friend_see")) {
throw VideoPrivateException(html = html)
} else if (html.contains("\"statusMsg\":\"author_secret")) {
throw VideoPrivateException(html = html)
}
}
}

View file

@ -4,14 +4,21 @@ import okhttp3.ResponseBody
import org.fnives.tiktokdownloader.Logger import org.fnives.tiktokdownloader.Logger
import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredException import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredException
import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException
import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException
import org.fnives.tiktokdownloader.data.network.exceptions.VideoPrivateException
import org.fnives.tiktokdownloader.data.network.parsing.response.VideoFileUrl import org.fnives.tiktokdownloader.data.network.parsing.response.VideoFileUrl
class VideoFileUrlConverter( class VideoFileUrlConverter(
private val throwIfIsCaptchaResponse: ThrowIfIsCaptchaResponse, private val throwIfIsCaptchaResponse: ThrowIfIsCaptchaResponse,
private val throwIfVideoIsDeletedResponse: ThrowIfVideoIsDeletedResponse, private val throwIfVideoIsDeletedResponse: ThrowIfVideoIsDeletedResponse,
private val throwIfVideoIsPrivateResponse: ThrowIfVideoIsPrivateResponse
) : ParsingExceptionThrowingConverter<VideoFileUrl>() { ) : ParsingExceptionThrowingConverter<VideoFileUrl>() {
@Throws(IllegalArgumentException::class, IndexOutOfBoundsException::class, CaptchaRequiredException::class) @Throws(
IllegalArgumentException::class,
IndexOutOfBoundsException::class,
CaptchaRequiredException::class
)
override fun convertSafely(responseBody: ResponseBody): VideoFileUrl { override fun convertSafely(responseBody: ResponseBody): VideoFileUrl {
return convert(responseBody.string()) return convert(responseBody.string())
} }
@ -21,11 +28,19 @@ class VideoFileUrlConverter(
return doActionSafely { convert(responseBody) } return doActionSafely { convert(responseBody) }
} }
@Throws(IllegalArgumentException::class, IndexOutOfBoundsException::class, CaptchaRequiredException::class) @Throws(
IllegalArgumentException::class,
IndexOutOfBoundsException::class,
CaptchaRequiredException::class,
VideoDeletedException::class,
VideoPrivateException::class,
)
private fun convert(responseBody: String): VideoFileUrl { private fun convert(responseBody: String): VideoFileUrl {
val html = responseBody.also(throwIfIsCaptchaResponse::invoke) val html = responseBody.also(throwIfIsCaptchaResponse::invoke)
.also(throwIfVideoIsDeletedResponse::invoke) .also(throwIfVideoIsDeletedResponse::invoke)
val url = tryToParseDownloadLink(html).also { Logger.logMessage("parsed download link = $it") } .also(throwIfVideoIsPrivateResponse::invoke)
val url =
tryToParseDownloadLink(html).also { Logger.logMessage("parsed download link = $it") }
?: tryToParseVideoSrc(html).also { Logger.logMessage("parsed video src = $it") } ?: tryToParseVideoSrc(html).also { Logger.logMessage("parsed video src = $it") }
?: throw IllegalArgumentException("Couldn't parse url from HTML: $html") ?: throw IllegalArgumentException("Couldn't parse url from HTML: $html")

View file

@ -32,6 +32,7 @@ import org.fnives.tiktokdownloader.data.network.exceptions.CaptchaRequiredExcept
import org.fnives.tiktokdownloader.data.network.exceptions.NetworkException import org.fnives.tiktokdownloader.data.network.exceptions.NetworkException
import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException import org.fnives.tiktokdownloader.data.network.exceptions.ParsingException
import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException
import org.fnives.tiktokdownloader.data.network.exceptions.VideoPrivateException
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
class VideoDownloadingProcessorUseCase( class VideoDownloadingProcessorUseCase(
@ -112,6 +113,8 @@ class VideoDownloadingProcessorUseCase(
ProcessState.ParsingError ProcessState.ParsingError
} catch (videoDeletedException: VideoDeletedException) { } catch (videoDeletedException: VideoDeletedException) {
ProcessState.VideoDeletedError ProcessState.VideoDeletedError
} catch (videoPrivateException: VideoPrivateException) {
ProcessState.VideoPrivateError
} catch (storageException: StorageException) { } catch (storageException: StorageException) {
ProcessState.StorageError ProcessState.StorageError
} catch (captchaRequiredException: CaptchaRequiredException) { } catch (captchaRequiredException: CaptchaRequiredException) {
@ -140,6 +143,7 @@ class VideoDownloadingProcessorUseCase(
ProcessState.StorageError, ProcessState.StorageError,
ProcessState.UnknownError, ProcessState.UnknownError,
ProcessState.VideoDeletedError, ProcessState.VideoDeletedError,
ProcessState.VideoPrivateError,
ProcessState.CaptchaError -> true ProcessState.CaptchaError -> true
} }

View file

@ -8,6 +8,7 @@ import org.fnives.tiktokdownloader.data.network.TikTokRetrofitService
import org.fnives.tiktokdownloader.data.network.parsing.TikTokWebPageConverterFactory import org.fnives.tiktokdownloader.data.network.parsing.TikTokWebPageConverterFactory
import org.fnives.tiktokdownloader.data.network.parsing.converter.ThrowIfIsCaptchaResponse import org.fnives.tiktokdownloader.data.network.parsing.converter.ThrowIfIsCaptchaResponse
import org.fnives.tiktokdownloader.data.network.parsing.converter.ThrowIfVideoIsDeletedResponse import org.fnives.tiktokdownloader.data.network.parsing.converter.ThrowIfVideoIsDeletedResponse
import org.fnives.tiktokdownloader.data.network.parsing.converter.ThrowIfVideoIsPrivateResponse
import org.fnives.tiktokdownloader.data.network.parsing.converter.VideoFileUrlConverter import org.fnives.tiktokdownloader.data.network.parsing.converter.VideoFileUrlConverter
import org.fnives.tiktokdownloader.data.network.session.CookieSavingInterceptor import org.fnives.tiktokdownloader.data.network.session.CookieSavingInterceptor
import org.fnives.tiktokdownloader.data.network.session.CookieStore import org.fnives.tiktokdownloader.data.network.session.CookieStore
@ -22,8 +23,15 @@ class NetworkModule(private val delayBeforeRequest: Long) {
private val throwIfVideoIsDeletedResponse: ThrowIfVideoIsDeletedResponse private val throwIfVideoIsDeletedResponse: ThrowIfVideoIsDeletedResponse
get() = ThrowIfVideoIsDeletedResponse() get() = ThrowIfVideoIsDeletedResponse()
private val throwIfVideoIsPrivateResponse: ThrowIfVideoIsPrivateResponse
get() = ThrowIfVideoIsPrivateResponse()
private val tikTokConverterFactory: Converter.Factory private val tikTokConverterFactory: Converter.Factory
get() = TikTokWebPageConverterFactory(throwIfIsCaptchaResponse, throwIfVideoIsDeletedResponse) get() = TikTokWebPageConverterFactory(
throwIfIsCaptchaResponse,
throwIfVideoIsDeletedResponse,
throwIfVideoIsPrivateResponse
)
private val cookieSavingInterceptor: CookieSavingInterceptor by lazy { CookieSavingInterceptor() } private val cookieSavingInterceptor: CookieSavingInterceptor by lazy { CookieSavingInterceptor() }
@ -34,7 +42,9 @@ class NetworkModule(private val delayBeforeRequest: Long) {
.addInterceptor(cookieSavingInterceptor) .addInterceptor(cookieSavingInterceptor)
.let { .let {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
it.addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) it.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
} else { } else {
it it
} }
@ -52,5 +62,14 @@ class NetworkModule(private val delayBeforeRequest: Long) {
get() = retrofit.create(TikTokRetrofitService::class.java) get() = retrofit.create(TikTokRetrofitService::class.java)
val tikTokDownloadRemoteSource: TikTokDownloadRemoteSource val tikTokDownloadRemoteSource: TikTokDownloadRemoteSource
get() = TikTokDownloadRemoteSource(delayBeforeRequest, tikTokRetrofitService, cookieStore, VideoFileUrlConverter(throwIfIsCaptchaResponse, throwIfVideoIsDeletedResponse)) get() = TikTokDownloadRemoteSource(
delayBeforeRequest,
tikTokRetrofitService,
cookieStore,
VideoFileUrlConverter(
throwIfIsCaptchaResponse,
throwIfVideoIsDeletedResponse,
throwIfVideoIsPrivateResponse
)
)
} }

View file

@ -105,6 +105,7 @@ class MainActivity : AppCompatActivity() {
MainViewModel.ErrorMessage.CAPTCHA -> R.string.captcha_error MainViewModel.ErrorMessage.CAPTCHA -> R.string.captcha_error
MainViewModel.ErrorMessage.UNKNOWN -> R.string.unexpected_error MainViewModel.ErrorMessage.UNKNOWN -> R.string.unexpected_error
MainViewModel.ErrorMessage.DELETED -> R.string.video_deleted_error MainViewModel.ErrorMessage.DELETED -> R.string.video_deleted_error
MainViewModel.ErrorMessage.PRIVATE -> R.string.video_private_error
} }
private fun animateFabClicked(downloadFab: FloatingActionButton) { private fun animateFabClicked(downloadFab: FloatingActionButton) {

View file

@ -5,7 +5,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.fnives.tiktokdownloader.data.model.ProcessState import org.fnives.tiktokdownloader.data.model.ProcessState
import org.fnives.tiktokdownloader.data.usecase.AddVideoToQueueUseCase import org.fnives.tiktokdownloader.data.usecase.AddVideoToQueueUseCase
@ -22,11 +21,13 @@ class MainViewModel(
private val _refreshActionVisibility = MutableLiveData<Boolean>() private val _refreshActionVisibility = MutableLiveData<Boolean>()
private val currentScreen = MutableLiveData<Screen>() private val currentScreen = MutableLiveData<Screen>()
val refreshActionVisibility: LiveData<Boolean?> = combineNullable(_refreshActionVisibility, currentScreen) { refreshVisibility, screen -> val refreshActionVisibility: LiveData<Boolean?> =
combineNullable(_refreshActionVisibility, currentScreen) { refreshVisibility, screen ->
refreshVisibility == true && screen == Screen.QUEUE refreshVisibility == true && screen == Screen.QUEUE
} }
private val _errorMessage = MutableLiveData<Event<ErrorMessage>>() private val _errorMessage = MutableLiveData<Event<ErrorMessage>>()
val errorMessage: LiveData<Event<ErrorMessage>?> = combineNullable(_errorMessage, currentScreen) { event, screen -> val errorMessage: LiveData<Event<ErrorMessage>?> =
combineNullable(_errorMessage, currentScreen) { event, screen ->
event?.takeIf { screen == Screen.QUEUE } event?.takeIf { screen == Screen.QUEUE }
} }
@ -40,22 +41,26 @@ class MainViewModel(
is ProcessState.Processing, is ProcessState.Processing,
is ProcessState.Processed, is ProcessState.Processed,
ProcessState.Finished -> null ProcessState.Finished -> null
ProcessState.NetworkError -> ErrorMessage.NETWORK ProcessState.NetworkError -> ErrorMessage.NETWORK
ProcessState.ParsingError -> ErrorMessage.PARSING ProcessState.ParsingError -> ErrorMessage.PARSING
ProcessState.StorageError -> ErrorMessage.STORAGE ProcessState.StorageError -> ErrorMessage.STORAGE
ProcessState.CaptchaError -> ErrorMessage.CAPTCHA ProcessState.CaptchaError -> ErrorMessage.CAPTCHA
ProcessState.UnknownError -> ErrorMessage.UNKNOWN ProcessState.UnknownError -> ErrorMessage.UNKNOWN
ProcessState.VideoDeletedError -> ErrorMessage.DELETED ProcessState.VideoDeletedError -> ErrorMessage.DELETED
ProcessState.VideoPrivateError -> ErrorMessage.PRIVATE
} }
val refreshActionVisibility = when (it) { val refreshActionVisibility = when (it) {
is ProcessState.Processing, is ProcessState.Processing,
is ProcessState.Processed, is ProcessState.Processed,
ProcessState.Finished -> false ProcessState.Finished -> false
ProcessState.NetworkError, ProcessState.NetworkError,
ProcessState.ParsingError, ProcessState.ParsingError,
ProcessState.StorageError, ProcessState.StorageError,
ProcessState.UnknownError, ProcessState.UnknownError,
ProcessState.VideoDeletedError, ProcessState.VideoDeletedError,
ProcessState.VideoPrivateError,
ProcessState.CaptchaError -> true ProcessState.CaptchaError -> true
} }
_errorMessage.postValue(errorMessage?.let(::Event)) _errorMessage.postValue(errorMessage?.let(::Event))
@ -73,7 +78,7 @@ class MainViewModel(
} }
enum class ErrorMessage { enum class ErrorMessage {
NETWORK, PARSING, STORAGE, CAPTCHA, UNKNOWN, DELETED NETWORK, PARSING, STORAGE, CAPTCHA, UNKNOWN, DELETED, PRIVATE
} }
enum class Screen { enum class Screen {

View file

@ -54,6 +54,7 @@ class QueueServiceViewModel(
NotificationState.Error(R.string.captcha_error) NotificationState.Error(R.string.captcha_error)
ProcessState.VideoDeletedError -> NotificationState.Error(R.string.video_deleted_error) ProcessState.VideoDeletedError -> NotificationState.Error(R.string.video_deleted_error)
ProcessState.VideoPrivateError -> NotificationState.Error(R.string.video_private_error)
} }
_notificationState.postValue(value) _notificationState.postValue(value)
} }

View file

@ -19,7 +19,8 @@
<string name="parsing_error">Parsing Error</string> <string name="parsing_error">Parsing Error</string>
<string name="storage_error">Failed to Store Video</string> <string name="storage_error">Failed to Store Video</string>
<string name="unexpected_error">Unexpected Error</string> <string name="unexpected_error">Unexpected Error</string>
<string name="video_deleted_error">Video seems to be Deleted</string> <string name="video_deleted_error">Video seems to be DELETED</string>
<string name="video_private_error">Video seems to be PRIVATE</string>
<string name="permission_request">Permission Needed</string> <string name="permission_request">Permission Needed</string>
<string name="permission_rationale">External Storage permission is needed in order to save the video to your device</string> <string name="permission_rationale">External Storage permission is needed in order to save the video to your device</string>
<string name="ok">OK</string> <string name="ok">OK</string>

View file

@ -5,6 +5,7 @@ import org.apache.commons.io.FileUtils
import org.fnives.tiktokdownloader.data.model.VideoInPending import org.fnives.tiktokdownloader.data.model.VideoInPending
import org.fnives.tiktokdownloader.data.network.TikTokDownloadRemoteSource import org.fnives.tiktokdownloader.data.network.TikTokDownloadRemoteSource
import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException import org.fnives.tiktokdownloader.data.network.exceptions.VideoDeletedException
import org.fnives.tiktokdownloader.data.network.exceptions.VideoPrivateException
import org.fnives.tiktokdownloader.di.module.NetworkModule import org.fnives.tiktokdownloader.di.module.NetworkModule
import org.fnives.tiktokdownloader.helper.getResourceFile import org.fnives.tiktokdownloader.helper.getResourceFile
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
@ -64,6 +65,15 @@ class TikTokDownloadRemoteSourceUpToDateTest {
} }
} }
@Timeout(value = 120)
@Test
fun GIVEN_private_WHEN_downloading_THEN_proper_exception_is_thrown() {
val parameter = VideoInPending("123", PRIVATE_VIDEO_URL)
Assertions.assertThrows(VideoPrivateException::class.java) {
runBlocking { sut.getVideo(parameter) }
}
}
companion object { companion object {
private const val ACTUAL_FILE_PATH = "actual.mp4" private const val ACTUAL_FILE_PATH = "actual.mp4"
private val EXPECTED_FILE_PATHS = private val EXPECTED_FILE_PATHS =