From 45e05c69302eed40e25485c0c611daa12d2e1466 Mon Sep 17 00:00:00 2001 From: amtoaer Date: Tue, 9 Jul 2024 22:34:02 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/bili_sync/src/adapter/mod.rs | 4 + crates/bili_sync/src/adapter/watch_later.rs | 198 ++++++++++++++++++ crates/bili_sync/src/bilibili/mod.rs | 1 + crates/bili_sync/src/bilibili/watch_later.rs | 0 crates/bili_sync_entity/src/entities/mod.rs | 1 + crates/bili_sync_entity/src/entities/video.rs | 1 + .../src/entities/watch_later.rs | 17 ++ crates/bili_sync_migration/src/lib.rs | 2 + .../src/m20240709_130914_watch_later.rs | 88 ++++++++ 9 files changed, 312 insertions(+) create mode 100644 crates/bili_sync/src/adapter/watch_later.rs create mode 100644 crates/bili_sync/src/bilibili/watch_later.rs create mode 100644 crates/bili_sync_entity/src/entities/watch_later.rs create mode 100644 crates/bili_sync_migration/src/m20240709_130914_watch_later.rs diff --git a/crates/bili_sync/src/adapter/mod.rs b/crates/bili_sync/src/adapter/mod.rs index 25ba8ef..92e796f 100644 --- a/crates/bili_sync/src/adapter/mod.rs +++ b/crates/bili_sync/src/adapter/mod.rs @@ -1,5 +1,6 @@ mod collection; mod favorite; +mod watch_later; use std::collections::HashSet; use std::path::Path; @@ -11,12 +12,14 @@ pub use collection::collection_from; pub use favorite::favorite_from; use futures::Stream; use sea_orm::DatabaseConnection; +use watch_later::watch_later_from; use crate::bilibili::{BiliClient, CollectionItem, VideoInfo}; pub enum Args<'a> { Favorite { fid: &'a str }, Collection { collection_item: &'a CollectionItem }, + WatchLater, } pub async fn video_list_from<'a>( @@ -28,6 +31,7 @@ pub async fn video_list_from<'a>( match args { Args::Favorite { fid } => favorite_from(fid, path, bili_client, connection).await, Args::Collection { collection_item } => collection_from(collection_item, path, bili_client, connection).await, + Args::WatchLater => watch_later_from(path, bili_client, connection).await, } } diff --git a/crates/bili_sync/src/adapter/watch_later.rs b/crates/bili_sync/src/adapter/watch_later.rs new file mode 100644 index 0000000..c103892 --- /dev/null +++ b/crates/bili_sync/src/adapter/watch_later.rs @@ -0,0 +1,198 @@ +use std::collections::HashSet; +use std::path::Path; +use std::pin::Pin; + +use anyhow::Result; +use bili_sync_entity::*; +use bili_sync_migration::OnConflict; +use filenamify::filenamify; +use futures::Stream; +use sea_orm::entity::prelude::*; +use sea_orm::ActiveValue::Set; +use sea_orm::{DatabaseConnection, QuerySelect, TransactionTrait}; + +use super::VideoListModel; +use crate::bilibili::{BiliClient, BiliError, Collection, CollectionItem, CollectionType, Video, VideoInfo}; +use crate::config::TEMPLATE; +use crate::utils::id_time_key; +use crate::utils::model::create_video_pages; +use crate::utils::status::Status; + +pub async fn watch_later_from<'a>( + path: &Path, + bili_client: &'a BiliClient, + connection: &DatabaseConnection, +) -> Result<(Box, Pin + 'a>>)> { + let watch_later = WatchLater::new(bili_client); + watch_later::Entity::insert(watch_later::ActiveModel { + id: Set(1), + path: Set(path.to_string_lossy().to_string()), + ..Default::default() + }) + .on_conflict( + OnConflict::column(watch_later::Column::Id) + .update_column(watch_later::Column::Path) + .to_owned(), + ) + .exec(connection) + .await?; + Ok(( + Box::new( + watch_later::Entity::find() + .filter(watch_later::Column::Id.eq(1)) + .one(connection) + .await? + .unwrap(), + ), + Box::pin(watch_later.into_video_stream()), + )) +} +use async_trait::async_trait; + +#[async_trait] +impl VideoListModel for watch_later::Model { + async fn video_count(&self, connection: &DatabaseConnection) -> Result { + Ok(video::Entity::find() + .filter(video::Column::WatchLaterId.eq(self.id)) + .count(connection) + .await?) + } + + async fn unfilled_videos(&self, connection: &DatabaseConnection) -> Result> { + Ok(video::Entity::find() + .filter( + video::Column::WatchLaterId + .eq(self.id) + .and(video::Column::Valid.eq(true)) + .and(video::Column::DownloadStatus.eq(0)) + .and(video::Column::Category.eq(2)) + .and(video::Column::SinglePage.is_null()), + ) + .all(connection) + .await?) + } + + async fn unhandled_video_pages( + &self, + connection: &DatabaseConnection, + ) -> Result)>> { + Ok(video::Entity::find() + .filter( + video::Column::WatchLaterId + .eq(self.id) + .and(video::Column::Valid.eq(true)) + .and(video::Column::DownloadStatus.lt(Status::handled())) + .and(video::Column::Category.eq(2)) + .and(video::Column::SinglePage.is_not_null()), + ) + .find_with_related(page::Entity) + .all(connection) + .await?) + } + + async fn exist_labels( + &self, + videos_info: &[VideoInfo], + connection: &DatabaseConnection, + ) -> Result> { + let bvids = videos_info.iter().map(|v| v.bvid().to_string()).collect::>(); + Ok(video::Entity::find() + .filter( + video::Column::WatchLaterId + .eq(self.id) + .and(video::Column::Bvid.is_in(bvids)), + ) + .select_only() + .columns([video::Column::Bvid, video::Column::Pubtime]) + .into_tuple() + .all(connection) + .await? + .into_iter() + .map(|(bvid, time)| id_time_key(&bvid, &time)) + .collect::>()) + } + + fn video_model_by_info(&self, video_info: &VideoInfo, base_model: Option) -> video::ActiveModel { + let mut video_model = video_info.to_model(base_model); + video_model.watch_later_id = Set(Some(self.id)); + if let Some(fmt_args) = &video_info.to_fmt_args() { + video_model.path = Set(Path::new(&self.path) + .join(filenamify( + TEMPLATE + .render("video", fmt_args) + .unwrap_or_else(|_| video_info.bvid().to_string()), + )) + .to_string_lossy() + .to_string()); + } + video_model + } + + async fn fetch_videos_detail( + &self, + bili_clent: &BiliClient, + videos_model: Vec, + connection: &DatabaseConnection, + ) -> Result<()> { + for video_model in videos_model { + let video = Video::new(bili_clent, video_model.bvid.clone()); + let info: Result<_> = async { Ok((video.get_tags().await?, video.get_view_info().await?)) }.await; + match info { + Ok((tags, view_info)) => { + let VideoInfo::View { pages, .. } = &view_info else { + unreachable!("view_info must be VideoInfo::View") + }; + let txn = connection.begin().await?; + // 将分页信息写入数据库 + create_video_pages(pages, &video_model, &txn).await?; + // 将页标记和 tag 写入数据库 + let mut video_active_model = self.video_model_by_info(&view_info, Some(video_model)); + video_active_model.single_page = Set(Some(pages.len() == 1)); + video_active_model.tags = Set(Some(serde_json::to_value(tags).unwrap())); + video_active_model.save(&txn).await?; + txn.commit().await?; + } + Err(e) => { + error!( + "获取视频 {} - {} 的详细信息失败,错误为:{}", + &video_model.bvid, &video_model.name, e + ); + if let Some(BiliError::RequestFailed(-404, _)) = e.downcast_ref::() { + let mut video_active_model: video::ActiveModel = video_model.into(); + video_active_model.valid = Set(false); + video_active_model.save(connection).await?; + } + continue; + } + }; + } + Ok(()) + } + + fn log_fetch_video_start(&self) { + info!("开始获取稍后再看的视频与分页信息..."); + } + + fn log_fetch_video_end(&self) { + info!("获取稍后再看的视频与分页信息完成"); + } + + fn log_download_video_start(&self) { + info!("开始下载稍后再看中所有未处理过的视频..."); + } + + fn log_download_video_end(&self) { + info!("下载稍后再看中未处理过的视频完成"); + } + + fn log_refresh_video_start(&self) { + info!("开始扫描稍后再看的新视频..."); + } + + fn log_refresh_video_end(&self, got_count: usize, new_count: u64) { + info!( + "扫描稍后再看的新视频完成,获取了 {} 条新视频,其中有 {} 条新视频", + got_count, new_count, + ); + } +} diff --git a/crates/bili_sync/src/bilibili/mod.rs b/crates/bili_sync/src/bilibili/mod.rs index 45567a7..516c258 100644 --- a/crates/bili_sync/src/bilibili/mod.rs +++ b/crates/bili_sync/src/bilibili/mod.rs @@ -19,6 +19,7 @@ mod danmaku; mod error; mod favorite_list; mod video; +mod watch_later; pub(crate) trait Validate { type Output; diff --git a/crates/bili_sync/src/bilibili/watch_later.rs b/crates/bili_sync/src/bilibili/watch_later.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/bili_sync_entity/src/entities/mod.rs b/crates/bili_sync_entity/src/entities/mod.rs index 0050fbd..908557e 100644 --- a/crates/bili_sync_entity/src/entities/mod.rs +++ b/crates/bili_sync_entity/src/entities/mod.rs @@ -6,3 +6,4 @@ pub mod collection; pub mod favorite; pub mod page; pub mod video; +pub mod watch_later; diff --git a/crates/bili_sync_entity/src/entities/video.rs b/crates/bili_sync_entity/src/entities/video.rs index 29a0c1f..db74c60 100644 --- a/crates/bili_sync_entity/src/entities/video.rs +++ b/crates/bili_sync_entity/src/entities/video.rs @@ -9,6 +9,7 @@ pub struct Model { pub id: i32, pub collection_id: Option, pub favorite_id: Option, + pub watch_later_id: Option, pub upper_id: i64, pub upper_name: String, pub upper_face: String, diff --git a/crates/bili_sync_entity/src/entities/watch_later.rs b/crates/bili_sync_entity/src/entities/watch_later.rs new file mode 100644 index 0000000..66066f3 --- /dev/null +++ b/crates/bili_sync_entity/src/entities/watch_later.rs @@ -0,0 +1,17 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "watch_later")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub path: String, + pub created_at: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/bili_sync_migration/src/lib.rs b/crates/bili_sync_migration/src/lib.rs index d349cc1..6439a80 100644 --- a/crates/bili_sync_migration/src/lib.rs +++ b/crates/bili_sync_migration/src/lib.rs @@ -2,6 +2,7 @@ pub use sea_orm_migration::prelude::*; mod m20240322_000001_create_table; mod m20240505_130850_add_collection; +mod m20240709_130914_watch_later; pub struct Migrator; @@ -11,6 +12,7 @@ impl MigratorTrait for Migrator { vec![ Box::new(m20240322_000001_create_table::Migration), Box::new(m20240505_130850_add_collection::Migration), + Box::new(m20240709_130914_watch_later::Migration), ] } } diff --git a/crates/bili_sync_migration/src/m20240709_130914_watch_later.rs b/crates/bili_sync_migration/src/m20240709_130914_watch_later.rs new file mode 100644 index 0000000..a9d44a0 --- /dev/null +++ b/crates/bili_sync_migration/src/m20240709_130914_watch_later.rs @@ -0,0 +1,88 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + manager + .create_table( + Table::create() + .table(WatchLater::Table) + .if_not_exists() + .col( + ColumnDef::new(WatchLater::Id) + .unsigned() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(WatchLater::Path).string().not_null()) + .col( + ColumnDef::new(WatchLater::CreatedAt) + .timestamp() + .default(Expr::current_timestamp()) + .not_null(), + ) + .to_owned(), + ) + .await?; + manager + .drop_index( + Index::drop() + .table(Video::Table) + .name("idx_video_cid_fid_bvid") + .to_owned(), + ) + .await?; + manager + .alter_table( + Table::alter() + .table(Video::Table) + .add_column(ColumnDef::new(Video::WatchLaterId).unsigned().null()) + .to_owned(), + ) + .await?; + db.execute_unprepared("CREATE UNIQUE INDEX `idx_video_unique` ON `video` (ifnull(`collection_id`, -1), ifnull(`favorite_id`, -1), ifnull(`watch_later_id`, -1), `bvid`)") + .await?; + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + manager + .drop_index(Index::drop().table(Video::Table).name("idx_video_unique").to_owned()) + .await?; + db.execute_unprepared("DELETE FROM video WHERE watch_later_id IS NOT NULL") + .await?; + manager + .alter_table( + Table::alter() + .table(Video::Table) + .drop_column(Video::WatchLaterId) + .to_owned(), + ) + .await?; + db.execute_unprepared("CREATE UNIQUE INDEX `idx_video_cid_fid_bvid` ON `video` (ifnull(`collection_id`, -1), ifnull(`favorite_id`, -1), `bvid`)") + .await?; + manager + .drop_table(Table::drop().table(WatchLater::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum WatchLater { + Table, + Id, + Path, + CreatedAt, +} + +#[derive(DeriveIden)] +enum Video { + Table, + WatchLaterId, +} From d7896ae1259a8a42383538caacd534c783baa45f Mon Sep 17 00:00:00 2001 From: amtoaer Date: Wed, 10 Jul 2024 19:58:59 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E5=86=99=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/bili_sync/src/adapter/watch_later.rs | 2 +- crates/bili_sync/src/bilibili/collection.rs | 2 +- .../bili_sync/src/bilibili/favorite_list.rs | 2 +- crates/bili_sync/src/bilibili/mod.rs | 33 +++++++++++++ crates/bili_sync/src/bilibili/watch_later.rs | 48 +++++++++++++++++++ crates/bili_sync/src/config.rs | 19 +++++++- crates/bili_sync/src/main.rs | 8 ++++ 7 files changed, 109 insertions(+), 5 deletions(-) diff --git a/crates/bili_sync/src/adapter/watch_later.rs b/crates/bili_sync/src/adapter/watch_later.rs index c103892..6a071bc 100644 --- a/crates/bili_sync/src/adapter/watch_later.rs +++ b/crates/bili_sync/src/adapter/watch_later.rs @@ -12,7 +12,7 @@ use sea_orm::ActiveValue::Set; use sea_orm::{DatabaseConnection, QuerySelect, TransactionTrait}; use super::VideoListModel; -use crate::bilibili::{BiliClient, BiliError, Collection, CollectionItem, CollectionType, Video, VideoInfo}; +use crate::bilibili::{BiliClient, BiliError, Video, VideoInfo, WatchLater}; use crate::config::TEMPLATE; use crate::utils::id_time_key; use crate::utils::model::create_video_pages; diff --git a/crates/bili_sync/src/bilibili/collection.rs b/crates/bili_sync/src/bilibili/collection.rs index 3360e90..97767d6 100644 --- a/crates/bili_sync/src/bilibili/collection.rs +++ b/crates/bili_sync/src/bilibili/collection.rs @@ -181,7 +181,7 @@ impl<'a> Collection<'a> { break; }, }; - for video_info in videos_info.into_iter(){ + for video_info in videos_info{ yield video_info; } let fields = match self.collection.collection_type{ diff --git a/crates/bili_sync/src/bilibili/favorite_list.rs b/crates/bili_sync/src/bilibili/favorite_list.rs index 211eb65..9cc6e9e 100644 --- a/crates/bili_sync/src/bilibili/favorite_list.rs +++ b/crates/bili_sync/src/bilibili/favorite_list.rs @@ -82,7 +82,7 @@ impl<'a> FavoriteList<'a> { break; }, }; - for video_info in videos_info.into_iter(){ + for video_info in videos_info{ yield video_info; } if videos["data"]["has_more"].is_boolean() && videos["data"]["has_more"].as_bool().unwrap(){ diff --git a/crates/bili_sync/src/bilibili/mod.rs b/crates/bili_sync/src/bilibili/mod.rs index 516c258..56d261f 100644 --- a/crates/bili_sync/src/bilibili/mod.rs +++ b/crates/bili_sync/src/bilibili/mod.rs @@ -10,6 +10,7 @@ pub use error::BiliError; pub use favorite_list::FavoriteList; use favorite_list::Upper; pub use video::{Dimension, PageInfo, Video}; +pub use watch_later::WatchLater; mod analyzer; mod client; @@ -93,3 +94,35 @@ pub enum VideoInfo { pubtime: DateTime, }, } + +#[cfg(test)] +mod tests { + use futures::StreamExt; + use tokio::pin; + + use super::*; + + #[tokio::test] + async fn assert_video_info() { + let bili_client = BiliClient::new(); + let video = Video::new(&bili_client, "BV1Z54y1C7ZB".to_string()); + assert!(matches!(video.get_view_info().await, Ok(VideoInfo::View { .. }))); + let collection_item = CollectionItem { + mid: "521722088".to_string(), + sid: "387214".to_string(), + collection_type: CollectionType::Series, + }; + let collection = Collection::new(&bili_client, &collection_item); + let stream = collection.into_simple_video_stream(); + pin!(stream); + assert!(matches!(stream.next().await, Some(VideoInfo::Simple { .. }))); + let favorite = FavoriteList::new(&bili_client, "3084505258".to_string()); + let stream = favorite.into_video_stream(); + pin!(stream); + assert!(matches!(stream.next().await, Some(VideoInfo::Detail { .. }))); + let watch_later = WatchLater::new(&bili_client); + let stream = watch_later.into_video_stream(); + pin!(stream); + assert!(matches!(stream.next().await, Some(VideoInfo::Simple { .. }))); + } +} diff --git a/crates/bili_sync/src/bilibili/watch_later.rs b/crates/bili_sync/src/bilibili/watch_later.rs index e69de29..942629e 100644 --- a/crates/bili_sync/src/bilibili/watch_later.rs +++ b/crates/bili_sync/src/bilibili/watch_later.rs @@ -0,0 +1,48 @@ +use anyhow::Result; +use async_stream::stream; +use futures::Stream; +use serde_json::Value; + +use crate::bilibili::{BiliClient, Validate, VideoInfo}; +pub struct WatchLater<'a> { + client: &'a BiliClient, +} + +impl<'a> WatchLater<'a> { + pub fn new(client: &'a BiliClient) -> Self { + Self { client } + } + + async fn get_videos(&self) -> Result { + self.client + .request(reqwest::Method::GET, "https://api.bilibili.com/x/v2/history/toview") + .send() + .await? + .error_for_status()? + .json::() + .await? + .validate() + } + + pub fn into_video_stream(self) -> impl Stream + 'a { + stream! { + let Ok(mut videos) = self.get_videos().await else { + error!("Failed to get watch later list"); + return; + }; + if !videos["data"]["list"].is_array() { + error!("Watch later list is not an array"); + } + let videos_info = match serde_json::from_value::>(videos["data"]["list"].take()) { + Ok(v) => v, + Err(e) => { + error!("Failed to parse watch later list: {}", e); + return; + } + }; + for video in videos_info { + yield video; + } + } + } +} diff --git a/crates/bili_sync/src/config.rs b/crates/bili_sync/src/config.rs index 63801bf..6b7bc45 100644 --- a/crates/bili_sync/src/config.rs +++ b/crates/bili_sync/src/config.rs @@ -68,6 +68,8 @@ pub struct Config { deserialize_with = "deserialize_collection_list" )] pub collection_list: HashMap, + #[serde(default)] + pub watch_later: WatchLaterConfig, pub video_name: Cow<'static, str>, pub page_name: Cow<'static, str>, pub interval: u64, @@ -76,6 +78,12 @@ pub struct Config { pub nfo_time_type: NFOTimeType, } +#[derive(Serialize, Deserialize, Default)] +pub struct WatchLaterConfig { + pub enabled: bool, + pub path: PathBuf, +} + #[derive(Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] pub enum NFOTimeType { @@ -92,6 +100,7 @@ impl Default for Config { danmaku_option: DanmakuOption::default(), favorite_list: HashMap::new(), collection_list: HashMap::new(), + watch_later: Default::default(), video_name: Cow::Borrowed("{{title}}"), page_name: Cow::Borrowed("{{bvid}}"), interval: 1200, @@ -105,9 +114,15 @@ impl Config { /// 简单的预检查 pub fn check(&self) { let mut ok = true; - if self.favorite_list.is_empty() && self.collection_list.is_empty() { + if self.favorite_list.is_empty() && self.collection_list.is_empty() && !self.watch_later.enabled { ok = false; - error!("未设置需监听的收藏夹或视频合集,程序空转没有意义"); + error!("没有配置任何需要扫描的内容,程序空转没有意义"); + } + if self.watch_later.enabled && !self.watch_later.path.is_absolute() { + error!( + "稍后再看保存的路径应为绝对路径,检测到:{}", + self.watch_later.path.display() + ); } for path in self.favorite_list.values() { if !path.is_absolute() { diff --git a/crates/bili_sync/src/main.rs b/crates/bili_sync/src/main.rs index ea5d430..d170d55 100644 --- a/crates/bili_sync/src/main.rs +++ b/crates/bili_sync/src/main.rs @@ -29,6 +29,7 @@ async fn main() { let connection = database_connection().await.expect("获取数据库连接失败"); let mut anchor = chrono::Local::now().date_naive(); let bili_client = BiliClient::new(); + let watch_later_config = &CONFIG.watch_later; loop { if let Err(e) = bili_client.is_login().await { error!("检查登录状态时遇到错误:{e},等待下一轮执行"); @@ -57,6 +58,13 @@ async fn main() { } } info!("所有合集处理完毕"); + if watch_later_config.enabled { + if let Err(e) = + process_video_list(Args::WatchLater, &bili_client, &watch_later_config.path, &connection).await + { + error!("处理稍后再看时遇到非预期的错误:{e}"); + } + } info!("本轮任务执行完毕,等待下一轮执行"); tokio::time::sleep(std::time::Duration::from_secs(CONFIG.interval)).await; } From 97e2f57c60d918e5e6fef7365aba6eb9acc14e98 Mon Sep 17 00:00:00 2001 From: amtoaer Date: Thu, 11 Jul 2024 01:36:18 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E7=A8=8D?= =?UTF-8?q?=E5=90=8E=E5=86=8D=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/bili_sync/src/adapter/watch_later.rs | 15 ++++----- crates/bili_sync/src/bilibili/mod.rs | 31 ++++++++++++++---- crates/bili_sync/src/main.rs | 1 + crates/bili_sync/src/utils/convert.rs | 36 +++++++++++++++++++++ 4 files changed, 68 insertions(+), 15 deletions(-) diff --git a/crates/bili_sync/src/adapter/watch_later.rs b/crates/bili_sync/src/adapter/watch_later.rs index 6a071bc..3f8e214 100644 --- a/crates/bili_sync/src/adapter/watch_later.rs +++ b/crates/bili_sync/src/adapter/watch_later.rs @@ -103,7 +103,7 @@ impl VideoListModel for watch_later::Model { .and(video::Column::Bvid.is_in(bvids)), ) .select_only() - .columns([video::Column::Bvid, video::Column::Pubtime]) + .columns([video::Column::Bvid, video::Column::Favtime]) .into_tuple() .all(connection) .await? @@ -136,18 +136,15 @@ impl VideoListModel for watch_later::Model { ) -> Result<()> { for video_model in videos_model { let video = Video::new(bili_clent, video_model.bvid.clone()); - let info: Result<_> = async { Ok((video.get_tags().await?, video.get_view_info().await?)) }.await; + let info: Result<_> = async { Ok((video.get_tags().await?, video.get_pages().await?)) }.await; match info { - Ok((tags, view_info)) => { - let VideoInfo::View { pages, .. } = &view_info else { - unreachable!("view_info must be VideoInfo::View") - }; + Ok((tags, pages_info)) => { let txn = connection.begin().await?; // 将分页信息写入数据库 - create_video_pages(pages, &video_model, &txn).await?; + create_video_pages(&pages_info, &video_model, &txn).await?; // 将页标记和 tag 写入数据库 - let mut video_active_model = self.video_model_by_info(&view_info, Some(video_model)); - video_active_model.single_page = Set(Some(pages.len() == 1)); + let mut video_active_model: video::ActiveModel = video_model.into(); + video_active_model.single_page = Set(Some(pages_info.len() == 1)); video_active_model.tags = Set(Some(serde_json::to_value(tags).unwrap())); video_active_model.save(&txn).await?; txn.commit().await?; diff --git a/crates/bili_sync/src/bilibili/mod.rs b/crates/bili_sync/src/bilibili/mod.rs index 56d261f..cdb1f4f 100644 --- a/crates/bili_sync/src/bilibili/mod.rs +++ b/crates/bili_sync/src/bilibili/mod.rs @@ -83,6 +83,24 @@ pub enum VideoInfo { pubtime: DateTime, attr: i32, }, + /// 从稍后再看中获取的视频信息 + WatchLater { + title: String, + bvid: String, + #[serde(rename = "desc")] + intro: String, + #[serde(rename = "pic")] + cover: String, + #[serde(rename = "owner")] + upper: Upper, + #[serde(with = "ts_seconds")] + ctime: DateTime, + #[serde(rename = "add_at", with = "ts_seconds")] + fav_time: DateTime, + #[serde(rename = "pubdate", with = "ts_seconds")] + pubtime: DateTime, + state: i32, + }, /// 从视频列表中获取的视频信息 Simple { bvid: String, @@ -97,11 +115,11 @@ pub enum VideoInfo { #[cfg(test)] mod tests { - use futures::StreamExt; - use tokio::pin; + use futures::{pin_mut, StreamExt}; use super::*; + #[ignore = "only for manual test"] #[tokio::test] async fn assert_video_info() { let bili_client = BiliClient::new(); @@ -114,15 +132,16 @@ mod tests { }; let collection = Collection::new(&bili_client, &collection_item); let stream = collection.into_simple_video_stream(); - pin!(stream); + pin_mut!(stream); assert!(matches!(stream.next().await, Some(VideoInfo::Simple { .. }))); let favorite = FavoriteList::new(&bili_client, "3084505258".to_string()); let stream = favorite.into_video_stream(); - pin!(stream); + pin_mut!(stream); assert!(matches!(stream.next().await, Some(VideoInfo::Detail { .. }))); let watch_later = WatchLater::new(&bili_client); let stream = watch_later.into_video_stream(); - pin!(stream); - assert!(matches!(stream.next().await, Some(VideoInfo::Simple { .. }))); + pin_mut!(stream); + println!("{:?}", stream.next().await); + assert!(matches!(stream.next().await, Some(VideoInfo::WatchLater { .. }))); } } diff --git a/crates/bili_sync/src/main.rs b/crates/bili_sync/src/main.rs index d170d55..bacaa19 100644 --- a/crates/bili_sync/src/main.rs +++ b/crates/bili_sync/src/main.rs @@ -65,6 +65,7 @@ async fn main() { error!("处理稍后再看时遇到非预期的错误:{e}"); } } + info!("稍后再看处理完毕"); info!("本轮任务执行完毕,等待下一轮执行"); tokio::time::sleep(std::time::Duration::from_secs(CONFIG.interval)).await; } diff --git a/crates/bili_sync/src/utils/convert.rs b/crates/bili_sync/src/utils/convert.rs index 399936f..99a0191 100644 --- a/crates/bili_sync/src/utils/convert.rs +++ b/crates/bili_sync/src/utils/convert.rs @@ -90,6 +90,34 @@ impl VideoInfo { upper_face: Set(upper.face.clone()), ..base_model }, + VideoInfo::WatchLater { + title, + bvid, + intro, + cover, + upper, + ctime, + fav_time, + pubtime, + state, + } => bili_sync_entity::video::ActiveModel { + bvid: Set(bvid.clone()), + name: Set(title.clone()), + category: Set(2), // 稍后再看里的内容类型肯定是视频 + intro: Set(intro.clone()), + cover: Set(cover.clone()), + ctime: Set(ctime.naive_utc()), + pubtime: Set(pubtime.naive_utc()), + favtime: Set(fav_time.naive_utc()), + download_status: Set(0), + valid: Set(*state == 0), + tags: Set(None), + single_page: Set(None), + upper_id: Set(upper.mid), + upper_name: Set(upper.name.clone()), + upper_face: Set(upper.face.clone()), + ..base_model + }, } } @@ -108,6 +136,12 @@ impl VideoInfo { "upper_name": &upper.name, "upper_mid": &upper.mid, })), + VideoInfo::WatchLater { title, bvid, upper, .. } => Some(json!({ + "bvid": &bvid, + "title": &title, + "upper_name": &upper.name, + "upper_mid": &upper.mid, + })), } } @@ -116,6 +150,7 @@ impl VideoInfo { // 对于合集没有 fav_time,只能用 pubtime 代替 VideoInfo::Simple { bvid, pubtime, .. } => id_time_key(bvid, pubtime), VideoInfo::Detail { bvid, fav_time, .. } => id_time_key(bvid, fav_time), + VideoInfo::WatchLater { bvid, fav_time, .. } => id_time_key(bvid, fav_time), // 详情接口返回的数据仅用于填充详情,不会被作为 video_key _ => unreachable!(), } @@ -125,6 +160,7 @@ impl VideoInfo { match self { VideoInfo::Simple { bvid, .. } => bvid, VideoInfo::Detail { bvid, .. } => bvid, + VideoInfo::WatchLater { bvid, .. } => bvid, // 同上 _ => unreachable!(), } From 762043a1a702e78d3f5fe213b25c0b35995ad5e3 Mon Sep 17 00:00:00 2001 From: amtoaer Date: Thu, 11 Jul 2024 11:16:10 +0800 Subject: [PATCH 4/4] =?UTF-8?q?chore:=20=E5=B9=B2=E6=8E=89=20print?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/bili_sync/src/bilibili/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bili_sync/src/bilibili/mod.rs b/crates/bili_sync/src/bilibili/mod.rs index cdb1f4f..03a9a88 100644 --- a/crates/bili_sync/src/bilibili/mod.rs +++ b/crates/bili_sync/src/bilibili/mod.rs @@ -121,7 +121,7 @@ mod tests { #[ignore = "only for manual test"] #[tokio::test] - async fn assert_video_info() { + async fn assert_video_info_type() { let bili_client = BiliClient::new(); let video = Video::new(&bili_client, "BV1Z54y1C7ZB".to_string()); assert!(matches!(video.get_view_info().await, Ok(VideoInfo::View { .. }))); @@ -141,7 +141,6 @@ mod tests { let watch_later = WatchLater::new(&bili_client); let stream = watch_later.into_video_stream(); pin_mut!(stream); - println!("{:?}", stream.next().await); assert!(matches!(stream.next().await, Some(VideoInfo::WatchLater { .. }))); } }