feat: basic support rss

This commit is contained in:
2025-06-24 06:37:19 +08:00
parent cde3361458
commit 9fd3ae6563
26 changed files with 637 additions and 109 deletions

View File

@@ -17,7 +17,7 @@ use crate::{
MikanBangumiHash, MikanBangumiMeta, build_mikan_bangumi_subscription_rss_url,
scrape_mikan_poster_meta_from_image_url,
},
origin::{OriginCompTrait, SeasonComp},
origin::{BangumiComps, OriginCompTrait},
},
};
@@ -129,11 +129,12 @@ impl ActiveModel {
) -> RecorderResult<Self> {
let mikan_client = ctx.mikan();
let mikan_base_url = mikan_client.base_url();
let season_comp = SeasonComp::parse_comp(&meta.bangumi_title)
let season_comp = BangumiComps::parse_comp(&meta.bangumi_title)
.ok()
.map(|(_, s)| s);
.map(|(_, s)| s)
.and_then(|s| s.season);
let season_index = season_comp.as_ref().map(|s| s.num).unwrap_or(1);
let season_raw = season_comp.map(|s| s.source.into_owned());
let season_raw = season_comp.map(|s| s.source.to_string());
let rss_url = build_mikan_bangumi_subscription_rss_url(
mikan_base_url.clone(),
@@ -162,6 +163,7 @@ impl ActiveModel {
origin_poster_link: ActiveValue::Set(meta.origin_poster_src.map(|src| src.to_string())),
homepage: ActiveValue::Set(Some(meta.homepage.to_string())),
rss_link: ActiveValue::Set(Some(rss_url.to_string())),
bangumi_type: ActiveValue::Set(BangumiType::Mikan),
..Default::default()
})
}

View File

@@ -52,8 +52,8 @@ pub struct Model {
pub status: DownloadStatus,
pub mime: DownloadMime,
pub url: String,
pub all_size: Option<u64>,
pub curr_size: Option<u64>,
pub all_size: Option<i64>,
pub curr_size: Option<i64>,
pub homepage: Option<String>,
pub save_path: Option<String>,
}

View File

@@ -36,7 +36,7 @@ pub struct Model {
pub enclosure_torrent_link: Option<String>,
pub enclosure_magnet_link: Option<String>,
pub enclosure_pub_date: Option<DateTimeUtc>,
pub enclosure_content_length: Option<u64>,
pub enclosure_content_length: Option<i64>,
pub episode_type: EpisodeType,
pub origin_name: String,
pub display_name: String,
@@ -166,6 +166,7 @@ impl ActiveModel {
enclosure_magnet_link: ActiveValue::Set(enclosure_meta.magnet_link),
enclosure_pub_date: ActiveValue::Set(enclosure_meta.pub_date),
enclosure_content_length: ActiveValue::Set(enclosure_meta.content_length),
episode_type: ActiveValue::Set(EpisodeType::Mikan),
..Default::default()
};

View File

@@ -39,7 +39,9 @@ pub enum FeedSource {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "feeds")]
pub struct Model {
#[sea_orm(default_expr = "Expr::current_timestamp()")]
pub created_at: DateTimeUtc,
#[sea_orm(default_expr = "Expr::current_timestamp()")]
pub updated_at: DateTimeUtc,
#[sea_orm(primary_key)]
pub id: i32,

View File

@@ -1,5 +1,5 @@
use rss::Channel;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
use sea_orm::{ColumnTrait, EntityTrait, JoinType, QueryFilter, QuerySelect, RelationTrait};
use url::Url;
use crate::{
@@ -8,7 +8,7 @@ use crate::{
models::{
episodes,
feeds::{self, FeedSource, RssFeedTrait, SubscriptionEpisodesFeed},
subscriptions,
subscription_episode, subscriptions,
},
};
@@ -22,19 +22,30 @@ impl Feed {
FeedSource::SubscriptionEpisode => {
let db = ctx.db();
let (subscription, episodes) = if let Some(subscription_id) = m.subscription_id
&& let Some((subscription, episodes)) = subscriptions::Entity::find()
&& let Some(subscription) = subscriptions::Entity::find()
.filter(subscriptions::Column::Id.eq(subscription_id))
.find_with_related(episodes::Entity)
.all(db)
.one(db)
.await?
.pop()
{
let episodes = episodes::Entity::find()
.join(
JoinType::InnerJoin,
episodes::Relation::SubscriptionEpisode.def(),
)
.join(
JoinType::InnerJoin,
subscription_episode::Relation::Subscription.def(),
)
.filter(subscriptions::Column::Id.eq(subscription_id))
.all(db)
.await?;
(subscription, episodes)
} else {
return Err(RecorderError::ModelEntityNotFound {
entity: "Subscription".into(),
});
};
Ok(Feed::SubscritpionEpisodes(
SubscriptionEpisodesFeed::from_model(m, subscription, episodes),
))

View File

@@ -23,7 +23,7 @@ pub trait RssFeedItemTrait: Sized {
fn get_enclosure_link(&self, ctx: &dyn AppContextTrait, api_base: &Url)
-> Option<Cow<'_, str>>;
fn get_enclosure_pub_date(&self) -> Option<DateTime<Utc>>;
fn get_enclosure_content_length(&self) -> Option<u64>;
fn get_enclosure_content_length(&self) -> Option<i64>;
fn into_item(self, ctx: &dyn AppContextTrait, api_base: &Url) -> RecorderResult<Item> {
let enclosure_mime_type =
self.get_enclosure_mime()
@@ -43,12 +43,7 @@ pub trait RssFeedItemTrait: Sized {
source: None.into(),
}
})?;
let enclosure_pub_date = self.get_enclosure_pub_date().ok_or_else(|| {
RecorderError::MikanRssInvalidFieldError {
field: "enclosure_pub_date".into(),
source: None.into(),
}
})?;
let enclosure_pub_date = self.get_enclosure_pub_date();
let link = self.get_link(ctx, api_base).ok_or_else(|| {
RecorderError::MikanRssInvalidFieldError {
field: "link".into(),
@@ -58,9 +53,8 @@ pub trait RssFeedItemTrait: Sized {
let mut extensions = ExtensionMap::default();
if enclosure_mime_type == BITTORRENT_MIME_TYPE {
extensions.insert(
"torrent".to_string(),
btreemap! {
extensions.insert("torrent".to_string(), {
let mut map = btreemap! {
"link".to_string() => vec![
ExtensionBuilder::default().name(
"link"
@@ -71,13 +65,20 @@ pub trait RssFeedItemTrait: Sized {
"contentLength"
).value(enclosure_content_length.to_string()).build()
],
"pubDate".to_string() => vec![
ExtensionBuilder::default().name(
"pubDate"
).value(enclosure_pub_date.to_rfc3339()).build()
],
},
);
};
if let Some(pub_date) = enclosure_pub_date {
map.insert(
"pubDate".to_string(),
vec![
ExtensionBuilder::default()
.name("pubDate")
.value(pub_date.to_rfc3339())
.build(),
],
);
}
map
});
};
let enclosure = EnclosureBuilder::default()
@@ -97,7 +98,6 @@ pub trait RssFeedItemTrait: Sized {
.description(self.get_description().to_string())
.link(link.to_string())
.enclosure(enclosure)
.pub_date(enclosure_pub_date.to_rfc3339())
.extensions(extensions)
.build();

View File

@@ -74,7 +74,7 @@ impl RssFeedItemTrait for episodes::Model {
self.enclosure_pub_date
}
fn get_enclosure_content_length(&self) -> Option<u64> {
fn get_enclosure_content_length(&self) -> Option<i64> {
self.enclosure_content_length
}
}
@@ -84,8 +84,8 @@ impl RssFeedTrait for SubscriptionEpisodesFeed {
fn get_description(&self) -> Cow<'_, str> {
Cow::Owned(format!(
"{PROJECT_NAME} - episodes of subscription \"{}\"",
self.subscription.display_name
"{PROJECT_NAME} - episodes of subscription {}",
self.subscription.id
))
}

View File

@@ -41,6 +41,10 @@ pub enum Relation {
Auth,
#[sea_orm(has_many = "super::credential_3rd::Entity")]
Credential3rd,
#[sea_orm(has_many = "super::feeds::Entity")]
Feed,
#[sea_orm(has_many = "super::subscriber_tasks::Entity")]
SubscriberTask,
}
impl Related<super::subscriptions::Entity> for Entity {
@@ -79,6 +83,18 @@ impl Related<super::credential_3rd::Entity> for Entity {
}
}
impl Related<super::feeds::Entity> for Entity {
fn to() -> RelationDef {
Relation::Feed.def()
}
}
impl Related<super::subscriber_tasks::Entity> for Entity {
fn to() -> RelationDef {
Relation::SubscriberTask.def()
}
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)]
pub enum RelatedEntity {
#[sea_orm(entity = "super::subscriptions::Entity")]
@@ -91,6 +107,10 @@ pub enum RelatedEntity {
Episode,
#[sea_orm(entity = "super::credential_3rd::Entity")]
Credential3rd,
#[sea_orm(entity = "super::feeds::Entity")]
Feed,
#[sea_orm(entity = "super::subscriber_tasks::Entity")]
SubscriberTask,
}
#[derive(Debug, Deserialize, Serialize)]

View File

@@ -59,6 +59,8 @@ pub enum Relation {
on_delete = "SetNull"
)]
Credential3rd,
#[sea_orm(has_many = "super::feeds::Entity")]
Feed,
}
impl Related<super::subscribers::Entity> for Entity {
@@ -93,6 +95,12 @@ impl Related<super::bangumi::Entity> for Entity {
}
}
impl Related<super::feeds::Entity> for Entity {
fn to() -> RelationDef {
Relation::Feed.def()
}
}
impl Related<super::episodes::Entity> for Entity {
fn to() -> RelationDef {
super::subscription_episode::Relation::Episode.def()
@@ -127,6 +135,8 @@ pub enum RelatedEntity {
SubscriptionBangumi,
#[sea_orm(entity = "super::credential_3rd::Entity")]
Credential3rd,
#[sea_orm(entity = "super::feeds::Entity")]
Feed,
}
#[async_trait]