Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 2 additions & 0 deletions src/api/urls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ 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 USER_FOLLOWING: &str = formatcp!("{}/following", USER);
pub const STATUS: &str = formatcp!("{}/statuses/", OPENAPI);
pub const COMMENTS_PATH: &str = "comments";

Expand Down
60 changes: 59 additions & 1 deletion src/api/user.rs
Original file line number Diff line number Diff line change
@@ -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::{UserFollow, UserInfo},
tools::IntoAnyhowResult,
};

pub async fn raw_user_info(c: &Client) -> Result<Response> {
c.get(urls::USER).send().await.into_anyhow_result()
Expand All @@ -10,3 +14,57 @@ pub async fn raw_user_info(c: &Client) -> Result<Response> {
pub async fn user_info(c: &Client) -> Result<UserInfo> {
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<Response> {
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<UserFollow> {
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<Response> {
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<UserFollow> {
raw_user_following(c, page)
.await?
.json()
.await
.into_anyhow_result()
}
17 changes: 17 additions & 0 deletions src/commands/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ pub struct UserCommand {
/// 提供通过access token登录,状态查询,退出,显示当前token功能
#[derive(Debug, Subcommand)]
pub enum UserAction {
/// 查看用户粉丝列表
Follower(FollowArg),
/// 查看用户关注列表
Following(FollowArg),
/// 用户登录,需提供access token。
Login {
#[clap(value_parser = NonEmptyStringValueParser::new())]
Expand All @@ -25,3 +29,16 @@ pub enum UserAction {
/// 显示当前登录token
Token,
}

#[derive(serde::Serialize, Debug, Args)]
pub struct FollowArg {
/// 分页页码(从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,
}
38 changes: 37 additions & 1 deletion src/logic/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ use owo_colors::OwoColorize;
use reqwest::header::{AUTHORIZATION, HeaderMap};
use reqwest::{ClientBuilder, StatusCode};

use crate::commands::user::{UserAction, UserCommand};
use crate::commands::user::{FollowArg, UserAction, UserCommand};
use crate::context::Context;
use crate::context::config::Cache;
use crate::tools::http::IntoNoParseResult;
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,
Expand Down Expand Up @@ -68,3 +70,37 @@ async fn user_info(ctx: &mut Context) -> Result<()> {
fn handle_logout(ctx: &Context) -> Result<()> {
ctx.clean()
}

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
.iter()
.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(())
}
35 changes: 35 additions & 0 deletions src/models/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,38 @@ impl UserInfo {
info.join("\n")
}
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct FollowInfo {
pub alias: String,
pub space_user_id: u64,
pub display_name: String,
pub blog_app: Option<String>,
}

impl FollowInfo {
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 UserFollow {
pub items: Vec<FollowInfo>,
pub total_count: u64,
}