diff --git a/app/src/main/kotlin/com/muedsa/jcytv/ui/features/detail/AnimeDetailScreen.kt b/app/src/main/kotlin/com/muedsa/jcytv/ui/features/detail/AnimeDetailScreen.kt index 2960bfc..d4f91c0 100644 --- a/app/src/main/kotlin/com/muedsa/jcytv/ui/features/detail/AnimeDetailScreen.kt +++ b/app/src/main/kotlin/com/muedsa/jcytv/ui/features/detail/AnimeDetailScreen.kt @@ -60,7 +60,6 @@ import com.muedsa.compose.tv.widget.LocalRightSideDrawerState import com.muedsa.compose.tv.widget.NoBackground import com.muedsa.compose.tv.widget.ScreenBackground import com.muedsa.compose.tv.widget.ScreenBackgroundType -import com.muedsa.compose.tv.widget.StandardImageCardsRow import com.muedsa.compose.tv.widget.TwoSideWideButton import com.muedsa.compose.tv.widget.rememberScreenBackgroundState import com.muedsa.jcytv.PlaybackActivity @@ -71,7 +70,6 @@ import com.muedsa.jcytv.ui.RankIconColor import com.muedsa.jcytv.ui.nav.LocalAppNavController import com.muedsa.jcytv.ui.nav.NavigationItems import com.muedsa.jcytv.ui.nav.navigate -import com.muedsa.jcytv.util.JcyDocTool import com.muedsa.jcytv.util.Upscayl import com.muedsa.jcytv.viewmodel.AnimeDetailViewModel import com.muedsa.jcytv.viewmodel.AppSettingViewModel diff --git a/app/src/main/kotlin/com/muedsa/jcytv/ui/features/home/HomeNavTabWidget.kt b/app/src/main/kotlin/com/muedsa/jcytv/ui/features/home/HomeNavTabWidget.kt index 70d72ea..66490db 100644 --- a/app/src/main/kotlin/com/muedsa/jcytv/ui/features/home/HomeNavTabWidget.kt +++ b/app/src/main/kotlin/com/muedsa/jcytv/ui/features/home/HomeNavTabWidget.kt @@ -23,8 +23,8 @@ import androidx.tv.material3.TabDefaults import androidx.tv.material3.TabRow import androidx.tv.material3.TabRowDefaults import androidx.tv.material3.Text -import com.muedsa.compose.tv.widget.NotImplementScreen import com.muedsa.compose.tv.widget.ScreenBackgroundType +import com.muedsa.jcytv.ui.features.home.catalog.CatalogScreen import com.muedsa.jcytv.ui.features.home.favorites.FavoritesScreen import com.muedsa.jcytv.ui.features.home.main.MainScreen import com.muedsa.jcytv.ui.features.home.rank.RankScreen @@ -119,7 +119,7 @@ fun HomeContent( viewModel = homePageViewModel ) } - HomeNavTab.Catalog -> NotImplementScreen() + HomeNavTab.Catalog -> CatalogScreen() HomeNavTab.Rank -> RankScreen() HomeNavTab.Search -> SearchScreen() HomeNavTab.Favorites -> FavoritesScreen() diff --git a/app/src/main/kotlin/com/muedsa/jcytv/ui/features/home/catalog/CatalogOptionsWidget.kt b/app/src/main/kotlin/com/muedsa/jcytv/ui/features/home/catalog/CatalogOptionsWidget.kt new file mode 100644 index 0000000..db41e65 --- /dev/null +++ b/app/src/main/kotlin/com/muedsa/jcytv/ui/features/home/catalog/CatalogOptionsWidget.kt @@ -0,0 +1,59 @@ +package com.muedsa.jcytv.ui.features.home.catalog + +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Check +import androidx.compose.material3.HorizontalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.tv.material3.ExperimentalTvMaterial3Api +import androidx.tv.material3.FilterChip +import androidx.tv.material3.FilterChipDefaults +import androidx.tv.material3.Icon +import androidx.tv.material3.MaterialTheme +import androidx.tv.material3.Text + +@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalLayoutApi::class) +@Composable +fun CatalogOptionsWidget( + title: String, + selectedKey: String?, + options: Map, + onClick: (String, String) -> Unit = { _, _ -> } +) { + Text( + text = title, + color = MaterialTheme.colorScheme.onBackground, + style = MaterialTheme.typography.labelLarge + ) + Spacer(modifier = Modifier.height(4.dp)) + FlowRow { + options.forEach { (key, text) -> + FilterChip( + modifier = Modifier.padding(8.dp), + selected = key == selectedKey, + leadingIcon = if (key == selectedKey) { + { + Icon( + modifier = Modifier.size(FilterChipDefaults.IconSize), + imageVector = Icons.Outlined.Check, + contentDescription = "选择${text}" + ) + } + } else null, + onClick = { + onClick(key, text) + } + ) { + Text(text = text) + } + } + } + HorizontalDivider(modifier = Modifier.padding(bottom = 10.dp)) +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/muedsa/jcytv/ui/features/home/catalog/CatalogScreen.kt b/app/src/main/kotlin/com/muedsa/jcytv/ui/features/home/catalog/CatalogScreen.kt new file mode 100644 index 0000000..cab50dd --- /dev/null +++ b/app/src/main/kotlin/com/muedsa/jcytv/ui/features/home/catalog/CatalogScreen.kt @@ -0,0 +1,300 @@ +package com.muedsa.jcytv.ui.features.home.catalog + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowDropDown +import androidx.compose.material.icons.outlined.KeyboardArrowUp +import androidx.compose.material.icons.outlined.Refresh +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.tv.foundation.lazy.grid.TvGridCells +import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid +import androidx.tv.foundation.lazy.grid.itemsIndexed +import androidx.tv.foundation.lazy.list.TvLazyColumn +import androidx.tv.material3.ButtonDefaults +import androidx.tv.material3.Card +import androidx.tv.material3.ExperimentalTvMaterial3Api +import androidx.tv.material3.Icon +import androidx.tv.material3.OutlinedButton +import androidx.tv.material3.OutlinedIconButton +import androidx.tv.material3.Text +import com.muedsa.compose.tv.model.ContentModel +import com.muedsa.compose.tv.theme.ImageCardRowCardPadding +import com.muedsa.compose.tv.theme.ScreenPaddingLeft +import com.muedsa.compose.tv.widget.CardType +import com.muedsa.compose.tv.widget.ImageContentCard +import com.muedsa.compose.tv.widget.LocalErrorMsgBoxState +import com.muedsa.compose.tv.widget.ScreenBackgroundType +import com.muedsa.jcytv.ui.GirdLastItemHeight +import com.muedsa.jcytv.ui.VideoPosterSize +import com.muedsa.jcytv.ui.features.home.LocalHomeScreenBackgroundState +import com.muedsa.jcytv.ui.nav.LocalAppNavController +import com.muedsa.jcytv.ui.nav.NavigationItems +import com.muedsa.jcytv.ui.nav.navigate +import com.muedsa.jcytv.viewmodel.CatalogViewModel +import com.muedsa.model.LazyType +import com.muedsa.uitl.LogUtil + +@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class) +@Composable +fun CatalogScreen( + viewModel: CatalogViewModel = hiltViewModel() +) { + val navController = LocalAppNavController.current + val backgroundState = LocalHomeScreenBackgroundState.current + val errorMsgBoxState = LocalErrorMsgBoxState.current + + var optionId by viewModel.optionIdState + var optionArea by viewModel.optionAreaState + var optionClass by viewModel.optionClassState + var optionLang by viewModel.optionLangState + var optionYear by viewModel.optionYearState + var optionLetter by viewModel.optionLetterState + var optionBy by viewModel.optionByState + + val searchAnimeLP by viewModel.animeLPSF.collectAsState() + + var optionsExpand by remember { + mutableStateOf(false) + } + + LaunchedEffect(key1 = searchAnimeLP) { + if (searchAnimeLP.type == LazyType.FAILURE) { + errorMsgBoxState.error(searchAnimeLP.error) + } + } + + BackHandler(enabled = optionsExpand) { + optionsExpand = false + } + + Column(modifier = Modifier.padding(start = ScreenPaddingLeft)) { + Row( + modifier = Modifier + .fillMaxWidth() + .offset(x = -ScreenPaddingLeft) + .padding(vertical = 30.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + OutlinedButton(onClick = { + optionsExpand = !optionsExpand + }) { + Text(text = "筛选项") + Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing)) + Icon( + modifier = Modifier.size(ButtonDefaults.IconSize), + imageVector = if (optionsExpand) Icons.Outlined.KeyboardArrowUp else Icons.Outlined.ArrowDropDown, + contentDescription = "展开筛选项" + ) + } + Spacer(modifier = Modifier.width(16.dp)) + OutlinedIconButton(onClick = { + viewModel.resetCatalogOptions() + }) { + Icon( + modifier = Modifier.size(ButtonDefaults.IconSize), + imageVector = Icons.Outlined.Refresh, + contentDescription = "重置筛选项" + ) + } + } + + if (optionsExpand) { + // 筛选项 + TvLazyColumn(contentPadding = PaddingValues(top = ImageCardRowCardPadding)) { + item { + CatalogOptionsWidget( + title = "频道", + selectedKey = optionId, + options = CatalogViewModel.ID_OPTIONS, + onClick = { key, _ -> + optionId = key + viewModel.catalogNew() + } + ) + } + item { + CatalogOptionsWidget( + title = "地区", + selectedKey = optionArea, + options = CatalogViewModel.AREA_OPTIONS, + onClick = { key, _ -> + optionArea = if (optionArea == key) null else key + viewModel.catalogNew() + } + ) + } + item { + CatalogOptionsWidget( + title = "剧情", + selectedKey = optionClass, + options = CatalogViewModel.CLASS_OPTIONS, + onClick = { key, _ -> + optionClass = if (optionClass == key) null else key + viewModel.catalogNew() + } + ) + } + item { + CatalogOptionsWidget( + title = "语言", + selectedKey = optionLang, + options = CatalogViewModel.LANG_OPTIONS, + onClick = { key, _ -> + optionLang = if (optionLang == key) null else key + viewModel.catalogNew() + } + ) + } + item { + CatalogOptionsWidget( + title = "年份", + selectedKey = optionYear, + options = CatalogViewModel.YEAR_OPTIONS, + onClick = { key, _ -> + optionYear = if (optionYear == key) null else key + viewModel.catalogNew() + } + ) + } + item { + CatalogOptionsWidget( + title = "字母", + selectedKey = optionLetter, + options = CatalogViewModel.LETTER_OPTIONS, + onClick = { key, _ -> + optionLetter = if (optionLetter == key) null else key + viewModel.catalogNew() + } + ) + } + item { + CatalogOptionsWidget( + title = "排序", + selectedKey = optionBy, + options =CatalogViewModel.ORDER_BY_OPTIONS, + onClick = { key, _ -> + optionBy = if (optionBy == key) null else key + viewModel.catalogNew() + } + ) + } + } + } else { + val gridFocusRequester = remember { FocusRequester() } + + TvLazyVerticalGrid( + columns = TvGridCells.Adaptive(VideoPosterSize.width + ImageCardRowCardPadding), + contentPadding = PaddingValues( + top = ImageCardRowCardPadding, + bottom = ImageCardRowCardPadding + ), + modifier = Modifier + .focusRequester(gridFocusRequester) + .focusProperties { + exit = { gridFocusRequester.saveFocusedChild(); FocusRequester.Default } + enter = { + if (gridFocusRequester.restoreFocusedChild()) { + LogUtil.d("grid restoreFocusedChild") + FocusRequester.Cancel + } else { + LogUtil.d("grid focused default child") + FocusRequester.Default + } + } + } + ) { + itemsIndexed( + items = searchAnimeLP.list, + key = { _, item -> item.id } + ) { index, item -> + val itemFocusRequester = remember { + FocusRequester() + } + ImageContentCard( + modifier = Modifier + .padding(end = ImageCardRowCardPadding) + .focusRequester(itemFocusRequester), + url = item.imageUrl, + imageSize = VideoPosterSize, + type = CardType.STANDARD, + model = ContentModel( + item.title, + subtitle = item.subTitle, + ), + onItemFocus = { + backgroundState.url = item.imageUrl + backgroundState.type = ScreenBackgroundType.BLUR + }, + onItemClick = { + LogUtil.d("Click $item") + navController.navigate( + NavigationItems.Detail, + listOf(item.id.toString()) + ) + } + ) + + LaunchedEffect(key1 = Unit) { + if (searchAnimeLP.offset == index) { + itemFocusRequester.requestFocus() + } + } + } + + if (searchAnimeLP.type != LazyType.LOADING && searchAnimeLP.hasNext) { + item { + Card( + modifier = Modifier + .size(VideoPosterSize) + .padding(end = ImageCardRowCardPadding), + onClick = { + viewModel.catalog(searchAnimeLP) + } + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = "继续加载") + } + } + } + } + + // 最后一行占位 + item { + Spacer(modifier = Modifier.height(GirdLastItemHeight)) + } + } + } + } +} + diff --git a/app/src/main/kotlin/com/muedsa/jcytv/util/JcyDocTool.kt b/app/src/main/kotlin/com/muedsa/jcytv/util/JcyHtmlTool.kt similarity index 93% rename from app/src/main/kotlin/com/muedsa/jcytv/util/JcyDocTool.kt rename to app/src/main/kotlin/com/muedsa/jcytv/util/JcyHtmlTool.kt index 7a7ced9..3373ce6 100644 --- a/app/src/main/kotlin/com/muedsa/jcytv/util/JcyDocTool.kt +++ b/app/src/main/kotlin/com/muedsa/jcytv/util/JcyHtmlTool.kt @@ -14,7 +14,7 @@ import java.net.URI import java.net.URLDecoder import java.nio.charset.StandardCharsets -object JcyDocTool { +object JcyHtmlTool { const val MAIN_SITE_URL = "https://9ciyuan.com/" @@ -22,6 +22,8 @@ object JcyDocTool { const val RANK_URL = "https://9ciyuan.com/index.php/label/ranking.html" + const val CATALOG_URL = "https://9ciyuan.com/index.php/vod/show{query}.html" + const val DETAIL_URL = "https://9ciyuan.com/index.php/vod/detail/id/{id}.html" const val PLAYER_SITE_URL = "https://play.silisili.top/player/ec.php?code=ttnb&if=1&url=" @@ -119,6 +121,17 @@ object JcyDocTool { } } + fun catalog(queryMap: Map): List { + val query = queryMap.toSortedMap().map { + "/${it.key}/${it.value}" + }.joinToString("") + val doc: Document = Jsoup.connect(CATALOG_URL.replace("{query}", query)) + .header(HttpHeaders.REFERER, MAIN_SITE_URL) + .get() + val body = doc.body() + return getRowInfo(body.selectFirst(".vod-list")!!).second + } + fun getVideoDetailById(id: Long): JcyVideoDetail { return getVideoDetailByUrl(DETAIL_URL.replace("{id}", id.toString())) } diff --git a/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/AnimeDetailViewModel.kt b/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/AnimeDetailViewModel.kt index 1ef54fb..be0c26a 100644 --- a/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/AnimeDetailViewModel.kt +++ b/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/AnimeDetailViewModel.kt @@ -12,7 +12,7 @@ import com.muedsa.jcytv.room.dao.FavoriteAnimeDao import com.muedsa.jcytv.room.model.FavoriteAnimeModel import com.muedsa.jcytv.service.DanDanPlayApiService import com.muedsa.jcytv.ui.nav.NavigationItems -import com.muedsa.jcytv.util.JcyDocTool +import com.muedsa.jcytv.util.JcyHtmlTool import com.muedsa.model.LazyData import com.muedsa.model.LazyType import com.muedsa.uitl.LogUtil @@ -89,7 +89,7 @@ class AnimeDetailViewModel @Inject constructor( private suspend fun fetchAnimeDetail(aid: Long): LazyData { return try { - LazyData.success(JcyDocTool.getVideoDetailById(aid)) + LazyData.success(JcyHtmlTool.getVideoDetailById(aid)) } catch (t: Throwable) { LogUtil.fb(t) LazyData.fail(t) @@ -158,10 +158,10 @@ class AnimeDetailViewModel @Inject constructor( ) { viewModelScope.launch(context = Dispatchers.IO) { try { - val rawPlaySource = JcyDocTool.getRawPlaySource( - JcyDocTool.getAbsoluteUrl(url) + val rawPlaySource = JcyHtmlTool.getRawPlaySource( + JcyHtmlTool.getAbsoluteUrl(url) ) - val realPlayUrl = JcyDocTool.getRealPlayUrl(rawPlaySource) + val realPlayUrl = JcyHtmlTool.getRealPlayUrl(rawPlaySource) withContext(Dispatchers.Main) { onSuccess(realPlayUrl) } diff --git a/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/CatalogViewModel.kt b/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/CatalogViewModel.kt new file mode 100644 index 0000000..c00ac98 --- /dev/null +++ b/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/CatalogViewModel.kt @@ -0,0 +1,152 @@ +package com.muedsa.jcytv.viewmodel + +import android.icu.util.Calendar +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.muedsa.jcytv.model.JcySimpleVideoInfo +import com.muedsa.jcytv.util.JcyHtmlTool +import com.muedsa.model.LazyPagedList +import com.muedsa.uitl.LogUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.math.max + +class CatalogViewModel @Inject constructor() : ViewModel() { + + val optionIdState = mutableStateOf(ID_OPTIONS.keys.first()) + val optionAreaState = mutableStateOf(null) + val optionClassState = mutableStateOf(null) + val optionLangState = mutableStateOf(null) + val optionYearState = mutableStateOf(null) + val optionLetterState = mutableStateOf(null) + val optionByState = mutableStateOf(null) + + private val _animeLPSF = MutableStateFlow( + LazyPagedList.new, JcySimpleVideoInfo>( + buildQueryParams() + ) + ) + val animeLPSF: StateFlow, JcySimpleVideoInfo>> = _animeLPSF + + fun catalog(lp: LazyPagedList, JcySimpleVideoInfo>) { + viewModelScope.launch { + val loadingLP = lp.loadingNext() + _animeLPSF.value = loadingLP + _animeLPSF.value = withContext(Dispatchers.IO) { + fetchCatalog(loadingLP) + } + } + } + + fun catalogNew() { + catalog(LazyPagedList.new(buildQueryParams())) + } + + private suspend fun fetchCatalog( + lp: LazyPagedList, JcySimpleVideoInfo> + ): LazyPagedList, JcySimpleVideoInfo> { + return try { + val pageNum = lp.nextPage + JcyHtmlTool.catalog(lp.query.toMutableMap().apply { + this["page"] = pageNum.toString() + }).let { + lp.successNext(it, if (it.isEmpty()) pageNum else pageNum + 1) + } + } catch (t: Throwable) { + LogUtil.fb(t) + lp.failNext(t) + } + } + + fun resetCatalogOptions() { + optionIdState.value = ID_OPTIONS.keys.first() + optionAreaState.value = null + optionClassState.value = null + optionLangState.value = null + optionYearState.value = null + optionLetterState.value = null + optionByState.value = null + catalogNew() + } + + private fun buildQueryParams(): Map { + return buildMap { + put("id", optionIdState.value) + optionAreaState.value?.let { put("area", it) } + optionClassState.value?.let { put("class", it) } + optionLangState.value?.let { put("lang", it) } + optionYearState.value?.let { put("year", it) } + optionLetterState.value?.let { put("letter", it) } + optionByState.value?.let { put("by", it) } + } + } + + init { + catalog(_animeLPSF.value) + } + + companion object { + val ID_OPTIONS: Map = mapOf( + "20" to "新番放送", + "3" to "追番计划", + "4" to "动漫剧场" + ) + + val AREA_OPTIONS: Map = + listOf("大陆", "日本", "欧美", "其他").associateBy { it } + + val CLASS_OPTIONS: Map = listOf( + "日韩动漫", + "国产动漫", + "青春", + "战斗", + "奇幻", + "热血", + "搞笑", + "萝莉", + "校园", + "冒险", + "动作", + "励志", + "动画", + "情感", + "玄幻", + "推理", + "机战", + "运动", + "战争", + "少年", + "少女", + "社会", + "原创", + "亲子", + "益智", + "小说改" + ).associateBy { it } + + val LANG_OPTIONS: Map = + listOf("国语", "英语", "粤语", "闽南语", "韩语", "日语", "法语", "德语", "其他") + .associateBy { it } + + val YEAR_OPTIONS: Map = + (2000..max(Calendar.getInstance().get(Calendar.YEAR), 2024)) + .map { it.toString() } + .associateBy { it } + + val LETTER_OPTIONS: Map = listOf( + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" + ).associateBy { it } + + val ORDER_BY_OPTIONS: Map = mapOf( + "time" to "最近更新", + "hits" to "最多播放", + "score" to "最好评" + ) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/HomePageViewModel.kt b/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/HomePageViewModel.kt index 14181bf..19d3e2a 100644 --- a/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/HomePageViewModel.kt +++ b/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/HomePageViewModel.kt @@ -3,8 +3,7 @@ package com.muedsa.jcytv.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.muedsa.jcytv.model.JcySimpleVideoInfo -import com.muedsa.jcytv.repository.AppRepository -import com.muedsa.jcytv.util.JcyDocTool +import com.muedsa.jcytv.util.JcyHtmlTool import com.muedsa.model.LazyData import com.muedsa.uitl.LogUtil import dagger.hilt.android.lifecycle.HiltViewModel @@ -31,7 +30,7 @@ class HomePageViewModel @Inject constructor() : ViewModel() { private suspend fun fetchHomeRows(): LazyData>>> { return try { - LazyData.success(JcyDocTool.getHomeVideoRows()) + LazyData.success(JcyHtmlTool.getHomeVideoRows()) } catch (t: Throwable) { LogUtil.fb(t) LazyData.fail(t) diff --git a/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/RankViewModel.kt b/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/RankViewModel.kt index f4fa54c..0cdd04d 100644 --- a/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/RankViewModel.kt +++ b/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/RankViewModel.kt @@ -3,7 +3,7 @@ package com.muedsa.jcytv.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.muedsa.jcytv.model.JcyRankVideoInfo -import com.muedsa.jcytv.util.JcyDocTool +import com.muedsa.jcytv.util.JcyHtmlTool import com.muedsa.model.LazyData import com.muedsa.uitl.LogUtil import dagger.hilt.android.lifecycle.HiltViewModel @@ -31,7 +31,7 @@ class RankViewModel @Inject constructor() : ViewModel() { private suspend fun rankList(): LazyData>>> { return try { - LazyData.success(JcyDocTool.rankList()) + LazyData.success(JcyHtmlTool.rankList()) } catch (t: Throwable) { LogUtil.fb(t) LazyData.fail(t) diff --git a/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/SearchViewModel.kt b/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/SearchViewModel.kt index f2143cb..98fadc1 100644 --- a/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/SearchViewModel.kt +++ b/app/src/main/kotlin/com/muedsa/jcytv/viewmodel/SearchViewModel.kt @@ -3,7 +3,7 @@ package com.muedsa.jcytv.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.muedsa.jcytv.model.JcySimpleVideoInfo -import com.muedsa.jcytv.util.JcyDocTool +import com.muedsa.jcytv.util.JcyHtmlTool import com.muedsa.model.LazyData import com.muedsa.uitl.LogUtil import dagger.hilt.android.lifecycle.HiltViewModel @@ -36,7 +36,7 @@ class SearchViewModel @Inject constructor() : ViewModel() { query: String ): LazyData> { return try { - LazyData.success(JcyDocTool.searchVideo(query)) + LazyData.success(JcyHtmlTool.searchVideo(query)) } catch (t: Throwable) { LogUtil.fb(t) LazyData.fail(t) diff --git a/app/src/test/kotlin/com/muedsa/jcytv/util/JcyDocToolTest.kt b/app/src/test/kotlin/com/muedsa/jcytv/util/JcyHtmlToolTest.kt similarity index 73% rename from app/src/test/kotlin/com/muedsa/jcytv/util/JcyDocToolTest.kt rename to app/src/test/kotlin/com/muedsa/jcytv/util/JcyHtmlToolTest.kt index 9c9798d..164f939 100644 --- a/app/src/test/kotlin/com/muedsa/jcytv/util/JcyDocToolTest.kt +++ b/app/src/test/kotlin/com/muedsa/jcytv/util/JcyHtmlToolTest.kt @@ -5,7 +5,7 @@ import org.junit.Test import java.security.Security -class JcyDocToolTest { +class JcyHtmlToolTest { init { @@ -16,7 +16,7 @@ class JcyDocToolTest { @Test fun decryptPlayUrl_test() { - val url = JcyDocTool.decryptPlayUrl( + val url = JcyHtmlTool.decryptPlayUrl( "pUP07m71iJsJEVHJNNmUWc7KxjyjGHFxbcUUx5HUjUn+/vTBo7+VWEZQE9gyDN13akUf1lpx1EpdDb6bkl3xrYG7/ZMlKzav3cqgWd8cXy5RS5lGh3OOhlU2aKQjliqwsILEoZ8CfXHAJ8XC43/E/MKgwtiKSjSCoiURCldrDvN8w+9L1NOzJODWqpTNL66t/L2/KAKzHg1wmvMr7HC+f/nntQ8qqnd0WsTMsnOaE5ksEY7Jo36ZJkixaccsq+PXs0ECK54TNNu2a734aLm7bz0TeAHCUdtSNSR8BOeenq7TS4xiaeGuU1C3eLK+vfaY4WmUX8QcGYd41V55msAPH9XIYP6PtknZYau9I2H/c0IlyRMMx1W6WTW3r5nMi3oURarJG964uJgMzrxDpLTBJw==", "VMnMnV" ) @@ -26,14 +26,14 @@ class JcyDocToolTest { @Test fun getDecryptPlayUrlForUrl_test() { val url = - JcyDocTool.getDecryptPlayUrlForUrl("https://play.silisili.top/player/ec.php?code=ttnb&if=1&url=acg-oN1bTZQhisyrB20UkQ5sdtfszxbEw9UECamaNW45S1Q=") + JcyHtmlTool.getDecryptPlayUrlForUrl("https://play.silisili.top/player/ec.php?code=ttnb&if=1&url=acg-oN1bTZQhisyrB20UkQ5sdtfszxbEw9UECamaNW45S1Q=") println(url) } @Test fun getVideoDetailById_test() { val id = 3010L - val detail = JcyDocTool.getVideoDetailById(id) + val detail = JcyHtmlTool.getVideoDetailById(id) println(detail.id) println(detail.detailPagePath) println(detail.title) @@ -51,7 +51,7 @@ class JcyDocToolTest { @Test fun getVideoDetailByUrl_test() { val url = "https://www.9ciyuan.com/index.php/vod/detail/id/3010.html" - val detail = JcyDocTool.getVideoDetailByUrl(url) + val detail = JcyHtmlTool.getVideoDetailByUrl(url) println(detail.id) println(detail.detailPagePath) println(detail.title) @@ -69,7 +69,7 @@ class JcyDocToolTest { @Test fun getRawPlaySource_test() { val testUrl = "https://www.9ciyuan.com/index.php/vod/play/id/22/sid/4/nid/1.html" - val (url, urlNext, from) = JcyDocTool.getRawPlaySource(testUrl) + val (url, urlNext, from) = JcyHtmlTool.getRawPlaySource(testUrl) println(url) println(urlNext) println(from) @@ -78,8 +78,8 @@ class JcyDocToolTest { @Test fun getRealPlayUrl_test() { val url = "https://www.9ciyuan.com/index.php/vod/play/id/22/sid/4/nid/1.html" - val rawPlaySource = JcyDocTool.getRawPlaySource(url) - val realUrl = JcyDocTool.getRealPlayUrl(rawPlaySource) + val rawPlaySource = JcyHtmlTool.getRawPlaySource(url) + val realUrl = JcyHtmlTool.getRealPlayUrl(rawPlaySource) println(realUrl) } @@ -87,13 +87,13 @@ class JcyDocToolTest { @Test fun getRealPlayUrl_detail_all_test() { val url = "https://www.9ciyuan.com/index.php/vod/play/id/22/sid/1/nid/1.html" - val detail = JcyDocTool.getVideoDetailByUrl(url) + val detail = JcyHtmlTool.getVideoDetailByUrl(url) detail.playList.forEach { println("# ${it.first}") it.second.forEach { e -> - val playPageUrl = JcyDocTool.getAbsoluteUrl(e.second) - val rawPlaySource = JcyDocTool.getRawPlaySource(playPageUrl) - val realUrl = JcyDocTool.getRealPlayUrl(rawPlaySource) + val playPageUrl = JcyHtmlTool.getAbsoluteUrl(e.second) + val rawPlaySource = JcyHtmlTool.getRawPlaySource(playPageUrl) + val realUrl = JcyHtmlTool.getRealPlayUrl(rawPlaySource) check(realUrl.startsWith("http")) { "## ${it.first} ${rawPlaySource.from} -> $realUrl" } println("## ${e.first} -> $realUrl") } @@ -102,7 +102,7 @@ class JcyDocToolTest { @Test fun getHomeVideoRows_test() { - val rows = JcyDocTool.getHomeVideoRows() + val rows = JcyHtmlTool.getHomeVideoRows() rows.forEach { println("# ${it.first}") it.second.forEach { e -> @@ -113,7 +113,7 @@ class JcyDocToolTest { @Test fun searchVideo_test() { - val list = JcyDocTool.searchVideo("1") + val list = JcyHtmlTool.searchVideo("1") list.forEach { println("${it.title} ${it.subTitle} ${it.detailPagePath} ${it.imageUrl}") } @@ -121,7 +121,7 @@ class JcyDocToolTest { @Test fun rankList_test() { - val list = JcyDocTool.rankList() + val list = JcyHtmlTool.rankList() list.forEach { println("# ${it.first}") it.second.forEach { anime -> @@ -129,4 +129,16 @@ class JcyDocToolTest { } } } + + @Test + fun catalog_test() { + val list = JcyHtmlTool.catalog(mapOf( + "id" to "20", + "page" to "2", + "area" to "日本", + )) + list.forEach { + println("${it.title} ${it.subTitle} ${it.detailPagePath} ${it.imageUrl}") + } + } } \ No newline at end of file