From a451b6dca378c82e1cc11e777c26a5891acb6464 Mon Sep 17 00:00:00 2001 From: amtoaer Date: Sat, 30 Mar 2024 01:42:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=20NFO=20=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/utils.rs | 360 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 360 insertions(+) diff --git a/src/core/utils.rs b/src/core/utils.rs index 9056cad..6428daa 100644 --- a/src/core/utils.rs +++ b/src/core/utils.rs @@ -3,13 +3,31 @@ use std::path::Path; use entity::*; use migration::OnConflict; +use quick_xml::events::BytesText; +use quick_xml::writer::Writer; +use quick_xml::Error; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; use sea_orm::QuerySelect; +use tokio::io::AsyncWriteExt; use crate::bilibili::{FavoriteListInfo, PageInfo, VideoInfo}; use crate::Result; +pub enum NFOMode { + MOVIE, + TVSHOW, + EPOSODE, + UPPER, +} + +pub enum ModelWrapper<'a> { + Video(&'a video::Model), + Page(&'a page::Model), +} + +pub struct NFOSerializer<'a>(pub ModelWrapper<'a>, pub NFOMode); + /// 根据获得的收藏夹信息,插入或更新数据库中的收藏夹,并返回收藏夹对象 pub async fn handle_favorite_info( info: &FavoriteListInfo, @@ -192,3 +210,345 @@ pub async fn unhandled_videos_pages( .all(connection) .await?) } + +/// serde xml 似乎不太好用,先这么裸着写 +/// (真是又臭又长啊 +impl<'a> NFOSerializer<'a> { + pub async fn generate_nfo(self) -> Result { + let mut buffer = r#" +"# + .as_bytes() + .to_vec(); + let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer); + let mut writer = Writer::new_with_indent(&mut tokio_buffer, b' ', 4); + match self { + NFOSerializer(ModelWrapper::Video(v), NFOMode::MOVIE) => { + writer + .create_element("movie") + .write_inner_content_async::<_, _, Error>(|writer| async move { + writer + .create_element("plot") + .write_text_content_async(BytesText::new(&format!( + r#"![CDATA[{}]]"#, + &v.intro + ))) + .await + .unwrap(); + writer + .create_element("outline") + .write_empty_async() + .await + .unwrap(); + writer + .create_element("title") + .write_text_content_async(BytesText::new(&v.name)) + .await + .unwrap(); + writer + .create_element("actor") + .write_inner_content_async::<_, _, Error>(|writer| async move { + writer + .create_element("name") + .write_text_content_async(BytesText::new( + &v.upper_id.to_string(), + )) + .await + .unwrap(); + writer + .create_element("role") + .write_text_content_async(BytesText::new(&v.upper_name)) + .await + .unwrap(); + Ok(writer) + }) + .await + .unwrap(); + writer + .create_element("year") + .write_text_content_async(BytesText::new( + &v.pubtime.format("%Y").to_string(), + )) + .await + .unwrap(); + if let Some(tags) = &v.tags { + let tags: Vec = serde_json::from_value(tags.clone()).unwrap(); + for tag in tags { + writer + .create_element("genre") + .write_text_content_async(BytesText::new(&tag)) + .await + .unwrap(); + } + } + writer + .create_element("uniqueid") + .with_attribute(("type", "bilibili")) + .write_text_content_async(BytesText::new(&v.bvid)) + .await + .unwrap(); + writer + .create_element("aired") + .write_text_content_async(BytesText::new( + &v.pubtime.format("%Y-%m-%d").to_string(), + )) + .await + .unwrap(); + Ok(writer) + }) + .await + .unwrap(); + } + NFOSerializer(ModelWrapper::Video(v), NFOMode::TVSHOW) => { + writer + .create_element("tvshow") + .write_inner_content_async::<_, _, Error>(|writer| async move { + writer + .create_element("plot") + .write_text_content_async(BytesText::new(&format!( + r#"![CDATA[{}]]"#, + &v.intro + ))) + .await + .unwrap(); + writer + .create_element("outline") + .write_empty_async() + .await + .unwrap(); + writer + .create_element("title") + .write_text_content_async(BytesText::new(&v.name)) + .await + .unwrap(); + writer + .create_element("actor") + .write_inner_content_async::<_, _, Error>(|writer| async move { + writer + .create_element("name") + .write_text_content_async(BytesText::new( + &v.upper_id.to_string(), + )) + .await + .unwrap(); + writer + .create_element("role") + .write_text_content_async(BytesText::new(&v.upper_name)) + .await + .unwrap(); + Ok(writer) + }) + .await + .unwrap(); + writer + .create_element("year") + .write_text_content_async(BytesText::new( + &v.pubtime.format("%Y").to_string(), + )) + .await + .unwrap(); + if let Some(tags) = &v.tags { + let tags: Vec = serde_json::from_value(tags.clone()).unwrap(); + for tag in tags { + writer + .create_element("genre") + .write_text_content_async(BytesText::new(&tag)) + .await + .unwrap(); + } + } + writer + .create_element("uniqueid") + .with_attribute(("type", "bilibili")) + .write_text_content_async(BytesText::new(&v.bvid)) + .await + .unwrap(); + writer + .create_element("aired") + .write_text_content_async(BytesText::new( + &v.pubtime.format("%Y-%m-%d").to_string(), + )) + .await + .unwrap(); + Ok(writer) + }) + .await + .unwrap(); + } + NFOSerializer(ModelWrapper::Video(v), NFOMode::UPPER) => { + writer + .create_element("person") + .write_inner_content_async::<_, _, Error>(|writer| async move { + writer + .create_element("plot") + .write_empty_async() + .await + .unwrap(); + writer + .create_element("outline") + .write_empty_async() + .await + .unwrap(); + writer + .create_element("lockdata") + .write_text_content_async(BytesText::new("false")) + .await + .unwrap(); + writer + .create_element("dateadded") + .write_text_content_async(BytesText::new( + &v.pubtime.format("%Y-%m-%d").to_string(), + )) + .await + .unwrap(); + writer + .create_element("title") + .write_text_content_async(BytesText::new(&v.upper_id.to_string())) + .await + .unwrap(); + writer + .create_element("sorttitle") + .write_text_content_async(BytesText::new(&v.upper_id.to_string())) + .await + .unwrap(); + Ok(writer) + }) + .await + .unwrap(); + } + NFOSerializer(ModelWrapper::Page(p), NFOMode::EPOSODE) => { + writer + .create_element("episodedetails") + .write_inner_content_async::<_, _, Error>(|writer| async move { + writer + .create_element("plot") + .write_empty_async() + .await + .unwrap(); + writer + .create_element("outline") + .write_empty_async() + .await + .unwrap(); + writer + .create_element("title") + .write_text_content_async(BytesText::new(&p.name)) + .await + .unwrap(); + writer + .create_element("season") + .write_text_content_async(BytesText::new("1")) + .await + .unwrap(); + writer + .create_element("episode") + .write_text_content_async(BytesText::new(&p.pid.to_string())) + .await + .unwrap(); + Ok(writer) + }) + .await + .unwrap(); + } + _ => unreachable!(), + } + tokio_buffer.flush().await?; + Ok(std::str::from_utf8(&buffer).unwrap().to_owned()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[tokio::test] + async fn test_generate_nfo() { + let video = video::Model { + intro: "intro".to_string(), + name: "name".to_string(), + upper_id: 1, + upper_name: "upper_name".to_string(), + pubtime: chrono::NaiveDateTime::new( + chrono::NaiveDate::from_ymd_opt(2022, 2, 2).unwrap(), + chrono::NaiveTime::from_hms_opt(2, 2, 2).unwrap(), + ), + bvid: "bvid".to_string(), + tags: Some(serde_json::json!(["tag1", "tag2"])), + ..Default::default() + }; + assert_eq!( + NFOSerializer(ModelWrapper::Video(&video), NFOMode::MOVIE) + .generate_nfo() + .await + .unwrap(), + r#" + + ![CDATA[intro]] + + name + + 1 + upper_name + + 2022 + tag1 + tag2 + bvid + 2022-02-02 +"#, + ); + assert_eq!( + NFOSerializer(ModelWrapper::Video(&video), NFOMode::TVSHOW) + .generate_nfo() + .await + .unwrap(), + r#" + + ![CDATA[intro]] + + name + + 1 + upper_name + + 2022 + tag1 + tag2 + bvid + 2022-02-02 +"#, + ); + assert_eq!( + NFOSerializer(ModelWrapper::Video(&video), NFOMode::UPPER) + .generate_nfo() + .await + .unwrap(), + r#" + + + + false + 2022-02-02 + 1 + 1 +"#, + ); + let page = page::Model { + name: "name".to_string(), + pid: 3, + ..Default::default() + }; + assert_eq!( + NFOSerializer(ModelWrapper::Page(&page), NFOMode::EPOSODE) + .generate_nfo() + .await + .unwrap(), + r#" + + + + name + 1 + 3 +"#, + ); + } +}