Skip to content

Commit

Permalink
[bilibili.video] Add support for the source platform
Browse files Browse the repository at this point in the history
  • Loading branch information
SpriteOvO committed Apr 7, 2024
1 parent 62a7199 commit 57f26c7
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 22 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Subscribe to updates from people you follow, from any platform to any platform.

- Social media
- [Twitter (twitter.com)](https://twitter.com/)
- [bilibili 动态 (space.bilibili.com)](https://space.bilibili.com/)
- [bilibili 动态 (t.bilibili.com)](https://t.bilibili.com/)
- [bilibili 视频 (space.bilibili.com)](https://space.bilibili.com/)

- Live streaming
- [bilibili 直播 (live.bilibili.com)](https://live.bilibili.com/)
Expand Down
30 changes: 17 additions & 13 deletions src/notify/telegram/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,20 +436,24 @@ impl Notifier {
let quote_begin = content.encode_utf16().count();
content.write_str("🔁 ")?;

let nickname_begin = content.encode_utf16().count();
content.write_str(&repost_from.user.nickname)?;
entities.push((
nickname_begin..content.encode_utf16().count(),
Entity::Link(
// In order for Telegram to display more relevant information about the
// post, we don't use `profile_url` here
//
// &repost_from.user.profile_url,
&repost_from.url,
),
));
if let Some(user) = &repost_from.user {
let nickname_begin = content.encode_utf16().count();
content.write_str(&user.nickname)?;
entities.push((
nickname_begin..content.encode_utf16().count(),
Entity::Link(
// In order for Telegram to display more relevant information about the
// post, we don't use `profile_url` here
//
// &repost_from.user.profile_url,
&repost_from.url,
),
));
write!(content, ": {}", repost_from.content)?;
} else {
write!(content, "{}", repost_from.content)?;
}

write!(content, ": {}", repost_from.content)?;
entities.push((quote_begin..content.encode_utf16().count(), Entity::Quote));
}
Some(RepostFrom::Legacy {
Expand Down
1 change: 1 addition & 0 deletions src/source/bilibili/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod live;
pub mod space;
pub mod video;

use serde::Deserialize;

Expand Down
10 changes: 5 additions & 5 deletions src/source/bilibili/space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ fn parse_response(resp: data::SpaceHistory) -> anyhow::Result<Posts> {
.map_err(|err| anyhow!("failed to parse origin card: {err}"))?;

Ok(Post {
user: item.modules.author.clone().into(),
user: Some(item.modules.author.clone().into()),
content,
url: item
.modules
Expand Down Expand Up @@ -547,11 +547,11 @@ mod tests {
assert!(fetcher.post_filter(make_notification!()).await.is_none());

posts.push(Post {
user: User {
user: Some(User {
nickname: "test display name".into(),
profile_url: "https://test.profile".into(),
avatar_url: "https://test.avatar".into(),
},
}),
content: "test1".into(),
url: "https://test1".into(),
repost_from: None,
Expand All @@ -571,11 +571,11 @@ mod tests {
assert!(filtered.is_none());

posts.push(Post {
user: User {
user: Some(User {
nickname: "test display name".into(),
profile_url: "https://test.profile".into(),
avatar_url: "https://test.avatar".into(),
},
}),
content: "test2".into(),
url: "https://test2".into(),
repost_from: None,
Expand Down
137 changes: 137 additions & 0 deletions src/source/bilibili/video.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use std::{fmt, future::Future, pin::Pin};

use anyhow::{anyhow, ensure};
use serde::Deserialize;
use serde_json as json;

use super::Response;
use crate::source::{
FetcherTrait, Post, PostAttachment, PostAttachmentImage, Posts, SourcePlatformName, Status,
StatusKind, StatusSource,
};

#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct ConfigParams {
pub user_id: u64,
pub series_id: u64,
}

impl fmt::Display for ConfigParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"bilibili.video:{},series{}",
self.user_id, self.series_id
)
}
}

mod data {
use super::*;

#[derive(Debug, Deserialize)]
pub struct SeriesArchives {
pub aids: Vec<u64>,
pub archives: Vec<Archive>,
}

#[derive(Debug, Deserialize)]
pub struct Archive {
pub aid: u64,
pub title: String,
pub pic: String, // Image URL
pub bvid: String,
}
}

pub struct Fetcher {
params: ConfigParams,
}

impl FetcherTrait for Fetcher {
fn fetch_status(&self) -> Pin<Box<dyn Future<Output = anyhow::Result<Status>> + Send + '_>> {
Box::pin(self.fetch_status_impl())
}
}

impl fmt::Display for Fetcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.params)
}
}

impl Fetcher {
pub fn new(params: ConfigParams) -> Self {
Self { params }
}

async fn fetch_status_impl(&self) -> anyhow::Result<Status> {
let videos = fetch_series_archives(self.params.user_id, self.params.series_id).await?;

Ok(Status {
kind: StatusKind::Posts(videos),
source: StatusSource {
platform_name: SourcePlatformName::BilibiliVideo,
user: None,
},
})
}
}

async fn fetch_series_archives(user_id: u64, series_id: u64) -> anyhow::Result<Posts> {
let resp = reqwest::Client::new()
.get(format!(
"https://api.bilibili.com/x/series/archives?mid={user_id}&series_id={series_id}"
))
.send()
.await
.map_err(|err| anyhow!("failed to send request: {err}"))?;

let status = resp.status();
ensure!(
status.is_success(),
"response status is not success: {resp:?}"
);

let text = resp
.text()
.await
.map_err(|err| anyhow!("failed to obtain text from response: {err}"))?;

let resp: Response<data::SeriesArchives> = json::from_str(&text)
.map_err(|err| anyhow!("failed to deserialize response: {err}, text: {text}"))?;
ensure!(resp.code == 0, "response code is not 0. text: {text}");

parse_response(resp.data.unwrap())
}

fn parse_response(resp: data::SeriesArchives) -> anyhow::Result<Posts> {
let videos = resp
.archives
.into_iter()
.map(|archive| Post {
user: None,
content: archive.title,
url: format!("https://www.bilibili.com/video/{}", archive.bvid),
repost_from: None,
attachments: vec![PostAttachment::Image(PostAttachmentImage {
media_url: archive.pic,
})],
})
.collect();

Ok(Posts(videos))
}

#[cfg(test)]
mod tests {
use super::*;

#[tokio::test]
async fn deser() {
let videos = fetch_series_archives(522384919, 3747026).await.unwrap();

assert!(videos.0.iter().all(|post| !post.url.is_empty()));
assert!(videos.0.iter().all(|post| !post.content.is_empty()));
}
}
10 changes: 9 additions & 1 deletion src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub enum ConfigSourcePlatform {
BilibiliLive(bilibili::live::ConfigParams),
#[serde(rename = "bilibili.space")]
BilibiliSpace(bilibili::space::ConfigParams),
#[serde(rename = "bilibili.video")]
BilibiliVideo(bilibili::video::ConfigParams),
#[serde(rename = "Twitter")]
Twitter(twitter::ConfigParams),
}
Expand All @@ -26,6 +28,7 @@ impl fmt::Display for ConfigSourcePlatform {
match self {
ConfigSourcePlatform::BilibiliLive(p) => write!(f, "{p}"),
ConfigSourcePlatform::BilibiliSpace(p) => write!(f, "{p}"),
ConfigSourcePlatform::BilibiliVideo(p) => write!(f, "{p}"),
ConfigSourcePlatform::Twitter(p) => write!(f, "{p}"),
}
}
Expand All @@ -36,6 +39,7 @@ impl fmt::Display for ConfigSourcePlatform {
pub enum SourcePlatformName {
BilibiliLive,
BilibiliSpace,
BilibiliVideo,
Twitter,
}

Expand All @@ -44,6 +48,7 @@ impl fmt::Display for SourcePlatformName {
match self {
Self::BilibiliLive => write!(f, "bilibili 直播"),
Self::BilibiliSpace => write!(f, "bilibili 动态"),
Self::BilibiliVideo => write!(f, "bilibili 视频"),
Self::Twitter => write!(f, "Twitter"),
}
}
Expand Down Expand Up @@ -131,7 +136,7 @@ pub struct User {

#[derive(Debug)]
pub struct Post {
pub user: User,
pub user: Option<User>,
pub content: String,
pub url: String,
pub repost_from: Option<RepostFrom>,
Expand Down Expand Up @@ -266,6 +271,9 @@ pub fn fetcher(platform: &ConfigSourcePlatform) -> Box<dyn FetcherTrait> {
ConfigSourcePlatform::BilibiliSpace(p) => {
Box::new(bilibili::space::Fetcher::new(p.clone()))
}
ConfigSourcePlatform::BilibiliVideo(p) => {
Box::new(bilibili::video::Fetcher::new(p.clone()))
}
ConfigSourcePlatform::Twitter(p) => Box::new(twitter::Fetcher::new(p.clone())),
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/source/twitter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,11 @@ impl Fetcher {
.timeline
.into_iter()
.map(|tweet| Post {
user: User {
user: Some(User {
nickname: status.fullname.clone(),
profile_url: format!("https://twitter.com/{}", self.params.username),
avatar_url: status.avatar_url.real_url(),
},
}),
content: tweet.content,
url: tweet.url.real_url(),
repost_from: Some(RepostFrom::Legacy {
Expand Down

0 comments on commit 57f26c7

Please sign in to comment.