Skip to content

Commit 938a42b

Browse files
committed
update: refactor code & captcha validate
1 parent d7bfa8f commit 938a42b

File tree

67 files changed

+4387
-3622
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+4387
-3622
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ android {
2828
applicationId = "com.muedsa.jcytv"
2929
minSdk = 24
3030
targetSdk = 34
31-
versionCode = 12
32-
versionName = "1.0.0-rc01"
31+
versionCode = 13
32+
versionName = "1.0.0-rc02"
3333
vectorDrawables {
3434
useSupportLibrary = true
3535
}

app/src/main/kotlin/com/muedsa/compose/tv/widget/FullScreenWidgets.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.padding
1010
import androidx.compose.material.icons.Icons
1111
import androidx.compose.material.icons.outlined.Refresh
1212
import androidx.compose.runtime.Composable
13+
import androidx.compose.runtime.LaunchedEffect
1314
import androidx.compose.ui.Alignment
1415
import androidx.compose.ui.Modifier
1516
import androidx.compose.ui.graphics.Color
@@ -41,7 +42,10 @@ fun EmptyDataScreen(model: Boolean = false) {
4142

4243

4344
@Composable
44-
fun ErrorScreen(onRefresh: (() -> Unit)? = null) {
45+
fun ErrorScreen(
46+
onError: (() -> Unit)? = null,
47+
onRefresh: (() -> Unit)? = null
48+
) {
4549
Column(
4650
modifier = Modifier
4751
.fillMaxSize()
@@ -57,6 +61,12 @@ fun ErrorScreen(onRefresh: (() -> Unit)? = null) {
5761
}
5862
}
5963
}
64+
65+
if (onError != null) {
66+
LaunchedEffect(key1 = Unit) {
67+
onError.invoke()
68+
}
69+
}
6070
}
6171

6272

app/src/main/kotlin/com/muedsa/jcytv/AppModule.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
package com.muedsa.jcytv
22

3+
import android.content.Context
4+
import androidx.room.Room
35
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
6+
import com.muedsa.jcytv.repository.DataStoreRepo
7+
import com.muedsa.jcytv.repository.JcyRepo
8+
import com.muedsa.jcytv.room.AppDatabase
49
import com.muedsa.jcytv.service.DanDanPlayApiService
510
import com.muedsa.uitl.LenientJson
611
import dagger.Module
712
import dagger.Provides
813
import dagger.hilt.InstallIn
14+
import dagger.hilt.android.qualifiers.ApplicationContext
915
import dagger.hilt.components.SingletonComponent
1016
import okhttp3.MediaType.Companion.toMediaType
1117
import okhttp3.OkHttpClient
@@ -17,6 +23,32 @@ import javax.inject.Singleton
1723
@InstallIn(SingletonComponent::class)
1824
internal object AppModule {
1925

26+
@Singleton
27+
@Provides
28+
fun provideDataStoreRepository(@ApplicationContext app: Context) = DataStoreRepo(app)
29+
30+
@Provides
31+
@Singleton
32+
fun provideAppDatabase(@ApplicationContext appContext: Context): AppDatabase {
33+
return Room.databaseBuilder(
34+
appContext,
35+
AppDatabase::class.java,
36+
"JCYTV"
37+
).build()
38+
}
39+
40+
@Provides
41+
@Singleton
42+
fun provideFavoriteAnimeDao(appDatabase: AppDatabase) = appDatabase.favoriteAnimeDao()
43+
44+
@Provides
45+
@Singleton
46+
fun provideEpisodeProgressDao(appDatabase: AppDatabase) = appDatabase.episodeProgressDao()
47+
48+
@Provides
49+
@Singleton
50+
fun provideJcyRepository(dataStoreRepo: DataStoreRepo): JcyRepo = JcyRepo(dataStoreRepo)
51+
2052
@Provides
2153
@Singleton
2254
fun provideDanDanPlayApiService(): DanDanPlayApiService {

app/src/main/kotlin/com/muedsa/jcytv/DatabaseModule.kt

Lines changed: 0 additions & 34 deletions
This file was deleted.

app/src/main/kotlin/com/muedsa/jcytv/DateStoreModule.kt

Lines changed: 0 additions & 19 deletions
This file was deleted.

app/src/main/kotlin/com/muedsa/jcytv/MainActivity.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@ import androidx.activity.viewModels
77
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
88
import com.muedsa.compose.tv.theme.TvTheme
99
import com.muedsa.compose.tv.widget.Scaffold
10-
import com.muedsa.jcytv.ui.nav.AppNavigation
11-
import com.muedsa.jcytv.viewmodel.HomePageViewModel
12-
import com.muedsa.model.LazyType
10+
import com.muedsa.jcytv.screens.AppNavigation
11+
import com.muedsa.jcytv.screens.home.main.MainScreenUiState
12+
import com.muedsa.jcytv.screens.home.main.MainScreenViewModel
1313
import dagger.hilt.android.AndroidEntryPoint
1414

1515
@AndroidEntryPoint
1616
class MainActivity : ComponentActivity() {
1717

18-
private val homePageViewModel: HomePageViewModel by viewModels()
18+
private val mainScreenViewModel: MainScreenViewModel by viewModels()
1919

2020
override fun onCreate(savedInstanceState: Bundle?) {
21-
val splashScreen = installSplashScreen()
2221
super.onCreate(savedInstanceState)
22+
val splashScreen = installSplashScreen()
2323
splashScreen.setKeepOnScreenCondition {
24-
homePageViewModel.homeRowsSF.value.type == LazyType.LOADING
24+
mainScreenViewModel.uiState.value is MainScreenUiState.Loading
2525
}
2626
setContent {
2727
TvTheme {

app/src/main/kotlin/com/muedsa/jcytv/Perfs.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.muedsa.jcytv
22

33
import androidx.datastore.preferences.core.booleanPreferencesKey
44
import androidx.datastore.preferences.core.intPreferencesKey
5+
import androidx.datastore.preferences.core.stringPreferencesKey
56

67
val KEY_DANMAKU_ENABLE = booleanPreferencesKey("danmaku_enable")
78

@@ -11,4 +12,6 @@ val KEY_DANMAKU_SIZE_SCALE = intPreferencesKey("danmaku_size_scale")
1112

1213
val KEY_DANMAKU_ALPHA = intPreferencesKey("danmaku_alpha")
1314

14-
val KEY_DANMAKU_SCREEN_PART = intPreferencesKey("danmaku_size_part")
15+
val KEY_DANMAKU_SCREEN_PART = intPreferencesKey("danmaku_size_part")
16+
17+
val KEY_CAPTCHA_GUARD_OK = stringPreferencesKey("captcha_guard_ok")

app/src/main/kotlin/com/muedsa/jcytv/PlaybackActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import com.muedsa.compose.tv.useLocalErrorMsgBoxController
1313
import com.muedsa.compose.tv.widget.AppBackHandler
1414
import com.muedsa.compose.tv.widget.FillTextScreen
1515
import com.muedsa.compose.tv.widget.Scaffold
16-
import com.muedsa.jcytv.ui.features.playback.PlaybackScreen
16+
import com.muedsa.jcytv.screens.playback.PlaybackScreen
1717
import dagger.hilt.android.AndroidEntryPoint
1818

1919
@AndroidEntryPoint
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.muedsa.jcytv.exception
2+
3+
class NeedValidateCaptchaException : RuntimeException()

app/src/main/kotlin/com/muedsa/jcytv/model/AppSettingModel.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.muedsa.jcytv.model
22

33
import androidx.datastore.preferences.core.Preferences
4+
import com.muedsa.jcytv.KEY_CAPTCHA_GUARD_OK
45
import com.muedsa.jcytv.KEY_DANMAKU_ALPHA
56
import com.muedsa.jcytv.KEY_DANMAKU_ENABLE
67
import com.muedsa.jcytv.KEY_DANMAKU_MERGE_ENABLE
@@ -13,6 +14,7 @@ data class AppSettingModel(
1314
val danmakuSizeScale: Int,
1415
val danmakuAlpha: Int,
1516
val danmakuScreenPart: Int,
17+
val captchaGuardOk: String,
1618
) {
1719

1820
companion object {
@@ -24,6 +26,7 @@ data class AppSettingModel(
2426
danmakuSizeScale = prefs[KEY_DANMAKU_SIZE_SCALE] ?: 140,
2527
danmakuAlpha = prefs[KEY_DANMAKU_ALPHA] ?: 100,
2628
danmakuScreenPart = prefs[KEY_DANMAKU_SCREEN_PART] ?: 100,
29+
captchaGuardOk = prefs[KEY_CAPTCHA_GUARD_OK] ?: "",
2730
)
2831

2932
}

app/src/main/kotlin/com/muedsa/jcytv/model/JcyHomeData.kt

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.muedsa.jcytv.model
2+
3+
class JcyRankList(
4+
val title: String,
5+
val list: List<JcyRankVideoInfo>
6+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.muedsa.jcytv.model
2+
3+
class JcyVideoRow(
4+
val title: String,
5+
val list: List<JcySimpleVideoInfo>
6+
)

app/src/main/kotlin/com/muedsa/jcytv/repository/AppRepository.kt

Lines changed: 0 additions & 4 deletions
This file was deleted.

app/src/main/kotlin/com/muedsa/jcytv/repository/DataStoreRepo.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ private const val PREFS_NAME = "setting"
1010

1111
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = PREFS_NAME)
1212

13-
class DataStoreRepo @Inject constructor(private val context: Context) {
13+
class DataStoreRepo @Inject constructor(context: Context) {
1414
val dataStore: DataStore<Preferences> = context.dataStore
1515
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.muedsa.jcytv.repository
2+
3+
import com.google.common.net.HttpHeaders
4+
import com.muedsa.jcytv.KEY_CAPTCHA_GUARD_OK
5+
import com.muedsa.jcytv.exception.NeedValidateCaptchaException
6+
import com.muedsa.jcytv.model.JcyRankList
7+
import com.muedsa.jcytv.model.JcySimpleVideoInfo
8+
import com.muedsa.jcytv.model.JcyVideoDetail
9+
import com.muedsa.jcytv.model.JcyVideoRow
10+
import com.muedsa.jcytv.util.JcyConst
11+
import com.muedsa.jcytv.util.JcyHtmlParserTool
12+
import com.muedsa.jcytv.util.JcyPlaySourceTool.CHROME_USER_AGENT
13+
import com.muedsa.jcytv.util.JcyRotateCaptchaTool
14+
import kotlinx.coroutines.flow.Flow
15+
import kotlinx.coroutines.flow.firstOrNull
16+
import kotlinx.coroutines.flow.map
17+
import org.jsoup.Jsoup
18+
import org.jsoup.nodes.Document
19+
import org.jsoup.nodes.Element
20+
import javax.inject.Inject
21+
22+
class JcyRepo @Inject constructor(dataStoreRepo: DataStoreRepo) {
23+
24+
private val guardOkFlow: Flow<String> = dataStoreRepo.dataStore.data
25+
.map { it[KEY_CAPTCHA_GUARD_OK] ?: "" }
26+
27+
private suspend fun getGuardOk(): String = guardOkFlow.firstOrNull() ?: ""
28+
29+
suspend fun fetchHomeVideoRows(): List<JcyVideoRow> {
30+
val doc: Document = Jsoup.connect(JcyConst.HOME_URL)
31+
.header(HttpHeaders.REFERER, JcyConst.HOME_URL)
32+
.header(HttpHeaders.USER_AGENT, JcyConst.CHROME_USER_AGENT)
33+
.cookie(JcyRotateCaptchaTool.COOKIE_GUARD_OK, getGuardOk())
34+
.get()
35+
checkIfNeedValidateCaptcha(doc.head())
36+
return JcyHtmlParserTool.parseHomVideoRows(doc.body())
37+
}
38+
39+
suspend fun fetchRankList(): List<JcyRankList> {
40+
val doc: Document = Jsoup.connect(JcyConst.RANK_URL)
41+
.header(HttpHeaders.REFERER, JcyConst.RANK_URL)
42+
.header(HttpHeaders.USER_AGENT, JcyConst.CHROME_USER_AGENT)
43+
.cookie(JcyRotateCaptchaTool.COOKIE_GUARD_OK, getGuardOk())
44+
.get()
45+
checkIfNeedValidateCaptcha(doc.head())
46+
return JcyHtmlParserTool.parseRankList(doc.body())
47+
}
48+
49+
suspend fun searchVideos(query: String): List<JcySimpleVideoInfo> {
50+
val doc: Document = Jsoup.connect("${JcyConst.SEARCH_URL}$query")
51+
.header(HttpHeaders.REFERER, JcyConst.RANK_URL)
52+
.header(HttpHeaders.USER_AGENT, JcyConst.CHROME_USER_AGENT)
53+
.cookie(JcyRotateCaptchaTool.COOKIE_GUARD_OK, getGuardOk())
54+
.get()
55+
checkIfNeedValidateCaptcha(doc.head())
56+
val vodListEl = doc.body().selectFirst(".vod-list")!!
57+
return JcyHtmlParserTool.parseVideoRow(vodListEl).list
58+
}
59+
60+
suspend fun catalog(queryMap: Map<String, String>): List<JcySimpleVideoInfo> {
61+
val query = queryMap.toSortedMap().map {
62+
"/${it.key}/${it.value}"
63+
}.joinToString("")
64+
val doc: Document = Jsoup.connect(JcyConst.CATALOG_URL.replace("{query}", query))
65+
.header(HttpHeaders.REFERER, JcyConst.RANK_URL)
66+
.header(HttpHeaders.USER_AGENT, JcyConst.CHROME_USER_AGENT)
67+
.cookie(JcyRotateCaptchaTool.COOKIE_GUARD_OK, getGuardOk())
68+
.get()
69+
checkIfNeedValidateCaptcha(doc.head())
70+
val vodListEl = doc.body().selectFirst(".vod-list")!!
71+
return JcyHtmlParserTool.parseVideoRow(vodListEl).list
72+
}
73+
74+
suspend fun fetchVideoDetail(id: Long): JcyVideoDetail {
75+
val url = JcyConst.DETAIL_URL.replace("{id}", id.toString())
76+
val doc: Document = Jsoup.connect(url)
77+
.header(HttpHeaders.REFERER, url)
78+
.header(HttpHeaders.USER_AGENT, CHROME_USER_AGENT)
79+
.cookie(JcyRotateCaptchaTool.COOKIE_GUARD_OK, getGuardOk())
80+
.get()
81+
checkIfNeedValidateCaptcha(doc.head())
82+
return JcyHtmlParserTool.parseVideoDetail(doc.body())
83+
}
84+
85+
private fun checkIfNeedValidateCaptcha(body: Element) {
86+
if (JcyRotateCaptchaTool.checkIfNeedValidateCaptcha(body))
87+
throw NeedValidateCaptchaException()
88+
}
89+
}

0 commit comments

Comments
 (0)