From dd1eb570046b5f76082a69f030af5f7072f0a4db Mon Sep 17 00:00:00 2001 From: ckw1 Date: Thu, 19 Dec 2024 16:31:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=A8=8D=E5=90=8E=E5=86=8D?= =?UTF-8?q?=E7=9C=8B/=E7=8E=B0=E5=9C=A8=E4=B8=8D=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 5 + .../main/kotlin/dev/aaa1115910/bv/BVApp.kt | 4 + .../bv/activities/user/ToViewActivity.kt | 20 +++ .../dev/aaa1115910/bv/screen/MainScreen.kt | 3 +- .../aaa1115910/bv/screen/user/ToViewScreen.kt | 124 ++++++++++++++++++ .../bv/viewmodel/user/ToViewViewModel.kt | 99 ++++++++++++++ app/src/main/res/values/strings.xml | 1 + .../aaa1115910/biliapi/entity/user/ToView.kt | 116 ++++++++++++++++ .../aaa1115910/biliapi/http/BiliHttpApi.kt | 19 +++ .../biliapi/http/entity/toview/ToViewData.kt | 96 ++++++++++++++ .../biliapi/repositories/ToViewRepository.kt | 42 ++++++ 11 files changed, 528 insertions(+), 1 deletion(-) create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/activities/user/ToViewActivity.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/user/ToViewScreen.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/user/ToViewViewModel.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/user/ToView.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/toview/ToViewData.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/ToViewRepository.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 442636ca..6df6636b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -130,6 +130,11 @@ android:exported="false" android:label="@string/title_activity_history" android:theme="@style/Theme.BV" /> + + TvLazyVerticalGrid( + modifier = Modifier.padding(innerPadding), + columns = TvGridCells.Fixed(4), + contentPadding = PaddingValues(24.dp), + verticalArrangement = Arrangement.spacedBy(24.dp), + horizontalArrangement = Arrangement.spacedBy(24.dp) + ) { + itemsIndexed(ToViewViewModel.histories) { index, item -> + Box( + contentAlignment = Alignment.Center + ) { + SmallVideoCard( + data = item, + onClick = { + VideoInfoActivity.actionStart( + context = context, + aid = item.avid, + proxyArea = ProxyArea.checkProxyArea(item.title) + ) + }, + onFocus = { + currentIndex = index + //预加载 + // if (index + 20 > ToViewViewModel.histories.size) { + // ToViewViewModel.update() + // } + } + ) + } + } + } + } +} diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/user/ToViewViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/user/ToViewViewModel.kt new file mode 100644 index 00000000..4dd5c423 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/user/ToViewViewModel.kt @@ -0,0 +1,99 @@ +package dev.aaa1115910.bv.viewmodel.user + +import android.content.Context +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dev.aaa1115910.biliapi.http.entity.AuthFailureException +import dev.aaa1115910.biliapi.repositories.HistoryRepository +import dev.aaa1115910.biliapi.repositories.ToViewRepository +import dev.aaa1115910.bv.BVApp +import dev.aaa1115910.bv.BuildConfig +import dev.aaa1115910.bv.R +import dev.aaa1115910.bv.entity.carddata.VideoCardData +import dev.aaa1115910.bv.repository.UserRepository +import dev.aaa1115910.bv.util.Prefs +import dev.aaa1115910.bv.util.fInfo +import dev.aaa1115910.bv.util.fWarn +import dev.aaa1115910.bv.util.formatMinSec +import dev.aaa1115910.bv.util.toast +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class ToViewViewModel( + private val userRepository: UserRepository, + private val ToViewRepository: ToViewRepository +) : ViewModel() { + companion object { + private val logger = KotlinLogging.logger { } + } + + var histories = mutableStateListOf() + var noMore by mutableStateOf(false) + + private var cursor = 0L + private var updating = false + + fun update() { + viewModelScope.launch(Dispatchers.IO) { + updateToView() + } + } + + private suspend fun updateToView(context: Context = BVApp.context) { + if (updating || noMore) return + logger.fInfo { "Updating histories with params [cursor=$cursor, apiType=${Prefs.apiType}]" } + updating = true + runCatching { + val data = ToViewRepository.getToView( + cursor = cursor, + preferApiType = Prefs.apiType + ) + + data.data.forEach { ToViewItem -> + histories.add( + VideoCardData( + avid = ToViewItem.oid, + title = ToViewItem.title, + cover = ToViewItem.cover, + upName = ToViewItem.author, + timeString = if (ToViewItem.progress == -1) context.getString(R.string.play_time_finish) + else context.getString( + R.string.play_time_history, + (ToViewItem.progress * 1000L).formatMinSec(), + (ToViewItem.duration * 1000L).formatMinSec() + ) + ) + ) + } + //update cursor + cursor = data.cursor + logger.fInfo { "Update toview cursor: [cursor=$cursor]" } + logger.fInfo { "Update histories success" } + if (cursor == 0L) { + noMore = true + logger.fInfo { "No more toview" } + } + }.onFailure { + logger.fWarn { "Update histories failed: ${it.stackTraceToString()}" } + when (it) { + is AuthFailureException -> { + withContext(Dispatchers.Main) { + BVApp.context.getString(R.string.exception_auth_failure) + .toast(BVApp.context) + } + logger.fInfo { "User auth failure" } + if (!BuildConfig.DEBUG) userRepository.logout() + } + + else -> {} + } + } + updating = false + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 08cfae31..182c3b29 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -179,6 +179,7 @@ HDR 历史记录 + 现在不看 bilibili 热搜 清空 删除 diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/user/ToView.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/user/ToView.kt new file mode 100644 index 00000000..91d7d15c --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/user/ToView.kt @@ -0,0 +1,116 @@ +package dev.aaa1115910.biliapi.entity.user + +import bilibili.app.interfaces.v1.CursorItem + +//TODO 暂时仅解析 UGC 和 PGC +data class ToViewData( + val cursor: Long, + val data: List +) { + companion object { + fun fromToViewResponse(data: dev.aaa1115910.biliapi.http.entity.toview.ToViewData) = + ToViewData( +// cursor = data.cursor.viewAt, + cursor = 0, + data = data.list +// .filter { it.history.business == "archive" || it.history.business == "pgc" } + .map { ToViewItem.fromToViewItem(it) } + ) + + // fun fromToViewResponse(data: bilibili.app.interfaces.v1.CursorV2Reply) = ToViewData( + // cursor = data.cursor.max, + // data = data.itemsList + // .filter { it.cardItemCase == CursorItem.CardItemCase.CARD_UGC || it.cardItemCase == CursorItem.CardItemCase.CARD_OGV } + // .map { ToViewItem.fromToViewItem(it) } + // ) + } +} + +data class ToViewItem( + val oid: Long, + val bvid: String, + val cid: Long, + val kid: Int, + val epid: Int?, + val seasonId: Int?, + val title: String, + val cover: String, + val author: String, + val duration: Int, + val progress: Int, + val type: ToViewItemType +) { + companion object { + fun fromToViewItem(item: dev.aaa1115910.biliapi.http.entity.toview.ToViewItem) = + ToViewItem( + oid = item.aid, + bvid = item.bvid, + cid = item.cid, + kid = 0, + epid = 0, + seasonId = null, + title = item.title, + cover = item.pic, + author = item.owner.name, + duration = item.duration, + progress = item.progress, + type = ToViewItemType.Archive + // type = when (item.history.business) { + // "archive" -> HistoryItemType.Archive + // "pgc" -> HistoryItemType.Pgc + // else -> HistoryItemType.Unknown + // } + ) + + @Suppress("RemoveRedundantQualifierName") + fun fromToViewItem(item: bilibili.app.interfaces.v1.CursorItem) = ToViewItem( + oid = item.oid, + bvid = when (item.cardItemCase) { + CursorItem.CardItemCase.CARD_UGC -> item.cardUgc.bvid + CursorItem.CardItemCase.CARD_OGV -> "" + else -> "" + }, + cid = when (item.cardItemCase) { + CursorItem.CardItemCase.CARD_UGC -> item.cardUgc.cid + CursorItem.CardItemCase.CARD_OGV -> 0 + else -> 0 + }, + kid = item.kid.toInt(), + epid = null, + seasonId = when (item.cardItemCase) { + CursorItem.CardItemCase.CARD_OGV -> item.kid.toInt() + else -> null + }, + title = item.title, + cover = when (item.cardItemCase) { + CursorItem.CardItemCase.CARD_UGC -> item.cardUgc.cover + CursorItem.CardItemCase.CARD_OGV -> item.cardOgv.cover + else -> "" + }, + author = when (item.cardItemCase) { + CursorItem.CardItemCase.CARD_UGC -> item.cardUgc.name + CursorItem.CardItemCase.CARD_OGV -> "" + else -> "" + }, + duration = when (item.cardItemCase) { + CursorItem.CardItemCase.CARD_UGC -> item.cardUgc.duration.toInt() + CursorItem.CardItemCase.CARD_OGV -> item.cardOgv.duration.toInt() + else -> 0 + }, + progress = when (item.cardItemCase) { + CursorItem.CardItemCase.CARD_UGC -> item.cardUgc.progress.toInt() + CursorItem.CardItemCase.CARD_OGV -> item.cardOgv.progress.toInt() + else -> 0 + }, + type = when (item.cardItemCase) { + CursorItem.CardItemCase.CARD_UGC -> ToViewItemType.Archive + CursorItem.CardItemCase.CARD_OGV -> ToViewItemType.Pgc + else -> ToViewItemType.Unknown + } + ) + } +} + +enum class ToViewItemType { + Unknown, Archive, Pgc +} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApi.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApi.kt index 7a2e7a18..d91b163a 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApi.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApi.kt @@ -8,6 +8,7 @@ import dev.aaa1115910.biliapi.http.entity.danmaku.DanmakuData import dev.aaa1115910.biliapi.http.entity.danmaku.DanmakuResponse import dev.aaa1115910.biliapi.http.entity.dynamic.DynamicData import dev.aaa1115910.biliapi.http.entity.history.HistoryData +import dev.aaa1115910.biliapi.http.entity.toview.ToViewData import dev.aaa1115910.biliapi.http.entity.home.RcmdIndexData import dev.aaa1115910.biliapi.http.entity.home.RcmdTopData import dev.aaa1115910.biliapi.http.entity.index.IndexResultData @@ -367,6 +368,24 @@ object BiliHttpApi { header("Cookie", "SESSDATA=$sessData;") }.body() + /** + * 获取稍后再看列表 + */ + + suspend fun getToView( + // max: Long = 0, + // business: String = "", + // viewAt: Long = 0, + // pageSize: Int = 20, + sessData: String = "" + ): BiliResponse = client.get("/x/v2/history/toview") { + // parameter("max", max) + // parameter("business", business) + // parameter("view_at", viewAt) + // parameter("ps", pageSize) + header("Cookie", "SESSDATA=$sessData;") + }.body() + /** * 获取与视频[avid]或[bvid]有关的相关推荐视频 */ diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/toview/ToViewData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/toview/ToViewData.kt new file mode 100644 index 00000000..e1e73aae --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/toview/ToViewData.kt @@ -0,0 +1,96 @@ +// package dev.aaa1115910.biliapi.http.entity.history +package dev.aaa1115910.biliapi.http.entity.toview + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ToViewData( + // val cursor: Cursor, + // val tab: List, + val list: List +) { + /** + * 历史记录页面信息 + * + * @param max 最后一项目标 id 见请求参数 + * @param viewAt 最后一项时间节点 时间戳 + * @param business 最后一项业务类型 见请求参数 + * @param ps 每页项数 + */ + @Serializable + data class Cursor( + val max: Long, + @SerialName("view_at") + val viewAt: Long, + val business: String, + val ps: Int + ) + + /** + * 历史记录筛选类型 + * + * @param type 类型 + * @param name 类型名 + */ + @Serializable + data class TabItem( + val type: String, + val name: String + ) +} + +@Serializable +data class ToViewItem( + var aid: Long, + var bvid: String, + var cid: Long, + var owner: Owner, + val title: String, + // @SerialName("long_title") + // val longTitle: String, + val pic: String, + // val cover: String, + // val covers: List? = null, + // val uri: String, + // val history: HistoryInfo, + val videos: Int, + // @SerialName("author_name") + // val authorName: String, + // @SerialName("author_face") + // val authorFace: String, + // @SerialName("author_mid") + // val authorMid: Long, + // @SerialName("view_at") + // val viewAt: Int, + val progress: Int, + // val badge: String, + // @SerialName("show_title") + // val showTitle: String, + val duration: Int, + // val current: String, + // val total: Int, + // @SerialName("new_desc") + // val newDesc: String, + // @SerialName("is_finish") + // val isFinish: Int, + // @SerialName("is_fav") + // val isFav: Int, + // val kid: Int, +) { + @Serializable + data class Owner( + val name: String + ) + // @Serializable + // data class HistoryInfo( + // val oid: Long, + // val epid: Int, + // val bvid: String, + // val page: Int, + // val cid: Long, + // val part: String, + // val business: String, + // val dt: Int + // ) +} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/ToViewRepository.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/ToViewRepository.kt new file mode 100644 index 00000000..7dd60b8e --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/ToViewRepository.kt @@ -0,0 +1,42 @@ +package dev.aaa1115910.biliapi.repositories + +import bilibili.app.interfaces.v1.HistoryGrpcKt +import bilibili.app.interfaces.v1.cursor +import bilibili.app.interfaces.v1.cursorV2Req +import dev.aaa1115910.biliapi.entity.ApiType +import dev.aaa1115910.biliapi.entity.user.ToViewData +import dev.aaa1115910.biliapi.http.BiliHttpApi + +class ToViewRepository( + private val authRepository: AuthRepository, + private val channelRepository: ChannelRepository +) { + private val historyStub + get() = runCatching { + HistoryGrpcKt.HistoryCoroutineStub(channelRepository.defaultChannel!!) + }.getOrNull() + + suspend fun getToView( + cursor: Long, + preferApiType: ApiType = ApiType.Web + ): ToViewData { + return when (preferApiType) { + ApiType.Web -> { + val data = BiliHttpApi.getToView( + // viewAt = cursor, + sessData = authRepository.sessionData!!, + ).getResponseData() + print(data) + ToViewData.fromToViewResponse(data) + } + + ApiType.App -> { + val data = BiliHttpApi.getToView( + // viewAt = cursor, + sessData = authRepository.sessionData!!, + ).getResponseData() + ToViewData.fromToViewResponse(data) + } + } + } +}