From de590e2d352d01a858d4a21b790018e432419d04 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 27 Jan 2026 02:54:39 +0800 Subject: [PATCH 1/4] feat: add follower list command --- src/api/urls.rs | 1 + src/api/user.rs | 29 ++++++++++++++++++++++++++++- src/commands/user.rs | 15 +++++++++++++++ src/logic/user.rs | 13 ++++++++++++- src/models/user.rs | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/api/urls.rs b/src/api/urls.rs index da7b75f..70d0caf 100644 --- a/src/api/urls.rs +++ b/src/api/urls.rs @@ -5,6 +5,7 @@ pub const OPENAPI: &str = "https://api.cnblogs.com/api"; pub const OAUTH: &str = "https://oauth.cnblogs.com"; pub const USER: &str = formatcp!("{}/users", OPENAPI); +pub const USER_FOLLOWERS: &str = formatcp!("{}/followers", USER); pub const STATUS: &str = formatcp!("{}/statuses/", OPENAPI); pub const COMMENTS_PATH: &str = "comments"; diff --git a/src/api/user.rs b/src/api/user.rs index a0732ba..5c25c7d 100644 --- a/src/api/user.rs +++ b/src/api/user.rs @@ -1,7 +1,11 @@ use anyhow::Result; use reqwest::{Client, Response}; -use crate::{api::urls, models::user::UserInfo, tools::IntoAnyhowResult}; +use crate::{ + api::urls, + models::user::{UserFollowers, UserInfo}, + tools::IntoAnyhowResult, +}; pub async fn raw_user_info(c: &Client) -> Result { c.get(urls::USER).send().await.into_anyhow_result() @@ -10,3 +14,26 @@ pub async fn raw_user_info(c: &Client) -> Result { pub async fn user_info(c: &Client) -> Result { raw_user_info(c).await?.json().await.into_anyhow_result() } + +/// 获取用户粉丝列表的原始响应 +/// page - 分页参数,包含 pageIndex 和 pageSize +pub async fn raw_user_followers( + c: &Client, + page: impl serde::Serialize + Send + Sync, +) -> Result { + c.get(urls::USER_FOLLOWERS) + .query(&page) + .send() + .await + .into_anyhow_result() +} + +/// 获取用户粉丝列表 +/// +/// page - 分页参数,包含 pageIndex 和 pageSize +pub async fn user_followers( + c: &Client, + page: impl serde::Serialize + Send + Sync, +) -> Result { + raw_user_followers(c, page).await?.json().await.into_anyhow_result() +} diff --git a/src/commands/user.rs b/src/commands/user.rs index 8b0f81f..49d1e2e 100644 --- a/src/commands/user.rs +++ b/src/commands/user.rs @@ -13,6 +13,8 @@ pub struct UserCommand { /// 提供通过access token登录,状态查询,退出,显示当前token功能 #[derive(Debug, Subcommand)] pub enum UserAction { + Follower(FollowerArg), + /// 用户登录,需提供access token。 Login { #[clap(value_parser = NonEmptyStringValueParser::new())] @@ -25,3 +27,16 @@ pub enum UserAction { /// 显示当前登录token Token, } + +#[derive(serde::Serialize, Debug, Args)] +pub struct FollowerArg { + /// 分页页码(从1开始) + #[arg(long = "page-index", default_value_t = 1)] + #[serde(rename = "pageIndex")] + pub page_index: u64, + + /// 每页显示的条数,默认20 + #[arg(long = "page-size", default_value_t = 20)] + #[serde(rename = "pageSize")] + pub page_size: u64, +} diff --git a/src/logic/user.rs b/src/logic/user.rs index f01bdab..a0a4bb6 100644 --- a/src/logic/user.rs +++ b/src/logic/user.rs @@ -7,7 +7,7 @@ use owo_colors::OwoColorize; use reqwest::header::{AUTHORIZATION, HeaderMap}; use reqwest::{ClientBuilder, StatusCode}; -use crate::commands::user::{UserAction, UserCommand}; +use crate::commands::user::{FollowerArg, UserAction, UserCommand}; use crate::context::Context; use crate::context::config::Cache; use crate::tools::http::IntoNoParseResult; @@ -15,6 +15,7 @@ use crate::{api, models}; pub async fn endpoint(cmd: UserCommand, ctx: &mut Context) -> anyhow::Result<()> { match cmd.commands { + UserAction::Follower(arg) => handle_followers(ctx, arg).await, UserAction::Login { token } => handle_login(token, ctx).await, UserAction::Logout => handle_logout(ctx), UserAction::Status => user_info(ctx).await, @@ -68,3 +69,13 @@ async fn user_info(ctx: &mut Context) -> Result<()> { fn handle_logout(ctx: &Context) -> Result<()> { ctx.clean() } + +async fn handle_followers(ctx: &mut Context, arg: FollowerArg) -> Result<()> { + let followers = api::user::user_followers(&ctx.client, arg).await?; + + followers + .items + .iter() + .for_each(|f| ctx.terminal.writeln(f.as_format()).unwrap()); + Ok(()) +} diff --git a/src/models/user.rs b/src/models/user.rs index bb8681d..e9c9eb9 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -43,3 +43,39 @@ impl UserInfo { info.join("\n") } } + + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct FollowerInfo { + pub alias: String, + pub space_user_id: u64, + pub display_name: String, + pub blog_app: Option, +} + +impl FollowerInfo { + pub fn as_format(&self) -> String { + format!( + "{name} [#{id}] [{blog}]", + name = self.display_name.bright_blue(), + id = self.space_user_id.bright_green(), + blog = self + .blog_app + .as_ref() + .map_or("无博客".red().to_string(), |app| format!( + "https://www.cnblogs.com/{}", + app + ) + .blue() + .to_string()) + ) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct UserFollowers { + pub items: Vec, + pub total_count: u64, +} From 24c5ea08f0d77262df725c36f2f76fa1710f5cea Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 27 Jan 2026 02:57:08 +0800 Subject: [PATCH 2/4] style: format follower list command --- src/api/user.rs | 8 ++++++-- src/models/user.rs | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/api/user.rs b/src/api/user.rs index 5c25c7d..aed28f1 100644 --- a/src/api/user.rs +++ b/src/api/user.rs @@ -29,11 +29,15 @@ pub async fn raw_user_followers( } /// 获取用户粉丝列表 -/// +/// /// page - 分页参数,包含 pageIndex 和 pageSize pub async fn user_followers( c: &Client, page: impl serde::Serialize + Send + Sync, ) -> Result { - raw_user_followers(c, page).await?.json().await.into_anyhow_result() + raw_user_followers(c, page) + .await? + .json() + .await + .into_anyhow_result() } diff --git a/src/models/user.rs b/src/models/user.rs index e9c9eb9..4699ccc 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -44,7 +44,6 @@ impl UserInfo { } } - #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct FollowerInfo { From 74bfbca92527f50d7b678f15b00862f864a706d6 Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Tue, 27 Jan 2026 03:11:35 +0800 Subject: [PATCH 3/4] feat: add following list command --- src/api/urls.rs | 1 + src/api/user.rs | 31 +++++++++++++++++++++++++++++-- src/commands/user.rs | 8 +++++--- src/logic/user.rs | 31 ++++++++++++++++++++++++++++--- src/models/user.rs | 8 ++++---- 5 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/api/urls.rs b/src/api/urls.rs index 70d0caf..f8713c3 100644 --- a/src/api/urls.rs +++ b/src/api/urls.rs @@ -6,6 +6,7 @@ pub const OAUTH: &str = "https://oauth.cnblogs.com"; pub const USER: &str = formatcp!("{}/users", OPENAPI); pub const USER_FOLLOWERS: &str = formatcp!("{}/followers", USER); +pub const USER_FOLLOWING: &str = formatcp!("{}/following", USER); pub const STATUS: &str = formatcp!("{}/statuses/", OPENAPI); pub const COMMENTS_PATH: &str = "comments"; diff --git a/src/api/user.rs b/src/api/user.rs index aed28f1..129d247 100644 --- a/src/api/user.rs +++ b/src/api/user.rs @@ -3,7 +3,7 @@ use reqwest::{Client, Response}; use crate::{ api::urls, - models::user::{UserFollowers, UserInfo}, + models::user::{UserFollow, UserInfo}, tools::IntoAnyhowResult, }; @@ -34,10 +34,37 @@ pub async fn raw_user_followers( pub async fn user_followers( c: &Client, page: impl serde::Serialize + Send + Sync, -) -> Result { +) -> Result { raw_user_followers(c, page) .await? .json() .await .into_anyhow_result() } + +/// 获取用户粉丝列表的原始响应 +/// page - 分页参数,包含 pageIndex 和 pageSize +pub async fn raw_user_following( + c: &Client, + page: impl serde::Serialize + Send + Sync, +) -> Result { + c.get(urls::USER_FOLLOWING) + .query(&page) + .send() + .await + .into_anyhow_result() +} + +/// 获取用户粉丝列表 +/// +/// page - 分页参数,包含 pageIndex 和 pageSize +pub async fn user_following( + c: &Client, + page: impl serde::Serialize + Send + Sync, +) -> Result { + raw_user_following(c, page) + .await? + .json() + .await + .into_anyhow_result() +} diff --git a/src/commands/user.rs b/src/commands/user.rs index 49d1e2e..1e47b57 100644 --- a/src/commands/user.rs +++ b/src/commands/user.rs @@ -13,8 +13,10 @@ pub struct UserCommand { /// 提供通过access token登录,状态查询,退出,显示当前token功能 #[derive(Debug, Subcommand)] pub enum UserAction { - Follower(FollowerArg), - + /// 查看用户粉丝列表 + Follower(FollowArg), + /// 查看用户关注列表 + Following(FollowArg), /// 用户登录,需提供access token。 Login { #[clap(value_parser = NonEmptyStringValueParser::new())] @@ -29,7 +31,7 @@ pub enum UserAction { } #[derive(serde::Serialize, Debug, Args)] -pub struct FollowerArg { +pub struct FollowArg { /// 分页页码(从1开始) #[arg(long = "page-index", default_value_t = 1)] #[serde(rename = "pageIndex")] diff --git a/src/logic/user.rs b/src/logic/user.rs index a0a4bb6..c614b88 100644 --- a/src/logic/user.rs +++ b/src/logic/user.rs @@ -7,7 +7,7 @@ use owo_colors::OwoColorize; use reqwest::header::{AUTHORIZATION, HeaderMap}; use reqwest::{ClientBuilder, StatusCode}; -use crate::commands::user::{FollowerArg, UserAction, UserCommand}; +use crate::commands::user::{FollowArg, UserAction, UserCommand}; use crate::context::Context; use crate::context::config::Cache; use crate::tools::http::IntoNoParseResult; @@ -16,6 +16,7 @@ use crate::{api, models}; pub async fn endpoint(cmd: UserCommand, ctx: &mut Context) -> anyhow::Result<()> { match cmd.commands { UserAction::Follower(arg) => handle_followers(ctx, arg).await, + UserAction::Following(arg) => handle_following(ctx, arg).await, UserAction::Login { token } => handle_login(token, ctx).await, UserAction::Logout => handle_logout(ctx), UserAction::Status => user_info(ctx).await, @@ -70,8 +71,16 @@ fn handle_logout(ctx: &Context) -> Result<()> { ctx.clean() } -async fn handle_followers(ctx: &mut Context, arg: FollowerArg) -> Result<()> { - let followers = api::user::user_followers(&ctx.client, arg).await?; +async fn handle_followers(ctx: &mut Context, arg: FollowArg) -> Result<()> { + let followers = api::user::user_followers(&ctx.client, &arg).await?; + + ctx.terminal.writeln( + format!( + "{} 人关注了您, 当前{}页,每页数量{}", + followers.total_count, arg.page_index, arg.page_size + ) + .bright_green(), + )?; followers .items @@ -79,3 +88,19 @@ async fn handle_followers(ctx: &mut Context, arg: FollowerArg) -> Result<()> { .for_each(|f| ctx.terminal.writeln(f.as_format()).unwrap()); Ok(()) } + +async fn handle_following(ctx: &mut Context, arg: FollowArg) -> Result<()> { + let following = api::user::user_following(&ctx.client, &arg).await?; + ctx.terminal.writeln( + format!( + "您关注了 {} 人, 当前{}页,每页数量{}", + following.total_count, arg.page_index, arg.page_size + ) + .bright_green(), + )?; + following + .items + .iter() + .for_each(|f| ctx.terminal.writeln(f.as_format()).unwrap()); + Ok(()) +} diff --git a/src/models/user.rs b/src/models/user.rs index 4699ccc..8f00d0f 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -46,14 +46,14 @@ impl UserInfo { #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] -pub struct FollowerInfo { +pub struct FollowInfo { pub alias: String, pub space_user_id: u64, pub display_name: String, pub blog_app: Option, } -impl FollowerInfo { +impl FollowInfo { pub fn as_format(&self) -> String { format!( "{name} [#{id}] [{blog}]", @@ -74,7 +74,7 @@ impl FollowerInfo { #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] -pub struct UserFollowers { - pub items: Vec, +pub struct UserFollow { + pub items: Vec, pub total_count: u64, } From c0d10afe9c61172dbd14d71ebb36b423a045848d Mon Sep 17 00:00:00 2001 From: RocSun <710989028@qq.com> Date: Thu, 5 Feb 2026 00:45:28 +0800 Subject: [PATCH 4/4] feat: update version of cnblogs_lib to 0.3.0 in Cargo.toml and Cargo.lock --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf35e16..85f1dd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,7 +230,7 @@ dependencies = [ [[package]] name = "cnblogs_lib" -version = "0.0.0-dev" +version = "0.3.0" dependencies = [ "anstream", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index fe5c0cc..20e2b60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cnblogs_lib" # WRN: Version will be updated by CI while create a tag, NERVER change this. -version = "0.0.0-dev" +version = "0.3.0" edition = "2024" description = "Cnblogs' command line tool" license = "MIT"